From 488322edff4ac7ee762c75acffe5d99b992468a2 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 11:31:09 +0200 Subject: [PATCH 001/176] [Capsule] Import sources --- Sources/Capsule/Common.swift | 189 ++++++++++ Sources/Capsule/HashMap.swift | 507 ++++++++++++++++++++++++++ Tests/CapsuleTests/CapsuleTests.swift | 42 +++ 3 files changed, 738 insertions(+) create mode 100644 Sources/Capsule/Common.swift create mode 100644 Sources/Capsule/HashMap.swift create mode 100644 Tests/CapsuleTests/CapsuleTests.swift diff --git a/Sources/Capsule/Common.swift b/Sources/Capsule/Common.swift new file mode 100644 index 000000000..e5f099896 --- /dev/null +++ b/Sources/Capsule/Common.swift @@ -0,0 +1,189 @@ +// +// Common.swift +// +// +// Created by Michael J. Steindorfer on 11/23/19. +// + +import func Foundation.ceil + +func computeHash(_ value: T) -> Int { + value.hashValue +} + +var HashCodeLength: Int { Int.bitWidth } + +var BitPartitionSize: Int { 5 } + +var BitPartitionMask: Int { (1 << BitPartitionSize) - 1 } + +let MaxDepth = Int(ceil(Double(HashCodeLength) / Double(BitPartitionSize))) + +func maskFrom(_ hash: Int, _ shift: Int) -> Int { + (hash >> shift) & BitPartitionMask +} + +func bitposFrom(_ mask: Int) -> Int { + 1 << mask +} + +func indexFrom(_ bitmap: Int, _ bitpos: Int) -> Int { + (bitmap & (bitpos - 1)).nonzeroBitCount +} + +func indexFrom(_ bitmap: Int, _ mask: Int, _ bitpos: Int) -> Int { + (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) +} + +protocol Node { + associatedtype ReturnPayload + associatedtype ReturnNode : Node + + var hasNodes: Bool { get } + + var nodeArity: Int { get } + + func getNode(_ index: Int) -> ReturnNode + + var hasPayload: Bool { get } + + var payloadArity: Int { get } + + func getPayload(_ index: Int) -> ReturnPayload +} + +/// +/// Base class for fixed-stack iterators that traverse 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). +/// +struct ChampBaseIterator { + + var currentValueCursor: Int = 0 + var currentValueLength: Int = 0 + var currentValueNode: T? = nil + + private var currentStackLevel: Int = -1 + private var nodeCursorsAndLengths: Array = Array(repeating: 0, count: MaxDepth * 2) + private var nodes: Array = Array(repeating: nil, count: MaxDepth) + + init(rootNode: T) { + if (rootNode.hasNodes) { pushNode(rootNode) } + if (rootNode.hasPayload) { setupPayloadNode(rootNode) } + } + + private mutating func setupPayloadNode(_ node: T) { + currentValueNode = node + currentValueCursor = 0 + currentValueLength = node.payloadArity + } + + private mutating func pushNode(_ node: T) { + currentStackLevel = currentStackLevel + 1 + + let cursorIndex = currentStackLevel * 2 + let lengthIndex = currentStackLevel * 2 + 1 + + nodes[currentStackLevel] = node + nodeCursorsAndLengths[cursorIndex] = 0 + nodeCursorsAndLengths[lengthIndex] = node.nodeArity + } + + private mutating func popNode() { + currentStackLevel = currentStackLevel - 1 + } + + /// + /// Searches for next node that contains payload values, + /// and pushes encountered sub-nodes on a stack for depth-first traversal. + /// + private mutating func searchNextValueNode() -> Bool { + while (currentStackLevel >= 0) { + let cursorIndex = currentStackLevel * 2 + let lengthIndex = currentStackLevel * 2 + 1 + + let nodeCursor = nodeCursorsAndLengths[cursorIndex] + let nodeLength = nodeCursorsAndLengths[lengthIndex] + + if (nodeCursor < nodeLength) { + nodeCursorsAndLengths[cursorIndex] += 1 + + let nextNode = nodes[currentStackLevel]!.getNode(nodeCursor) as! T + + if (nextNode.hasNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + } else { + popNode() + } + } + + return false + } + + mutating func hasNext() -> Bool { + return (currentValueCursor < currentValueLength) || searchNextValueNode() + } + +} + +/// +/// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base +/// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). +/// +struct ChampBaseReverseIterator { + + var currentValueCursor: Int = -1 + var currentValueNode: T? = nil + + private var currentStackLevel: Int = -1 + private var nodeIndex: Array = Array(repeating: 0, count: MaxDepth + 1) + private var nodeStack: Array = Array(repeating: nil, count: MaxDepth + 1) + + init(rootNode: T) { + pushNode(rootNode) + searchNextValueNode() + } + + private mutating func setupPayloadNode(_ node: T) { + currentValueNode = node + currentValueCursor = node.payloadArity - 1 + } + + private mutating func pushNode(_ node: T) { + currentStackLevel = currentStackLevel + 1 + + nodeStack[currentStackLevel] = node + nodeIndex[currentStackLevel] = node.nodeArity - 1 + } + + private mutating func popNode() { + currentStackLevel = currentStackLevel - 1 + } + + /// + /// Searches for rightmost node that contains payload values, + /// and pushes encountered sub-nodes on a stack for depth-first traversal. + /// + @discardableResult + private mutating func searchNextValueNode() -> Bool { + while (currentStackLevel >= 0) { + let nodeCursor = nodeIndex[currentStackLevel] ; nodeIndex[currentStackLevel] = nodeCursor - 1 + + if (nodeCursor >= 0) { + let nextNode = nodeStack[currentStackLevel]!.getNode(nodeCursor) as! T + pushNode(nextNode) + } else { + let currNode = nodeStack[currentStackLevel]! + popNode() + + if (currNode.hasPayload) { setupPayloadNode(currNode) ; return true } + } + } + + return false + } + + mutating func hasNext() -> Bool { + return (currentValueCursor >= 0) || searchNextValueNode() + } +} diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift new file mode 100644 index 000000000..97c1a7af1 --- /dev/null +++ b/Sources/Capsule/HashMap.swift @@ -0,0 +1,507 @@ +// +// HashMap.swift +// +// +// Created by Michael J. Steindorfer on 11/21/19. +// + +public struct HashMap where Key : Hashable { + let rootNode: MapNode + let cachedKeySetHashCode: Int + let cachedSize: Int + + fileprivate init(_ rootNode: MapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { + self.rootNode = rootNode + self.cachedKeySetHashCode = cachedKeySetHashCode + self.cachedSize = cachedSize + } + + public init() { + self.init(BitmapIndexedMapNode(0, 0, Array()), 0, 0) + } + + public init(_ map: HashMap) { + self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) + } + + /// + /// Inspecting a Dictionary + /// + + var isEmpty: Bool { cachedSize == 0 } + + var count: Int { cachedSize } + + var capacity: Int { count } + + /// + /// Accessing Keys and Values + /// + + subscript(_ key: Key) -> Value? { + get { + return get(key) + } + mutating set(optionalValue) { + if let value = optionalValue { + self = insert(key: key, value: value) + } else { + self = delete(key) + } + } + } + + subscript(_ key: Key, default: () -> Value) -> Value { + return get(key) ?? `default`() + } + + public func contains(_ key: Key) -> Bool { + rootNode.containsKey(key, computeHash(key), 0) + } + + public func get(_ key: Key) -> Value? { + rootNode.get(key, computeHash(key), 0) + } + + public func insert(key: Key, value: Value) -> Self { + var effect = MapEffect() + let keyHash = computeHash(key) + let newRootNode = rootNode.updated(key, value, keyHash, 0, &effect) + + if (effect.modified) { + if (effect.replacedValue) { + return Self(newRootNode, cachedKeySetHashCode, cachedSize) + } else { + return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize + 1) + } + } else { return self } + } + + public func delete(_ key: Key) -> Self { + var effect = MapEffect() + let keyHash = computeHash(key) + let newRootNode = rootNode.removed(key, keyHash, 0, &effect) + + if (effect.modified) { + return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) + } else { return self } + } +} + +extension HashMap : ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (Key, Value)...) { + let map = elements.reduce(Self()) { (map, element) in let (key, value) = element + return map.insert(key: key, value: value) + } + self.init(map) + } +} + +extension HashMap : Equatable, Hashable { + public static func == (lhs: HashMap, rhs: HashMap) -> Bool { + lhs.cachedSize == rhs.cachedSize && + lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && + (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) + } + + public var hashValue: Int { + preconditionFailure("Not yet implemented") + } + + public func hash(into: inout Hasher) { + preconditionFailure("Not yet implemented") + } +} + +extension HashMap : Sequence { + public __consuming func makeIterator() -> MapKeyValueTupleIterator { + return MapKeyValueTupleIterator(rootNode: rootNode) + } +} + +fileprivate let EmptyMapNode = BitmapIndexedMapNode(0, 0, Array()) + +// TODO assess if convertible to a `protocol`. Variance / constraints on return types make it difficult. +class MapNode : Node where Key : Hashable { + + func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { + preconditionFailure("This method must be overridden") + } + + func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { + preconditionFailure("This method must be overridden") + } + + func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + preconditionFailure("This method must be overridden") + } + + func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + preconditionFailure("This method must be overridden") + } + + var hasNodes: Bool { + preconditionFailure("This method must be overridden") + } + + var nodeArity: Int { + preconditionFailure("This method must be overridden") + } + + func getNode(_ index: Int) -> MapNode { + preconditionFailure("This method must be overridden") + } + + var hasPayload: Bool { + preconditionFailure("This method must be overridden") + } + + var payloadArity: Int { + preconditionFailure("This method must be overridden") + } + + func getPayload(_ index: Int) -> (Key, Value) { + preconditionFailure("This method must be overridden") + } +} + +extension MapNode : Equatable { + static func == (lhs: MapNode, rhs: MapNode) -> Bool { + preconditionFailure("Not yet implemented") + } +} + +fileprivate var TupleLength: Int { 2 } + +final class BitmapIndexedMapNode : MapNode where Key : Hashable { + let dataMap: Int + let nodeMap: Int + let content: [Any] + + init(_ dataMap: Int, _ nodeMap: Int, _ content: [Any]) { + self.dataMap = dataMap + self.nodeMap = nodeMap + self.content = content + } + + override func getPayload(_ index: Int) -> (Key, Value) { + (content[TupleLength * index + 0] as! Key, + content[TupleLength * index + 1] as! Value) + } + + override func getNode(_ index: Int) -> MapNode { + content[content.count - 1 - index] as! MapNode + } + + override func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let payload = self.getPayload(index) + if (key == payload.0) { return payload.1 } else { return nil } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + } + + return nil + } + + override func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let payload = self.getPayload(index) + return key == payload.0 + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + } + + return false + } + + override func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let (key0, value0) = self.getPayload(index) + + if (key0 == key) { + effect.setReplacedValue() + return copyAndSetValue(bitpos, value) + } else { + let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) + effect.setModified() + return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self + } else { + return copyAndSetNode(bitpos, subNodeNew) + } + } + + effect.setModified() + return copyAndInsertValue(bitpos, key, value) + } + + override func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let (key0, _) = self.getPayload(index) + + if (key0 == key) { + effect.setModified() + if (self.payloadArity == 2 && self.nodeArity == 0) { + /* + * Create new node with remaining pair. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + let newDataMap: Int + if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } + if (index == 0) { + let (k, v) = getPayload(1) + return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v) ) + } else { + let (k, v) = getPayload(0) + return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) + } + } else { return copyAndRemoveValue(bitpos) } + } else { return self } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + return subNodeNew + } + else { // inline value (move to front) + return copyAndMigrateFromNodeToInline(bitpos, subNodeNew) + } + + default: // equivalent to `case 2...` + // modify current node (set replacement node) + return copyAndSetNode(bitpos, subNodeNew) + } + } + + return self + } + + func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> MapNode { + if (shift >= HashCodeLength) { + preconditionFailure("Not yet implemented") + } else { + let mask0 = maskFrom(keyHash0, shift) + let mask1 = maskFrom(keyHash1, shift) + + if (mask0 != mask1) { + // unique prefixes, payload fits on same level + let dataMap = bitposFrom(mask0) | bitposFrom(mask1) + + if (mask0 < mask1) { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) + } else { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) + } + } else { + // identical prefixes, payload must be disambiguated deeper in the trie + let nodeMap = bitposFrom(mask0) + let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) + + return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) + } + } + } + + override var hasNodes: Bool { nodeMap != 0 } + + override var nodeArity: Int { nodeMap.nonzeroBitCount } + + override var hasPayload: Bool { dataMap != 0 } + + override var payloadArity: Int { dataMap.nonzeroBitCount } + + func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos - 1)).nonzeroBitCount } + + func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos - 1)).nonzeroBitCount } + + /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design + /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: + /// + /// ``` + /// class Node { + /// var src: [Any] + /// func updateInlineOrCopy(idx: Int, newValue: Any) { + /// if isKnownUniquelyReferenced(&self) { // this isn't supported ... + /// src[idx] = newValue + /// return self + /// } else { + /// var dst = self.content + /// dst[idx] = newValue + /// return Node(dst) + /// } + /// } + /// } + /// ``` + /// + /// Some more context: + /// * Node should be a reference counted data type (i.e., `class`) + /// * In a optimized version `src` would be gone, and `Node` likely become a subclass of `ManagedBuffer` + /// * I want to check `isKnownUniquelyReferenced(&self)` since `updateInlineOrCopy` should be recursive call that decides upon returning from recursion if modifications are necessary + /// + /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. + /// This would be very invasive though and make problem logic hard to understand and maintain. + func copyAndSetValue(_ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + 1 + + var dst = self.content + dst[idx] = newValue + + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } + + func copyAndSetNode(_ bitpos: Int, _ newNode: MapNode) -> BitmapIndexedMapNode { + let idx = self.content.count - 1 - self.nodeIndex(bitpos) + + var dst = self.content + dst[idx] = newNode + + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } + + func copyAndInsertValue(_ bitpos: Int, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + + var dst = self.content + dst.insert(contentsOf: [key, value], at: idx) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, dst) + } + + func copyAndRemoveValue(_ bitpos: Int) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + + var dst = self.content + dst.removeSubrange(idx..) -> BitmapIndexedMapNode { + let idxOld = TupleLength * dataIndex(bitpos) + let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) + + var dst = self.content + dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { + let idxOld = self.content.count - 1 - nodeIndex(bitpos) + let idxNew = TupleLength * dataIndex(bitpos) + + let (key, value) = node.getPayload(0) + + var dst = self.content + dst.remove(at: idxOld) + dst.insert(contentsOf: [key, value], at: idxNew) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, dst) + } +} + +extension BitmapIndexedMapNode /* : Equatable */ { + static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { + lhs === rhs || + lhs.nodeMap == rhs.nodeMap && + lhs.dataMap == rhs.dataMap && + deepContentEquality(lhs.content, rhs.content, lhs.content.count) + } + + private static func deepContentEquality(_ a1: [Any], _ a2: [Any], _ length: Int) -> Bool { + preconditionFailure("Not yet implemented") + } +} + +struct MapEffect { + var modified: Bool = false + var replacedValue: Bool = false + + mutating func setModified() { + self.modified = true + } + + mutating func setReplacedValue() { + self.modified = true + self.replacedValue = true + } +} + +public struct MapKeyValueTupleIterator { + private var baseIterator: ChampBaseIterator> + + init(rootNode: MapNode) { + self.baseIterator = ChampBaseIterator(rootNode: rootNode) + } +} + +extension MapKeyValueTupleIterator : IteratorProtocol { + public mutating func next() -> (Key, Value)? { + guard baseIterator.hasNext() else { return nil } + + let payload = baseIterator.currentValueNode?.getPayload(baseIterator.currentValueCursor) + baseIterator.currentValueCursor += 1 + + return payload + } +} + +public struct MapKeyValueTupleReverseIterator { + private var baseIterator: ChampBaseReverseIterator> + + init(rootNode: MapNode) { + self.baseIterator = ChampBaseReverseIterator(rootNode: rootNode) + } +} + +extension MapKeyValueTupleReverseIterator : IteratorProtocol { + public mutating func next() -> (Key, Value)? { + guard baseIterator.hasNext() else { return nil } + + let payload = baseIterator.currentValueNode?.getPayload(baseIterator.currentValueCursor) + baseIterator.currentValueCursor -= 1 + + return payload + } +} diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift new file mode 100644 index 000000000..60f7fca68 --- /dev/null +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -0,0 +1,42 @@ +import XCTest +@testable import Capsule + +final class CapsuleTests: XCTestCase { + func testSubscriptAdd() { + var copyAndSetTest: HashMap = + [ 1 : "a", 2 : "b" ] + + copyAndSetTest[3] = "x" + copyAndSetTest[4] = "y" + + XCTAssertEqual(copyAndSetTest.count, 4) + XCTAssertEqual(copyAndSetTest[1], "a") + XCTAssertEqual(copyAndSetTest[2], "b") + XCTAssertEqual(copyAndSetTest[3], "x") + XCTAssertEqual(copyAndSetTest[4], "y") + } + + func testSubscriptOverwrite() { + var copyAndSetTest: HashMap = + [ 1 : "a", 2 : "b" ] + + copyAndSetTest[1] = "x" + copyAndSetTest[2] = "y" + + XCTAssertEqual(copyAndSetTest.count, 2) + XCTAssertEqual(copyAndSetTest[1], "x") + XCTAssertEqual(copyAndSetTest[2], "y") + } + + func testSubscriptRemove() { + var copyAndSetTest: HashMap = + [ 1 : "a", 2 : "b" ] + + copyAndSetTest[1] = nil + copyAndSetTest[2] = nil + + XCTAssertEqual(copyAndSetTest.count, 0) + XCTAssertEqual(copyAndSetTest[1], nil) + XCTAssertEqual(copyAndSetTest[2], nil) + } +} From 449e5706bd3ec9e118886aa41cb2d02e3cc19953 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 11:54:03 +0200 Subject: [PATCH 002/176] [Capsule] Add package configuration --- Package.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Package.swift b/Package.swift index 97f17ce44..b817fde78 100644 --- a/Package.swift +++ b/Package.swift @@ -113,5 +113,14 @@ let package = Package( name: "PriorityQueueTests", dependencies: ["PriorityQueueModule"], swiftSettings: settings), + + // HashMap + .target( + name: "Capsule", + swiftSettings: settings), + .testTarget( + name: "CapsuleTests", + dependencies: ["Capsule", "_CollectionsTestSupport"], + swiftSettings: settings), ] ) From 13bd00bda43a3d009b51f95a2a5c1ac5df78c40d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 11:55:14 +0200 Subject: [PATCH 003/176] [Capsule] Adhere to `CollectionsTestSupport` in test code --- Tests/CapsuleTests/CapsuleTests.swift | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift index 60f7fca68..ef6d6ab9e 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -1,7 +1,8 @@ import XCTest -@testable import Capsule +import _CollectionsTestSupport +@_spi(Testing) import Capsule -final class CapsuleTests: XCTestCase { +final class CapsuleTests: CollectionTestCase { func testSubscriptAdd() { var copyAndSetTest: HashMap = [ 1 : "a", 2 : "b" ] @@ -9,11 +10,11 @@ final class CapsuleTests: XCTestCase { copyAndSetTest[3] = "x" copyAndSetTest[4] = "y" - XCTAssertEqual(copyAndSetTest.count, 4) - XCTAssertEqual(copyAndSetTest[1], "a") - XCTAssertEqual(copyAndSetTest[2], "b") - XCTAssertEqual(copyAndSetTest[3], "x") - XCTAssertEqual(copyAndSetTest[4], "y") + expectEqual(copyAndSetTest.count, 4) + expectEqual(copyAndSetTest[1], "a") + expectEqual(copyAndSetTest[2], "b") + expectEqual(copyAndSetTest[3], "x") + expectEqual(copyAndSetTest[4], "y") } func testSubscriptOverwrite() { @@ -23,9 +24,9 @@ final class CapsuleTests: XCTestCase { copyAndSetTest[1] = "x" copyAndSetTest[2] = "y" - XCTAssertEqual(copyAndSetTest.count, 2) - XCTAssertEqual(copyAndSetTest[1], "x") - XCTAssertEqual(copyAndSetTest[2], "y") + expectEqual(copyAndSetTest.count, 2) + expectEqual(copyAndSetTest[1], "x") + expectEqual(copyAndSetTest[2], "y") } func testSubscriptRemove() { @@ -35,8 +36,8 @@ final class CapsuleTests: XCTestCase { copyAndSetTest[1] = nil copyAndSetTest[2] = nil - XCTAssertEqual(copyAndSetTest.count, 0) - XCTAssertEqual(copyAndSetTest[1], nil) - XCTAssertEqual(copyAndSetTest[2], nil) + expectEqual(copyAndSetTest.count, 0) + expectEqual(copyAndSetTest[1], nil) + expectEqual(copyAndSetTest[2], nil) } } From 03913fd7454a4f7f45726ec9cc4f26932a016463 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 11:58:39 +0200 Subject: [PATCH 004/176] [Capsule] Increase visibility of `count` and `subscript` to `public` --- Sources/Capsule/HashMap.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 97c1a7af1..79f0317ca 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -30,7 +30,7 @@ public struct HashMap where Key : Hashable { var isEmpty: Bool { cachedSize == 0 } - var count: Int { cachedSize } + public var count: Int { cachedSize } var capacity: Int { count } @@ -38,7 +38,7 @@ public struct HashMap where Key : Hashable { /// Accessing Keys and Values /// - subscript(_ key: Key) -> Value? { + public subscript(_ key: Key) -> Value? { get { return get(key) } @@ -51,7 +51,7 @@ public struct HashMap where Key : Hashable { } } - subscript(_ key: Key, default: () -> Value) -> Value { + public subscript(_ key: Key, default: () -> Value) -> Value { return get(key) ?? `default`() } From 097e6b75b791f9a7d80f9bbd61510fdb3204d102 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 12:20:15 +0200 Subject: [PATCH 005/176] [Capsule] Update copyright headers --- Sources/Capsule/Common.swift | 10 +++++++--- Sources/Capsule/HashMap.swift | 10 +++++++--- Tests/CapsuleTests/CapsuleTests.swift | 11 +++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Sources/Capsule/Common.swift b/Sources/Capsule/Common.swift index e5f099896..65086b9e8 100644 --- a/Sources/Capsule/Common.swift +++ b/Sources/Capsule/Common.swift @@ -1,9 +1,13 @@ +//===----------------------------------------------------------------------===// // -// Common.swift -// +// This source file is part of the Swift Collections open source project // -// Created by Michael J. Steindorfer on 11/23/19. +// Copyright (c) 2019 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 func Foundation.ceil diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 79f0317ca..6bdbf1534 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -1,9 +1,13 @@ +//===----------------------------------------------------------------------===// // -// HashMap.swift -// +// This source file is part of the Swift Collections open source project // -// Created by Michael J. Steindorfer on 11/21/19. +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// public struct HashMap where Key : Hashable { let rootNode: MapNode diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift index ef6d6ab9e..0aa0e7d2c 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 XCTest import _CollectionsTestSupport @_spi(Testing) import Capsule From 3cb6fb3251fec9adef7eec53e0abef9897851c62 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 13:36:21 +0200 Subject: [PATCH 006/176] [Capsule] Split `HashMap` extensions into separate files --- Sources/Capsule/HashMap+Equatable.swift | 18 +++++++++++ ...shMap+ExpressibleByDictionaryLiteral.swift | 19 ++++++++++++ Sources/Capsule/HashMap+Hashable.swift | 20 ++++++++++++ Sources/Capsule/HashMap+Sequence.swift | 16 ++++++++++ Sources/Capsule/HashMap.swift | 31 ------------------- 5 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 Sources/Capsule/HashMap+Equatable.swift create mode 100644 Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift create mode 100644 Sources/Capsule/HashMap+Hashable.swift create mode 100644 Sources/Capsule/HashMap+Sequence.swift diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift new file mode 100644 index 000000000..904ab0536 --- /dev/null +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -0,0 +1,18 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 HashMap : Equatable { + public static func == (lhs: HashMap, rhs: HashMap) -> Bool { + lhs.cachedSize == rhs.cachedSize && + lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && + (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) + } +} diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift new file mode 100644 index 000000000..ee42328d7 --- /dev/null +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 HashMap : ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (Key, Value)...) { + let map = elements.reduce(Self()) { (map, element) in let (key, value) = element + return map.insert(key: key, value: value) + } + self.init(map) + } +} diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift new file mode 100644 index 000000000..48f5b79af --- /dev/null +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 HashMap : Hashable { + public var hashValue: Int { + preconditionFailure("Not yet implemented") + } + + public func hash(into: inout Hasher) { + preconditionFailure("Not yet implemented") + } +} diff --git a/Sources/Capsule/HashMap+Sequence.swift b/Sources/Capsule/HashMap+Sequence.swift new file mode 100644 index 000000000..c77c46735 --- /dev/null +++ b/Sources/Capsule/HashMap+Sequence.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 HashMap : Sequence { + public __consuming func makeIterator() -> MapKeyValueTupleIterator { + return MapKeyValueTupleIterator(rootNode: rootNode) + } +} diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 6bdbf1534..e7dc8dc97 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -92,37 +92,6 @@ public struct HashMap where Key : Hashable { } } -extension HashMap : ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (Key, Value)...) { - let map = elements.reduce(Self()) { (map, element) in let (key, value) = element - return map.insert(key: key, value: value) - } - self.init(map) - } -} - -extension HashMap : Equatable, Hashable { - public static func == (lhs: HashMap, rhs: HashMap) -> Bool { - lhs.cachedSize == rhs.cachedSize && - lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && - (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) - } - - public var hashValue: Int { - preconditionFailure("Not yet implemented") - } - - public func hash(into: inout Hasher) { - preconditionFailure("Not yet implemented") - } -} - -extension HashMap : Sequence { - public __consuming func makeIterator() -> MapKeyValueTupleIterator { - return MapKeyValueTupleIterator(rootNode: rootNode) - } -} - fileprivate let EmptyMapNode = BitmapIndexedMapNode(0, 0, Array()) // TODO assess if convertible to a `protocol`. Variance / constraints on return types make it difficult. From 88051725bbafc89a761f3720257c600abe643763 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 13:40:14 +0200 Subject: [PATCH 007/176] [Capsule] Rename `Common.swift` -> `_Common.swift` --- Sources/Capsule/{Common.swift => _Common.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/Capsule/{Common.swift => _Common.swift} (100%) diff --git a/Sources/Capsule/Common.swift b/Sources/Capsule/_Common.swift similarity index 100% rename from Sources/Capsule/Common.swift rename to Sources/Capsule/_Common.swift From 1598e22e45668e72c759f18dd3dc77e4973cb63d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 14:28:46 +0200 Subject: [PATCH 008/176] [Capsule] Split nested structs and classes into separate files --- Sources/Capsule/HashMap.swift | 347 -------------------- Sources/Capsule/_BitmapIndexedMapNode.swift | 293 +++++++++++++++++ Sources/Capsule/_MapEffect.swift | 24 ++ Sources/Capsule/_MapNode.swift | 60 ++++ 4 files changed, 377 insertions(+), 347 deletions(-) create mode 100644 Sources/Capsule/_BitmapIndexedMapNode.swift create mode 100644 Sources/Capsule/_MapEffect.swift create mode 100644 Sources/Capsule/_MapNode.swift diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index e7dc8dc97..d0a6237d8 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -94,353 +94,6 @@ public struct HashMap where Key : Hashable { fileprivate let EmptyMapNode = BitmapIndexedMapNode(0, 0, Array()) -// TODO assess if convertible to a `protocol`. Variance / constraints on return types make it difficult. -class MapNode : Node where Key : Hashable { - - func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { - preconditionFailure("This method must be overridden") - } - - func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { - preconditionFailure("This method must be overridden") - } - - func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - preconditionFailure("This method must be overridden") - } - - func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - preconditionFailure("This method must be overridden") - } - - var hasNodes: Bool { - preconditionFailure("This method must be overridden") - } - - var nodeArity: Int { - preconditionFailure("This method must be overridden") - } - - func getNode(_ index: Int) -> MapNode { - preconditionFailure("This method must be overridden") - } - - var hasPayload: Bool { - preconditionFailure("This method must be overridden") - } - - var payloadArity: Int { - preconditionFailure("This method must be overridden") - } - - func getPayload(_ index: Int) -> (Key, Value) { - preconditionFailure("This method must be overridden") - } -} - -extension MapNode : Equatable { - static func == (lhs: MapNode, rhs: MapNode) -> Bool { - preconditionFailure("Not yet implemented") - } -} - -fileprivate var TupleLength: Int { 2 } - -final class BitmapIndexedMapNode : MapNode where Key : Hashable { - let dataMap: Int - let nodeMap: Int - let content: [Any] - - init(_ dataMap: Int, _ nodeMap: Int, _ content: [Any]) { - self.dataMap = dataMap - self.nodeMap = nodeMap - self.content = content - } - - override func getPayload(_ index: Int) -> (Key, Value) { - (content[TupleLength * index + 0] as! Key, - content[TupleLength * index + 1] as! Value) - } - - override func getNode(_ index: Int) -> MapNode { - content[content.count - 1 - index] as! MapNode - } - - override func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { - let mask = maskFrom(keyHash, shift) - let bitpos = bitposFrom(mask) - - if ((dataMap & bitpos) != 0) { - let index = indexFrom(dataMap, mask, bitpos) - let payload = self.getPayload(index) - if (key == payload.0) { return payload.1 } else { return nil } - } - - if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) - } - - return nil - } - - override func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { - let mask = maskFrom(keyHash, shift) - let bitpos = bitposFrom(mask) - - if ((dataMap & bitpos) != 0) { - let index = indexFrom(dataMap, mask, bitpos) - let payload = self.getPayload(index) - return key == payload.0 - } - - if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) - } - - return false - } - - override func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - let mask = maskFrom(keyHash, shift) - let bitpos = bitposFrom(mask) - - if ((dataMap & bitpos) != 0) { - let index = indexFrom(dataMap, mask, bitpos) - let (key0, value0) = self.getPayload(index) - - if (key0 == key) { - effect.setReplacedValue() - return copyAndSetValue(bitpos, value) - } else { - let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) - effect.setModified() - return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) - } - } - - if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) - - let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { - return self - } else { - return copyAndSetNode(bitpos, subNodeNew) - } - } - - effect.setModified() - return copyAndInsertValue(bitpos, key, value) - } - - override func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - let mask = maskFrom(keyHash, shift) - let bitpos = bitposFrom(mask) - - if ((dataMap & bitpos) != 0) { - let index = indexFrom(dataMap, mask, bitpos) - let (key0, _) = self.getPayload(index) - - if (key0 == key) { - effect.setModified() - if (self.payloadArity == 2 && self.nodeArity == 0) { - /* - * Create new node with remaining pair. The new node will a) either become the new root - * returned, or b) unwrapped and inlined during returning. - */ - let newDataMap: Int - if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } - if (index == 0) { - let (k, v) = getPayload(1) - return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v) ) - } else { - let (k, v) = getPayload(0) - return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) - } - } else { return copyAndRemoveValue(bitpos) } - } else { return self } - } - - if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) - - let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) - - if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: - if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result - return subNodeNew - } - else { // inline value (move to front) - return copyAndMigrateFromNodeToInline(bitpos, subNodeNew) - } - - default: // equivalent to `case 2...` - // modify current node (set replacement node) - return copyAndSetNode(bitpos, subNodeNew) - } - } - - return self - } - - func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> MapNode { - if (shift >= HashCodeLength) { - preconditionFailure("Not yet implemented") - } else { - let mask0 = maskFrom(keyHash0, shift) - let mask1 = maskFrom(keyHash1, shift) - - if (mask0 != mask1) { - // unique prefixes, payload fits on same level - let dataMap = bitposFrom(mask0) | bitposFrom(mask1) - - if (mask0 < mask1) { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) - } else { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) - } - } else { - // identical prefixes, payload must be disambiguated deeper in the trie - let nodeMap = bitposFrom(mask0) - let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) - - return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) - } - } - } - - override var hasNodes: Bool { nodeMap != 0 } - - override var nodeArity: Int { nodeMap.nonzeroBitCount } - - override var hasPayload: Bool { dataMap != 0 } - - override var payloadArity: Int { dataMap.nonzeroBitCount } - - func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos - 1)).nonzeroBitCount } - - func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos - 1)).nonzeroBitCount } - - /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design - /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: - /// - /// ``` - /// class Node { - /// var src: [Any] - /// func updateInlineOrCopy(idx: Int, newValue: Any) { - /// if isKnownUniquelyReferenced(&self) { // this isn't supported ... - /// src[idx] = newValue - /// return self - /// } else { - /// var dst = self.content - /// dst[idx] = newValue - /// return Node(dst) - /// } - /// } - /// } - /// ``` - /// - /// Some more context: - /// * Node should be a reference counted data type (i.e., `class`) - /// * In a optimized version `src` would be gone, and `Node` likely become a subclass of `ManagedBuffer` - /// * I want to check `isKnownUniquelyReferenced(&self)` since `updateInlineOrCopy` should be recursive call that decides upon returning from recursion if modifications are necessary - /// - /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. - /// This would be very invasive though and make problem logic hard to understand and maintain. - func copyAndSetValue(_ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) + 1 - - var dst = self.content - dst[idx] = newValue - - return BitmapIndexedMapNode(dataMap, nodeMap, dst) - } - - func copyAndSetNode(_ bitpos: Int, _ newNode: MapNode) -> BitmapIndexedMapNode { - let idx = self.content.count - 1 - self.nodeIndex(bitpos) - - var dst = self.content - dst[idx] = newNode - - return BitmapIndexedMapNode(dataMap, nodeMap, dst) - } - - func copyAndInsertValue(_ bitpos: Int, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) - - var dst = self.content - dst.insert(contentsOf: [key, value], at: idx) - - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, dst) - } - - func copyAndRemoveValue(_ bitpos: Int) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) - - var dst = self.content - dst.removeSubrange(idx..) -> BitmapIndexedMapNode { - let idxOld = TupleLength * dataIndex(bitpos) - let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) - - var dst = self.content - dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - nodeIndex(bitpos) - let idxNew = TupleLength * dataIndex(bitpos) - - let (key, value) = node.getPayload(0) - - var dst = self.content - dst.remove(at: idxOld) - dst.insert(contentsOf: [key, value], at: idxNew) - - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, dst) - } -} - -extension BitmapIndexedMapNode /* : Equatable */ { - static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { - lhs === rhs || - lhs.nodeMap == rhs.nodeMap && - lhs.dataMap == rhs.dataMap && - deepContentEquality(lhs.content, rhs.content, lhs.content.count) - } - - private static func deepContentEquality(_ a1: [Any], _ a2: [Any], _ length: Int) -> Bool { - preconditionFailure("Not yet implemented") - } -} - -struct MapEffect { - var modified: Bool = false - var replacedValue: Bool = false - - mutating func setModified() { - self.modified = true - } - - mutating func setReplacedValue() { - self.modified = true - self.replacedValue = true - } -} - public struct MapKeyValueTupleIterator { private var baseIterator: ChampBaseIterator> diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift new file mode 100644 index 000000000..37fd25638 --- /dev/null +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -0,0 +1,293 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// + +fileprivate var TupleLength: Int { 2 } + +final class BitmapIndexedMapNode : MapNode where Key : Hashable { + let dataMap: Int + let nodeMap: Int + let content: [Any] + + init(_ dataMap: Int, _ nodeMap: Int, _ content: [Any]) { + self.dataMap = dataMap + self.nodeMap = nodeMap + self.content = content + } + + override func getPayload(_ index: Int) -> (Key, Value) { + (content[TupleLength * index + 0] as! Key, + content[TupleLength * index + 1] as! Value) + } + + override func getNode(_ index: Int) -> MapNode { + content[content.count - 1 - index] as! MapNode + } + + override func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let payload = self.getPayload(index) + if (key == payload.0) { return payload.1 } else { return nil } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + } + + return nil + } + + override func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let payload = self.getPayload(index) + return key == payload.0 + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + } + + return false + } + + override func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let (key0, value0) = self.getPayload(index) + + if (key0 == key) { + effect.setReplacedValue() + return copyAndSetValue(bitpos, value) + } else { + let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) + effect.setModified() + return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self + } else { + return copyAndSetNode(bitpos, subNodeNew) + } + } + + effect.setModified() + return copyAndInsertValue(bitpos, key, value) + } + + override func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + let index = indexFrom(dataMap, mask, bitpos) + let (key0, _) = self.getPayload(index) + + if (key0 == key) { + effect.setModified() + if (self.payloadArity == 2 && self.nodeArity == 0) { + /* + * Create new node with remaining pair. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + let newDataMap: Int + if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } + if (index == 0) { + let (k, v) = getPayload(1) + return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v) ) + } else { + let (k, v) = getPayload(0) + return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) + } + } else { return copyAndRemoveValue(bitpos) } + } else { return self } + } + + if ((nodeMap & bitpos) != 0) { + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + return subNodeNew + } + else { // inline value (move to front) + return copyAndMigrateFromNodeToInline(bitpos, subNodeNew) + } + + default: // equivalent to `case 2...` + // modify current node (set replacement node) + return copyAndSetNode(bitpos, subNodeNew) + } + } + + return self + } + + func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> MapNode { + if (shift >= HashCodeLength) { + preconditionFailure("Not yet implemented") + } else { + let mask0 = maskFrom(keyHash0, shift) + let mask1 = maskFrom(keyHash1, shift) + + if (mask0 != mask1) { + // unique prefixes, payload fits on same level + let dataMap = bitposFrom(mask0) | bitposFrom(mask1) + + if (mask0 < mask1) { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) + } else { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) + } + } else { + // identical prefixes, payload must be disambiguated deeper in the trie + let nodeMap = bitposFrom(mask0) + let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) + + return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) + } + } + } + + override var hasNodes: Bool { nodeMap != 0 } + + override var nodeArity: Int { nodeMap.nonzeroBitCount } + + override var hasPayload: Bool { dataMap != 0 } + + override var payloadArity: Int { dataMap.nonzeroBitCount } + + func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos - 1)).nonzeroBitCount } + + func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos - 1)).nonzeroBitCount } + + /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design + /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: + /// + /// ``` + /// class Node { + /// var src: [Any] + /// func updateInlineOrCopy(idx: Int, newValue: Any) { + /// if isKnownUniquelyReferenced(&self) { // this isn't supported ... + /// src[idx] = newValue + /// return self + /// } else { + /// var dst = self.content + /// dst[idx] = newValue + /// return Node(dst) + /// } + /// } + /// } + /// ``` + /// + /// Some more context: + /// * Node should be a reference counted data type (i.e., `class`) + /// * In a optimized version `src` would be gone, and `Node` likely become a subclass of `ManagedBuffer` + /// * I want to check `isKnownUniquelyReferenced(&self)` since `updateInlineOrCopy` should be recursive call that decides upon returning from recursion if modifications are necessary + /// + /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. + /// This would be very invasive though and make problem logic hard to understand and maintain. + func copyAndSetValue(_ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + 1 + + var dst = self.content + dst[idx] = newValue + + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } + + func copyAndSetNode(_ bitpos: Int, _ newNode: MapNode) -> BitmapIndexedMapNode { + let idx = self.content.count - 1 - self.nodeIndex(bitpos) + + var dst = self.content + dst[idx] = newNode + + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } + + func copyAndInsertValue(_ bitpos: Int, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + + var dst = self.content + dst.insert(contentsOf: [key, value], at: idx) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, dst) + } + + func copyAndRemoveValue(_ bitpos: Int) -> BitmapIndexedMapNode { + let idx = TupleLength * dataIndex(bitpos) + + var dst = self.content + dst.removeSubrange(idx..) -> BitmapIndexedMapNode { + let idxOld = TupleLength * dataIndex(bitpos) + let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) + + var dst = self.content + dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { + let idxOld = self.content.count - 1 - nodeIndex(bitpos) + let idxNew = TupleLength * dataIndex(bitpos) + + let (key, value) = node.getPayload(0) + + var dst = self.content + dst.remove(at: idxOld) + dst.insert(contentsOf: [key, value], at: idxNew) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, dst) + } +} + +extension BitmapIndexedMapNode /* : Equatable */ { + static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { + lhs === rhs || + lhs.nodeMap == rhs.nodeMap && + lhs.dataMap == rhs.dataMap && + deepContentEquality(lhs.content, rhs.content, lhs.content.count) + } + + private static func deepContentEquality(_ a1: [Any], _ a2: [Any], _ length: Int) -> Bool { + preconditionFailure("Not yet implemented") + } +} diff --git a/Sources/Capsule/_MapEffect.swift b/Sources/Capsule/_MapEffect.swift new file mode 100644 index 000000000..70ae452bf --- /dev/null +++ b/Sources/Capsule/_MapEffect.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// + +struct MapEffect { + var modified: Bool = false + var replacedValue: Bool = false + + mutating func setModified() { + self.modified = true + } + + mutating func setReplacedValue() { + self.modified = true + self.replacedValue = true + } +} diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift new file mode 100644 index 000000000..ce80c70f3 --- /dev/null +++ b/Sources/Capsule/_MapNode.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// + +// TODO assess if convertible to a `protocol`. Variance / constraints on return types make it difficult. +class MapNode : Node where Key : Hashable { + + func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { + preconditionFailure("This method must be overridden") + } + + func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { + preconditionFailure("This method must be overridden") + } + + func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + preconditionFailure("This method must be overridden") + } + + func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + preconditionFailure("This method must be overridden") + } + + var hasNodes: Bool { + preconditionFailure("This method must be overridden") + } + + var nodeArity: Int { + preconditionFailure("This method must be overridden") + } + + func getNode(_ index: Int) -> MapNode { + preconditionFailure("This method must be overridden") + } + + var hasPayload: Bool { + preconditionFailure("This method must be overridden") + } + + var payloadArity: Int { + preconditionFailure("This method must be overridden") + } + + func getPayload(_ index: Int) -> (Key, Value) { + preconditionFailure("This method must be overridden") + } +} + +extension MapNode : Equatable { + static func == (lhs: MapNode, rhs: MapNode) -> Bool { + preconditionFailure("Not yet implemented") + } +} From b3e1e9d446dce95072c0d5ed4f42256f4afee201 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 14:37:00 +0200 Subject: [PATCH 009/176] [Capsule] Harmonize identation --- Sources/Capsule/HashMap.swift | 12 +++--- Sources/Capsule/_BitmapIndexedMapNode.swift | 48 ++++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index d0a6237d8..fcd93f017 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -73,11 +73,11 @@ public struct HashMap where Key : Hashable { let newRootNode = rootNode.updated(key, value, keyHash, 0, &effect) if (effect.modified) { - if (effect.replacedValue) { - return Self(newRootNode, cachedKeySetHashCode, cachedSize) - } else { - return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize + 1) - } + if (effect.replacedValue) { + return Self(newRootNode, cachedKeySetHashCode, cachedSize) + } else { + return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize + 1) + } } else { return self } } @@ -87,7 +87,7 @@ public struct HashMap where Key : Hashable { let newRootNode = rootNode.removed(key, keyHash, 0, &effect) if (effect.modified) { - return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) + return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) } else { return self } } } diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 37fd25638..f8c15a822 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -42,8 +42,8 @@ final class BitmapIndexedMapNode : MapNode where Key : H } if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) } return nil @@ -60,8 +60,8 @@ final class BitmapIndexedMapNode : MapNode where Key : H } if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } return false @@ -72,29 +72,29 @@ final class BitmapIndexedMapNode : MapNode where Key : H let bitpos = bitposFrom(mask) if ((dataMap & bitpos) != 0) { - let index = indexFrom(dataMap, mask, bitpos) - let (key0, value0) = self.getPayload(index) - - if (key0 == key) { - effect.setReplacedValue() - return copyAndSetValue(bitpos, value) - } else { - let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) - effect.setModified() - return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) - } + let index = indexFrom(dataMap, mask, bitpos) + let (key0, value0) = self.getPayload(index) + + if (key0 == key) { + effect.setReplacedValue() + return copyAndSetValue(bitpos, value) + } else { + let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) + effect.setModified() + return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + } } if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) - - let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { - return self - } else { - return copyAndSetNode(bitpos, subNodeNew) - } + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self + } else { + return copyAndSetNode(bitpos, subNodeNew) + } } effect.setModified() From ce5a6bd71a9520e6e928d586a48b706dd8c6f651 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 14:41:21 +0200 Subject: [PATCH 010/176] [Capsule] Update copyright headers Correctly reflects refining edits as well and not only initial draft date. --- Sources/Capsule/HashMap+Equatable.swift | 2 +- Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift | 2 +- Sources/Capsule/HashMap+Hashable.swift | 2 +- Sources/Capsule/HashMap+Sequence.swift | 2 +- Sources/Capsule/HashMap.swift | 2 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- Sources/Capsule/_Common.swift | 2 +- Sources/Capsule/_MapEffect.swift | 2 +- Sources/Capsule/_MapNode.swift | 2 +- Tests/CapsuleTests/CapsuleTests.swift | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift index 904ab0536..ee3f355e3 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index ee42328d7..53b43c9b0 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index 48f5b79af..1bdea4ad1 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/HashMap+Sequence.swift b/Sources/Capsule/HashMap+Sequence.swift index c77c46735..38a75cf79 100644 --- a/Sources/Capsule/HashMap+Sequence.swift +++ b/Sources/Capsule/HashMap+Sequence.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index fcd93f017..07c0a285b 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index f8c15a822..138a55f05 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 65086b9e8..4cca278b0 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/_MapEffect.swift b/Sources/Capsule/_MapEffect.swift index 70ae452bf..71e81732b 100644 --- a/Sources/Capsule/_MapEffect.swift +++ b/Sources/Capsule/_MapEffect.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index ce80c70f3..403168dd4 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift index 0aa0e7d2c..035eb4a1f 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 2020 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 From 29dd2c8b4f00c7d64446d2e490d1b35798f6d14e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 29 Apr 2021 15:06:09 +0200 Subject: [PATCH 011/176] [Capsule] Remove unused `XCTest` import --- Tests/CapsuleTests/CapsuleTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift index 035eb4a1f..55b5c1a5f 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -import XCTest import _CollectionsTestSupport @_spi(Testing) import Capsule From b518d56b6518de90b2547145f7be341a4dc10af3 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 30 Apr 2021 09:23:45 +0200 Subject: [PATCH 012/176] [Capsule] Implement `Hashable` extension for `HashMap` --- Sources/Capsule/HashMap+Hashable.swift | 20 +++++++++++------- Tests/CapsuleTests/CapsuleTests.swift | 29 +++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index 1bdea4ad1..9c834b79d 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -2,19 +2,23 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 HashMap : Hashable { - public var hashValue: Int { - preconditionFailure("Not yet implemented") - } - - public func hash(into: inout Hasher) { - preconditionFailure("Not yet implemented") +// TODO settle on (commutative) hash semantics that is reconcilable with `cachedKeySetHashCode` +extension HashMap : Hashable where Value : Hashable { + public func hash(into hasher: inout Hasher) { + var commutativeHash = 0 + for (k, v) in self { + var elementHasher = Hasher() + elementHasher.combine(k) + elementHasher.combine(v) + commutativeHash ^= elementHasher.finalize() + } + hasher.combine(commutativeHash) } } diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleTests.swift index 55b5c1a5f..af656cd48 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 @@ -50,4 +50,31 @@ final class CapsuleTests: CollectionTestCase { expectEqual(copyAndSetTest[1], nil) expectEqual(copyAndSetTest[2], nil) } + + private func hashPair(_ k: Key, _ v: Value) -> Int { + var hasher = Hasher() + hasher.combine(k) + hasher.combine(v) + return hasher.finalize() + } + + func testHashable() { + let copyAndSetTest: HashMap = + [ 1 : "a", 2 : "b" ] + + let hashPair1 = hashPair(1, "a") + let hashPair2 = hashPair(2, "b") + + var commutativeHasher = Hasher() + commutativeHasher.combine(hashPair1 ^ hashPair2) + + let expectedHashValue = commutativeHasher.finalize() + + expectEqual(copyAndSetTest.hashValue, expectedHashValue) + + var inoutHasher = Hasher() + copyAndSetTest.hash(into: &inoutHasher) + + expectEqual(inoutHasher.finalize(), expectedHashValue) + } } From 77ef6122d36c589e6f8ab3690d6bfe349bd3c6ec Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 30 Apr 2021 09:38:17 +0200 Subject: [PATCH 013/176] [Capsule] Rename and simplify smoke tests --- ...uleTests.swift => CapsuleSmokeTests.swift} | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) rename Tests/CapsuleTests/{CapsuleTests.swift => CapsuleSmokeTests.swift} (51%) diff --git a/Tests/CapsuleTests/CapsuleTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift similarity index 51% rename from Tests/CapsuleTests/CapsuleTests.swift rename to Tests/CapsuleTests/CapsuleSmokeTests.swift index af656cd48..0861adef3 100644 --- a/Tests/CapsuleTests/CapsuleTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -12,43 +12,40 @@ import _CollectionsTestSupport @_spi(Testing) import Capsule -final class CapsuleTests: CollectionTestCase { +final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { - var copyAndSetTest: HashMap = - [ 1 : "a", 2 : "b" ] + var map: HashMap = [ 1 : "a", 2 : "b" ] - copyAndSetTest[3] = "x" - copyAndSetTest[4] = "y" + map[3] = "x" + map[4] = "y" - expectEqual(copyAndSetTest.count, 4) - expectEqual(copyAndSetTest[1], "a") - expectEqual(copyAndSetTest[2], "b") - expectEqual(copyAndSetTest[3], "x") - expectEqual(copyAndSetTest[4], "y") + expectEqual(map.count, 4) + expectEqual(map[1], "a") + expectEqual(map[2], "b") + expectEqual(map[3], "x") + expectEqual(map[4], "y") } func testSubscriptOverwrite() { - var copyAndSetTest: HashMap = - [ 1 : "a", 2 : "b" ] + var map: HashMap = [ 1 : "a", 2 : "b" ] - copyAndSetTest[1] = "x" - copyAndSetTest[2] = "y" + map[1] = "x" + map[2] = "y" - expectEqual(copyAndSetTest.count, 2) - expectEqual(copyAndSetTest[1], "x") - expectEqual(copyAndSetTest[2], "y") + expectEqual(map.count, 2) + expectEqual(map[1], "x") + expectEqual(map[2], "y") } func testSubscriptRemove() { - var copyAndSetTest: HashMap = - [ 1 : "a", 2 : "b" ] + var map: HashMap = [ 1 : "a", 2 : "b" ] - copyAndSetTest[1] = nil - copyAndSetTest[2] = nil + map[1] = nil + map[2] = nil - expectEqual(copyAndSetTest.count, 0) - expectEqual(copyAndSetTest[1], nil) - expectEqual(copyAndSetTest[2], nil) + expectEqual(map.count, 0) + expectEqual(map[1], nil) + expectEqual(map[2], nil) } private func hashPair(_ k: Key, _ v: Value) -> Int { @@ -59,8 +56,7 @@ final class CapsuleTests: CollectionTestCase { } func testHashable() { - let copyAndSetTest: HashMap = - [ 1 : "a", 2 : "b" ] + let map: HashMap = [ 1 : "a", 2 : "b" ] let hashPair1 = hashPair(1, "a") let hashPair2 = hashPair(2, "b") @@ -70,10 +66,10 @@ final class CapsuleTests: CollectionTestCase { let expectedHashValue = commutativeHasher.finalize() - expectEqual(copyAndSetTest.hashValue, expectedHashValue) + expectEqual(map.hashValue, expectedHashValue) var inoutHasher = Hasher() - copyAndSetTest.hash(into: &inoutHasher) + map.hash(into: &inoutHasher) expectEqual(inoutHasher.finalize(), expectedHashValue) } From e09c8db4284e34b1c8f47331b84d952d4004f23b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 4 May 2021 09:08:49 +0200 Subject: [PATCH 014/176] [Capsule] Implement preliminary hash-collision resolution --- Sources/Capsule/HashMap+Equatable.swift | 3 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 +- Sources/Capsule/_HashCollisionMapNode.swift | 92 +++++++++++ Sources/Capsule/_MapNode.swift | 2 +- Tests/CapsuleTests/CapsuleSmokeTests.swift | 169 ++++++++++++++++++++ 5 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 Sources/Capsule/_HashCollisionMapNode.swift diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift index ee3f355e3..c1c34776f 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -9,7 +9,8 @@ // //===----------------------------------------------------------------------===// -extension HashMap : Equatable { +// TODO check Dictionary semantics of Equatable (i.e., if it only compares keys or also values) +extension HashMap : Equatable where Value : Equatable { public static func == (lhs: HashMap, rhs: HashMap) -> Bool { lhs.cachedSize == rhs.cachedSize && lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 138a55f05..5a31a9105 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -156,7 +156,7 @@ final class BitmapIndexedMapNode : MapNode where Key : H func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> MapNode { if (shift >= HashCodeLength) { - preconditionFailure("Not yet implemented") + return HashCollisionMapNode(keyHash0, [(key0, value0), (key1, value1)]) } else { let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(keyHash1, shift) @@ -279,7 +279,7 @@ final class BitmapIndexedMapNode : MapNode where Key : H } } -extension BitmapIndexedMapNode /* : Equatable */ { +extension BitmapIndexedMapNode /* : Equatable where Value : Equatable */ { static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { lhs === rhs || lhs.nodeMap == rhs.nodeMap && diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift new file mode 100644 index 000000000..57648cb6c --- /dev/null +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +final class HashCollisionMapNode : MapNode where Key : Hashable { + let hash: Int + let content: Array<(Key, Value)> + + init(_ hash: Int, _ content: Array<(Key, Value)>) { + precondition(content.count >= 2) + precondition(content.map { $0.0 }.allSatisfy {$0.hashValue == hash}) + + self.hash = hash + self.content = content + } + + override func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { + if (self.hash == hash) { + return content.first(where: { key == $0.0 }).map { $0.1 } + } else { return nil } + } + + override func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { + return self.hash == hash && content.contains(where: { key == $0.0 }) + } + +// // TODO requires Value to be Equatable +// func contains(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int) -> Bool { +// return self.hash == hash && content.contains(where: { key == $0.0 && value == $0.1 }) +// } + + override func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + + // TODO check if key/value-pair check should be added (requires value to be Equitable) + if (self.containsKey(key, hash, shift)) { + let index = content.firstIndex(where: { key == $0.0 })! + let updatedContent = content[0.. MapNode { + if (!self.containsKey(key, hash, shift)) { + return self + } else { + effect.setModified() + let updatedContent = content.filter { $0.0 != key } + assert(updatedContent.count == content.count - 1) + + switch updatedContent.count { + case 1: + let (k, v) = updatedContent[0].self + return BitmapIndexedMapNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) + default: + return HashCollisionMapNode(hash, updatedContent) + } + } + } + + override var hasNodes: Bool { false } + + override var nodeArity: Int { 0 } + + override func getNode(_ index: Int) -> MapNode { + preconditionFailure("No sub-nodes present in hash-collision leaf node") + } + + override var hasPayload: Bool { true } + + override var payloadArity: Int { content.count } + + override func getPayload(_ index: Int) -> (Key, Value) { content[index] } +} + +extension HashCollisionMapNode /* : Equatable where Value : Equatable */ { + static func == (lhs: HashCollisionMapNode, rhs: HashCollisionMapNode) -> Bool { + preconditionFailure("Not yet implemented") + } +} diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index 403168dd4..b047d4a7f 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -53,7 +53,7 @@ class MapNode : Node where Key : Hashable { } } -extension MapNode : Equatable { +extension MapNode : Equatable where Value : Equatable { static func == (lhs: MapNode, rhs: MapNode) -> Bool { preconditionFailure("Not yet implemented") } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 0861adef3..b937d154d 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -73,4 +73,173 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(inoutHasher.finalize(), expectedHashValue) } + + func testCompactionWhenDeletingFromHashCollisionNode1() { + let map: HashMap = [:] + + + var res1 = map + res1[CollidableInt(11, 1)] = CollidableInt(11, 1) + res1[CollidableInt(12, 1)] = CollidableInt(12, 1) + + expectEqual(res1.count, 2) + expectTrue(res1.contains(CollidableInt(11, 1))) + expectTrue(res1.contains(CollidableInt(12, 1))) + + + var res2 = res1 + res2[CollidableInt(12, 1)] = nil + expectTrue(res2.contains(CollidableInt(11, 1))) + expectFalse(res2.contains(CollidableInt(12, 1))) + + expectEqual(res2.count, 1) + // expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1)]), res2) + + + var res3 = res1 + res3[CollidableInt(11, 1)] = nil + expectFalse(res3.contains(CollidableInt(11, 1))) + expectTrue(res3.contains(CollidableInt(12, 1))) + + expectEqual(res3.count, 1) + // expectEqual(HashMap.init([CollidableInt(12, 1) : CollidableInt(12, 1)]), res3) + + + var resX = res1 + resX[CollidableInt(32769)] = CollidableInt(32769) + resX[CollidableInt(12, 1)] = nil + expectTrue(resX.contains(CollidableInt(11, 1))) + expectFalse(resX.contains(CollidableInt(12, 1))) + expectTrue(resX.contains(CollidableInt(32769))) + + expectEqual(resX.count, 2) + // expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(32769) : CollidableInt(32769)]), resX) + } + + func testCompactionWhenDeletingFromHashCollisionNode2() { + let map: HashMap = [:] + + + var res1 = map + res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) + res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) + + expectEqual(res1.count, 2) + expectTrue(res1.contains(CollidableInt(32769_1, 32769))) + expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + + + var res2 = res1 + res2[CollidableInt(1)] = CollidableInt(1) + + expectEqual(res2.count, 3) + expectTrue(res2.contains(CollidableInt(1))) + expectTrue(res2.contains(CollidableInt(32769_1, 32769))) + expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + + + var res3 = res2 + res3[CollidableInt(32769_2, 32769)] = nil + + expectEqual(res3.count, 2) + expectTrue(res3.contains(CollidableInt(1))) + expectTrue(res3.contains(CollidableInt(32769_1, 32769))) + + + // expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + } + + func testCompactionWhenDeletingFromHashCollisionNode3() { + let map: HashMap = [:] + + + var res1 = map + res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) + res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) + + expectEqual(res1.count, 2) + expectTrue(res1.contains(CollidableInt(32769_1, 32769))) + expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + + + var res2 = res1 + res2[CollidableInt(1)] = CollidableInt(1) + + expectEqual(res2.count, 3) + expectTrue(res2.contains(CollidableInt(1))) + expectTrue(res2.contains(CollidableInt(32769_1, 32769))) + expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + + + var res3 = res2 + res3[CollidableInt(1)] = nil + + expectEqual(res3.count, 2) + expectTrue(res3.contains(CollidableInt(32769_1, 32769))) + expectTrue(res3.contains(CollidableInt(32769_2, 32769))) + + + // expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + + } + + func testCompactionWhenDeletingFromHashCollisionNode4() { + let map: HashMap = [:] + + + var res1 = map + res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) + res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) + + expectEqual(res1.count, 2) + expectTrue(res1.contains(CollidableInt(32769_1, 32769))) + expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + + + var res2 = res1 + res2[CollidableInt(5)] = CollidableInt(5) + + expectEqual(res2.count, 3) + expectTrue(res2.contains(CollidableInt(5))) + expectTrue(res2.contains(CollidableInt(32769_1, 32769))) + expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + + + var res3 = res2 + res3[CollidableInt(5)] = nil + + expectEqual(res3.count, 2) + expectTrue(res3.contains(CollidableInt(32769_1, 32769))) + expectTrue(res3.contains(CollidableInt(32769_2, 32769))) + + + // expectEqual(res1, res3) + } +} + +fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hashable { + let value: Int + let hashValue: Int + + fileprivate init(_ value: Int) { + self.value = value + self.hashValue = value + } + + fileprivate init(_ value: Int, _ hashValue: Int) { + self.value = value + self.hashValue = hashValue + } + + var description: String { + return "\(value) [hash = \(hashValue)]" + } + + func hash(into hasher: inout Hasher) { + hasher.combine(hashValue) + } + + static func == (lhs: CollidableInt, rhs: CollidableInt) -> Bool { + return lhs.value == rhs.value + } } From d4ecaa0f75bfa13023582bf58797fb1cd3222655 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 4 May 2021 16:27:05 +0200 Subject: [PATCH 015/176] [Capsule] Make spine of trie monomorphic --- Sources/Capsule/HashMap.swift | 12 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 198 ++++++++++++++------ Sources/Capsule/_HashCollisionMapNode.swift | 40 ++-- Sources/Capsule/_MapNode.swift | 51 ++--- 4 files changed, 178 insertions(+), 123 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 07c0a285b..768cf277f 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -10,11 +10,11 @@ //===----------------------------------------------------------------------===// public struct HashMap where Key : Hashable { - let rootNode: MapNode + let rootNode: BitmapIndexedMapNode let cachedKeySetHashCode: Int let cachedSize: Int - fileprivate init(_ rootNode: MapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { + fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { self.rootNode = rootNode self.cachedKeySetHashCode = cachedKeySetHashCode self.cachedSize = cachedSize @@ -95,9 +95,9 @@ public struct HashMap where Key : Hashable { fileprivate let EmptyMapNode = BitmapIndexedMapNode(0, 0, Array()) public struct MapKeyValueTupleIterator { - private var baseIterator: ChampBaseIterator> + private var baseIterator: ChampBaseIterator> - init(rootNode: MapNode) { + init(rootNode: BitmapIndexedMapNode) { self.baseIterator = ChampBaseIterator(rootNode: rootNode) } } @@ -114,9 +114,9 @@ extension MapKeyValueTupleIterator : IteratorProtocol { } public struct MapKeyValueTupleReverseIterator { - private var baseIterator: ChampBaseReverseIterator> + private var baseIterator: ChampBaseReverseIterator> - init(rootNode: MapNode) { + init(rootNode: BitmapIndexedMapNode) { self.baseIterator = ChampBaseReverseIterator(rootNode: rootNode) } } diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 5a31a9105..fd11bf090 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,7 +11,7 @@ fileprivate var TupleLength: Int { 2 } -final class BitmapIndexedMapNode : MapNode where Key : Hashable { +final class BitmapIndexedMapNode : MapNode where Key : Hashable { let dataMap: Int let nodeMap: Int let content: [Any] @@ -22,16 +22,7 @@ final class BitmapIndexedMapNode : MapNode where Key : H self.content = content } - override func getPayload(_ index: Int) -> (Key, Value) { - (content[TupleLength * index + 0] as! Key, - content[TupleLength * index + 1] as! Value) - } - - override func getNode(_ index: Int) -> MapNode { - content[content.count - 1 - index] as! MapNode - } - - override func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { + func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -43,13 +34,18 @@ final class BitmapIndexedMapNode : MapNode where Key : H if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + + if (shift + BitPartitionSize >= HashCodeLength) { + return self.getCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) + } else { + return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + } } return nil } - override func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { + func containsKey(_ key: Key, _ keyHash: Int, _ shift: Int) -> Bool { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -61,13 +57,17 @@ final class BitmapIndexedMapNode : MapNode where Key : H if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + if (shift + BitPartitionSize >= HashCodeLength) { + return self.getCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + } else { + return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + } } return false } - override func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -86,14 +86,31 @@ final class BitmapIndexedMapNode : MapNode where Key : H } if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) + // TODO avoid code duplication and specialization + if (shift + BitPartitionSize >= HashCodeLength) { + // hash-collison sub-node - let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { - return self + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getCollisionNode(index) // NOTE difference in callee + + let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self + } else { + return copyAndSetCollisionNode(bitpos, subNodeNew) // NOTE difference in callee + } } else { - return copyAndSetNode(bitpos, subNodeNew) + // regular sub-node + + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self + } else { + return copyAndSetNode(bitpos, subNodeNew) + } } } @@ -101,7 +118,7 @@ final class BitmapIndexedMapNode : MapNode where Key : H return copyAndInsertValue(bitpos, key, value) } - override func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -130,48 +147,85 @@ final class BitmapIndexedMapNode : MapNode where Key : H } if ((nodeMap & bitpos) != 0) { - let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) + // TODO avoid code duplication and specialization + if (shift + BitPartitionSize >= HashCodeLength) { + // hash-collison sub-node - let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getCollisionNode(index) // NOTE difference in callee - if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: - if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result - return subNodeNew - } - else { // inline value (move to front) - return copyAndMigrateFromNodeToInline(bitpos, subNodeNew) + let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) + let newDataMap: Int = bitposFrom(maskFrom(subNodeNew.hash, 0)) + let (k, v) = subNodeNew.getPayload(0) + + return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) + } + else { // inline value (move to front) + return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) + } + + default: // equivalent to `case 2...` + // modify current node (set replacement node) + return copyAndSetCollisionNode(bitpos, subNodeNew) // NOTE difference in callee } + } else { + // regular sub-node + + let index = indexFrom(nodeMap, mask, bitpos) + let subNode = self.getNode(index) + + let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) - default: // equivalent to `case 2...` - // modify current node (set replacement node) - return copyAndSetNode(bitpos, subNodeNew) + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + return subNodeNew + } + else { // inline value (move to front) + return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) + } + + default: // equivalent to `case 2...` + // modify current node (set replacement node) + return copyAndSetNode(bitpos, subNodeNew) + } } } return self } - func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> MapNode { - if (shift >= HashCodeLength) { - return HashCollisionMapNode(keyHash0, [(key0, value0), (key1, value1)]) + func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { + let mask0 = maskFrom(keyHash0, shift) + let mask1 = maskFrom(keyHash1, shift) + + if (mask0 != mask1) { + // unique prefixes, payload fits on same level + let dataMap = bitposFrom(mask0) | bitposFrom(mask1) + + if (mask0 < mask1) { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) + } else { + return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) + } } else { - let mask0 = maskFrom(keyHash0, shift) - let mask1 = maskFrom(keyHash1, shift) + if (shift + BitPartitionSize >= HashCodeLength) { + // hash collision: prefix exhausted on next level - if (mask0 != mask1) { - // unique prefixes, payload fits on same level - let dataMap = bitposFrom(mask0) | bitposFrom(mask1) + let nodeMap = bitposFrom(mask0) + let node = HashCollisionMapNode(keyHash0, [(key0, value0), (key1, value1)]) - if (mask0 < mask1) { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) - } else { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) - } + return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) } else { - // identical prefixes, payload must be disambiguated deeper in the trie + // recurse: identical prefixes, payload must be disambiguated deeper in the trie + let nodeMap = bitposFrom(mask0) let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) @@ -180,13 +234,28 @@ final class BitmapIndexedMapNode : MapNode where Key : H } } - override var hasNodes: Bool { nodeMap != 0 } + var hasNodes: Bool { nodeMap != 0 } + + var nodeArity: Int { nodeMap.nonzeroBitCount } + + // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) + func getNode(_ index: Int) -> BitmapIndexedMapNode { + content[content.count - 1 - index] as! BitmapIndexedMapNode + } + + // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) + func getCollisionNode(_ index: Int) -> HashCollisionMapNode { + content[content.count - 1 - index] as! HashCollisionMapNode + } - override var nodeArity: Int { nodeMap.nonzeroBitCount } + var hasPayload: Bool { dataMap != 0 } - override var hasPayload: Bool { dataMap != 0 } + var payloadArity: Int { dataMap.nonzeroBitCount } - override var payloadArity: Int { dataMap.nonzeroBitCount } + func getPayload(_ index: Int) -> (Key, Value) { + (content[TupleLength * index + 0] as! Key, + content[TupleLength * index + 1] as! Value) + } func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos - 1)).nonzeroBitCount } @@ -227,7 +296,18 @@ final class BitmapIndexedMapNode : MapNode where Key : H return BitmapIndexedMapNode(dataMap, nodeMap, dst) } - func copyAndSetNode(_ bitpos: Int, _ newNode: MapNode) -> BitmapIndexedMapNode { + // TODO rework temporarily duplicated methods for type-safe access + func copyAndSetNode(_ bitpos: Int, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + let idx = self.content.count - 1 - self.nodeIndex(bitpos) + + var dst = self.content + dst[idx] = newNode + + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } + + // TODO rework temporarily duplicated methods for type-safe access + func copyAndSetCollisionNode(_ bitpos: Int, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) var dst = self.content @@ -254,7 +334,7 @@ final class BitmapIndexedMapNode : MapNode where Key : H return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, dst) } - func copyAndMigrateFromInlineToNode(_ bitpos: Int, _ node: MapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToNode(_ bitpos: Int, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idxOld = TupleLength * dataIndex(bitpos) let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) @@ -265,15 +345,13 @@ final class BitmapIndexedMapNode : MapNode where Key : H return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap | bitpos, dst) } - func copyAndMigrateFromNodeToInline(_ bitpos: Int, _ node: MapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromNodeToInline(_ bitpos: Int, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) let idxNew = TupleLength * dataIndex(bitpos) - let (key, value) = node.getPayload(0) - var dst = self.content dst.remove(at: idxOld) - dst.insert(contentsOf: [key, value], at: idxNew) + dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, dst) } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 57648cb6c..b8f43a93a 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -9,25 +9,25 @@ // //===----------------------------------------------------------------------===// -final class HashCollisionMapNode : MapNode where Key : Hashable { +final class HashCollisionMapNode : MapNode where Key : Hashable { let hash: Int let content: Array<(Key, Value)> init(_ hash: Int, _ content: Array<(Key, Value)>) { - precondition(content.count >= 2) + // precondition(content.count >= 2) precondition(content.map { $0.0 }.allSatisfy {$0.hashValue == hash}) self.hash = hash self.content = content } - override func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { + func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { if (self.hash == hash) { return content.first(where: { key == $0.0 }).map { $0.1 } } else { return nil } } - override func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { + func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { return self.hash == hash && content.contains(where: { key == $0.0 }) } @@ -36,7 +36,7 @@ final class HashCollisionMapNode : MapNode where Key : H // return self.hash == hash && content.contains(where: { key == $0.0 && value == $0.1 }) // } - override func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) if (self.containsKey(key, hash, shift)) { @@ -52,7 +52,9 @@ final class HashCollisionMapNode : MapNode where Key : H } } - override func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { + // TODO rethink such that `precondition(content.count >= 2)` holds + // TODO consider returning either type of `BitmapIndexedMapNode` and `HashCollisionMapNode` + func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { if (!self.containsKey(key, hash, shift)) { return self } else { @@ -60,29 +62,29 @@ final class HashCollisionMapNode : MapNode where Key : H let updatedContent = content.filter { $0.0 != key } assert(updatedContent.count == content.count - 1) - switch updatedContent.count { - case 1: - let (k, v) = updatedContent[0].self - return BitmapIndexedMapNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) - default: - return HashCollisionMapNode(hash, updatedContent) - } +// switch updatedContent.count { +// case 1: +// let (k, v) = updatedContent[0].self +// return BitmapIndexedMapNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) +// default: + return HashCollisionMapNode(hash, updatedContent) +// } } } - override var hasNodes: Bool { false } + var hasNodes: Bool { false } - override var nodeArity: Int { 0 } + var nodeArity: Int { 0 } - override func getNode(_ index: Int) -> MapNode { + func getNode(_ index: Int) -> HashCollisionMapNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } - override var hasPayload: Bool { true } + var hasPayload: Bool { true } - override var payloadArity: Int { content.count } + var payloadArity: Int { content.count } - override func getPayload(_ index: Int) -> (Key, Value) { content[index] } + func getPayload(_ index: Int) -> (Key, Value) { content[index] } } extension HashCollisionMapNode /* : Equatable where Value : Equatable */ { diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index b047d4a7f..e06a3de42 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -9,52 +9,27 @@ // //===----------------------------------------------------------------------===// -// TODO assess if convertible to a `protocol`. Variance / constraints on return types make it difficult. -class MapNode : Node where Key : Hashable { +protocol MapNode : Node { + associatedtype Key : Hashable + associatedtype Value - func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { - preconditionFailure("This method must be overridden") - } + func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? - func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { - preconditionFailure("This method must be overridden") - } + func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool - func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - preconditionFailure("This method must be overridden") - } + func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode - func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> MapNode { - preconditionFailure("This method must be overridden") - } + func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode - var hasNodes: Bool { - preconditionFailure("This method must be overridden") - } + var hasNodes: Bool { get } - var nodeArity: Int { - preconditionFailure("This method must be overridden") - } + var nodeArity: Int { get } - func getNode(_ index: Int) -> MapNode { - preconditionFailure("This method must be overridden") - } + func getNode(_ index: Int) -> ReturnNode - var hasPayload: Bool { - preconditionFailure("This method must be overridden") - } + var hasPayload: Bool { get } - var payloadArity: Int { - preconditionFailure("This method must be overridden") - } + var payloadArity: Int { get } - func getPayload(_ index: Int) -> (Key, Value) { - preconditionFailure("This method must be overridden") - } -} - -extension MapNode : Equatable where Value : Equatable { - static func == (lhs: MapNode, rhs: MapNode) -> Bool { - preconditionFailure("Not yet implemented") - } + func getPayload(_ index: Int) -> ReturnPayload /* (Key, Value) */ } From 8e2ec3c49bf1b6267effd69216daaba62c9622d0 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 4 May 2021 16:32:08 +0200 Subject: [PATCH 016/176] [Capsule] Make `copyAndSetNode` generic --- Sources/Capsule/_BitmapIndexedMapNode.swift | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index fd11bf090..5ffe0bdd1 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -97,7 +97,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (!effect.modified) { return self } else { - return copyAndSetCollisionNode(bitpos, subNodeNew) // NOTE difference in callee + return copyAndSetNode(bitpos, subNodeNew) } } else { // regular sub-node @@ -172,7 +172,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { default: // equivalent to `case 2...` // modify current node (set replacement node) - return copyAndSetCollisionNode(bitpos, subNodeNew) // NOTE difference in callee + return copyAndSetNode(bitpos, subNodeNew) } } else { // regular sub-node @@ -296,18 +296,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return BitmapIndexedMapNode(dataMap, nodeMap, dst) } - // TODO rework temporarily duplicated methods for type-safe access - func copyAndSetNode(_ bitpos: Int, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idx = self.content.count - 1 - self.nodeIndex(bitpos) - - var dst = self.content - dst[idx] = newNode - - return BitmapIndexedMapNode(dataMap, nodeMap, dst) - } - - // TODO rework temporarily duplicated methods for type-safe access - func copyAndSetCollisionNode(_ bitpos: Int, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { + func copyAndSetNode(_ bitpos: Int, _ newNode: T) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) var dst = self.content From b34c8dad97fa04a4352b2df95ef6fdf17fb51bfb Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 4 May 2021 17:23:01 +0200 Subject: [PATCH 017/176] [Capsule] Implement `Equatable` protocol --- Sources/Capsule/_BitmapIndexedMapNode.swift | 38 +++++++++++++-- Sources/Capsule/_HashCollisionMapNode.swift | 4 +- Tests/CapsuleTests/CapsuleSmokeTests.swift | 53 ++++++++++++++------- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 5ffe0bdd1..4082eafe3 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -248,6 +248,11 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[content.count - 1 - index] as! HashCollisionMapNode } + // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) + func getAnyNode(_ index: Int) -> Any { + content[content.count - 1 - index] + } + var hasPayload: Bool { dataMap != 0 } var payloadArity: Int { dataMap.nonzeroBitCount } @@ -346,15 +351,40 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } -extension BitmapIndexedMapNode /* : Equatable where Value : Equatable */ { +extension BitmapIndexedMapNode : Equatable where Value : Equatable { static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { lhs === rhs || lhs.nodeMap == rhs.nodeMap && lhs.dataMap == rhs.dataMap && - deepContentEquality(lhs.content, rhs.content, lhs.content.count) + deepContentEquality(lhs, rhs) } - private static func deepContentEquality(_ a1: [Any], _ a2: [Any], _ length: Int) -> Bool { - preconditionFailure("Not yet implemented") + private static func deepContentEquality(_ lhs: BitmapIndexedMapNode, _ rhs: BitmapIndexedMapNode) -> Bool { + for index in 0.., + let rhsNode = rhs.getAnyNode(index) as? BitmapIndexedMapNode { + if (lhsNode != rhsNode) { + return false + } + } else if let lhsNode = lhs.getAnyNode(index) as? HashCollisionMapNode, + let rhsNode = rhs.getAnyNode(index) as? HashCollisionMapNode { + if (lhsNode != rhsNode) { + return false + } + } else { + return false + } + } + + return true } } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index b8f43a93a..296468ebc 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -87,8 +87,8 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { func getPayload(_ index: Int) -> (Key, Value) { content[index] } } -extension HashCollisionMapNode /* : Equatable where Value : Equatable */ { +extension HashCollisionMapNode : Equatable where Value : Equatable { static func == (lhs: HashCollisionMapNode, rhs: HashCollisionMapNode) -> Bool { - preconditionFailure("Not yet implemented") + Dictionary.init(uniqueKeysWithValues: lhs.content) == Dictionary.init(uniqueKeysWithValues: rhs.content) } } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index b937d154d..92d4b496e 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -82,38 +82,43 @@ final class CapsuleSmokeTests: CollectionTestCase { res1[CollidableInt(11, 1)] = CollidableInt(11, 1) res1[CollidableInt(12, 1)] = CollidableInt(12, 1) - expectEqual(res1.count, 2) expectTrue(res1.contains(CollidableInt(11, 1))) expectTrue(res1.contains(CollidableInt(12, 1))) + expectEqual(res1.count, 2) + expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(12, 1) : CollidableInt(12, 1)]), res1) + var res2 = res1 res2[CollidableInt(12, 1)] = nil + expectTrue(res2.contains(CollidableInt(11, 1))) expectFalse(res2.contains(CollidableInt(12, 1))) expectEqual(res2.count, 1) - // expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1)]), res2) + expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1)]), res2) var res3 = res1 res3[CollidableInt(11, 1)] = nil + expectFalse(res3.contains(CollidableInt(11, 1))) expectTrue(res3.contains(CollidableInt(12, 1))) expectEqual(res3.count, 1) - // expectEqual(HashMap.init([CollidableInt(12, 1) : CollidableInt(12, 1)]), res3) + expectEqual(HashMap.init([CollidableInt(12, 1) : CollidableInt(12, 1)]), res3) var resX = res1 resX[CollidableInt(32769)] = CollidableInt(32769) resX[CollidableInt(12, 1)] = nil + expectTrue(resX.contains(CollidableInt(11, 1))) expectFalse(resX.contains(CollidableInt(12, 1))) expectTrue(resX.contains(CollidableInt(32769))) expectEqual(resX.count, 2) - // expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(32769) : CollidableInt(32769)]), resX) + expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(32769) : CollidableInt(32769)]), resX) } func testCompactionWhenDeletingFromHashCollisionNode2() { @@ -124,29 +129,32 @@ final class CapsuleSmokeTests: CollectionTestCase { res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) - expectEqual(res1.count, 2) expectTrue(res1.contains(CollidableInt(32769_1, 32769))) expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + expectEqual(res1.count, 2) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + var res2 = res1 res2[CollidableInt(1)] = CollidableInt(1) - expectEqual(res2.count, 3) expectTrue(res2.contains(CollidableInt(1))) expectTrue(res2.contains(CollidableInt(32769_1, 32769))) expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + expectEqual(res2.count, 3) + expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + var res3 = res2 res3[CollidableInt(32769_2, 32769)] = nil - expectEqual(res3.count, 2) expectTrue(res3.contains(CollidableInt(1))) expectTrue(res3.contains(CollidableInt(32769_1, 32769))) - - // expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + expectEqual(res3.count, 2) + expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769)]), res3) } func testCompactionWhenDeletingFromHashCollisionNode3() { @@ -157,30 +165,35 @@ final class CapsuleSmokeTests: CollectionTestCase { res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) - expectEqual(res1.count, 2) expectTrue(res1.contains(CollidableInt(32769_1, 32769))) expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + expectEqual(res1.count, 2) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + var res2 = res1 res2[CollidableInt(1)] = CollidableInt(1) - expectEqual(res2.count, 3) expectTrue(res2.contains(CollidableInt(1))) expectTrue(res2.contains(CollidableInt(32769_1, 32769))) expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + expectEqual(res2.count, 3) + expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + var res3 = res2 res3[CollidableInt(1)] = nil - expectEqual(res3.count, 2) expectTrue(res3.contains(CollidableInt(32769_1, 32769))) expectTrue(res3.contains(CollidableInt(32769_2, 32769))) + expectEqual(res3.count, 2) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) - // expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + expectEqual(res1, res3) } func testCompactionWhenDeletingFromHashCollisionNode4() { @@ -191,29 +204,35 @@ final class CapsuleSmokeTests: CollectionTestCase { res1[CollidableInt(32769_1, 32769)] = CollidableInt(32769_1, 32769) res1[CollidableInt(32769_2, 32769)] = CollidableInt(32769_2, 32769) - expectEqual(res1.count, 2) expectTrue(res1.contains(CollidableInt(32769_1, 32769))) expectTrue(res1.contains(CollidableInt(32769_2, 32769))) + expectEqual(res1.count, 2) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + var res2 = res1 res2[CollidableInt(5)] = CollidableInt(5) - expectEqual(res2.count, 3) expectTrue(res2.contains(CollidableInt(5))) expectTrue(res2.contains(CollidableInt(32769_1, 32769))) expectTrue(res2.contains(CollidableInt(32769_2, 32769))) + expectEqual(res2.count, 3) + expectEqual(HashMap.init([CollidableInt(5) : CollidableInt(5), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + var res3 = res2 res3[CollidableInt(5)] = nil - expectEqual(res3.count, 2) expectTrue(res3.contains(CollidableInt(32769_1, 32769))) expectTrue(res3.contains(CollidableInt(32769_2, 32769))) + expectEqual(res3.count, 2) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + - // expectEqual(res1, res3) + expectEqual(res1, res3) } } From 212fa0eb982709d2d9262f8b6a5dddca36014094 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 4 May 2021 17:40:00 +0200 Subject: [PATCH 018/176] [Capsule] Update copyright headers --- Sources/Capsule/HashMap+Equatable.swift | 2 +- Sources/Capsule/HashMap.swift | 2 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- Sources/Capsule/_MapNode.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift index c1c34776f..55190497f 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 768cf277f..15655ce29 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 4082eafe3..d1ac6852c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index e06a3de42..3a4369673 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 From b175001db21c3bfc9ed34df3880a7c3c474377e6 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 5 May 2021 17:19:37 +0200 Subject: [PATCH 019/176] [Capsule] Add support for Copy-On-Write This is work-in-progress, since it's not yet working deeper in the tree. --- ...shMap+ExpressibleByDictionaryLiteral.swift | 6 +- Sources/Capsule/HashMap.swift | 64 ++++++++++++++++--- Sources/Capsule/_BitmapIndexedMapNode.swift | 62 +++++++++++------- Sources/Capsule/_HashCollisionMapNode.swift | 4 +- Sources/Capsule/_MapNode.swift | 4 +- 5 files changed, 100 insertions(+), 40 deletions(-) diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index 53b43c9b0..3d213d42f 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 @@ -12,7 +12,9 @@ extension HashMap : ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (Key, Value)...) { let map = elements.reduce(Self()) { (map, element) in let (key, value) = element - return map.insert(key: key, value: value) + var tmp = map + tmp.insert(key: key, value: value) + return tmp } self.init(map) } diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 15655ce29..1384d10ab 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -10,9 +10,9 @@ //===----------------------------------------------------------------------===// public struct HashMap where Key : Hashable { - let rootNode: BitmapIndexedMapNode - let cachedKeySetHashCode: Int - let cachedSize: Int + var rootNode: BitmapIndexedMapNode + var cachedKeySetHashCode: Int + var cachedSize: Int fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { self.rootNode = rootNode @@ -48,9 +48,9 @@ public struct HashMap where Key : Hashable { } mutating set(optionalValue) { if let value = optionalValue { - self = insert(key: key, value: value) + insert(isKnownUniquelyReferenced(&self.rootNode), key: key, value: value) } else { - self = delete(key) + delete(isKnownUniquelyReferenced(&self.rootNode), key: key) } } } @@ -66,11 +66,36 @@ public struct HashMap where Key : Hashable { public func get(_ key: Key) -> Value? { rootNode.get(key, computeHash(key), 0) } - - public func insert(key: Key, value: Value) -> Self { + + public mutating func insert(key: Key, value: Value) { + let mutate = isKnownUniquelyReferenced(&self.rootNode) + insert(mutate, key: key, value: value) + } + + // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` + mutating func insert(_ isStorageKnownUniquelyReferenced: Bool, key: Key, value: Value) { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.updated(key, value, keyHash, 0, &effect) + let newRootNode = rootNode.updated(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) + + if (effect.modified) { + if (effect.replacedValue) { + self.rootNode = newRootNode + // self.cachedKeySetHashCode = cachedKeySetHashCode + // self.cachedSize = cachedSize + } else { + self.rootNode = newRootNode + self.cachedKeySetHashCode = cachedKeySetHashCode ^ keyHash + self.cachedSize = cachedSize + 1 + } + } + } + + // fluid/immutable API + public func with(key: Key, value: Value) -> Self { + var effect = MapEffect() + let keyHash = computeHash(key) + let newRootNode = rootNode.updated(false, key, value, keyHash, 0, &effect) if (effect.modified) { if (effect.replacedValue) { @@ -81,10 +106,29 @@ public struct HashMap where Key : Hashable { } else { return self } } - public func delete(_ key: Key) -> Self { + public mutating func delete(_ key: Key) { + let mutate = isKnownUniquelyReferenced(&self.rootNode) + delete(mutate, key: key) + } + + // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` + mutating func delete(_ isStorageKnownUniquelyReferenced: Bool, key: Key) { + var effect = MapEffect() + let keyHash = computeHash(key) + let newRootNode = rootNode.removed(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) + + if (effect.modified) { + self.rootNode = newRootNode + self.cachedKeySetHashCode = cachedKeySetHashCode ^ keyHash + self.cachedSize = cachedSize - 1 + } + } + + // fluid/immutable API + public func without(key: Key) -> Self { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.removed(key, keyHash, 0, &effect) + let newRootNode = rootNode.removed(false, key, keyHash, 0, &effect) if (effect.modified) { return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index d1ac6852c..8e79b6731 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -14,7 +14,7 @@ fileprivate var TupleLength: Int { 2 } final class BitmapIndexedMapNode : MapNode where Key : Hashable { let dataMap: Int let nodeMap: Int - let content: [Any] + var content: [Any] init(_ dataMap: Int, _ nodeMap: Int, _ content: [Any]) { self.dataMap = dataMap @@ -67,7 +67,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return false } - func updated(_ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -77,7 +77,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (key0 == key) { effect.setReplacedValue() - return copyAndSetValue(bitpos, value) + return copyAndSetValue(isStorageKnownUniquelyReferenced, bitpos, value) } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) effect.setModified() @@ -91,25 +91,25 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // hash-collison sub-node let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getCollisionNode(index) // NOTE difference in callee + var subNode = self.getCollisionNode(index) // NOTE difference in callee - let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(isKnownUniquelyReferenced(&subNode), key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { - return copyAndSetNode(bitpos, subNodeNew) + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } else { // regular sub-node let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) + var subNode = self.getNode(index) - let subNodeNew = subNode.updated(key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(isKnownUniquelyReferenced(&subNode), key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { - return copyAndSetNode(bitpos, subNodeNew) + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } } @@ -118,7 +118,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return copyAndInsertValue(bitpos, key, value) } - func removed(_ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -152,9 +152,9 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // hash-collison sub-node let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getCollisionNode(index) // NOTE difference in callee + var subNode = self.getCollisionNode(index) // NOTE difference in callee - let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(isKnownUniquelyReferenced(&subNode), key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } switch subNodeNew.payloadArity { @@ -172,15 +172,15 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { default: // equivalent to `case 2...` // modify current node (set replacement node) - return copyAndSetNode(bitpos, subNodeNew) + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } else { // regular sub-node let index = indexFrom(nodeMap, mask, bitpos) - let subNode = self.getNode(index) + var subNode = self.getNode(index) - let subNodeNew = subNode.removed(key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(isKnownUniquelyReferenced(&subNode), key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } switch subNodeNew.payloadArity { @@ -194,7 +194,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { default: // equivalent to `case 2...` // modify current node (set replacement node) - return copyAndSetNode(bitpos, subNodeNew) + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } } @@ -292,22 +292,36 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { /// /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. /// This would be very invasive though and make problem logic hard to understand and maintain. - func copyAndSetValue(_ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { + func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) + 1 - var dst = self.content - dst[idx] = newValue + if (isStorageKnownUniquelyReferenced) { + // no copying if already editable + self.content[idx] = newValue + + return self + } else { + var dst = self.content + dst[idx] = newValue - return BitmapIndexedMapNode(dataMap, nodeMap, dst) + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } } - func copyAndSetNode(_ bitpos: Int, _ newNode: T) -> BitmapIndexedMapNode { + func copyAndSetNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Int, _ newNode: T) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) - var dst = self.content - dst[idx] = newNode + if (isStorageKnownUniquelyReferenced) { + // no copying if already editable + self.content[idx] = newNode + + return self + } else { + var dst = self.content + dst[idx] = newNode - return BitmapIndexedMapNode(dataMap, nodeMap, dst) + return BitmapIndexedMapNode(dataMap, nodeMap, dst) + } } func copyAndInsertValue(_ bitpos: Int, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 296468ebc..953f4856b 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -36,7 +36,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { // return self.hash == hash && content.contains(where: { key == $0.0 && value == $0.1 }) // } - func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) if (self.containsKey(key, hash, shift)) { @@ -54,7 +54,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { // TODO rethink such that `precondition(content.count >= 2)` holds // TODO consider returning either type of `BitmapIndexedMapNode` and `HashCollisionMapNode` - func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { if (!self.containsKey(key, hash, shift)) { return self } else { diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index 3a4369673..0cf3483b0 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -17,9 +17,9 @@ protocol MapNode : Node { func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool - func updated(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode + func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode - func removed(_ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode + func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode var hasNodes: Bool { get } From 72950a90f33adb850b5f7763289ccd58a953706c Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 6 May 2021 08:28:43 +0200 Subject: [PATCH 020/176] [Capsule] Fix Copy-On-Write semantics for nested nodes --- Sources/Capsule/_BitmapIndexedMapNode.swift | 37 ++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 8e79b6731..bffb7372a 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -91,9 +91,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // hash-collison sub-node let index = indexFrom(nodeMap, mask, bitpos) - var subNode = self.getCollisionNode(index) // NOTE difference in callee + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getCollisionNode(index) // NOTE difference in callee - let subNodeNew = subNode.updated(isKnownUniquelyReferenced(&subNode), key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { @@ -103,9 +104,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // regular sub-node let index = indexFrom(nodeMap, mask, bitpos) - var subNode = self.getNode(index) + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getNode(index) - let subNodeNew = subNode.updated(isKnownUniquelyReferenced(&subNode), key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { @@ -152,9 +154,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // hash-collison sub-node let index = indexFrom(nodeMap, mask, bitpos) - var subNode = self.getCollisionNode(index) // NOTE difference in callee + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getCollisionNode(index) // NOTE difference in callee - let subNodeNew = subNode.removed(isKnownUniquelyReferenced(&subNode), key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } switch subNodeNew.payloadArity { @@ -178,9 +181,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // regular sub-node let index = indexFrom(nodeMap, mask, bitpos) - var subNode = self.getNode(index) + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getNode(index) - let subNodeNew = subNode.removed(isKnownUniquelyReferenced(&subNode), key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } switch subNodeNew.payloadArity { @@ -253,6 +257,23 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[content.count - 1 - index] } + + // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference + // to pass into `isKnownUniquelyReferenced` + private func isNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + let slotIndex = content.count - 1 - index + + let fakeNode = BitmapIndexedMapNode(0, 0, Array()) + + var realNode = content[slotIndex] as AnyObject + content[slotIndex] = fakeNode + + let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) + content[slotIndex] = realNode + + return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced + } + var hasPayload: Bool { dataMap != 0 } var payloadArity: Int { dataMap.nonzeroBitCount } From 7d0cc05262b077e2f61a641bb37e34f3c9275e3d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 6 May 2021 19:41:38 +0200 Subject: [PATCH 021/176] [Capsule] Add test functions for feature exploration Triggers the following features: * copy-on-write * property caching (e.g., `cachedSize`) --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 92d4b496e..9b9a45fcf 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -48,6 +48,103 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map[2], nil) } + func testTriggerOverwrite1() { + let map: HashMap = [ 1 : "a", 2 : "b" ] + + let _ = map + .with(key: 1, value: "x") // triggers COW + .with(key: 2, value: "y") // triggers COW + + var res1: HashMap = [:] + res1.insert(key: 1, value: "a") // in-place + res1.insert(key: 2, value: "b") // in-place + + var res2: HashMap = [:] + res2[1] = "a" // in-place + res2[2] = "b" // in-place + + var res3: HashMap = res2 + res3[1] = "x" // triggers COW + res3[2] = "y" // in-place + + expectEqual(res2.count, 2) + expectEqual(res2.get(1), "a") + expectEqual(res2.get(2), "b") + + expectEqual(res3.count, 2) + expectEqual(res3.get(1), "x") + expectEqual(res3.get(2), "y") + } + + func testTriggerOverwrite2() { + var res1: HashMap = [:] + res1.insert(key: CollidableInt(10, 01), value: "a") // in-place + res1.insert(key: CollidableInt(11, 33), value: "a") // in-place + res1.insert(key: CollidableInt(20, 02), value: "b") // in-place + + res1.insert(key: CollidableInt(10, 01), value: "x") // in-place + res1.insert(key: CollidableInt(11, 33), value: "x") // in-place + res1.insert(key: CollidableInt(20, 02), value: "y") // in-place + + print("Yeah!") + + var res2: HashMap = res1 + res2.insert(key: CollidableInt(10, 01), value: "a") // triggers COW + res2.insert(key: CollidableInt(11, 33), value: "a") // in-place + res2.insert(key: CollidableInt(20, 02), value: "b") // in-place + + print("Yeah!") + + expectEqual(res1.get(CollidableInt(10, 01)), "x") + expectEqual(res1.get(CollidableInt(11, 33)), "x") + expectEqual(res1.get(CollidableInt(20, 02)), "y") + + expectEqual(res2.get(CollidableInt(10, 01)), "a") + expectEqual(res2.get(CollidableInt(11, 33)), "a") + expectEqual(res2.get(CollidableInt(20, 02)), "b") + + } + + func testTriggerOverwrite3() { + let upperBound = 1_000_000 + + var map1: HashMap = [:] + for index in 0.. = map1 + for index in 0.. = map2 + for index in 0..(_ k: Key, _ v: Value) -> Int { var hasher = Hasher() hasher.combine(k) From 2be85458406c913f73d2ccb27d3abd07db7636c5 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 08:30:53 +0200 Subject: [PATCH 022/176] [Capsule] Use overflowing bit-wise operations --- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- Sources/Capsule/_Common.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index bffb7372a..396a72ff4 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -283,9 +283,9 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[TupleLength * index + 1] as! Value) } - func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos - 1)).nonzeroBitCount } + func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } - func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos - 1)).nonzeroBitCount } + func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 4cca278b0..b7fcd29a3 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -32,7 +32,7 @@ func bitposFrom(_ mask: Int) -> Int { } func indexFrom(_ bitmap: Int, _ bitpos: Int) -> Int { - (bitmap & (bitpos - 1)).nonzeroBitCount + (bitmap & (bitpos &- 1)).nonzeroBitCount } func indexFrom(_ bitmap: Int, _ mask: Int, _ bitpos: Int) -> Int { From ec017cba3a832e82b752e433c05c534be2772227 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 08:32:18 +0200 Subject: [PATCH 023/176] [Capsule] Use larger chunk size for more shallow tries --- Sources/Capsule/_Common.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index b7fcd29a3..6cff2cd55 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -17,7 +17,7 @@ func computeHash(_ value: T) -> Int { var HashCodeLength: Int { Int.bitWidth } -var BitPartitionSize: Int { 5 } +var BitPartitionSize: Int { 6 } var BitPartitionMask: Int { (1 << BitPartitionSize) - 1 } From 2c0b7d744ffc718b33ff645c6b23784494f5331a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 09:40:23 +0200 Subject: [PATCH 024/176] [Capsule] Generalize bitmap usage with `typealias` * Turns computed properties into constants. * Validate `Bitmap` and `BitPartitionSize` fit together. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 26 ++++++++++----------- Sources/Capsule/_Common.swift | 14 ++++++----- Tests/CapsuleTests/CapsuleSmokeTests.swift | 10 +++++++- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 396a72ff4..664a6ec42 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,11 +12,11 @@ fileprivate var TupleLength: Int { 2 } final class BitmapIndexedMapNode : MapNode where Key : Hashable { - let dataMap: Int - let nodeMap: Int + let dataMap: Bitmap + let nodeMap: Bitmap var content: [Any] - init(_ dataMap: Int, _ nodeMap: Int, _ content: [Any]) { + init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ content: [Any]) { self.dataMap = dataMap self.nodeMap = nodeMap self.content = content @@ -135,7 +135,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { * Create new node with remaining pair. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. */ - let newDataMap: Int + let newDataMap: Bitmap if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } if (index == 0) { let (k, v) = getPayload(1) @@ -164,7 +164,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case 1: if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) - let newDataMap: Int = bitposFrom(maskFrom(subNodeNew.hash, 0)) + let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (k, v) = subNodeNew.getPayload(0) return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) @@ -283,9 +283,9 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[TupleLength * index + 1] as! Value) } - func dataIndex(_ bitpos: Int) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } + func dataIndex(_ bitpos: Bitmap) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } - func nodeIndex(_ bitpos: Int) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } + func nodeIndex(_ bitpos: Bitmap) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: @@ -313,7 +313,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { /// /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. /// This would be very invasive though and make problem logic hard to understand and maintain. - func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Int, _ newValue: Value) -> BitmapIndexedMapNode { + func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) + 1 if (isStorageKnownUniquelyReferenced) { @@ -329,7 +329,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - func copyAndSetNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Int, _ newNode: T) -> BitmapIndexedMapNode { + func copyAndSetNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) if (isStorageKnownUniquelyReferenced) { @@ -345,7 +345,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - func copyAndInsertValue(_ bitpos: Int, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + func copyAndInsertValue(_ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) var dst = self.content @@ -354,7 +354,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, dst) } - func copyAndRemoveValue(_ bitpos: Int) -> BitmapIndexedMapNode { + func copyAndRemoveValue(_ bitpos: Bitmap) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) var dst = self.content @@ -363,7 +363,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, dst) } - func copyAndMigrateFromInlineToNode(_ bitpos: Int, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToNode(_ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idxOld = TupleLength * dataIndex(bitpos) let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) @@ -374,7 +374,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap | bitpos, dst) } - func copyAndMigrateFromNodeToInline(_ bitpos: Int, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { + func copyAndMigrateFromNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) let idxNew = TupleLength * dataIndex(bitpos) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 6cff2cd55..7e8220287 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -15,11 +15,13 @@ func computeHash(_ value: T) -> Int { value.hashValue } -var HashCodeLength: Int { Int.bitWidth } +typealias Bitmap = Int64 -var BitPartitionSize: Int { 6 } +let BitPartitionSize: Int = 6 -var BitPartitionMask: Int { (1 << BitPartitionSize) - 1 } +let BitPartitionMask: Int = (1 << BitPartitionSize) - 1 + +let HashCodeLength: Int = Int.bitWidth let MaxDepth = Int(ceil(Double(HashCodeLength) / Double(BitPartitionSize))) @@ -27,15 +29,15 @@ func maskFrom(_ hash: Int, _ shift: Int) -> Int { (hash >> shift) & BitPartitionMask } -func bitposFrom(_ mask: Int) -> Int { +func bitposFrom(_ mask: Int) -> Bitmap { 1 << mask } -func indexFrom(_ bitmap: Int, _ bitpos: Int) -> Int { +func indexFrom(_ bitmap: Bitmap, _ bitpos: Bitmap) -> Int { (bitmap & (bitpos &- 1)).nonzeroBitCount } -func indexFrom(_ bitmap: Int, _ mask: Int, _ bitpos: Int) -> Int { +func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 9b9a45fcf..1e5e6ce49 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import _CollectionsTestSupport -@_spi(Testing) import Capsule +@testable import Capsule final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { @@ -359,3 +359,11 @@ fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hash return lhs.value == rhs.value } } + +final class BitmapSmokeTests: CollectionTestCase { + func test_BitPartitionSize_isValid() { + expectTrue(BitPartitionSize > 0) + expectTrue((2 << (BitPartitionSize - 1)) != 0) + expectTrue((2 << (BitPartitionSize - 1)) <= Bitmap.bitWidth) + } +} From 21c0c0ef87b4e68bc2e5b87c4e287407b30ff683 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 11:29:51 +0200 Subject: [PATCH 025/176] [Capsule] Prepare introduction of `collMap` --- Sources/Capsule/HashMap.swift | 4 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 46 ++++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 1384d10ab..7d33cf64a 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -21,7 +21,7 @@ public struct HashMap where Key : Hashable { } public init() { - self.init(BitmapIndexedMapNode(0, 0, Array()), 0, 0) + self.init(BitmapIndexedMapNode(0, 0, 0, Array()), 0, 0) } public init(_ map: HashMap) { @@ -136,8 +136,6 @@ public struct HashMap where Key : Hashable { } } -fileprivate let EmptyMapNode = BitmapIndexedMapNode(0, 0, Array()) - public struct MapKeyValueTupleIterator { private var baseIterator: ChampBaseIterator> diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 664a6ec42..1eca27ec5 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,13 +12,19 @@ fileprivate var TupleLength: Int { 2 } final class BitmapIndexedMapNode : MapNode where Key : Hashable { - let dataMap: Bitmap - let nodeMap: Bitmap + let bitmap1: Bitmap + let bitmap2: Bitmap var content: [Any] - init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ content: [Any]) { - self.dataMap = dataMap - self.nodeMap = nodeMap + var dataMap: Bitmap { bitmap1 ^ collMap } + + var nodeMap: Bitmap { bitmap2 ^ collMap } + + var collMap: Bitmap { bitmap1 & bitmap2 } + + init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: [Any]) { + self.bitmap1 = dataMap ^ collMap + self.bitmap2 = nodeMap ^ collMap self.content = content } @@ -139,10 +145,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } if (index == 0) { let (k, v) = getPayload(1) - return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v) ) + return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v) ) } else { let (k, v) = getPayload(0) - return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) + return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) } } else { return copyAndRemoveValue(bitpos) } } else { return self } @@ -167,7 +173,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (k, v) = subNodeNew.getPayload(0) - return BitmapIndexedMapNode(newDataMap, 0, Array(arrayLiteral: k, v)) + return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) } else { // inline value (move to front) return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) @@ -215,25 +221,25 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let dataMap = bitposFrom(mask0) | bitposFrom(mask1) if (mask0 < mask1) { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key0, value0, key1, value1)) + return BitmapIndexedMapNode(dataMap, 0, 0, Array(arrayLiteral: key0, value0, key1, value1)) } else { - return BitmapIndexedMapNode(dataMap, 0, Array(arrayLiteral: key1, value1, key0, value0)) + return BitmapIndexedMapNode(dataMap, 0, 0, Array(arrayLiteral: key1, value1, key0, value0)) } } else { if (shift + BitPartitionSize >= HashCodeLength) { // hash collision: prefix exhausted on next level - let nodeMap = bitposFrom(mask0) + let collMap = bitposFrom(mask0) let node = HashCollisionMapNode(keyHash0, [(key0, value0), (key1, value1)]) - return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) + return BitmapIndexedMapNode(0, collMap, 0, Array(arrayLiteral: node)) // TODO swap `collMap` <-> `0` } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let nodeMap = bitposFrom(mask0) let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(0, nodeMap, Array(arrayLiteral: node)) + return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) } } } @@ -263,7 +269,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { private func isNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let slotIndex = content.count - 1 - index - let fakeNode = BitmapIndexedMapNode(0, 0, Array()) + let fakeNode = BitmapIndexedMapNode(0, 0, 0, Array()) var realNode = content[slotIndex] as AnyObject content[slotIndex] = fakeNode @@ -325,7 +331,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var dst = self.content dst[idx] = newValue - return BitmapIndexedMapNode(dataMap, nodeMap, dst) + return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) } } @@ -341,7 +347,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var dst = self.content dst[idx] = newNode - return BitmapIndexedMapNode(dataMap, nodeMap, dst) + return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) } } @@ -351,7 +357,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var dst = self.content dst.insert(contentsOf: [key, value], at: idx) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, dst) + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) } func copyAndRemoveValue(_ bitpos: Bitmap) -> BitmapIndexedMapNode { @@ -360,7 +366,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var dst = self.content dst.removeSubrange(idx..) -> BitmapIndexedMapNode { @@ -371,7 +377,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { dst.removeSubrange(idxOld.. BitmapIndexedMapNode { @@ -382,7 +388,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { dst.remove(at: idxOld) dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, dst) + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) // TODO check correctness } } From 2ac27e669ab977fa9f6d6e8c4c7441f8d925ad53 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 18:29:01 +0200 Subject: [PATCH 026/176] [Capsule] Rework hash-collision logic based on monomorphic trie nodes --- Sources/Capsule/_BitmapIndexedMapNode.swift | 316 +++++++++++++------- Sources/Capsule/_Common.swift | 9 +- Sources/Capsule/_HashCollisionMapNode.swift | 8 + Tests/CapsuleTests/CapsuleSmokeTests.swift | 14 +- 4 files changed, 233 insertions(+), 114 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 1eca27ec5..c549e3938 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -40,12 +40,12 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) + return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + } - if (shift + BitPartitionSize >= HashCodeLength) { - return self.getCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) - } else { - return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) - } + if ((collMap & bitpos) != 0) { + let index = indexFrom(collMap, mask, bitpos) + return self.getCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) } return nil @@ -63,11 +63,12 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - if (shift + BitPartitionSize >= HashCodeLength) { - return self.getCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) - } else { - return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) - } + return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + } + + if ((collMap & bitpos) != 0) { + let index = indexFrom(collMap, mask, bitpos) + return self.getCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } return false @@ -85,40 +86,51 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { effect.setReplacedValue() return copyAndSetValue(isStorageKnownUniquelyReferenced, bitpos, value) } else { - let subNodeNew = mergeTwoKeyValPairs(key0, value0, computeHash(key0), key, value, keyHash, shift + BitPartitionSize) - effect.setModified() - return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + let keyHash0 = computeHash(key0) + + if (keyHash0 == keyHash) { + let subNodeNew = HashCollisionMapNode(keyHash0, [(key0, value0), (key, value)]) + effect.setModified() + return copyAndMigrateFromInlineToCollisionNode(bitpos, subNodeNew) + } else { + let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + BitPartitionSize) + effect.setModified() + return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + } } } if ((nodeMap & bitpos) != 0) { - // TODO avoid code duplication and specialization - if (shift + BitPartitionSize >= HashCodeLength) { - // hash-collison sub-node - - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getCollisionNode(index) // NOTE difference in callee + let index = indexFrom(nodeMap, mask, bitpos) + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getNode(index) - let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { - return self - } else { - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } + let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) + if (!effect.modified) { + return self } else { - // regular sub-node + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + } + } + + if ((collMap & bitpos) != 0) { + let index = indexFrom(collMap, mask, bitpos) + let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getCollisionNode(index) - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getNode(index) + let collisionHash = subNode.hash + if (keyHash == collisionHash) { let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } + } else { + let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + BitPartitionSize) + effect.setModified() + return copyAndMigrateFromCollisionNodeToNode(bitpos, subNodeNew) } } @@ -136,7 +148,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (key0 == key) { effect.setModified() - if (self.payloadArity == 2 && self.nodeArity == 0) { + // TODO check globally usage of `nodeArity` and `collisionNodeArity` + if (self.payloadArity == 2 && self.nodeArity == 0 && self.collisionNodeArity == 0) { /* * Create new node with remaining pair. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. @@ -150,69 +163,84 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let (k, v) = getPayload(0) return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) } + } else if (self.payloadArity == 1 && self.nodeArity == 0 && self.collisionNodeArity == 1) { + /* + * Create new node with collision node. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + let newCollMap: Bitmap = bitposFrom(maskFrom(getCollisionNode(0).hash, 0)) + return BitmapIndexedMapNode(0, 0, newCollMap, Array(arrayLiteral: getCollisionNode(0))) } else { return copyAndRemoveValue(bitpos) } } else { return self } } if ((nodeMap & bitpos) != 0) { - // TODO avoid code duplication and specialization - if (shift + BitPartitionSize >= HashCodeLength) { - // hash-collison sub-node - - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getCollisionNode(index) // NOTE difference in callee + let index = indexFrom(nodeMap, mask, bitpos) + let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getNode(index) - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: - if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result - // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) - let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) - let (k, v) = subNodeNew.getPayload(0) + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + return subNodeNew + } + else { // inline value (move to front) + return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) + } - return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) - } - else { // inline value (move to front) - return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) + default: // equivalent to `case 2...` + // TODO simplify hash-collision compaction (if feasible) + if (subNodeNew.payloadArity == 0 && subNodeNew.nodeArity == 0 && subNodeNew.collisionNodeArity == 1) { + if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result + return subNodeNew + } else { // inline value (move to front) + assertionFailure() + // return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getCollisionNode(0)) } - - default: // equivalent to `case 2...` - // modify current node (set replacement node) - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } - } else { - // regular sub-node - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getNode(index) + // modify current node (set replacement node) + return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + } + } - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) + if ((collMap & bitpos) != 0) { + let index = indexFrom(collMap, mask, bitpos) + let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getCollisionNode(index) - if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: - if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result - return subNodeNew - } - else { // inline value (move to front) - return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) - } + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) - default: // equivalent to `case 2...` - // modify current node (set replacement node) - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + if (!effect.modified) { return self } + switch subNodeNew.payloadArity { + case 1: + // TODO simplify hash-collision compaction (if feasible) + if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result + // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) + let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) + let (k, v) = subNodeNew.getPayload(0) + + return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) + } + else { // inline value (move to front) + return copyAndMigrateFromCollisionNodeToInline(bitpos, subNodeNew.getPayload(0)) } + + default: // equivalent to `case 2...` + // modify current node (set replacement node) + return copyAndSetCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } - } + } return self } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { + precondition(keyHash0 != keyHash1) + let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(keyHash1, shift) @@ -226,21 +254,34 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return BitmapIndexedMapNode(dataMap, 0, 0, Array(arrayLiteral: key1, value1, key0, value0)) } } else { - if (shift + BitPartitionSize >= HashCodeLength) { - // hash collision: prefix exhausted on next level + // recurse: identical prefixes, payload must be disambiguated deeper in the trie - let collMap = bitposFrom(mask0) - let node = HashCollisionMapNode(keyHash0, [(key0, value0), (key1, value1)]) + let nodeMap = bitposFrom(mask0) + let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(0, collMap, 0, Array(arrayLiteral: node)) // TODO swap `collMap` <-> `0` - } else { - // recurse: identical prefixes, payload must be disambiguated deeper in the trie + return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) + } + } - let nodeMap = bitposFrom(mask0) - let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) + func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionMapNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { + precondition(keyHash0 != nodeHash1) - return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) - } + let mask0 = maskFrom(keyHash0, shift) + let mask1 = maskFrom(nodeHash1, shift) + + if (mask0 != mask1) { + // unique prefixes, payload and collision node fit on same level + let dataMap = bitposFrom(mask0) + let collMap = bitposFrom(mask1) + + return BitmapIndexedMapNode(dataMap, 0, collMap, Array(arrayLiteral: key0, value0, node1)) + } else { + // recurse: identical prefixes, payload must be disambiguated deeper in the trie + + let nodeMap = bitposFrom(mask0) + let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + BitPartitionSize) + + return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) } } @@ -248,27 +289,23 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var nodeArity: Int { nodeMap.nonzeroBitCount } - // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) func getNode(_ index: Int) -> BitmapIndexedMapNode { content[content.count - 1 - index] as! BitmapIndexedMapNode } - // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) - func getCollisionNode(_ index: Int) -> HashCollisionMapNode { - content[content.count - 1 - index] as! HashCollisionMapNode + private func isNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + let slotIndex = content.count - 1 - index + return isAnyNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } - // TODO rework temporarily duplicated methods for type-safe access (requires changing protocol) - func getAnyNode(_ index: Int) -> Any { - content[content.count - 1 - index] + private func isCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + let slotIndex = content.count - 1 - nodeArity - index + return isAnyNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } - // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference // to pass into `isKnownUniquelyReferenced` - private func isNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = content.count - 1 - index - + private func isAnyNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let fakeNode = BitmapIndexedMapNode(0, 0, 0, Array()) var realNode = content[slotIndex] as AnyObject @@ -280,6 +317,14 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } + var hasCollisionNodes: Bool { collMap != 0 } + + var collisionNodeArity: Int { collMap.nonzeroBitCount } + + func getCollisionNode(_ index: Int) -> HashCollisionMapNode { + return content[content.count - 1 - nodeArity - index] as! HashCollisionMapNode + } + var hasPayload: Bool { dataMap != 0 } var payloadArity: Int { dataMap.nonzeroBitCount } @@ -293,6 +338,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { func nodeIndex(_ bitpos: Bitmap) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } + func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } + /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: /// @@ -337,7 +384,14 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { func copyAndSetNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) + return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } + func copyAndSetCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { + let idx = self.content.count - 1 - nodeArity - self.collIndex(bitpos) + return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) + } + + private func copyAndAnyNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { if (isStorageKnownUniquelyReferenced) { // no copying if already editable self.content[idx] = newNode @@ -377,7 +431,18 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { + let idxOld = TupleLength * dataIndex(bitpos) + let idxNew = self.content.count - TupleLength - nodeArity - collIndex(bitpos) + + var dst = self.content + dst.removeSubrange(idxOld.. BitmapIndexedMapNode { @@ -388,8 +453,41 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { dst.remove(at: idxOld) dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) // TODO check correctness + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) } + + func copyAndMigrateFromCollisionNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { + let idxOld = self.content.count - 1 - nodeArity - collIndex(bitpos) + let idxNew = TupleLength * dataIndex(bitpos) + + var dst = self.content + dst.remove(at: idxOld) + dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) + } + + func copyAndMigrateFromCollisionNodeToNode(_ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + let idxOld = self.content.count - 1 - nodeArity - collIndex(bitpos) + let idxNew = self.content.count - 1 - nodeIndex(bitpos) + + var dst = self.content + dst.remove(at: idxOld) + dst.insert(node, at: idxNew) + + return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) + } + +// func copyAndMigrateFromNodeToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { +// let idxOld = self.content.count - 1 - nodeIndex(bitpos) +// let idxNew = self.content.count - 1 - nodeArity - 1 - collIndex(bitpos) +// +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(node, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) +// } } extension BitmapIndexedMapNode : Equatable where Value : Equatable { @@ -397,6 +495,7 @@ extension BitmapIndexedMapNode : Equatable where Value : Equatable { lhs === rhs || lhs.nodeMap == rhs.nodeMap && lhs.dataMap == rhs.dataMap && + lhs.collMap == rhs.collMap && deepContentEquality(lhs, rhs) } @@ -407,21 +506,14 @@ extension BitmapIndexedMapNode : Equatable where Value : Equatable { } } - /// `==` has no context on how deep the current node is located in the trie. Thus it would be beneficial making it explict - /// how many regular and hash-collision nodes are stored on the current level. - for index in 0.., - let rhsNode = rhs.getAnyNode(index) as? BitmapIndexedMapNode { - if (lhsNode != rhsNode) { - return false - } - } else if let lhsNode = lhs.getAnyNode(index) as? HashCollisionMapNode, - let rhsNode = rhs.getAnyNode(index) as? HashCollisionMapNode { - if (lhsNode != rhsNode) { - return false - } - } else { + if (lhs.getNode(index) != rhs.getNode(index)) { + return false + } + } + + for index in 0..(_ value: T) -> Int { typealias Bitmap = Int64 -let BitPartitionSize: Int = 6 +let BitPartitionSize: Int = 5 let BitPartitionMask: Int = (1 << BitPartitionSize) - 1 @@ -44,12 +44,19 @@ func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { protocol Node { associatedtype ReturnPayload associatedtype ReturnNode : Node + associatedtype ReturnCollisionNode : Node var hasNodes: Bool { get } var nodeArity: Int { get } func getNode(_ index: Int) -> ReturnNode + + var hasCollisionNodes: Bool { get } + + var collisionNodeArity: Int { get } + + func getCollisionNode(_ index: Int) -> ReturnCollisionNode var hasPayload: Bool { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 953f4856b..a702686d6 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -80,6 +80,14 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { preconditionFailure("No sub-nodes present in hash-collision leaf node") } + var hasCollisionNodes: Bool { false } + + var collisionNodeArity: Int { 0 } + + func getCollisionNode(_ index: Int) -> HashCollisionMapNode { + preconditionFailure("No sub-nodes present in hash-collision leaf node") + } + var hasPayload: Bool { true } var payloadArity: Int { content.count } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 1e5e6ce49..6f457a7c9 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -106,7 +106,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testTriggerOverwrite3() { - let upperBound = 1_000_000 + let upperBound = 1_000 var map1: HashMap = [:] for index in 0.. Date: Fri, 7 May 2021 20:51:09 +0200 Subject: [PATCH 027/176] [Capsule] Adopt Swift naming convention for chaining methods --- Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift | 4 +--- Sources/Capsule/HashMap.swift | 4 ++-- Tests/CapsuleTests/CapsuleSmokeTests.swift | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index 3d213d42f..a1f85fca8 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -12,9 +12,7 @@ extension HashMap : ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (Key, Value)...) { let map = elements.reduce(Self()) { (map, element) in let (key, value) = element - var tmp = map - tmp.insert(key: key, value: value) - return tmp + return map.inserting(key: key, value: value) } self.init(map) } diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 7d33cf64a..ab32f5563 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -92,7 +92,7 @@ public struct HashMap where Key : Hashable { } // fluid/immutable API - public func with(key: Key, value: Value) -> Self { + public func inserting(key: Key, value: Value) -> Self { var effect = MapEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updated(false, key, value, keyHash, 0, &effect) @@ -125,7 +125,7 @@ public struct HashMap where Key : Hashable { } // fluid/immutable API - public func without(key: Key) -> Self { + public func deleting(key: Key) -> Self { var effect = MapEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removed(false, key, keyHash, 0, &effect) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 6f457a7c9..d349f3b08 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -52,8 +52,8 @@ final class CapsuleSmokeTests: CollectionTestCase { let map: HashMap = [ 1 : "a", 2 : "b" ] let _ = map - .with(key: 1, value: "x") // triggers COW - .with(key: 2, value: "y") // triggers COW + .inserting(key: 1, value: "x") // triggers COW + .inserting(key: 2, value: "y") // triggers COW var res1: HashMap = [:] res1.insert(key: 1, value: "a") // in-place From 5950335e9970a2ad3e95de32524e177b64a4da96 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 7 May 2021 23:37:16 +0200 Subject: [PATCH 028/176] [Capsule] Make test case deterministic --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index d349f3b08..9eca14983 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -108,16 +108,16 @@ final class CapsuleSmokeTests: CollectionTestCase { func testTriggerOverwrite3() { let upperBound = 1_000 - var map1: HashMap = [:] + var map1: HashMap = [:] for index in 0.. = map1 + var map2: HashMap = map1 for index in 0.. = map2 + var map3: HashMap = map2 for index in 0.. Date: Fri, 7 May 2021 23:39:18 +0200 Subject: [PATCH 029/176] [Capsule] Improve `==` for `CollidableInt` and fix white-space issues --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 9eca14983..54db507ee 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -15,7 +15,7 @@ import _CollectionsTestSupport final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { var map: HashMap = [ 1 : "a", 2 : "b" ] - + map[3] = "x" map[4] = "y" @@ -28,7 +28,7 @@ final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptOverwrite() { var map: HashMap = [ 1 : "a", 2 : "b" ] - + map[1] = "x" map[2] = "y" @@ -36,13 +36,13 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map[1], "x") expectEqual(map[2], "y") } - + func testSubscriptRemove() { var map: HashMap = [ 1 : "a", 2 : "b" ] - + map[1] = nil map[2] = nil - + expectEqual(map.count, 0) expectEqual(map[1], nil) expectEqual(map[2], nil) @@ -368,7 +368,11 @@ fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hash } static func == (lhs: CollidableInt, rhs: CollidableInt) -> Bool { - return lhs.value == rhs.value + if lhs.value == rhs.value { + precondition(lhs.hashValue == rhs.hashValue) + return true + } + return false } } From 4c7149f03d53b9f6172b87c187ab862e62264bda Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sun, 9 May 2021 23:09:38 +0200 Subject: [PATCH 030/176] [Capsule] Experiment with any nodes API --- Sources/Capsule/_BitmapIndexedMapNode.swift | 12 ++++ Sources/Capsule/_Common.swift | 67 ++++++++++++++++++++- Sources/Capsule/_HashCollisionMapNode.swift | 8 +++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index c549e3938..a9343d990 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -325,6 +325,18 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return content[content.count - 1 - nodeArity - index] as! HashCollisionMapNode } + var hasAnyNodes: Bool { (nodeMap | collMap) != 0 } + + var anyNodeArity: Int { (nodeMap | collMap).nonzeroBitCount } + + func getAnyNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + if index < nodeArity { + return AnyNode.bitmapIndexed(getNode(index)) + } else { + return AnyNode.hashCollision(getCollisionNode(index)) + } + } + var hasPayload: Bool { dataMap != 0 } var payloadArity: Int { dataMap.nonzeroBitCount } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 52b9169e3..2b3829cf9 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -41,6 +41,65 @@ func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) } +enum AnyNode { + case bitmapIndexed(BitmapIndexedNode) + case hashCollision(HashCollisionNode) + +// func value() -> T { +// switch self { +// case .bitmapIndexed(let node): +// return node as! T +// case .hashCollision(let node): +// return node as! T +// } +// } + + var hasPayload: Bool { + switch self { + case .bitmapIndexed(let node): + return node.hasPayload + case .hashCollision(let node): + return node.hasPayload + } + } + + var payloadArity: Int { + switch self { + case .bitmapIndexed(let node): + return node.payloadArity + case .hashCollision(let node): + return node.payloadArity + } + } + +// func getPayload(_ index: Int) -> Node.ReturnPayload { +// switch self { +// case .bitmapIndexed(let node): +// return node.getPayload(index) +// case .hashCollision(let node): +// return node.getPayload(index) +// } +// } + + var hasAnyNodes: Bool { + switch self { + case .bitmapIndexed(let node): + return node.hasAnyNodes + case .hashCollision(let node): + return node.hasAnyNodes + } + } + + var anyNodeArity: Int { + switch self { + case .bitmapIndexed(let node): + return node.anyNodeArity + case .hashCollision(let node): + return node.anyNodeArity + } + } +} + protocol Node { associatedtype ReturnPayload associatedtype ReturnNode : Node @@ -57,7 +116,13 @@ protocol Node { var collisionNodeArity: Int { get } func getCollisionNode(_ index: Int) -> ReturnCollisionNode - + + var hasAnyNodes: Bool { get } + + var anyNodeArity: Int { get } + + func getAnyNode(_ index: Int) -> AnyNode + var hasPayload: Bool { get } var payloadArity: Int { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index a702686d6..227b4fc13 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -88,6 +88,14 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { preconditionFailure("No sub-nodes present in hash-collision leaf node") } + var hasAnyNodes: Bool { false } + + var anyNodeArity: Int { 0 } + + func getAnyNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + preconditionFailure("No sub-nodes present in hash-collision leaf node") + } + var hasPayload: Bool { true } var payloadArity: Int { content.count } From 3803970073246ca359397934a89c7cbcdef1a6cc Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sun, 9 May 2021 23:14:25 +0200 Subject: [PATCH 031/176] [Capsule] Fix compaction upon delete with `SizePredicate` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 18 ++++++++++----- Sources/Capsule/_Common.swift | 25 +++++++++++++++++++++ Sources/Capsule/_HashCollisionMapNode.swift | 2 ++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index a9343d990..de83d913a 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -182,8 +182,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: + switch subNodeNew.sizePredicate { + case .sizeEmpty: + preconditionFailure() + case .sizeOne: if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result return subNodeNew } @@ -191,7 +193,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) } - default: // equivalent to `case 2...` + case .sizeMoreThanOne: // TODO simplify hash-collision compaction (if feasible) if (subNodeNew.payloadArity == 0 && subNodeNew.nodeArity == 0 && subNodeNew.collisionNodeArity == 1) { if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result @@ -215,8 +217,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } - switch subNodeNew.payloadArity { - case 1: + switch subNodeNew.sizePredicate { + case .sizeEmpty: + preconditionFailure() + case .sizeOne: // TODO simplify hash-collision compaction (if feasible) if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) @@ -229,7 +233,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return copyAndMigrateFromCollisionNodeToInline(bitpos, subNodeNew.getPayload(0)) } - default: // equivalent to `case 2...` + case .sizeMoreThanOne: // modify current node (set replacement node) return copyAndSetCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } @@ -346,6 +350,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[TupleLength * index + 1] as! Value) } + var sizePredicate: SizePredicate { SizePredicate(self) } + func dataIndex(_ bitpos: Bitmap) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } func nodeIndex(_ bitpos: Bitmap) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 2b3829cf9..1f906219e 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -100,6 +100,29 @@ enum AnyNode { } } +enum SizePredicate { + case sizeEmpty + case sizeOne + case sizeMoreThanOne +} + +extension SizePredicate { + init(_ node: T) { + if node.anyNodeArity == 0 { + switch node.payloadArity { + case 0: + self = .sizeEmpty + case 1: + self = .sizeOne + case _: + self = .sizeMoreThanOne + } + } else { + self = .sizeMoreThanOne + } + } +} + protocol Node { associatedtype ReturnPayload associatedtype ReturnNode : Node @@ -128,6 +151,8 @@ protocol Node { var payloadArity: Int { get } func getPayload(_ index: Int) -> ReturnPayload + + var sizePredicate: SizePredicate { get } } /// diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 227b4fc13..cbe762855 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -101,6 +101,8 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { var payloadArity: Int { content.count } func getPayload(_ index: Int) -> (Key, Value) { content[index] } + + var sizePredicate: SizePredicate { SizePredicate(self) } } extension HashCollisionMapNode : Equatable where Value : Equatable { From b489a6e786752fe5b29b4e2fdba75837668cc00a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sun, 9 May 2021 23:20:27 +0200 Subject: [PATCH 032/176] [Capsule] Add special case iterator test --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 54db507ee..d7f6a36a2 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -135,13 +135,16 @@ final class CapsuleSmokeTests: CollectionTestCase { var map3: HashMap = map2 for index in 0..(_ map: HashMap) -> Int { + var size = 0 + + for _ in map { + size += 1 + } + + return size + } + + func testIteratorEnumeratesAll() { + let map1: HashMap = [ + CollidableInt(11, 1) : "a", + CollidableInt(12, 1) : "a", + CollidableInt(32769) : "b" + ] + + var map2: HashMap = [:] + for (key, value) in map1 { + map2[key] = value + } + + expectEqual(map1, map2) + } } fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hashable { From da5b4269986173b27c4ad67a8bcb043c3928c6f5 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 08:40:40 +0200 Subject: [PATCH 033/176] [Capsule] Made iterators aware of node enum type --- Sources/Capsule/HashMap.swift | 28 +++++++++++++++----- Sources/Capsule/_Common.swift | 48 ++++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index ab32f5563..44679d9aa 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -137,10 +137,10 @@ public struct HashMap where Key : Hashable { } public struct MapKeyValueTupleIterator { - private var baseIterator: ChampBaseIterator> + private var baseIterator: ChampBaseIterator, HashCollisionMapNode> init(rootNode: BitmapIndexedMapNode) { - self.baseIterator = ChampBaseIterator(rootNode: rootNode) + self.baseIterator = ChampBaseIterator(rootNode: .bitmapIndexed(rootNode)) } } @@ -148,7 +148,15 @@ extension MapKeyValueTupleIterator : IteratorProtocol { public mutating func next() -> (Key, Value)? { guard baseIterator.hasNext() else { return nil } - let payload = baseIterator.currentValueNode?.getPayload(baseIterator.currentValueCursor) + let payload: (Key, Value) + + // TODO remove duplication in specialization + switch baseIterator.currentValueNode! { + case .bitmapIndexed(let node): + payload = node.getPayload(baseIterator.currentValueCursor) + case .hashCollision(let node): + payload = node.getPayload(baseIterator.currentValueCursor) + } baseIterator.currentValueCursor += 1 return payload @@ -156,10 +164,10 @@ extension MapKeyValueTupleIterator : IteratorProtocol { } public struct MapKeyValueTupleReverseIterator { - private var baseIterator: ChampBaseReverseIterator> + private var baseIterator: ChampBaseReverseIterator, HashCollisionMapNode> init(rootNode: BitmapIndexedMapNode) { - self.baseIterator = ChampBaseReverseIterator(rootNode: rootNode) + self.baseIterator = ChampBaseReverseIterator(rootNode: .bitmapIndexed(rootNode)) } } @@ -167,7 +175,15 @@ extension MapKeyValueTupleReverseIterator : IteratorProtocol { public mutating func next() -> (Key, Value)? { guard baseIterator.hasNext() else { return nil } - let payload = baseIterator.currentValueNode?.getPayload(baseIterator.currentValueCursor) + let payload: (Key, Value) + + // TODO remove duplication in specialization + switch baseIterator.currentValueNode! { + case .bitmapIndexed(let node): + payload = node.getPayload(baseIterator.currentValueCursor) + case .hashCollision(let node): + payload = node.getPayload(baseIterator.currentValueCursor) + } baseIterator.currentValueCursor -= 1 return payload diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 1f906219e..023dfade2 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -160,8 +160,9 @@ protocol Node { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -struct ChampBaseIterator { - +struct ChampBaseIterator { + typealias T = AnyNode + var currentValueCursor: Int = 0 var currentValueLength: Int = 0 var currentValueNode: T? = nil @@ -169,10 +170,10 @@ struct ChampBaseIterator { private var currentStackLevel: Int = -1 private var nodeCursorsAndLengths: Array = Array(repeating: 0, count: MaxDepth * 2) private var nodes: Array = Array(repeating: nil, count: MaxDepth) - + init(rootNode: T) { - if (rootNode.hasNodes) { pushNode(rootNode) } - if (rootNode.hasPayload) { setupPayloadNode(rootNode) } + if (rootNode.hasAnyNodes) { pushNode(rootNode) } + if (rootNode.hasPayload) { setupPayloadNode(rootNode) } } private mutating func setupPayloadNode(_ node: T) { @@ -189,7 +190,7 @@ struct ChampBaseIterator { nodes[currentStackLevel] = node nodeCursorsAndLengths[cursorIndex] = 0 - nodeCursorsAndLengths[lengthIndex] = node.nodeArity + nodeCursorsAndLengths[lengthIndex] = node.anyNodeArity } private mutating func popNode() { @@ -210,11 +211,20 @@ struct ChampBaseIterator { if (nodeCursor < nodeLength) { nodeCursorsAndLengths[cursorIndex] += 1 - - let nextNode = nodes[currentStackLevel]!.getNode(nodeCursor) as! T - - if (nextNode.hasNodes) { pushNode(nextNode) } - if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + + // TODO remove duplication in specialization + switch nodes[currentStackLevel]! { + case .bitmapIndexed(let currentNode): + let nextNode = currentNode.getAnyNode(nodeCursor) as! T + + if (nextNode.hasAnyNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + case .hashCollision(let currentNode): + let nextNode = currentNode.getAnyNode(nodeCursor) as! T + + if (nextNode.hasAnyNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + } } else { popNode() } @@ -233,7 +243,8 @@ struct ChampBaseIterator { /// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// -struct ChampBaseReverseIterator { +struct ChampBaseReverseIterator { + typealias T = AnyNode var currentValueCursor: Int = -1 var currentValueNode: T? = nil @@ -256,7 +267,7 @@ struct ChampBaseReverseIterator { currentStackLevel = currentStackLevel + 1 nodeStack[currentStackLevel] = node - nodeIndex[currentStackLevel] = node.nodeArity - 1 + nodeIndex[currentStackLevel] = node.anyNodeArity - 1 } private mutating func popNode() { @@ -273,8 +284,15 @@ struct ChampBaseReverseIterator { let nodeCursor = nodeIndex[currentStackLevel] ; nodeIndex[currentStackLevel] = nodeCursor - 1 if (nodeCursor >= 0) { - let nextNode = nodeStack[currentStackLevel]!.getNode(nodeCursor) as! T - pushNode(nextNode) + // TODO remove duplication in specialization + switch nodeStack[currentStackLevel]! { + case .bitmapIndexed(let currentNode): + let nextNode = currentNode.getAnyNode(nodeCursor) as! T + pushNode(nextNode) + case .hashCollision(let currentNode): + let nextNode = currentNode.getAnyNode(nodeCursor) as! T + pushNode(nextNode) + } } else { let currNode = nodeStack[currentStackLevel]! popNode() From e246bd5cbc822ef5a4e7e9b0358532fff1c3c20e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:03:00 +0200 Subject: [PATCH 034/176] [Capsule] Remove redundant `MapNode` protocol overrides --- Sources/Capsule/_MapNode.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index 0cf3483b0..5455d0873 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -20,16 +20,4 @@ protocol MapNode : Node { func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode - - var hasNodes: Bool { get } - - var nodeArity: Int { get } - - func getNode(_ index: Int) -> ReturnNode - - var hasPayload: Bool { get } - - var payloadArity: Int { get } - - func getPayload(_ index: Int) -> ReturnPayload /* (Key, Value) */ } From 31a7f073d36946534f8581842e65b4b5358db8bc Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:08:34 +0200 Subject: [PATCH 035/176] [Capsule] Rename hash collision node API in `Node` protocol --- Sources/Capsule/_BitmapIndexedMapNode.swift | 38 ++++++++++----------- Sources/Capsule/_Common.swift | 6 ++-- Sources/Capsule/_HashCollisionMapNode.swift | 6 ++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index de83d913a..eefd52e89 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -45,7 +45,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) - return self.getCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) + return self.getHashCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) } return nil @@ -68,7 +68,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) - return self.getCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } return false @@ -116,7 +116,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getCollisionNode(index) + let subNode = self.getHashCollisionNode(index) let collisionHash = subNode.hash @@ -148,8 +148,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (key0 == key) { effect.setModified() - // TODO check globally usage of `nodeArity` and `collisionNodeArity` - if (self.payloadArity == 2 && self.nodeArity == 0 && self.collisionNodeArity == 0) { + // TODO check globally usage of `nodeArity` and `hashCollisionNodeArity` + if (self.payloadArity == 2 && self.nodeArity == 0 && self.hashCollisionNodeArity == 0) { /* * Create new node with remaining pair. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. @@ -163,13 +163,13 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let (k, v) = getPayload(0) return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) } - } else if (self.payloadArity == 1 && self.nodeArity == 0 && self.collisionNodeArity == 1) { + } else if (self.payloadArity == 1 && self.nodeArity == 0 && self.hashCollisionNodeArity == 1) { /* * Create new node with collision node. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. */ - let newCollMap: Bitmap = bitposFrom(maskFrom(getCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(0, 0, newCollMap, Array(arrayLiteral: getCollisionNode(0))) + let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) + return BitmapIndexedMapNode(0, 0, newCollMap, Array(arrayLiteral: getHashCollisionNode(0))) } else { return copyAndRemoveValue(bitpos) } } else { return self } } @@ -195,12 +195,12 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case .sizeMoreThanOne: // TODO simplify hash-collision compaction (if feasible) - if (subNodeNew.payloadArity == 0 && subNodeNew.nodeArity == 0 && subNodeNew.collisionNodeArity == 1) { - if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result + if (subNodeNew.payloadArity == 0 && subNodeNew.nodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1) { + if (self.payloadArity == 0 && (self.nodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result return subNodeNew } else { // inline value (move to front) assertionFailure() - // return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getCollisionNode(0)) + // return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getHashCollisionNode(0)) } } @@ -212,7 +212,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getCollisionNode(index) + let subNode = self.getHashCollisionNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) @@ -222,7 +222,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { preconditionFailure() case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if (self.payloadArity == 0 && (self.nodeArity + self.collisionNodeArity) == 1) { // escalate (singleton or empty) result + if (self.payloadArity == 0 && (self.nodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (k, v) = subNodeNew.getPayload(0) @@ -321,11 +321,11 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - var hasCollisionNodes: Bool { collMap != 0 } + var hasHashCollisionNodes: Bool { collMap != 0 } - var collisionNodeArity: Int { collMap.nonzeroBitCount } + var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } - func getCollisionNode(_ index: Int) -> HashCollisionMapNode { + func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { return content[content.count - 1 - nodeArity - index] as! HashCollisionMapNode } @@ -337,7 +337,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if index < nodeArity { return AnyNode.bitmapIndexed(getNode(index)) } else { - return AnyNode.hashCollision(getCollisionNode(index)) + return AnyNode.hashCollision(getHashCollisionNode(index)) } } @@ -530,8 +530,8 @@ extension BitmapIndexedMapNode : Equatable where Value : Equatable { } } - for index in 0.. ReturnNode - var hasCollisionNodes: Bool { get } + var hasHashCollisionNodes: Bool { get } - var collisionNodeArity: Int { get } + var hashCollisionNodeArity: Int { get } - func getCollisionNode(_ index: Int) -> ReturnCollisionNode + func getHashCollisionNode(_ index: Int) -> ReturnCollisionNode var hasAnyNodes: Bool { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index cbe762855..76a8b2e83 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -80,11 +80,11 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { preconditionFailure("No sub-nodes present in hash-collision leaf node") } - var hasCollisionNodes: Bool { false } + var hasHashCollisionNodes: Bool { false } - var collisionNodeArity: Int { 0 } + var hashCollisionNodeArity: Int { 0 } - func getCollisionNode(_ index: Int) -> HashCollisionMapNode { + func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From e0ff97728c2bdb3cc7305d9bb2d3d332590fb1b8 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:13:55 +0200 Subject: [PATCH 036/176] [Capsule] Rename bitmap indexed node API in `Node` protocol --- Sources/Capsule/_BitmapIndexedMapNode.swift | 50 ++++++++++----------- Sources/Capsule/_Common.swift | 6 +-- Sources/Capsule/_HashCollisionMapNode.swift | 6 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index eefd52e89..3d5020684 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -40,7 +40,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).get(key, keyHash, shift + BitPartitionSize) + return self.getBitmapIndexedNode(index).get(key, keyHash, shift + BitPartitionSize) } if ((collMap & bitpos) != 0) { @@ -63,7 +63,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - return self.getNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } if ((collMap & bitpos) != 0) { @@ -103,7 +103,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getNode(index) + let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { @@ -148,8 +148,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (key0 == key) { effect.setModified() - // TODO check globally usage of `nodeArity` and `hashCollisionNodeArity` - if (self.payloadArity == 2 && self.nodeArity == 0 && self.hashCollisionNodeArity == 0) { + // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` + if (self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0) { /* * Create new node with remaining pair. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. @@ -163,7 +163,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let (k, v) = getPayload(0) return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) } - } else if (self.payloadArity == 1 && self.nodeArity == 0 && self.hashCollisionNodeArity == 1) { + } else if (self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1) { /* * Create new node with collision node. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. @@ -177,7 +177,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getNode(index) + let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) @@ -186,7 +186,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case .sizeEmpty: preconditionFailure() case .sizeOne: - if (self.payloadArity == 0 && self.nodeArity == 1) { // escalate (singleton or empty) result + if (self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1) { // escalate (singleton or empty) result return subNodeNew } else { // inline value (move to front) @@ -195,8 +195,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case .sizeMoreThanOne: // TODO simplify hash-collision compaction (if feasible) - if (subNodeNew.payloadArity == 0 && subNodeNew.nodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1) { - if (self.payloadArity == 0 && (self.nodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result + if (subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1) { + if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result return subNodeNew } else { // inline value (move to front) assertionFailure() @@ -222,7 +222,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { preconditionFailure() case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if (self.payloadArity == 0 && (self.nodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result + if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (k, v) = subNodeNew.getPayload(0) @@ -289,11 +289,11 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - var hasNodes: Bool { nodeMap != 0 } + var hasBitmapIndexedNodes: Bool { nodeMap != 0 } - var nodeArity: Int { nodeMap.nonzeroBitCount } + var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } - func getNode(_ index: Int) -> BitmapIndexedMapNode { + func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { content[content.count - 1 - index] as! BitmapIndexedMapNode } @@ -303,7 +303,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } private func isCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = content.count - 1 - nodeArity - index + let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index return isAnyNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } @@ -326,7 +326,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return content[content.count - 1 - nodeArity - index] as! HashCollisionMapNode + return content[content.count - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode } var hasAnyNodes: Bool { (nodeMap | collMap) != 0 } @@ -334,8 +334,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var anyNodeArity: Int { (nodeMap | collMap).nonzeroBitCount } func getAnyNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { - if index < nodeArity { - return AnyNode.bitmapIndexed(getNode(index)) + if index < bitmapIndexedNodeArity { + return AnyNode.bitmapIndexed(getBitmapIndexedNode(index)) } else { return AnyNode.hashCollision(getHashCollisionNode(index)) } @@ -405,7 +405,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } func copyAndSetCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { - let idx = self.content.count - 1 - nodeArity - self.collIndex(bitpos) + let idx = self.content.count - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } @@ -454,7 +454,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { func copyAndMigrateFromInlineToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { let idxOld = TupleLength * dataIndex(bitpos) - let idxNew = self.content.count - TupleLength - nodeArity - collIndex(bitpos) + let idxNew = self.content.count - TupleLength - bitmapIndexedNodeArity - collIndex(bitpos) var dst = self.content dst.removeSubrange(idxOld.. : MapNode where Key : Hashable { } func copyAndMigrateFromCollisionNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - nodeArity - collIndex(bitpos) + let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) let idxNew = TupleLength * dataIndex(bitpos) var dst = self.content @@ -486,7 +486,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } func copyAndMigrateFromCollisionNodeToNode(_ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - nodeArity - collIndex(bitpos) + let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) let idxNew = self.content.count - 1 - nodeIndex(bitpos) var dst = self.content @@ -498,7 +498,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // func copyAndMigrateFromNodeToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { // let idxOld = self.content.count - 1 - nodeIndex(bitpos) -// let idxNew = self.content.count - 1 - nodeArity - 1 - collIndex(bitpos) +// let idxNew = self.content.count - 1 - bitmapIndexedNodeArity - 1 - collIndex(bitpos) // // var dst = self.content // dst.remove(at: idxOld) @@ -524,8 +524,8 @@ extension BitmapIndexedMapNode : Equatable where Value : Equatable { } } - for index in 0.. ReturnNode + func getBitmapIndexedNode(_ index: Int) -> ReturnNode var hasHashCollisionNodes: Bool { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 76a8b2e83..05f9be790 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -72,11 +72,11 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { } } - var hasNodes: Bool { false } + var hasBitmapIndexedNodes: Bool { false } - var nodeArity: Int { 0 } + var bitmapIndexedNodeArity: Int { 0 } - func getNode(_ index: Int) -> HashCollisionMapNode { + func getBitmapIndexedNode(_ index: Int) -> HashCollisionMapNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From b87d6109a65c35a98055ab412160a48da9cd85e1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:25:56 +0200 Subject: [PATCH 037/176] [Capsule] Rename generic node API in `Node` protocol --- Sources/Capsule/_BitmapIndexedMapNode.swift | 6 +-- Sources/Capsule/_Common.swift | 44 ++++++++++----------- Sources/Capsule/_HashCollisionMapNode.swift | 6 +-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 3d5020684..21a758afc 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -329,11 +329,11 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { return content[content.count - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode } - var hasAnyNodes: Bool { (nodeMap | collMap) != 0 } + var hasNodes: Bool { (nodeMap | collMap) != 0 } - var anyNodeArity: Int { (nodeMap | collMap).nonzeroBitCount } + var nodeArity: Int { (nodeMap | collMap).nonzeroBitCount } - func getAnyNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { if index < bitmapIndexedNodeArity { return AnyNode.bitmapIndexed(getBitmapIndexedNode(index)) } else { diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index c6dacf861..fe65b307a 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -81,21 +81,21 @@ enum AnyNode { // } // } - var hasAnyNodes: Bool { + var hasNodes: Bool { switch self { case .bitmapIndexed(let node): - return node.hasAnyNodes + return node.hasNodes case .hashCollision(let node): - return node.hasAnyNodes + return node.hasNodes } } - var anyNodeArity: Int { + var nodeArity: Int { switch self { case .bitmapIndexed(let node): - return node.anyNodeArity + return node.nodeArity case .hashCollision(let node): - return node.anyNodeArity + return node.nodeArity } } } @@ -108,7 +108,7 @@ enum SizePredicate { extension SizePredicate { init(_ node: T) { - if node.anyNodeArity == 0 { + if node.nodeArity == 0 { switch node.payloadArity { case 0: self = .sizeEmpty @@ -140,11 +140,11 @@ protocol Node { func getHashCollisionNode(_ index: Int) -> ReturnCollisionNode - var hasAnyNodes: Bool { get } + var hasNodes: Bool { get } - var anyNodeArity: Int { get } + var nodeArity: Int { get } - func getAnyNode(_ index: Int) -> AnyNode + func getNode(_ index: Int) -> AnyNode var hasPayload: Bool { get } @@ -172,8 +172,8 @@ struct ChampBaseIterator { private var nodes: Array = Array(repeating: nil, count: MaxDepth) init(rootNode: T) { - if (rootNode.hasAnyNodes) { pushNode(rootNode) } - if (rootNode.hasPayload) { setupPayloadNode(rootNode) } + if (rootNode.hasNodes) { pushNode(rootNode) } + if (rootNode.hasPayload) { setupPayloadNode(rootNode) } } private mutating func setupPayloadNode(_ node: T) { @@ -190,7 +190,7 @@ struct ChampBaseIterator { nodes[currentStackLevel] = node nodeCursorsAndLengths[cursorIndex] = 0 - nodeCursorsAndLengths[lengthIndex] = node.anyNodeArity + nodeCursorsAndLengths[lengthIndex] = node.nodeArity } private mutating func popNode() { @@ -215,15 +215,15 @@ struct ChampBaseIterator { // TODO remove duplication in specialization switch nodes[currentStackLevel]! { case .bitmapIndexed(let currentNode): - let nextNode = currentNode.getAnyNode(nodeCursor) as! T + let nextNode = currentNode.getNode(nodeCursor) as! T - if (nextNode.hasAnyNodes) { pushNode(nextNode) } - if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + if (nextNode.hasNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } case .hashCollision(let currentNode): - let nextNode = currentNode.getAnyNode(nodeCursor) as! T + let nextNode = currentNode.getNode(nodeCursor) as! T - if (nextNode.hasAnyNodes) { pushNode(nextNode) } - if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + if (nextNode.hasNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } } } else { popNode() @@ -267,7 +267,7 @@ struct ChampBaseReverseIterator : MapNode where Key : Hashable { preconditionFailure("No sub-nodes present in hash-collision leaf node") } - var hasAnyNodes: Bool { false } + var hasNodes: Bool { false } - var anyNodeArity: Int { 0 } + var nodeArity: Int { 0 } - func getAnyNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From 25f6de0d6528cbc6c662c5a0884c53639ffbb4d1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:31:28 +0200 Subject: [PATCH 038/176] [Capsule] Clean-up associated types --- Sources/Capsule/_Common.swift | 10 +++++----- Sources/Capsule/_MapNode.swift | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index fe65b307a..bf85856da 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -125,26 +125,26 @@ extension SizePredicate { protocol Node { associatedtype ReturnPayload - associatedtype ReturnNode : Node - associatedtype ReturnCollisionNode : Node + associatedtype ReturnBitmapIndexedNode : Node + associatedtype ReturnHashCollisionNode : Node var hasBitmapIndexedNodes: Bool { get } var bitmapIndexedNodeArity: Int { get } - func getBitmapIndexedNode(_ index: Int) -> ReturnNode + func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode var hasHashCollisionNodes: Bool { get } var hashCollisionNodeArity: Int { get } - func getHashCollisionNode(_ index: Int) -> ReturnCollisionNode + func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode var hasNodes: Bool { get } var nodeArity: Int { get } - func getNode(_ index: Int) -> AnyNode + func getNode(_ index: Int) -> AnyNode var hasPayload: Bool { get } diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index 5455d0873..7644c153a 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -17,7 +17,7 @@ protocol MapNode : Node { func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool - func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode + func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode - func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnNode + func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode } From 2d45f7af8b11af8757626ff29be3654ab0ebbd5e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:38:33 +0200 Subject: [PATCH 039/176] [Capsule] Rename `AnyNode` to `TrieNode` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 6 +++--- Sources/Capsule/_Common.swift | 8 ++++---- Sources/Capsule/_HashCollisionMapNode.swift | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 21a758afc..3b2e41183 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -333,11 +333,11 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var nodeArity: Int { (nodeMap | collMap).nonzeroBitCount } - func getNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { if index < bitmapIndexedNodeArity { - return AnyNode.bitmapIndexed(getBitmapIndexedNode(index)) + return .bitmapIndexed(getBitmapIndexedNode(index)) } else { - return AnyNode.hashCollision(getHashCollisionNode(index)) + return .hashCollision(getHashCollisionNode(index)) } } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index bf85856da..e4a9ca592 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -41,7 +41,7 @@ func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) } -enum AnyNode { +enum TrieNode { case bitmapIndexed(BitmapIndexedNode) case hashCollision(HashCollisionNode) @@ -144,7 +144,7 @@ protocol Node { var nodeArity: Int { get } - func getNode(_ index: Int) -> AnyNode + func getNode(_ index: Int) -> TrieNode var hasPayload: Bool { get } @@ -161,7 +161,7 @@ protocol Node { /// node before traversing sub-nodes (left to right). /// struct ChampBaseIterator { - typealias T = AnyNode + typealias T = TrieNode var currentValueCursor: Int = 0 var currentValueLength: Int = 0 @@ -244,7 +244,7 @@ struct ChampBaseIterator { /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// struct ChampBaseReverseIterator { - typealias T = AnyNode + typealias T = TrieNode var currentValueCursor: Int = -1 var currentValueNode: T? = nil diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index e88740824..15e88c456 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -92,7 +92,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { var nodeArity: Int { 0 } - func getNode(_ index: Int) -> AnyNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From e9fe942c99b6a36b367085a65d1ea6d8250dc27f Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 10:49:10 +0200 Subject: [PATCH 040/176] [Capsule] Clean-up method names --- Sources/Capsule/_BitmapIndexedMapNode.swift | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 3b2e41183..4c0d1b082 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -102,20 +102,20 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) if (!effect.modified) { return self } else { - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) let collisionHash = subNode.hash @@ -125,7 +125,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (!effect.modified) { return self } else { - return copyAndSetCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + BitPartitionSize) @@ -176,7 +176,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((nodeMap & bitpos) != 0) { let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) @@ -205,13 +205,13 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } // modify current node (set replacement node) - return copyAndSetNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } if ((collMap & bitpos) != 0) { let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) @@ -235,7 +235,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case .sizeMoreThanOne: // modify current node (set replacement node) - return copyAndSetCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } @@ -297,19 +297,19 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { content[content.count - 1 - index] as! BitmapIndexedMapNode } - private func isNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let slotIndex = content.count - 1 - index - return isAnyNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) + return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } - private func isCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index - return isAnyNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) + return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference // to pass into `isKnownUniquelyReferenced` - private func isAnyNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let fakeNode = BitmapIndexedMapNode(0, 0, 0, Array()) var realNode = content[slotIndex] as AnyObject @@ -400,16 +400,16 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - func copyAndSetNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { + func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) - return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } - func copyAndSetCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: T) -> BitmapIndexedMapNode { + func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) - return copyAndAnyNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } - private func copyAndAnyNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { + private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { if (isStorageKnownUniquelyReferenced) { // no copying if already editable self.content[idx] = newNode From b4c7f1dd51acd6cbd68ac1634ef120b5684213c3 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 11:18:08 +0200 Subject: [PATCH 041/176] [Capsule] Name payload tuple components --- Sources/Capsule/_BitmapIndexedMapNode.swift | 6 +++--- Sources/Capsule/_HashCollisionMapNode.swift | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 4c0d1b082..b31de541c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -35,7 +35,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((dataMap & bitpos) != 0) { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) - if (key == payload.0) { return payload.1 } else { return nil } + if (key == payload.key) { return payload.value } else { return nil } } if ((nodeMap & bitpos) != 0) { @@ -58,7 +58,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if ((dataMap & bitpos) != 0) { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) - return key == payload.0 + return key == payload.key } if ((nodeMap & bitpos) != 0) { @@ -345,7 +345,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (Key, Value) { + func getPayload(_ index: Int) -> (key: Key, value: Value) { (content[TupleLength * index + 0] as! Key, content[TupleLength * index + 1] as! Value) } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 15e88c456..b8df8f04e 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -11,11 +11,11 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { let hash: Int - let content: Array<(Key, Value)> + let content: Array<(key: Key, value: Value)> - init(_ hash: Int, _ content: Array<(Key, Value)>) { + init(_ hash: Int, _ content: Array<(key: Key, value: Value)>) { // precondition(content.count >= 2) - precondition(content.map { $0.0 }.allSatisfy {$0.hashValue == hash}) + precondition(content.map { $0.key }.allSatisfy {$0.hashValue == hash}) self.hash = hash self.content = content @@ -23,24 +23,24 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { if (self.hash == hash) { - return content.first(where: { key == $0.0 }).map { $0.1 } + return content.first(where: { key == $0.key }).map { $0.value } } else { return nil } } func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { - return self.hash == hash && content.contains(where: { key == $0.0 }) + return self.hash == hash && content.contains(where: { key == $0.key }) } // // TODO requires Value to be Equatable // func contains(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int) -> Bool { -// return self.hash == hash && content.contains(where: { key == $0.0 && value == $0.1 }) +// return self.hash == hash && content.contains(where: { key == $0.key && value == $0.value }) // } func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) if (self.containsKey(key, hash, shift)) { - let index = content.firstIndex(where: { key == $0.0 })! + let index = content.firstIndex(where: { key == $0.key })! let updatedContent = content[0.. : MapNode where Key : Hashable { return self } else { effect.setModified() - let updatedContent = content.filter { $0.0 != key } + let updatedContent = content.filter { $0.key != key } assert(updatedContent.count == content.count - 1) // switch updatedContent.count { @@ -100,7 +100,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { var payloadArity: Int { content.count } - func getPayload(_ index: Int) -> (Key, Value) { content[index] } + func getPayload(_ index: Int) -> (key: Key, value: Value) { content[index] } var sizePredicate: SizePredicate { SizePredicate(self) } } From 68948e1da7cb13d2ba12e40fa8670ced07e045e8 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 11:50:35 +0200 Subject: [PATCH 042/176] [Capsule] Add precondition violation error message --- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index b31de541c..a4d781245 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -184,7 +184,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (!effect.modified) { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: - preconditionFailure() + preconditionFailure("Sub-node must have at least one element.") case .sizeOne: if (self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1) { // escalate (singleton or empty) result return subNodeNew @@ -219,7 +219,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (!effect.modified) { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: - preconditionFailure() + preconditionFailure("Sub-node must have at least one element.") case .sizeOne: // TODO simplify hash-collision compaction (if feasible) if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result From 11f7a8a221b1c375d3919e3b809ba6897dfc9ca1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 12:06:20 +0200 Subject: [PATCH 043/176] [Capsule] Remove outdated comment --- Sources/Capsule/_BitmapIndexedMapNode.swift | 26 --------------------- 1 file changed, 26 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index a4d781245..121337d25 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -358,32 +358,6 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } - /// TODO: leverage lazy copy-on-write only when aliased. The pattern required by the current data structure design - /// isn't expressible in Swift currently (i.e., `isKnownUniquelyReferenced(&self)` isn't supported). Example: - /// - /// ``` - /// class Node { - /// var src: [Any] - /// func updateInlineOrCopy(idx: Int, newValue: Any) { - /// if isKnownUniquelyReferenced(&self) { // this isn't supported ... - /// src[idx] = newValue - /// return self - /// } else { - /// var dst = self.content - /// dst[idx] = newValue - /// return Node(dst) - /// } - /// } - /// } - /// ``` - /// - /// Some more context: - /// * Node should be a reference counted data type (i.e., `class`) - /// * In a optimized version `src` would be gone, and `Node` likely become a subclass of `ManagedBuffer` - /// * I want to check `isKnownUniquelyReferenced(&self)` since `updateInlineOrCopy` should be recursive call that decides upon returning from recursion if modifications are necessary - /// - /// Possible mitigations: transform recursive to loop where `isKnownUniquelyReferenced` could be checked from the outside. - /// This would be very invasive though and make problem logic hard to understand and maintain. func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) + 1 From 0eae829f9d67bde626ca70a0a5324b8f05e3104a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 12:13:13 +0200 Subject: [PATCH 044/176] [Capsule] Clean-up `TrieNode` enum --- Sources/Capsule/_Common.swift | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index e4a9ca592..b7545f757 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -45,14 +45,7 @@ enum TrieNode { case bitmapIndexed(BitmapIndexedNode) case hashCollision(HashCollisionNode) -// func value() -> T { -// switch self { -// case .bitmapIndexed(let node): -// return node as! T -// case .hashCollision(let node): -// return node as! T -// } -// } + /// The convenience computed properties below are used in the base iterator implementations. var hasPayload: Bool { switch self { @@ -72,15 +65,6 @@ enum TrieNode { } } -// func getPayload(_ index: Int) -> Node.ReturnPayload { -// switch self { -// case .bitmapIndexed(let node): -// return node.getPayload(index) -// case .hashCollision(let node): -// return node.getPayload(index) -// } -// } - var hasNodes: Bool { switch self { case .bitmapIndexed(let node): From 43731f7aa1e7c3b40c6261e2cde3f34d5dfe1d36 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 10 May 2021 12:18:45 +0200 Subject: [PATCH 045/176] [Capsule] Update copyright headers --- Sources/Capsule/_Common.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index b7545f757..5c5375bf4 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 From 193753ecc32730a05dc8358448fa56423f3a61e7 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 08:26:27 +0200 Subject: [PATCH 046/176] [Capsule] Add convenience initializer for `BitmapIndexedMapNode` --- Sources/Capsule/HashMap.swift | 2 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 44679d9aa..f12d659b6 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -21,7 +21,7 @@ public struct HashMap where Key : Hashable { } public init() { - self.init(BitmapIndexedMapNode(0, 0, 0, Array()), 0, 0) + self.init(BitmapIndexedMapNode(), 0, 0) } public init(_ map: HashMap) { diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 121337d25..5a32a83bb 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -28,6 +28,10 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { self.content = content } + convenience init() { + self.init(0, 0, 0, Array()) + } + func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -310,7 +314,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference // to pass into `isKnownUniquelyReferenced` private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let fakeNode = BitmapIndexedMapNode(0, 0, 0, Array()) + let fakeNode = BitmapIndexedMapNode() var realNode = content[slotIndex] as AnyObject content[slotIndex] = fakeNode From f168e7fcbafbd793835ffbbb928c3d0364457783 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 09:32:50 +0200 Subject: [PATCH 047/176] [Capsule] Introduce convenience constructors for `BitmapIndexedMapNode` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 41 +++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 5a32a83bb..d143357db 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -28,8 +28,23 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { self.content = content } - convenience init() { - self.init(0, 0, 0, Array()) + convenience init(dataMap: Bitmap = 0, nodeMap: Bitmap = 0, collMap: Bitmap = 0, arrayLiteral content: Any...) { + self.init(dataMap, nodeMap, collMap, content) + } + + // TODO improve performance of variadic implementation or consider specializing for two key-value tuples + convenience init(dataMap: Bitmap, arrayLiteral elements: (Key, Value)...) { + self.init(dataMap, 0, 0, elements.flatMap { [$0.0, $0.1] }) + } + + // TODO improve performance of variadic implementation or consider specializing for singleton nodes + convenience init(nodeMap: Bitmap, arrayLiteral elements: BitmapIndexedMapNode...) { + self.init(0, nodeMap, 0, elements) + } + + // TODO improve performance of variadic implementation or consider specializing for singleton nodes + convenience init(collMap: Bitmap, arrayLiteral elements: HashCollisionMapNode...) { + self.init(0, 0, collMap, elements) } func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { @@ -161,11 +176,9 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let newDataMap: Bitmap if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } if (index == 0) { - let (k, v) = getPayload(1) - return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v) ) + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1)) } else { - let (k, v) = getPayload(0) - return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(0)) } } else if (self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1) { /* @@ -173,7 +186,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { * returned, or b) unwrapped and inlined during returning. */ let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(0, 0, newCollMap, Array(arrayLiteral: getHashCollisionNode(0))) + return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) } else { return copyAndRemoveValue(bitpos) } } else { return self } } @@ -229,9 +242,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) - let (k, v) = subNodeNew.getPayload(0) - - return BitmapIndexedMapNode(newDataMap, 0, 0, Array(arrayLiteral: k, v)) + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: subNodeNew.getPayload(0)) } else { // inline value (move to front) return copyAndMigrateFromCollisionNodeToInline(bitpos, subNodeNew.getPayload(0)) @@ -257,9 +268,9 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let dataMap = bitposFrom(mask0) | bitposFrom(mask1) if (mask0 < mask1) { - return BitmapIndexedMapNode(dataMap, 0, 0, Array(arrayLiteral: key0, value0, key1, value1)) + return BitmapIndexedMapNode(dataMap: dataMap, arrayLiteral: (key0, value0), (key1, value1)) } else { - return BitmapIndexedMapNode(dataMap, 0, 0, Array(arrayLiteral: key1, value1, key0, value0)) + return BitmapIndexedMapNode(dataMap: dataMap, arrayLiteral: (key1, value1), (key0, value0)) } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie @@ -267,7 +278,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let nodeMap = bitposFrom(mask0) let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) + return BitmapIndexedMapNode(nodeMap: nodeMap, arrayLiteral: node) } } @@ -282,14 +293,14 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let dataMap = bitposFrom(mask0) let collMap = bitposFrom(mask1) - return BitmapIndexedMapNode(dataMap, 0, collMap, Array(arrayLiteral: key0, value0, node1)) + return BitmapIndexedMapNode(dataMap: dataMap, collMap: collMap, arrayLiteral: key0, value0, node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let nodeMap = bitposFrom(mask0) let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(0, nodeMap, 0, Array(arrayLiteral: node)) + return BitmapIndexedMapNode(nodeMap: nodeMap, arrayLiteral: node) } } From 612252e344c1d8e094340e6a1ce388ac100778e6 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 09:52:19 +0200 Subject: [PATCH 048/176] [Capsule] Rework merge functions --- Sources/Capsule/_BitmapIndexedMapNode.swift | 25 +++++++-------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index d143357db..0fb167a40 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -263,22 +263,18 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(keyHash1, shift) - if (mask0 != mask1) { + if mask0 != mask1 { // unique prefixes, payload fits on same level - let dataMap = bitposFrom(mask0) | bitposFrom(mask1) - - if (mask0 < mask1) { - return BitmapIndexedMapNode(dataMap: dataMap, arrayLiteral: (key0, value0), (key1, value1)) + if mask0 < mask1 { + return BitmapIndexedMapNode(dataMap: bitposFrom(mask0) | bitposFrom(mask1), arrayLiteral: (key0, value0), (key1, value1)) } else { - return BitmapIndexedMapNode(dataMap: dataMap, arrayLiteral: (key1, value1), (key0, value0)) + return BitmapIndexedMapNode(dataMap: bitposFrom(mask1) | bitposFrom(mask0), arrayLiteral: (key1, value1), (key0, value0)) } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie - - let nodeMap = bitposFrom(mask0) let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(nodeMap: nodeMap, arrayLiteral: node) + return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) } } @@ -288,19 +284,14 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(nodeHash1, shift) - if (mask0 != mask1) { + if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - let dataMap = bitposFrom(mask0) - let collMap = bitposFrom(mask1) - - return BitmapIndexedMapNode(dataMap: dataMap, collMap: collMap, arrayLiteral: key0, value0, node1) + return BitmapIndexedMapNode(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), arrayLiteral: key0, value0, node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie - - let nodeMap = bitposFrom(mask0) let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + BitPartitionSize) - return BitmapIndexedMapNode(nodeMap: nodeMap, arrayLiteral: node) + return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) } } From 2e2297a83f16788203aa26ddaa8dfeebb8a6d175 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 10:36:20 +0200 Subject: [PATCH 049/176] [Capsule] Avoid wrapping `if` conditions in parentheses --- Sources/Capsule/HashMap.swift | 12 ++-- Sources/Capsule/_BitmapIndexedMapNode.swift | 68 ++++++++++----------- Sources/Capsule/_Common.swift | 18 +++--- Sources/Capsule/_HashCollisionMapNode.swift | 6 +- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index f12d659b6..226d348b2 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -78,8 +78,8 @@ public struct HashMap where Key : Hashable { let keyHash = computeHash(key) let newRootNode = rootNode.updated(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) - if (effect.modified) { - if (effect.replacedValue) { + if effect.modified { + if effect.replacedValue { self.rootNode = newRootNode // self.cachedKeySetHashCode = cachedKeySetHashCode // self.cachedSize = cachedSize @@ -97,8 +97,8 @@ public struct HashMap where Key : Hashable { let keyHash = computeHash(key) let newRootNode = rootNode.updated(false, key, value, keyHash, 0, &effect) - if (effect.modified) { - if (effect.replacedValue) { + if effect.modified { + if effect.replacedValue { return Self(newRootNode, cachedKeySetHashCode, cachedSize) } else { return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize + 1) @@ -117,7 +117,7 @@ public struct HashMap where Key : Hashable { let keyHash = computeHash(key) let newRootNode = rootNode.removed(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) - if (effect.modified) { + if effect.modified { self.rootNode = newRootNode self.cachedKeySetHashCode = cachedKeySetHashCode ^ keyHash self.cachedSize = cachedSize - 1 @@ -130,7 +130,7 @@ public struct HashMap where Key : Hashable { let keyHash = computeHash(key) let newRootNode = rootNode.removed(false, key, keyHash, 0, &effect) - if (effect.modified) { + if effect.modified { return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) } else { return self } } diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 0fb167a40..c65baa27c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -51,18 +51,18 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if ((dataMap & bitpos) != 0) { + if (dataMap & bitpos) != 0 { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) - if (key == payload.key) { return payload.value } else { return nil } + if key == payload.key { return payload.value } else { return nil } } - if ((nodeMap & bitpos) != 0) { + if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) return self.getBitmapIndexedNode(index).get(key, keyHash, shift + BitPartitionSize) } - if ((collMap & bitpos) != 0) { + if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) return self.getHashCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) } @@ -74,18 +74,18 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if ((dataMap & bitpos) != 0) { + if (dataMap & bitpos) != 0 { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) return key == payload.key } - if ((nodeMap & bitpos) != 0) { + if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } - if ((collMap & bitpos) != 0) { + if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) } @@ -97,17 +97,17 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if ((dataMap & bitpos) != 0) { + if (dataMap & bitpos) != 0 { let index = indexFrom(dataMap, mask, bitpos) let (key0, value0) = self.getPayload(index) - if (key0 == key) { + if key0 == key { effect.setReplacedValue() return copyAndSetValue(isStorageKnownUniquelyReferenced, bitpos, value) } else { let keyHash0 = computeHash(key0) - if (keyHash0 == keyHash) { + if keyHash0 == keyHash { let subNodeNew = HashCollisionMapNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() return copyAndMigrateFromInlineToCollisionNode(bitpos, subNodeNew) @@ -119,29 +119,29 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - if ((nodeMap & bitpos) != 0) { + if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { + if !effect.modified { return self } else { return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } - if ((collMap & bitpos) != 0) { + if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) let collisionHash = subNode.hash - if (keyHash == collisionHash) { + if keyHash == collisionHash { let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { + if !effect.modified { return self } else { return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -161,26 +161,26 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if ((dataMap & bitpos) != 0) { + if (dataMap & bitpos) != 0 { let index = indexFrom(dataMap, mask, bitpos) let (key0, _) = self.getPayload(index) - if (key0 == key) { + if key0 == key { effect.setModified() // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` - if (self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0) { + if self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0 { /* * Create new node with remaining pair. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. */ let newDataMap: Bitmap - if (shift == 0) { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } - if (index == 0) { + if shift == 0 { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } + if index == 0 { return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1)) } else { return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(0)) } - } else if (self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1) { + } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { /* * Create new node with collision node. The new node will a) either become the new root * returned, or b) unwrapped and inlined during returning. @@ -191,19 +191,19 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } else { return self } } - if ((nodeMap & bitpos) != 0) { + if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { return self } + if !effect.modified { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") case .sizeOne: - if (self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1) { // escalate (singleton or empty) result + if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 { // escalate (singleton or empty) result return subNodeNew } else { // inline value (move to front) @@ -212,8 +212,8 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { case .sizeMoreThanOne: // TODO simplify hash-collision compaction (if feasible) - if (subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1) { - if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result + if subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1 { + if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { // escalate (singleton or empty) result return subNodeNew } else { // inline value (move to front) assertionFailure() @@ -226,20 +226,20 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } } - if ((collMap & bitpos) != 0) { + if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) - if (!effect.modified) { return self } + if !effect.modified { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if (self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1) { // escalate (singleton or empty) result + if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { // escalate (singleton or empty) result // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: subNodeNew.getPayload(0)) @@ -367,7 +367,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { let idx = TupleLength * dataIndex(bitpos) + 1 - if (isStorageKnownUniquelyReferenced) { + if isStorageKnownUniquelyReferenced { // no copying if already editable self.content[idx] = newValue @@ -390,7 +390,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { } private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { - if (isStorageKnownUniquelyReferenced) { + if isStorageKnownUniquelyReferenced { // no copying if already editable self.content[idx] = newNode @@ -499,19 +499,19 @@ extension BitmapIndexedMapNode : Equatable where Value : Equatable { private static func deepContentEquality(_ lhs: BitmapIndexedMapNode, _ rhs: BitmapIndexedMapNode) -> Bool { for index in 0.. { private var nodes: Array = Array(repeating: nil, count: MaxDepth) init(rootNode: T) { - if (rootNode.hasNodes) { pushNode(rootNode) } - if (rootNode.hasPayload) { setupPayloadNode(rootNode) } + if rootNode.hasNodes { pushNode(rootNode) } + if rootNode.hasPayload { setupPayloadNode(rootNode) } } private mutating func setupPayloadNode(_ node: T) { @@ -193,7 +193,7 @@ struct ChampBaseIterator { let nodeCursor = nodeCursorsAndLengths[cursorIndex] let nodeLength = nodeCursorsAndLengths[lengthIndex] - if (nodeCursor < nodeLength) { + if nodeCursor < nodeLength { nodeCursorsAndLengths[cursorIndex] += 1 // TODO remove duplication in specialization @@ -201,13 +201,13 @@ struct ChampBaseIterator { case .bitmapIndexed(let currentNode): let nextNode = currentNode.getNode(nodeCursor) as! T - if (nextNode.hasNodes) { pushNode(nextNode) } - if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + if nextNode.hasNodes { pushNode(nextNode) } + if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } case .hashCollision(let currentNode): let nextNode = currentNode.getNode(nodeCursor) as! T - if (nextNode.hasNodes) { pushNode(nextNode) } - if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + if nextNode.hasNodes { pushNode(nextNode) } + if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } } } else { popNode() @@ -267,7 +267,7 @@ struct ChampBaseReverseIterator= 0) { let nodeCursor = nodeIndex[currentStackLevel] ; nodeIndex[currentStackLevel] = nodeCursor - 1 - if (nodeCursor >= 0) { + if nodeCursor >= 0 { // TODO remove duplication in specialization switch nodeStack[currentStackLevel]! { case .bitmapIndexed(let currentNode): @@ -281,7 +281,7 @@ struct ChampBaseReverseIterator : MapNode where Key : Hashable { } func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { - if (self.hash == hash) { + if self.hash == hash { return content.first(where: { key == $0.key }).map { $0.value } } else { return nil } } @@ -39,7 +39,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) - if (self.containsKey(key, hash, shift)) { + if self.containsKey(key, hash, shift) { let index = content.firstIndex(where: { key == $0.key })! let updatedContent = content[0.. : MapNode where Key : Hashable { // TODO rethink such that `precondition(content.count >= 2)` holds // TODO consider returning either type of `BitmapIndexedMapNode` and `HashCollisionMapNode` func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { - if (!self.containsKey(key, hash, shift)) { + if !self.containsKey(key, hash, shift) { return self } else { effect.setModified() From 7a4005eff323c79acb54dd83a479ba42c2cdd198 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 10:48:46 +0200 Subject: [PATCH 050/176] [Capsule] Put colons next to identifiers --- Sources/Capsule/HashMap+Equatable.swift | 2 +- .../HashMap+ExpressibleByDictionaryLiteral.swift | 2 +- Sources/Capsule/HashMap+Hashable.swift | 2 +- Sources/Capsule/HashMap+Sequence.swift | 4 ++-- Sources/Capsule/HashMap.swift | 10 +++++----- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- Sources/Capsule/_Common.swift | 12 ++++++------ Sources/Capsule/_HashCollisionMapNode.swift | 4 ++-- Sources/Capsule/_MapNode.swift | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift index 55190497f..a88c19ae4 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// // TODO check Dictionary semantics of Equatable (i.e., if it only compares keys or also values) -extension HashMap : Equatable where Value : Equatable { +extension HashMap: Equatable where Value: Equatable { public static func == (lhs: HashMap, rhs: HashMap) -> Bool { lhs.cachedSize == rhs.cachedSize && lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index a1f85fca8..0f5068839 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -extension HashMap : ExpressibleByDictionaryLiteral { +extension HashMap: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (Key, Value)...) { let map = elements.reduce(Self()) { (map, element) in let (key, value) = element return map.inserting(key: key, value: value) diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index 9c834b79d..289e8a70d 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// // TODO settle on (commutative) hash semantics that is reconcilable with `cachedKeySetHashCode` -extension HashMap : Hashable where Value : Hashable { +extension HashMap: Hashable where Value: Hashable { public func hash(into hasher: inout Hasher) { var commutativeHash = 0 for (k, v) in self { diff --git a/Sources/Capsule/HashMap+Sequence.swift b/Sources/Capsule/HashMap+Sequence.swift index 38a75cf79..144861ac4 100644 --- a/Sources/Capsule/HashMap+Sequence.swift +++ b/Sources/Capsule/HashMap+Sequence.swift @@ -2,14 +2,14 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 HashMap : Sequence { +extension HashMap: Sequence { public __consuming func makeIterator() -> MapKeyValueTupleIterator { return MapKeyValueTupleIterator(rootNode: rootNode) } diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 226d348b2..5d8cd6789 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -public struct HashMap where Key : Hashable { +public struct HashMap where Key: Hashable { var rootNode: BitmapIndexedMapNode var cachedKeySetHashCode: Int var cachedSize: Int @@ -136,7 +136,7 @@ public struct HashMap where Key : Hashable { } } -public struct MapKeyValueTupleIterator { +public struct MapKeyValueTupleIterator { private var baseIterator: ChampBaseIterator, HashCollisionMapNode> init(rootNode: BitmapIndexedMapNode) { @@ -144,7 +144,7 @@ public struct MapKeyValueTupleIterator { } } -extension MapKeyValueTupleIterator : IteratorProtocol { +extension MapKeyValueTupleIterator: IteratorProtocol { public mutating func next() -> (Key, Value)? { guard baseIterator.hasNext() else { return nil } @@ -163,7 +163,7 @@ extension MapKeyValueTupleIterator : IteratorProtocol { } } -public struct MapKeyValueTupleReverseIterator { +public struct MapKeyValueTupleReverseIterator { private var baseIterator: ChampBaseReverseIterator, HashCollisionMapNode> init(rootNode: BitmapIndexedMapNode) { @@ -171,7 +171,7 @@ public struct MapKeyValueTupleReverseIterator { } } -extension MapKeyValueTupleReverseIterator : IteratorProtocol { +extension MapKeyValueTupleReverseIterator: IteratorProtocol { public mutating func next() -> (Key, Value)? { guard baseIterator.hasNext() else { return nil } diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index c65baa27c..0b4a2bc1c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,7 +11,7 @@ fileprivate var TupleLength: Int { 2 } -final class BitmapIndexedMapNode : MapNode where Key : Hashable { +final class BitmapIndexedMapNode: MapNode where Key: Hashable { let bitmap1: Bitmap let bitmap2: Bitmap var content: [Any] @@ -488,7 +488,7 @@ final class BitmapIndexedMapNode : MapNode where Key : Hashable { // } } -extension BitmapIndexedMapNode : Equatable where Value : Equatable { +extension BitmapIndexedMapNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { lhs === rhs || lhs.nodeMap == rhs.nodeMap && diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 941bbc371..a3a147023 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -11,7 +11,7 @@ import func Foundation.ceil -func computeHash(_ value: T) -> Int { +func computeHash(_ value: T) -> Int { value.hashValue } @@ -41,7 +41,7 @@ func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) } -enum TrieNode { +enum TrieNode { case bitmapIndexed(BitmapIndexedNode) case hashCollision(HashCollisionNode) @@ -109,8 +109,8 @@ extension SizePredicate { protocol Node { associatedtype ReturnPayload - associatedtype ReturnBitmapIndexedNode : Node - associatedtype ReturnHashCollisionNode : Node + associatedtype ReturnBitmapIndexedNode: Node + associatedtype ReturnHashCollisionNode: Node var hasBitmapIndexedNodes: Bool { get } @@ -144,7 +144,7 @@ protocol Node { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -struct ChampBaseIterator { +struct ChampBaseIterator { typealias T = TrieNode var currentValueCursor: Int = 0 @@ -227,7 +227,7 @@ struct ChampBaseIterator { /// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// -struct ChampBaseReverseIterator { +struct ChampBaseReverseIterator { typealias T = TrieNode var currentValueCursor: Int = -1 diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 653a8e94a..eca8e2652 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -final class HashCollisionMapNode : MapNode where Key : Hashable { +final class HashCollisionMapNode: MapNode where Key: Hashable { let hash: Int let content: Array<(key: Key, value: Value)> @@ -105,7 +105,7 @@ final class HashCollisionMapNode : MapNode where Key : Hashable { var sizePredicate: SizePredicate { SizePredicate(self) } } -extension HashCollisionMapNode : Equatable where Value : Equatable { +extension HashCollisionMapNode: Equatable where Value: Equatable { static func == (lhs: HashCollisionMapNode, rhs: HashCollisionMapNode) -> Bool { Dictionary.init(uniqueKeysWithValues: lhs.content) == Dictionary.init(uniqueKeysWithValues: rhs.content) } diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index 7644c153a..b5b06801d 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -protocol MapNode : Node { - associatedtype Key : Hashable +protocol MapNode: Node { + associatedtype Key: Hashable associatedtype Value func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? From 5968608192f8f1ce534a78fd190ef5db5d4ec6ac Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 10:53:08 +0200 Subject: [PATCH 051/176] [Capsule] Remove trailing whitespace --- Sources/Capsule/HashMap.swift | 24 +++++++-------- Sources/Capsule/_Common.swift | 56 +++++++++++++++++------------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 5d8cd6789..08e11fcb6 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -13,17 +13,17 @@ public struct HashMap where Key: Hashable { var rootNode: BitmapIndexedMapNode var cachedKeySetHashCode: Int var cachedSize: Int - + fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { self.rootNode = rootNode self.cachedKeySetHashCode = cachedKeySetHashCode self.cachedSize = cachedSize } - + public init() { self.init(BitmapIndexedMapNode(), 0, 0) } - + public init(_ map: HashMap) { self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) } @@ -31,13 +31,13 @@ public struct HashMap where Key: Hashable { /// /// Inspecting a Dictionary /// - + var isEmpty: Bool { cachedSize == 0 } - + public var count: Int { cachedSize } - + var capacity: Int { count } - + /// /// Accessing Keys and Values /// @@ -54,15 +54,15 @@ public struct HashMap where Key: Hashable { } } } - + public subscript(_ key: Key, default: () -> Value) -> Value { return get(key) ?? `default`() } - + public func contains(_ key: Key) -> Bool { rootNode.containsKey(key, computeHash(key), 0) } - + public func get(_ key: Key) -> Value? { rootNode.get(key, computeHash(key), 0) } @@ -138,7 +138,7 @@ public struct HashMap where Key: Hashable { public struct MapKeyValueTupleIterator { private var baseIterator: ChampBaseIterator, HashCollisionMapNode> - + init(rootNode: BitmapIndexedMapNode) { self.baseIterator = ChampBaseIterator(rootNode: .bitmapIndexed(rootNode)) } @@ -165,7 +165,7 @@ extension MapKeyValueTupleIterator: IteratorProtocol { public struct MapKeyValueTupleReverseIterator { private var baseIterator: ChampBaseReverseIterator, HashCollisionMapNode> - + init(rootNode: BitmapIndexedMapNode) { self.baseIterator = ChampBaseReverseIterator(rootNode: .bitmapIndexed(rootNode)) } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index a3a147023..d8084ff83 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -111,11 +111,11 @@ protocol Node { associatedtype ReturnPayload associatedtype ReturnBitmapIndexedNode: Node associatedtype ReturnHashCollisionNode: Node - + var hasBitmapIndexedNodes: Bool { get } - + var bitmapIndexedNodeArity: Int { get } - + func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode var hasHashCollisionNodes: Bool { get } @@ -131,9 +131,9 @@ protocol Node { func getNode(_ index: Int) -> TrieNode var hasPayload: Bool { get } - + var payloadArity: Int { get } - + func getPayload(_ index: Int) -> ReturnPayload var sizePredicate: SizePredicate { get } @@ -150,7 +150,7 @@ struct ChampBaseIterator { var currentValueCursor: Int = 0 var currentValueLength: Int = 0 var currentValueNode: T? = nil - + private var currentStackLevel: Int = -1 private var nodeCursorsAndLengths: Array = Array(repeating: 0, count: MaxDepth * 2) private var nodes: Array = Array(repeating: nil, count: MaxDepth) @@ -159,28 +159,28 @@ struct ChampBaseIterator { if rootNode.hasNodes { pushNode(rootNode) } if rootNode.hasPayload { setupPayloadNode(rootNode) } } - + private mutating func setupPayloadNode(_ node: T) { currentValueNode = node currentValueCursor = 0 currentValueLength = node.payloadArity } - + private mutating func pushNode(_ node: T) { currentStackLevel = currentStackLevel + 1 - + let cursorIndex = currentStackLevel * 2 let lengthIndex = currentStackLevel * 2 + 1 - + nodes[currentStackLevel] = node nodeCursorsAndLengths[cursorIndex] = 0 nodeCursorsAndLengths[lengthIndex] = node.nodeArity } - + private mutating func popNode() { currentStackLevel = currentStackLevel - 1 } - + /// /// Searches for next node that contains payload values, /// and pushes encountered sub-nodes on a stack for depth-first traversal. @@ -189,10 +189,10 @@ struct ChampBaseIterator { while (currentStackLevel >= 0) { let cursorIndex = currentStackLevel * 2 let lengthIndex = currentStackLevel * 2 + 1 - + let nodeCursor = nodeCursorsAndLengths[cursorIndex] let nodeLength = nodeCursorsAndLengths[lengthIndex] - + if nodeCursor < nodeLength { nodeCursorsAndLengths[cursorIndex] += 1 @@ -213,14 +213,14 @@ struct ChampBaseIterator { popNode() } } - + return false } - + mutating func hasNext() -> Bool { return (currentValueCursor < currentValueLength) || searchNextValueNode() } - + } /// @@ -232,32 +232,32 @@ struct ChampBaseReverseIterator = Array(repeating: 0, count: MaxDepth + 1) private var nodeStack: Array = Array(repeating: nil, count: MaxDepth + 1) - + init(rootNode: T) { pushNode(rootNode) searchNextValueNode() } - + private mutating func setupPayloadNode(_ node: T) { currentValueNode = node currentValueCursor = node.payloadArity - 1 } - + private mutating func pushNode(_ node: T) { currentStackLevel = currentStackLevel + 1 - + nodeStack[currentStackLevel] = node nodeIndex[currentStackLevel] = node.nodeArity - 1 } - + private mutating func popNode() { currentStackLevel = currentStackLevel - 1 } - + /// /// Searches for rightmost node that contains payload values, /// and pushes encountered sub-nodes on a stack for depth-first traversal. @@ -266,7 +266,7 @@ struct ChampBaseReverseIterator Bool { while (currentStackLevel >= 0) { let nodeCursor = nodeIndex[currentStackLevel] ; nodeIndex[currentStackLevel] = nodeCursor - 1 - + if nodeCursor >= 0 { // TODO remove duplication in specialization switch nodeStack[currentStackLevel]! { @@ -280,14 +280,14 @@ struct ChampBaseReverseIterator Bool { return (currentValueCursor >= 0) || searchNextValueNode() } From 247b813de0a900a38f13875f240faf430ae22f0d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 11:08:44 +0200 Subject: [PATCH 052/176] [Capsule] Resolve various code style issues --- Sources/Capsule/HashMap+Hashable.swift | 6 +-- Sources/Capsule/_BitmapIndexedMapNode.swift | 54 ++++++++++----------- Sources/Capsule/_Common.swift | 22 ++++----- Sources/Capsule/_HashCollisionMapNode.swift | 4 +- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index 289e8a70d..a597fe0b2 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -13,10 +13,10 @@ extension HashMap: Hashable where Value: Hashable { public func hash(into hasher: inout Hasher) { var commutativeHash = 0 - for (k, v) in self { + for (key, value) in self { var elementHasher = Hasher() - elementHasher.combine(k) - elementHasher.combine(v) + elementHasher.combine(key) + elementHasher.combine(value) commutativeHash ^= elementHasher.finalize() } hasher.combine(commutativeHash) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 0b4a2bc1c..d20c9c914 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -fileprivate var TupleLength: Int { 2 } +fileprivate var tupleLength: Int { 2 } final class BitmapIndexedMapNode: MapNode where Key: Hashable { let bitmap1: Bitmap @@ -59,12 +59,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).get(key, keyHash, shift + BitPartitionSize) + return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) } if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).get(key, keyHash, shift + BitPartitionSize) + return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) } return nil @@ -82,12 +82,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if (nodeMap & bitpos) != 0 { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } if (collMap & bitpos) != 0 { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + BitPartitionSize) + return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } return false @@ -112,7 +112,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { effect.setModified() return copyAndMigrateFromInlineToCollisionNode(bitpos, subNodeNew) } else { - let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + BitPartitionSize) + let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) } @@ -124,7 +124,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) if !effect.modified { return self } else { @@ -140,14 +140,14 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let collisionHash = subNode.hash if keyHash == collisionHash { - let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) if !effect.modified { return self } else { return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } else { - let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + BitPartitionSize) + let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() return copyAndMigrateFromCollisionNodeToNode(bitpos, subNodeNew) } @@ -196,7 +196,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) if !effect.modified { return self } switch subNodeNew.sizePredicate { @@ -231,7 +231,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + BitPartitionSize, &effect) + let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) if !effect.modified { return self } switch subNodeNew.sizePredicate { @@ -272,7 +272,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie - let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + BitPartitionSize) + let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) } @@ -289,7 +289,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return BitmapIndexedMapNode(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), arrayLiteral: key0, value0, node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie - let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + BitPartitionSize) + let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) } @@ -352,8 +352,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var payloadArity: Int { dataMap.nonzeroBitCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { - (content[TupleLength * index + 0] as! Key, - content[TupleLength * index + 1] as! Value) + (content[tupleLength * index + 0] as! Key, + content[tupleLength * index + 1] as! Value) } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -365,7 +365,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) + 1 + let idx = tupleLength * dataIndex(bitpos) + 1 if isStorageKnownUniquelyReferenced { // no copying if already editable @@ -404,7 +404,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } func copyAndInsertValue(_ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) + let idx = tupleLength * dataIndex(bitpos) var dst = self.content dst.insert(contentsOf: [key, value], at: idx) @@ -413,31 +413,31 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } func copyAndRemoveValue(_ bitpos: Bitmap) -> BitmapIndexedMapNode { - let idx = TupleLength * dataIndex(bitpos) + let idx = tupleLength * dataIndex(bitpos) var dst = self.content - dst.removeSubrange(idx..) -> BitmapIndexedMapNode { - let idxOld = TupleLength * dataIndex(bitpos) - let idxNew = self.content.count - TupleLength - nodeIndex(bitpos) + let idxOld = tupleLength * dataIndex(bitpos) + let idxNew = self.content.count - tupleLength - nodeIndex(bitpos) var dst = self.content - dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { - let idxOld = TupleLength * dataIndex(bitpos) - let idxNew = self.content.count - TupleLength - bitmapIndexedNodeArity - collIndex(bitpos) + let idxOld = tupleLength * dataIndex(bitpos) + let idxNew = self.content.count - tupleLength - bitmapIndexedNodeArity - collIndex(bitpos) var dst = self.content - dst.removeSubrange(idxOld..: MapNode where Key: Hashable { func copyAndMigrateFromNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) - let idxNew = TupleLength * dataIndex(bitpos) + let idxNew = tupleLength * dataIndex(bitpos) var dst = self.content dst.remove(at: idxOld) @@ -456,7 +456,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func copyAndMigrateFromCollisionNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) - let idxNew = TupleLength * dataIndex(bitpos) + let idxNew = tupleLength * dataIndex(bitpos) var dst = self.content dst.remove(at: idxOld) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index d8084ff83..e53dee04a 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -17,16 +17,16 @@ func computeHash(_ value: T) -> Int { typealias Bitmap = Int64 -let BitPartitionSize: Int = 5 +let bitPartitionSize: Int = 5 -let BitPartitionMask: Int = (1 << BitPartitionSize) - 1 +let bitPartitionMask: Int = (1 << bitPartitionSize) - 1 -let HashCodeLength: Int = Int.bitWidth +let hashCodeLength: Int = Int.bitWidth -let MaxDepth = Int(ceil(Double(HashCodeLength) / Double(BitPartitionSize))) +let maxDepth = Int(ceil(Double(hashCodeLength) / Double(bitPartitionSize))) func maskFrom(_ hash: Int, _ shift: Int) -> Int { - (hash >> shift) & BitPartitionMask + (hash >> shift) & bitPartitionMask } func bitposFrom(_ mask: Int) -> Bitmap { @@ -152,8 +152,8 @@ struct ChampBaseIterator { var currentValueNode: T? = nil private var currentStackLevel: Int = -1 - private var nodeCursorsAndLengths: Array = Array(repeating: 0, count: MaxDepth * 2) - private var nodes: Array = Array(repeating: nil, count: MaxDepth) + private var nodeCursorsAndLengths: [Int] = Array(repeating: 0, count: maxDepth * 2) + private var nodes: [T?] = Array(repeating: nil, count: maxDepth) init(rootNode: T) { if rootNode.hasNodes { pushNode(rootNode) } @@ -186,7 +186,7 @@ struct ChampBaseIterator { /// and pushes encountered sub-nodes on a stack for depth-first traversal. /// private mutating func searchNextValueNode() -> Bool { - while (currentStackLevel >= 0) { + while currentStackLevel >= 0 { let cursorIndex = currentStackLevel * 2 let lengthIndex = currentStackLevel * 2 + 1 @@ -234,8 +234,8 @@ struct ChampBaseReverseIterator = Array(repeating: 0, count: MaxDepth + 1) - private var nodeStack: Array = Array(repeating: nil, count: MaxDepth + 1) + 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) @@ -264,7 +264,7 @@ struct ChampBaseReverseIterator Bool { - while (currentStackLevel >= 0) { + while currentStackLevel >= 0 { let nodeCursor = nodeIndex[currentStackLevel] ; nodeIndex[currentStackLevel] = nodeCursor - 1 if nodeCursor >= 0 { diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index eca8e2652..6b611a710 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -11,9 +11,9 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { let hash: Int - let content: Array<(key: Key, value: Value)> + let content: [(key: Key, value: Value)] - init(_ hash: Int, _ content: Array<(key: Key, value: Value)>) { + init(_ hash: Int, _ content: [(key: Key, value: Value)]) { // precondition(content.count >= 2) precondition(content.map { $0.key }.allSatisfy {$0.hashValue == hash}) From 155074378686929874d05f865ccf57268b81cb4b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 11:29:28 +0200 Subject: [PATCH 053/176] [Capsule] Rephrase return statement --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index d20c9c914..ced5f26f8 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -54,7 +54,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if (dataMap & bitpos) != 0 { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) - if key == payload.key { return payload.value } else { return nil } + return key == payload.key ? payload.value : nil } if (nodeMap & bitpos) != 0 { From 385058a6f4fd2a66705d22fba6ff117747fb1199 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 11:38:13 +0200 Subject: [PATCH 054/176] [Capsule] Use `guard` statements for bitmap case distinctions --- Sources/Capsule/_BitmapIndexedMapNode.swift | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index ced5f26f8..83179cd93 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -51,18 +51,18 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if (dataMap & bitpos) != 0 { + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) return key == payload.key ? payload.value : nil } - if (nodeMap & bitpos) != 0 { + guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) } - if (collMap & bitpos) != 0 { + guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) } @@ -74,18 +74,18 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if (dataMap & bitpos) != 0 { + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) return key == payload.key } - if (nodeMap & bitpos) != 0 { + guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } - if (collMap & bitpos) != 0 { + guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } @@ -97,7 +97,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if (dataMap & bitpos) != 0 { + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, value0) = self.getPayload(index) @@ -119,7 +119,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - if (nodeMap & bitpos) != 0 { + guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) @@ -132,7 +132,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - if (collMap & bitpos) != 0 { + guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) @@ -161,7 +161,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - if (dataMap & bitpos) != 0 { + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, _) = self.getPayload(index) @@ -191,7 +191,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } else { return self } } - if (nodeMap & bitpos) != 0 { + guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) @@ -226,7 +226,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - if (collMap & bitpos) != 0 { + guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) From 4a8a84f0bee0ad76f37c549e53b09637fcaf857f Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 11:52:03 +0200 Subject: [PATCH 055/176] [Capsule] Use `guard`s to check effects --- Sources/Capsule/_BitmapIndexedMapNode.swift | 69 ++++++++++----------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 83179cd93..e144793ca 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -125,11 +125,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - if !effect.modified { - return self - } else { - return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } + guard effect.modified else { return self } + + return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } guard (collMap & bitpos) == 0 else { @@ -141,11 +139,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if keyHash == collisionHash { let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - if !effect.modified { - return self - } else { - return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } + guard effect.modified else { return self } + + return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() @@ -164,31 +160,30 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, _) = self.getPayload(index) - - if key0 == key { - effect.setModified() - // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` - if self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0 { - /* - * Create new node with remaining pair. The new node will a) either become the new root - * returned, or b) unwrapped and inlined during returning. - */ - let newDataMap: Bitmap - if shift == 0 { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } - if index == 0 { - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1)) - } else { - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(0)) - } - } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { - /* - * Create new node with collision node. The new node will a) either become the new root - * returned, or b) unwrapped and inlined during returning. - */ - let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) - } else { return copyAndRemoveValue(bitpos) } - } else { return self } + guard key0 == key else { return self } + + effect.setModified() + // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` + if self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0 { + /* + * Create new node with remaining pair. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + let newDataMap: Bitmap + if shift == 0 { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } + if index == 0 { + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1)) + } else { + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(0)) + } + } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { + /* + * Create new node with collision node. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) + return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) + } else { return copyAndRemoveValue(bitpos) } } guard (nodeMap & bitpos) == 0 else { @@ -197,8 +192,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified else { return self } - if !effect.modified { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") @@ -232,8 +227,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNode = self.getHashCollisionNode(index) let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified else { return self } - if !effect.modified { return self } switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") From c87ca8401f34e8fc245a0fc67b1a17865881dc55 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 11:59:33 +0200 Subject: [PATCH 056/176] [Capsule] Make dual nature of update/remove methods explicit --- Sources/Capsule/HashMap.swift | 8 ++++---- Sources/Capsule/_BitmapIndexedMapNode.swift | 12 ++++++------ Sources/Capsule/_HashCollisionMapNode.swift | 4 ++-- Sources/Capsule/_MapNode.swift | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 08e11fcb6..881bc41ce 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -76,7 +76,7 @@ public struct HashMap where Key: Hashable { mutating func insert(_ isStorageKnownUniquelyReferenced: Bool, key: Key, value: Value) { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.updated(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) + let newRootNode = rootNode.updateOrUpdating(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) if effect.modified { if effect.replacedValue { @@ -95,7 +95,7 @@ public struct HashMap where Key: Hashable { public func inserting(key: Key, value: Value) -> Self { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.updated(false, key, value, keyHash, 0, &effect) + let newRootNode = rootNode.updateOrUpdating(false, key, value, keyHash, 0, &effect) if effect.modified { if effect.replacedValue { @@ -115,7 +115,7 @@ public struct HashMap where Key: Hashable { mutating func delete(_ isStorageKnownUniquelyReferenced: Bool, key: Key) { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.removed(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) + let newRootNode = rootNode.removeOrRemoving(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) if effect.modified { self.rootNode = newRootNode @@ -128,7 +128,7 @@ public struct HashMap where Key: Hashable { public func deleting(key: Key) -> Self { var effect = MapEffect() let keyHash = computeHash(key) - let newRootNode = rootNode.removed(false, key, keyHash, 0, &effect) + let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) if effect.modified { return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index e144793ca..cc65b3ec1 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -93,7 +93,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return false } - func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -124,7 +124,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -138,7 +138,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let collisionHash = subNode.hash if keyHash == collisionHash { - let subNodeNew = subNode.updated(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -153,7 +153,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return copyAndInsertValue(bitpos, key, value) } - func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -191,7 +191,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { @@ -226,7 +226,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) let subNode = self.getHashCollisionNode(index) - let subNodeNew = subNode.removed(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 6b611a710..7d123e486 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -36,7 +36,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { // return self.hash == hash && content.contains(where: { key == $0.key && value == $0.value }) // } - func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) if self.containsKey(key, hash, shift) { @@ -54,7 +54,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { // TODO rethink such that `precondition(content.count >= 2)` holds // TODO consider returning either type of `BitmapIndexedMapNode` and `HashCollisionMapNode` - func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { if !self.containsKey(key, hash, shift) { return self } else { diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_MapNode.swift index b5b06801d..406ab0662 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_MapNode.swift @@ -17,7 +17,7 @@ protocol MapNode: Node { func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool - func updated(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode - func removed(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode } From fcea661f5dbb374f2e04853042634fb51ff79ef0 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 12:11:43 +0200 Subject: [PATCH 057/176] [Capsule] Fix test case --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index d7f6a36a2..162fac108 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -406,8 +406,8 @@ fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hash final class BitmapSmokeTests: CollectionTestCase { func test_BitPartitionSize_isValid() { - expectTrue(BitPartitionSize > 0) - expectTrue((2 << (BitPartitionSize - 1)) != 0) - expectTrue((2 << (BitPartitionSize - 1)) <= Bitmap.bitWidth) + expectTrue(bitPartitionSize > 0) + expectTrue((2 << (bitPartitionSize - 1)) != 0) + expectTrue((2 << (bitPartitionSize - 1)) <= Bitmap.bitWidth) } } From 50bfc644322ee010e68e47e575f18cfba0c4ec27 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 13:52:45 +0200 Subject: [PATCH 058/176] [Capsule] Add test for hash-collision compaction --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 162fac108..caea48459 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -347,6 +347,37 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(res1, res3) } + func testCompactionWhenDeletingFromHashCollisionNode5() { + let map: HashMap = [:] + + + var res1 = map + res1[CollidableInt(1)] = CollidableInt(1) + res1[CollidableInt(1026)] = CollidableInt(1026) + res1[CollidableInt(32770_1, 32770)] = CollidableInt(32770_1, 32770) + res1[CollidableInt(32770_2, 32770)] = CollidableInt(32770_2, 32770) + + expectTrue(res1.contains(CollidableInt(1))) + expectTrue(res1.contains(CollidableInt(1026))) + expectTrue(res1.contains(CollidableInt(32770_1, 32770))) + expectTrue(res1.contains(CollidableInt(32770_2, 32770))) + + expectEqual(res1.count, 4) + expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(1026) : CollidableInt(1026), CollidableInt(32770_1, 32770) : CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770) : CollidableInt(32770_2, 32770)]), res1) + + + var res2 = res1 + res2[CollidableInt(1026)] = nil + + expectTrue(res2.contains(CollidableInt(1))) + expectFalse(res2.contains(CollidableInt(1026))) + expectTrue(res2.contains(CollidableInt(32770_1, 32770))) + expectTrue(res2.contains(CollidableInt(32770_2, 32770))) + + expectEqual(res2.count, 3) + expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32770_1, 32770) : CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770) : CollidableInt(32770_2, 32770)]), res2) + } + func inferSize(_ map: HashMap) -> Int { var size = 0 From d2611a68d5734da48d2d098d192b7cf3718a7a60 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 15:28:36 +0200 Subject: [PATCH 059/176] [Capsule] Fix hash-collision compaction scenario --- Sources/Capsule/_BitmapIndexedMapNode.swift | 76 +++++++++++---------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index cc65b3ec1..5a3d83dff 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -165,22 +165,17 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { effect.setModified() // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` if self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0 { - /* - * Create new node with remaining pair. The new node will a) either become the new root - * returned, or b) unwrapped and inlined during returning. - */ - let newDataMap: Bitmap - if shift == 0 { newDataMap = (dataMap ^ bitpos) } else { newDataMap = bitposFrom(maskFrom(keyHash, 0)) } - if index == 0 { - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1)) + if shift == 0 { + // keep remaining pair on root level + let newDataMap = (dataMap ^ bitpos) + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1 - index)) } else { - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(0)) + // create potential new root: will a) become new root, or b) inlined on another level + let newDataMap = bitposFrom(maskFrom(keyHash, 0)) + return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1 - index)) } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { - /* - * Create new node with collision node. The new node will a) either become the new root - * returned, or b) unwrapped and inlined during returning. - */ + // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) } else { return copyAndRemoveValue(bitpos) } @@ -197,22 +192,31 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") + case .sizeOne: - if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 { // escalate (singleton or empty) result + precondition(self.bitmapIndexedNodeArity >= 1) + + if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 && self.hashCollisionNodeArity == 0 { + // escalate singleton return subNodeNew - } - else { // inline value (move to front) + } else { + // inline singleton return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) } case .sizeMoreThanOne: - // TODO simplify hash-collision compaction (if feasible) - if subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1 { - if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { // escalate (singleton or empty) result + precondition(self.bitmapIndexedNodeArity >= 1) + + let isNodeThatOnlyHashOneSingleHashCollisionSubNode = + subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1 + + if (isNodeThatOnlyHashOneSingleHashCollisionSubNode) { + if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 && self.hashCollisionNodeArity == 0 { + // escalate node that has only a single hash-collision sub-node return subNodeNew - } else { // inline value (move to front) - assertionFailure() - // return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getHashCollisionNode(0)) + } else { + // unwrap hash-collision sub-node + return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getHashCollisionNode(0)) } } @@ -232,14 +236,16 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { switch subNodeNew.sizePredicate { case .sizeEmpty: preconditionFailure("Sub-node must have at least one element.") + case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { // escalate (singleton or empty) result + if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { + // escalate singleton // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: subNodeNew.getPayload(0)) - } - else { // inline value (move to front) + } else { + // inline value return copyAndMigrateFromCollisionNodeToInline(bitpos, subNodeNew.getPayload(0)) } @@ -471,16 +477,16 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) } -// func copyAndMigrateFromNodeToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { -// let idxOld = self.content.count - 1 - nodeIndex(bitpos) -// let idxNew = self.content.count - 1 - bitmapIndexedNodeArity - 1 - collIndex(bitpos) -// -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(node, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) -// } + func copyAndMigrateFromNodeToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { + let idxOld = self.content.count - 1 - nodeIndex(bitpos) + let idxNew = self.content.count - 1 - (bitmapIndexedNodeArity - 1) - collIndex(bitpos) + + var dst = self.content + dst.remove(at: idxOld) + dst.insert(node, at: idxNew) + + return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) + } } extension BitmapIndexedMapNode: Equatable where Value: Equatable { From 2dbf06702c5efea411bb7a430dce7cf5928ca3fb Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 15:34:18 +0200 Subject: [PATCH 060/176] [Capsule] Explode condition --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 5a3d83dff..512a56d1f 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -239,7 +239,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if self.payloadArity == 0 && (self.bitmapIndexedNodeArity + self.hashCollisionNodeArity) == 1 { + if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // escalate singleton // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) From 0350c95b09c14a0e92f6a97b7015aa460bb0af72 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 12 May 2021 15:44:40 +0200 Subject: [PATCH 061/176] [Capsule] Fix code style issues in test case --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index caea48459..7d5d7ac8e 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -14,7 +14,7 @@ import _CollectionsTestSupport final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { - var map: HashMap = [ 1 : "a", 2 : "b" ] + var map: HashMap = [1: "a", 2: "b"] map[3] = "x" map[4] = "y" @@ -27,7 +27,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testSubscriptOverwrite() { - var map: HashMap = [ 1 : "a", 2 : "b" ] + var map: HashMap = [1: "a", 2: "b"] map[1] = "x" map[2] = "y" @@ -38,7 +38,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testSubscriptRemove() { - var map: HashMap = [ 1 : "a", 2 : "b" ] + var map: HashMap = [1: "a", 2: "b"] map[1] = nil map[2] = nil @@ -49,9 +49,9 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testTriggerOverwrite1() { - let map: HashMap = [ 1 : "a", 2 : "b" ] + let map: HashMap = [1: "a", 2: "b"] - let _ = map + _ = map .inserting(key: 1, value: "x") // triggers COW .inserting(key: 2, value: "y") // triggers COW @@ -148,15 +148,15 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map3.count, 0) } - private func hashPair(_ k: Key, _ v: Value) -> Int { + private func hashPair(_ key: Key, _ value: Value) -> Int { var hasher = Hasher() - hasher.combine(k) - hasher.combine(v) + hasher.combine(key) + hasher.combine(value) return hasher.finalize() } func testHashable() { - let map: HashMap = [ 1 : "a", 2 : "b" ] + let map: HashMap = [1: "a", 2: "b"] let hashPair1 = hashPair(1, "a") let hashPair2 = hashPair(2, "b") @@ -186,7 +186,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(12, 1))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(12, 1) : CollidableInt(12, 1)]), res1) + expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), res1) var res2 = res1 @@ -196,7 +196,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectFalse(res2.contains(CollidableInt(12, 1))) expectEqual(res2.count, 1) - expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1)]), res2) + expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1)]), res2) var res3 = res1 @@ -206,7 +206,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(12, 1))) expectEqual(res3.count, 1) - expectEqual(HashMap.init([CollidableInt(12, 1) : CollidableInt(12, 1)]), res3) + expectEqual(HashMap.init([CollidableInt(12, 1): CollidableInt(12, 1)]), res3) var resX = res1 @@ -218,7 +218,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(resX.contains(CollidableInt(32769))) expectEqual(resX.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(32769) : CollidableInt(32769)]), resX) + expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(32769): CollidableInt(32769)]), resX) var resY = res1 @@ -230,7 +230,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectFalse(resY.contains(CollidableInt(32769))) expectEqual(resY.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1) : CollidableInt(11, 1), CollidableInt(12, 1) : CollidableInt(12, 1)]), resY) + expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), resY) } func testCompactionWhenDeletingFromHashCollisionNode2() { @@ -245,7 +245,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -256,7 +256,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -266,7 +266,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_1, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769)]), res3) + expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769)]), res3) } func testCompactionWhenDeletingFromHashCollisionNode3() { @@ -281,7 +281,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -292,7 +292,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -302,7 +302,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_2, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) expectEqual(res1, res3) @@ -320,7 +320,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res1) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -331,7 +331,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(5) : CollidableInt(5), CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res2) + expectEqual(HashMap.init([CollidableInt(5): CollidableInt(5), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -341,7 +341,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_2, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769) : CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769) : CollidableInt(32769_2, 32769)]), res3) + expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) expectEqual(res1, res3) @@ -363,7 +363,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32770_2, 32770))) expectEqual(res1.count, 4) - expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(1026) : CollidableInt(1026), CollidableInt(32770_1, 32770) : CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770) : CollidableInt(32770_2, 32770)]), res1) + expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(1026): CollidableInt(1026), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res1) var res2 = res1 @@ -375,7 +375,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32770_2, 32770))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1) : CollidableInt(1), CollidableInt(32770_1, 32770) : CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770) : CollidableInt(32770_2, 32770)]), res2) + expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res2) } func inferSize(_ map: HashMap) -> Int { @@ -390,9 +390,9 @@ final class CapsuleSmokeTests: CollectionTestCase { func testIteratorEnumeratesAll() { let map1: HashMap = [ - CollidableInt(11, 1) : "a", - CollidableInt(12, 1) : "a", - CollidableInt(32769) : "b" + CollidableInt(11, 1): "a", + CollidableInt(12, 1): "a", + CollidableInt(32769): "b" ] var map2: HashMap = [:] @@ -404,7 +404,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } } -fileprivate final class CollidableInt : CustomStringConvertible, Equatable, Hashable { +fileprivate final class CollidableInt: CustomStringConvertible, Equatable, Hashable { let value: Int let hashValue: Int From e80fdb72e4b253d69d413fcfb35644667e1bbd37 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 15 May 2021 19:19:45 +0200 Subject: [PATCH 062/176] [Capsule] Add helpers for hash-collision compaction --- Sources/Capsule/_BitmapIndexedMapNode.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 512a56d1f..1ead098c3 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -196,7 +196,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { case .sizeOne: precondition(self.bitmapIndexedNodeArity >= 1) - if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 && self.hashCollisionNodeArity == 0 { + if self.isCandiateForCompaction { // escalate singleton return subNodeNew } else { @@ -207,11 +207,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { case .sizeMoreThanOne: precondition(self.bitmapIndexedNodeArity >= 1) - let isNodeThatOnlyHashOneSingleHashCollisionSubNode = - subNodeNew.payloadArity == 0 && subNodeNew.bitmapIndexedNodeArity == 0 && subNodeNew.hashCollisionNodeArity == 1 - - if (isNodeThatOnlyHashOneSingleHashCollisionSubNode) { - if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 1 && self.hashCollisionNodeArity == 0 { + if (subNodeNew.isWrappingSingleHashCollisionNode) { + if self.isCandiateForCompaction { // escalate node that has only a single hash-collision sub-node return subNodeNew } else { @@ -239,7 +236,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { case .sizeOne: // TODO simplify hash-collision compaction (if feasible) - if self.payloadArity == 0 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { + if self.isCandiateForCompaction { // escalate singleton // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) @@ -258,6 +255,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return self } + var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } + + var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && bitmapIndexedNodeArity == 0 && hashCollisionNodeArity == 1 } + func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { precondition(keyHash0 != keyHash1) From e34d915fdb26000b023a8d4c33e9902f69fd1362 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 4 Jun 2021 10:48:00 +0200 Subject: [PATCH 063/176] [Capsule] Extend `BitmapIndexedMapNode` with `Sequence` protocol Further adds `count`to the node implementation for checking invariants. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 1ead098c3..07c301cec 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -26,6 +26,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.bitmap1 = dataMap ^ collMap self.bitmap2 = nodeMap ^ collMap self.content = content + + assert(count - payloadArity >= 2 * nodeArity) } convenience init(dataMap: Bitmap = 0, nodeMap: Bitmap = 0, collMap: Bitmap = 0, arrayLiteral content: Any...) { @@ -47,6 +49,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.init(0, 0, collMap, elements) } + var count: Int { + self.reduce(0, { count, _ in count + 1 }) + } + func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -521,3 +527,9 @@ extension BitmapIndexedMapNode: Equatable where Value: Equatable { return true } } + +extension BitmapIndexedMapNode: Sequence { + public __consuming func makeIterator() -> MapKeyValueTupleIterator { + return MapKeyValueTupleIterator(rootNode: self) + } +} From e28f675e3fd966415cb242f4237093563e70701f Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 4 Jun 2021 16:53:48 +0200 Subject: [PATCH 064/176] [Capsule] Copy `DictionaryBenchmark` and add some `Dictionary` API --- .../Benchmarks/CapsuleHashMapBenchmarks.swift | 343 ++++++++++++++++++ Benchmarks/benchmark-tool/main.swift | 1 + Package.swift | 2 + Sources/Capsule/HashMap.swift | 40 +- Sources/Collections/Collections.swift | 1 + 5 files changed, 382 insertions(+), 5 deletions(-) create mode 100644 Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift diff --git a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift new file mode 100644 index 000000000..f8f8a16a0 --- /dev/null +++ b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift @@ -0,0 +1,343 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import CollectionsBenchmark +import Capsule + +extension Benchmark { + public mutating func addCapsuleHashMapBenchmarks() { + self.add( + title: "HashMap init(uniqueKeysWithValues:)", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + return { timer in + blackHole(HashMap(uniqueKeysWithValues: keysAndValues)) + } + } + + self.add( + title: "HashMap sequential iteration", + input: [Int].self + ) { input in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for item in d { + blackHole(item) + } + } + } + +// self.add( +// title: "HashMap.Keys sequential iteration", +// input: [Int].self +// ) { input in +// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// return { timer in +// for item in d.keys { +// blackHole(item) +// } +// } +// } +// +// self.add( +// title: "HashMap.Values sequential iteration", +// input: [Int].self +// ) { input in +// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// return { timer in +// for item in d.values { +// blackHole(item) +// } +// } +// } + + self.add( + title: "HashMap subscript, successful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for i in lookups { + precondition(d[i] == 2 * i) + } + } + } + + self.add( + title: "HashMap subscript, unsuccessful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + return { timer in + for i in lookups { + precondition(d[i + c] == nil) + } + } + } + + self.add( + title: "HashMap subscript, noop setter", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in lookups { + d[i + c] = nil + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap subscript, set existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + d[i] = 0 + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap subscript, _modify", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + d[i]! *= 2 + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.addSimple( + title: "HashMap subscript, insert", + input: [Int].self + ) { input in + var d: [Int: Int] = [:] + for i in input { + d[i] = 2 * i + } + precondition(d.count == input.count) + blackHole(d) + } + + self.addSimple( + title: "HashMap subscript, insert, reserving capacity", + input: [Int].self + ) { input in + var d: [Int: Int] = [:] + d.reserveCapacity(input.count) + for i in input { + d[i] = 2 * i + } + precondition(d.count == input.count) + blackHole(d) + } + + self.add( + title: "HashMap subscript, remove existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + d[i] = nil + } + } + precondition(d.isEmpty) + blackHole(d) + } + } + + self.add( + title: "HashMap subscript, remove missing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in lookups { + d[i + c] = nil + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap defaulted subscript, successful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for i in lookups { + precondition(d[i, default: -1] != -1) + } + } + } + + self.add( + title: "HashMap defaulted subscript, unsuccessful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + let c = d.count + for i in lookups { + precondition(d[i + c, default: -1] == -1) + } + } + } + + self.add( + title: "HashMap defaulted subscript, _modify existing", + input: [Int].self + ) { input in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in input { + d[i, default: -1] *= 2 + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap defaulted subscript, _modify missing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in lookups { + d[c + i, default: -1] *= 2 + } + } + precondition(d.count == 2 * input.count) + blackHole(d) + } + } + +// self.add( +// title: "HashMap successful index(forKey:)", +// input: ([Int], [Int]).self +// ) { input, lookups in +// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// return { timer in +// for i in lookups { +// precondition(d.index(forKey: i) != nil) +// } +// } +// } +// +// self.add( +// title: "HashMap unsuccessful index(forKey:)", +// input: ([Int], [Int]).self +// ) { input, lookups in +// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// return { timer in +// for i in lookups { +// precondition(d.index(forKey: lookups.count + i) == nil) +// } +// } +// } + + self.add( + title: "HashMap updateValue(_:forKey:), existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + d.updateValue(0, forKey: i) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap updateValue(_:forKey:), insert", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + d.updateValue(0, forKey: input.count + i) + } + } + precondition(d.count == 2 * input.count) + blackHole(d) + } + } + + self.add( + title: "HashMap random removals (existing keys)", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + precondition(d.removeValue(forKey: i) != nil) + } + } + precondition(d.count == 0) + blackHole(d) + } + } + + self.add( + title: "HashMap random removals (missing keys)", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let c = input.count + var d = HashMap(uniqueKeysWithValues: input.lazy.map { (c + $0, 2 * $0) }) + timer.measure { + for i in lookups { + precondition(d.removeValue(forKey: i) == nil) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + + } +} diff --git a/Benchmarks/benchmark-tool/main.swift b/Benchmarks/benchmark-tool/main.swift index d2096d384..9e7c485e6 100644 --- a/Benchmarks/benchmark-tool/main.swift +++ b/Benchmarks/benchmark-tool/main.swift @@ -16,6 +16,7 @@ var benchmark = Benchmark(title: "Collection Benchmarks") benchmark.addArrayBenchmarks() benchmark.addSetBenchmarks() benchmark.addDictionaryBenchmarks() +benchmark.addCapsuleHashMapBenchmarks() benchmark.addDequeBenchmarks() benchmark.addOrderedSetBenchmarks() benchmark.addOrderedDictionaryBenchmarks() diff --git a/Package.swift b/Package.swift index b817fde78..c0b61531a 100644 --- a/Package.swift +++ b/Package.swift @@ -55,6 +55,7 @@ let package = Package( .library(name: "DequeModule", targets: ["DequeModule"]), .library(name: "OrderedCollections", targets: ["OrderedCollections"]), .library(name: "PriorityQueueModule", targets: ["PriorityQueueModule"]), + .library(name: "Capsule", targets: ["Capsule"]), ], targets: [ .target( @@ -63,6 +64,7 @@ let package = Package( "DequeModule", "OrderedCollections", "PriorityQueueModule", + "Capsule", ], path: "Sources/Collections", exclude: ["CMakeLists.txt"], diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 881bc41ce..2f7888c42 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -28,21 +28,28 @@ public struct HashMap where Key: Hashable { self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) } + public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { + let map = keysAndValues.reduce(Self()) { (map, element) in let (key, value) = element + return map.inserting(key: key, value: value) + } + self.init(map) + } + /// /// Inspecting a Dictionary /// - var isEmpty: Bool { cachedSize == 0 } + public var isEmpty: Bool { cachedSize == 0 } public var count: Int { cachedSize } - var capacity: Int { count } + public var capacity: Int { count } /// /// Accessing Keys and Values /// - public subscript(_ key: Key) -> Value? { + public subscript(key: Key) -> Value? { get { return get(key) } @@ -55,8 +62,13 @@ public struct HashMap where Key: Hashable { } } - public subscript(_ key: Key, default: () -> Value) -> Value { - return get(key) ?? `default`() + public subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value { + get { + return get(key) ?? defaultValue() + } + mutating set(value) { + insert(isKnownUniquelyReferenced(&self.rootNode), key: key, value: value) + } } public func contains(_ key: Key) -> Bool { @@ -106,6 +118,14 @@ public struct HashMap where Key: Hashable { } else { return self } } + // TODO signature adopted from `Dictionary`, unify with API + @discardableResult + public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { + let oldValue = get(key) + insert(key: key, value: value) + return oldValue + } + public mutating func delete(_ key: Key) { let mutate = isKnownUniquelyReferenced(&self.rootNode) delete(mutate, key: key) @@ -134,6 +154,16 @@ public struct HashMap where Key: Hashable { return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) } else { return self } } + + // TODO signature adopted from `Dictionary`, unify with API + @discardableResult + public mutating func removeValue(forKey key: Key) -> Value? { + if let value = get(key) { + delete(key) + return value + } + return nil + } } public struct MapKeyValueTupleIterator { diff --git a/Sources/Collections/Collections.swift b/Sources/Collections/Collections.swift index beacfc238..806cc8871 100644 --- a/Sources/Collections/Collections.swift +++ b/Sources/Collections/Collections.swift @@ -12,3 +12,4 @@ @_exported import DequeModule @_exported import OrderedCollections @_exported import PriorityQueueModule +@_exported import Capsule From f53ca4302b39109b5bb1e896b284d2f832ba6452 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 5 Jun 2021 09:37:28 +0200 Subject: [PATCH 065/176] [Capsule] Use mutable API inside initializer --- Sources/Capsule/HashMap.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 2f7888c42..ef0f8d115 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -29,10 +29,11 @@ public struct HashMap where Key: Hashable { } public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { - let map = keysAndValues.reduce(Self()) { (map, element) in let (key, value) = element - return map.inserting(key: key, value: value) + var builder = Self() + keysAndValues.forEach { key, value in + builder.insert(key: key, value: value) } - self.init(map) + self.init(builder) } /// From 1181a85e2b234096a298e01a61a044281b5f510a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 5 Jun 2021 09:38:02 +0200 Subject: [PATCH 066/176] [Capsule] Deduplicate `ExpressibleByDictionaryLiteral` implementation --- Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index 0f5068839..142e249f4 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -11,9 +11,6 @@ extension HashMap: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (Key, Value)...) { - let map = elements.reduce(Self()) { (map, element) in let (key, value) = element - return map.inserting(key: key, value: value) - } - self.init(map) + self.init(uniqueKeysWithValues: elements) } } From 5e50ccdcf679c9a6b7a73a47ac5346c502bdb4e2 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 5 Jun 2021 10:11:00 +0200 Subject: [PATCH 067/176] [Capsule] Turn some preconditions into asserts --- Sources/Capsule/_BitmapIndexedMapNode.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 07c301cec..03dc58ea0 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -200,7 +200,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { preconditionFailure("Sub-node must have at least one element.") case .sizeOne: - precondition(self.bitmapIndexedNodeArity >= 1) + assert(self.bitmapIndexedNodeArity >= 1) if self.isCandiateForCompaction { // escalate singleton @@ -211,7 +211,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } case .sizeMoreThanOne: - precondition(self.bitmapIndexedNodeArity >= 1) + assert(self.bitmapIndexedNodeArity >= 1) if (subNodeNew.isWrappingSingleHashCollisionNode) { if self.isCandiateForCompaction { @@ -266,7 +266,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && bitmapIndexedNodeArity == 0 && hashCollisionNodeArity == 1 } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { - precondition(keyHash0 != keyHash1) + assert(keyHash0 != keyHash1) let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(keyHash1, shift) @@ -287,7 +287,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionMapNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { - precondition(keyHash0 != nodeHash1) + assert(keyHash0 != nodeHash1) let mask0 = maskFrom(keyHash0, shift) let mask1 = maskFrom(nodeHash1, shift) From 8014cc65dc7818cbdc9a9a8ba9ea701d1b6c8100 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 5 Jun 2021 10:11:52 +0200 Subject: [PATCH 068/176] [Capsule] Add inline annotations to initializer --- Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift | 2 ++ Sources/Capsule/HashMap.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift index 142e249f4..a3de94db3 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift @@ -10,6 +10,8 @@ //===----------------------------------------------------------------------===// extension HashMap: ExpressibleByDictionaryLiteral { + @inlinable + @inline(__always) public init(dictionaryLiteral elements: (Key, Value)...) { self.init(uniqueKeysWithValues: elements) } diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index ef0f8d115..bd8b3f67e 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -28,6 +28,8 @@ public struct HashMap where Key: Hashable { self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) } + @inlinable + @inline(__always) public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { var builder = Self() keysAndValues.forEach { key, value in From 3a8ee52e4a10f74779acde8fbd632ad3eed1f06b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 09:32:02 +0200 Subject: [PATCH 069/176] [Capsule] Add `init(uniqueKeys keys: Keys, values: Values)` --- Sources/Capsule/HashMap.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index bd8b3f67e..7bf73900c 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -38,6 +38,12 @@ public struct HashMap where Key: Hashable { self.init(builder) } + @inlinable + @inline(__always) + public init(uniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { + self.init(uniqueKeysWithValues: zip(keys, values)) + } + /// /// Inspecting a Dictionary /// From 042d2ebe2e349ec02a605d531ff144e4220e4ece Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 09:32:27 +0200 Subject: [PATCH 070/176] [Capsule] Add tuple labels to iterator return values --- Sources/Capsule/HashMap.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 7bf73900c..79ef11897 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -184,7 +184,7 @@ public struct MapKeyValueTupleIterator { } extension MapKeyValueTupleIterator: IteratorProtocol { - public mutating func next() -> (Key, Value)? { + public mutating func next() -> (key: Key, value: Value)? { guard baseIterator.hasNext() else { return nil } let payload: (Key, Value) @@ -211,7 +211,7 @@ public struct MapKeyValueTupleReverseIterator { } extension MapKeyValueTupleReverseIterator: IteratorProtocol { - public mutating func next() -> (Key, Value)? { + public mutating func next() -> (key: Key, value: Value)? { guard baseIterator.hasNext() else { return nil } let payload: (Key, Value) From d9877e25cc02a7d90a020d4942a01ee61bbfc025 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 09:41:02 +0200 Subject: [PATCH 071/176] [Capsule] Add `underestimatedCount` --- Sources/Capsule/HashMap.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 79ef11897..a910934a0 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -52,6 +52,8 @@ public struct HashMap where Key: Hashable { public var count: Int { cachedSize } + public var underestimatedCount: Int { cachedSize } + public var capacity: Int { count } /// From f719b1c846b196110bd089796b6ae94471eeb274 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 09:43:25 +0200 Subject: [PATCH 072/176] [Capsule] Copy base of `OrderedDictionary` tests --- Tests/CapsuleTests/Capsule Tests.swift | 1379 ++++++++++++++++++++++++ Tests/CapsuleTests/Capsule Utils.swift | 31 + 2 files changed, 1410 insertions(+) create mode 100644 Tests/CapsuleTests/Capsule Tests.swift create mode 100644 Tests/CapsuleTests/Capsule Utils.swift diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift new file mode 100644 index 000000000..b80c301aa --- /dev/null +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -0,0 +1,1379 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import _CollectionsTestSupport +@testable import Capsule + +class HashMapTests: CollectionTestCase { + func test_empty() { + let d = HashMap() + expectEqualElements(d, []) + expectEqual(d.count, 0) + } + +// func test_init_minimumCapacity() { +// let d = HashMap(minimumCapacity: 1000) +// expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) +// expectGreaterThanOrEqual(d.values.elements.capacity, 1000) +// expectEqual(d.keys.__unstable.reservedScale, 0) +// } +// +// func test_init_minimumCapacity_persistent() { +// let d = HashMap(minimumCapacity: 1000, persistent: true) +// expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) +// expectGreaterThanOrEqual(d.values.elements.capacity, 1000) +// expectNotEqual(d.keys.__unstable.reservedScale, 0) +// } + +// func test_uniqueKeysWithValues_Dictionary() { +// let items: Dictionary = [ +// "zero": 0, +// "one": 1, +// "two": 2, +// "three": 3, +// ] +// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// expectEqualElements(d, items) +// } + +// func test_uniqueKeysWithValues_labeled_tuples() { +// let items: KeyValuePairs = [ +// "zero": 0, +// "one": 1, +// "two": 2, +// "three": 3, +// ] +// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// expectEqualElements(d, items) +// } + +// func test_uniqueKeysWithValues_unlabeled_tuples() { +// let items: [(String, Int)] = [ +// ("zero", 0), +// ("one", 1), +// ("two", 2), +// ("three", 3), +// ] +// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// expectEqualElements(d, items) +// } + +// func test_uniqueKeys_values() { +// let d = HashMap( +// uncheckedUniqueKeys: ["zero", "one", "two", "three"], +// values: [0, 1, 2, 3]) +// expectEqualElements(d, [ +// (key: "zero", value: 0), +// (key: "one", value: 1), +// (key: "two", value: 2), +// (key: "three", value: 3), +// ]) +// } + +// func test_uniquing_initializer_labeled_tuples() { +// let items: KeyValuePairs = [ +// "a": 1, +// "b": 1, +// "c": 1, +// "a": 2, +// "a": 2, +// "b": 1, +// "d": 3, +// ] +// let d = HashMap(items, uniquingKeysWith: +) +// expectEqualElements(d, [ +// (key: "a", value: 5), +// (key: "b", value: 2), +// (key: "c", value: 1), +// (key: "d", value: 3) +// ]) +// } + +// func test_uniquing_initializer_unlabeled_tuples() { +// let items: [(String, Int)] = [ +// ("a", 1), +// ("b", 1), +// ("c", 1), +// ("a", 2), +// ("a", 2), +// ("b", 1), +// ("d", 3), +// ] +// let d = HashMap(items, uniquingKeysWith: +) +// expectEqualElements(d, [ +// (key: "a", value: 5), +// (key: "b", value: 2), +// (key: "c", value: 1), +// (key: "d", value: 3) +// ]) +// } + +// func test_grouping_initializer() { +// let items: [String] = [ +// "one", "two", "three", "four", "five", +// "six", "seven", "eight", "nine", "ten" +// ] +// let d = HashMap(grouping: items, by: { $0.count }) +// expectEqualElements(d, [ +// (key: 3, value: ["one", "two", "six", "ten"]), +// (key: 5, value: ["three", "seven", "eight"]), +// (key: 4, value: ["four", "five", "nine"]), +// ]) +// } + +// func test_uncheckedUniqueKeysWithValues_labeled_tuples() { +// let items: KeyValuePairs = [ +// "zero": 0, +// "one": 1, +// "two": 2, +// "three": 3, +// ] +// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// expectEqualElements(d, items) +// } + +// func test_uncheckedUniqueKeysWithValues_unlabeled_tuples() { +// let items: [(String, Int)] = [ +// ("zero", 0), +// ("one", 1), +// ("two", 2), +// ("three", 3), +// ] +// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// expectEqualElements(d, items) +// } + +// func test_uncheckedUniqueKeys_values() { +// let d = HashMap( +// uncheckedUniqueKeys: ["zero", "one", "two", "three"], +// values: [0, 1, 2, 3]) +// expectEqualElements(d, [ +// (key: "zero", value: 0), +// (key: "one", value: 1), +// (key: "two", value: 2), +// (key: "three", value: 3), +// ]) +// } + + func test_ExpressibleByDictionaryLiteral() { + let d0: HashMap = [:] + expectTrue(d0.isEmpty) + + let d1: HashMap = [ + "one": 1, + "two": 2, + "three": 3, + "four": 4, + ] + expectEqualElements(d1.map { $0.key }, ["one", "two", "three", "four"]) + expectEqualElements(d1.map { $0.value }, [1, 2, 3, 4]) + } + +// func test_keys() { +// let d: HashMap = [ +// "one": 1, +// "two": 2, +// "three": 3, +// "four": 4, +// ] +// expectEqual(d.keys, ["one", "two", "three", "four"] as OrderedSet) +// } + + func test_counts() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) + expectEqual(d.isEmpty, count == 0) + expectEqual(d.count, count) + expectEqual(d.underestimatedCount, count) + } + } + } + +// func test_index_forKey() { +// withEvery("count", in: 0 ..< 30) { count in +// withLifetimeTracking { tracker in +// let (d, keys, _) = tracker.persistentDictionary(keys: 0 ..< count) +// withEvery("offset", in: 0 ..< count) { offset in +// expectEqual(d.index(forKey: keys[offset]), offset) +// } +// expectNil(d.index(forKey: tracker.instance(for: -1))) +// expectNil(d.index(forKey: tracker.instance(for: count))) +// } +// } +// } + +// func test_subscript_offset() { +// withEvery("count", in: 0 ..< 30) { count in +// withLifetimeTracking { tracker in +// let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withEvery("offset", in: 0 ..< count) { offset in +// let item = d[offset: offset] +// expectEqual(item.key, keys[offset]) +// expectEqual(item.value, values[offset]) +// } +// } +// } +// } + + func test_subscript_getter() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + expectEqual(d[keys[offset]], values[offset]) + } + expectNil(d[tracker.instance(for: -1)]) + expectNil(d[tracker.instance(for: count)]) + } + } + } + +// func test_subscript_setter_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// d[keys[offset]] = replacement +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_setter_remove() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// d[keys[offset]] = nil +// keys.remove(at: offset) +// values.remove(at: offset) +// withEvery("i", in: 0 ..< count - 1) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_setter_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// d[keys[offset]] = values[offset] +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_setter_noop() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let key = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// d[key] = nil +// } +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } + + func mutate( + _ value: inout T, + _ body: (inout T) throws -> R + ) rethrows -> R { + try body(&value) + } + +// func test_subscript_modify_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// mutate(&d[keys[offset]]) { $0 = replacement } +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_modify_remove() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// mutate(&d[key]) { v in +// expectEqual(v, values[offset]) +// v = nil +// } +// keys.remove(at: offset) +// values.remove(at: offset) +// withEvery("i", in: 0 ..< count - 1) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_modify_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// mutate(&d[keys[offset]]) { v in +// expectNil(v) +// v = values[offset] +// } +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_subscript_modify_noop() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let key = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// mutate(&d[key]) { v in +// expectNil(v) +// v = nil +// } +// } +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } + +// func test_defaulted_subscript_getter() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let fallback = tracker.instance(for: -1) +// withEvery("offset", in: 0 ..< count) { offset in +// let key = keys[offset] +// expectEqual(d[key, default: fallback], values[offset]) +// } +// expectEqual( +// d[tracker.instance(for: -1), default: fallback], +// fallback) +// expectEqual( +// d[tracker.instance(for: count), default: fallback], +// fallback) +// } +// } +// } +// } + +// func test_defaulted_subscript_modify_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// let fallback = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// mutate(&d[key, default: fallback]) { v in +// expectEqual(v, values[offset]) +// v = replacement +// } +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_defaulted_subscript_modify_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// let fallback = tracker.instance(for: -1) +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// mutate(&d[key, default: fallback]) { v in +// expectEqual(v, fallback) +// v = values[offset] +// } +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_updateValue_forKey_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// let old = d.updateValue(replacement, forKey: key) +// expectEqual(old, values[offset]) +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_updateValue_forKey_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// let old = d.updateValue(values[offset], forKey: key) +// expectNil(old) +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_updateValue_forKey_insertingAt_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// let (old, index) = +// d.updateValue(replacement, forKey: key, insertingAt: 0) +// expectEqual(old, values[offset]) +// expectEqual(index, offset) +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_updateValue_forKey_insertingAt_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[count - 1 - offset] +// let value = values[count - 1 - offset] +// let (old, index) = +// d.updateValue(value, forKey: key, insertingAt: 0) +// expectNil(old) +// expectEqual(index, 0) +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[count - 1 - offset + i]) +// expectEqual(v, values[count - 1 - offset + i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_modifyValue_forKey_default_closure_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// let fallback = tracker.instance(for: -2) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// d.modifyValue(forKey: key, default: fallback) { value in +// expectEqual(value, values[offset]) +// value = replacement +// } +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_modifyValue_forKey_default_closure_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// let fallback = tracker.instance(for: -2) +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// d.modifyValue(forKey: key, default: fallback) { value in +// expectEqual(value, fallback) +// value = values[offset] +// } +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_modifyValue_forKey_insertingDefault_at_closure_update() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// let replacement = tracker.instance(for: -1) +// let fallback = tracker.instance(for: -2) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[offset] +// let value = values[offset] +// d.modifyValue(forKey: key, insertingDefault: fallback, at: 0) { v in +// expectEqual(v, value) +// v = replacement +// } +// values[offset] = replacement +// withEvery("i", in: 0 ..< count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_modifyValue_forKey_insertingDefault_at_closure_insert() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// let keys = tracker.instances(for: 0 ..< count) +// let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) +// var d: HashMap, LifetimeTracked> = [:] +// let fallback = tracker.instance(for: -2) +// withEvery("offset", in: 0 ..< count) { offset in +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys[count - 1 - offset] +// let value = values[count - 1 - offset] +// d.modifyValue(forKey: key, insertingDefault: fallback, at: 0) { v in +// expectEqual(v, fallback) +// v = value +// } +// expectEqual(d.count, offset + 1) +// withEvery("i", in: 0 ... offset) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[count - 1 - offset + i]) +// expectEqual(v, values[count - 1 - offset + i]) +// } +// } +// } +// } +// } +// } +// } + +// func test_removeValue_forKey() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// let key = keys.remove(at: offset) +// let expected = values.remove(at: offset) +// let actual = d.removeValue(forKey: key) +// expectEqual(actual, expected) +// +// expectEqual(d.count, values.count) +// withEvery("i", in: 0 ..< values.count) { i in +// let (k, v) = d[offset: i] +// expectEqual(k, keys[i]) +// expectEqual(v, values[i]) +// } +// expectNil(d.removeValue(forKey: key)) +// } +// } +// } +// } +// } +// } + +// func test_merge_labeled_tuple() { +// var d: HashMap = [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] +// +// let items: KeyValuePairs = [ +// "one": 1, +// "one": 1, +// "three": 1, +// "four": 1, +// "one": 1, +// ] +// +// d.merge(items, uniquingKeysWith: +) +// +// expectEqualElements(d, [ +// "one": 4, +// "two": 1, +// "three": 2, +// "four": 1, +// ] as KeyValuePairs) +// } + +// func test_merge_unlabeled_tuple() { +// var d: HashMap = [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] +// +// let items: [(String, Int)] = [ +// ("one", 1), +// ("one", 1), +// ("three", 1), +// ("four", 1), +// ("one", 1), +// ] +// +// d.merge(items, uniquingKeysWith: +) +// +// expectEqualElements(d, [ +// "one": 4, +// "two": 1, +// "three": 2, +// "four": 1, +// ] as KeyValuePairs) +// } + +// func test_merging_labeled_tuple() { +// let d: HashMap = [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] +// +// let items: KeyValuePairs = [ +// "one": 1, +// "one": 1, +// "three": 1, +// "four": 1, +// "one": 1, +// ] +// +// let d2 = d.merging(items, uniquingKeysWith: +) +// +// expectEqualElements(d, [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] as KeyValuePairs) +// +// expectEqualElements(d2, [ +// "one": 4, +// "two": 1, +// "three": 2, +// "four": 1, +// ] as KeyValuePairs) +// } + +// func test_merging_unlabeled_tuple() { +// let d: HashMap = [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] +// +// let items: [(String, Int)] = [ +// ("one", 1), +// ("one", 1), +// ("three", 1), +// ("four", 1), +// ("one", 1), +// ] +// +// let d2 = d.merging(items, uniquingKeysWith: +) +// +// expectEqualElements(d, [ +// "one": 1, +// "two": 1, +// "three": 1, +// ] as KeyValuePairs) +// +// expectEqualElements(d2, [ +// "one": 4, +// "two": 1, +// "three": 2, +// "four": 1, +// ] as KeyValuePairs) +// } + +// func test_filter() { +// let items = (0 ..< 100).map { ($0, 100 * $0) } +// let d = HashMap(uniqueKeysWithValues: items) +// +// var c = 0 +// let d2 = d.filter { item in +// c += 1 +// expectEqual(item.value, 100 * item.key) +// return item.key.isMultiple(of: 2) +// } +// expectEqual(c, 100) +// expectEqualElements(d, items) +// +// expectEqualElements(d2, (0 ..< 50).compactMap { key in +// return (key: 2 * key, value: 200 * key) +// }) +// } + +// func test_mapValues() { +// let items = (0 ..< 100).map { ($0, 100 * $0) } +// let d = HashMap(uniqueKeysWithValues: items) +// +// var c = 0 +// let d2 = d.mapValues { value -> String in +// c += 1 +// expectTrue(value.isMultiple(of: 100)) +// return "\(value)" +// } +// expectEqual(c, 100) +// expectEqualElements(d, items) +// +// expectEqualElements(d2, (0 ..< 100).compactMap { key in +// (key: key, value: "\(100 * key)") +// }) +// } + +// func test_compactMapValue() { +// let items = (0 ..< 100).map { ($0, 100 * $0) } +// let d = HashMap(uniqueKeysWithValues: items) +// +// var c = 0 +// let d2 = d.compactMapValues { value -> String? in +// c += 1 +// guard value.isMultiple(of: 200) else { return nil } +// expectTrue(value.isMultiple(of: 100)) +// return "\(value)" +// } +// expectEqual(c, 100) +// expectEqualElements(d, items) +// +// expectEqualElements(d2, (0 ..< 50).map { key in +// (key: 2 * key, value: "\(200 * key)") +// }) +// } + +// func test_CustomStringConvertible() { +// let a: HashMap = [:] +// expectEqual(a.description, "[:]") +// +// let b: HashMap = [0: 1] +// expectEqual(b.description, "[0: 1]") +// +// let c: HashMap = [0: 1, 2: 3, 4: 5] +// expectEqual(c.description, "[0: 1, 2: 3, 4: 5]") +// } + +// func test_CustomDebugStringConvertible() { +// let a: HashMap = [:] +// expectEqual(a.debugDescription, +// "HashMap([:])") +// +// let b: HashMap = [0: 1] +// expectEqual(b.debugDescription, +// "HashMap([0: 1])") +// +// let c: HashMap = [0: 1, 2: 3, 4: 5] +// expectEqual(c.debugDescription, +// "HashMap([0: 1, 2: 3, 4: 5])") +// } + +// func test_customReflectable() { +// do { +// let d: HashMap = [1: 2, 3: 4, 5: 6] +// let mirror = Mirror(reflecting: d) +// expectEqual(mirror.displayStyle, .dictionary) +// expectNil(mirror.superclassMirror) +// expectTrue(mirror.children.compactMap { $0.label }.isEmpty) // No label +// expectEqualElements( +// mirror.children.compactMap { $0.value as? (key: Int, value: Int) }, +// d.map { $0 }) +// } +// } + + func test_Equatable_Hashable() { + let samples: [[HashMap]] = [ + [[:], [:]], + [[1: 100], [1: 100]], + [[2: 200], [2: 200]], + [[3: 300], [3: 300]], + [[100: 1], [100: 1]], + [[1: 1], [1: 1]], + [[100: 100], [100: 100]], + [[1: 100, 2: 200], [1: 100, 2: 200]], + [[2: 200, 1: 100], [2: 200, 1: 100]], + [[1: 100, 2: 200, 3: 300], [1: 100, 2: 200, 3: 300]], + [[2: 200, 1: 100, 3: 300], [2: 200, 1: 100, 3: 300]], + [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], + [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] + ] + checkHashable(equivalenceClasses: samples) + } + +// func test_Encodable() throws { +// let d1: HashMap = [:] +// let v1: MinimalEncoder.Value = .array([]) +// expectEqual(try MinimalEncoder.encode(d1), v1) +// +// let d2: HashMap = [0: 1] +// let v2: MinimalEncoder.Value = .array([.int(0), .int(1)]) +// expectEqual(try MinimalEncoder.encode(d2), v2) +// +// let d3: HashMap = [0: 1, 2: 3] +// let v3: MinimalEncoder.Value = +// .array([.int(0), .int(1), .int(2), .int(3)]) +// expectEqual(try MinimalEncoder.encode(d3), v3) +// +// let d4 = HashMap( +// uniqueKeys: 0 ..< 100, +// values: (0 ..< 100).map { 100 * $0 }) +// let v4: MinimalEncoder.Value = +// .array((0 ..< 100).flatMap { [.int($0), .int(100 * $0)] }) +// expectEqual(try MinimalEncoder.encode(d4), v4) +// } + +// func test_Decodable() throws { +// typealias OD = HashMap +// let d1: OD = [:] +// let v1: MinimalEncoder.Value = .array([]) +// expectEqual(try MinimalDecoder.decode(v1, as: OD.self), d1) +// +// let d2: OD = [0: 1] +// let v2: MinimalEncoder.Value = .array([.int(0), .int(1)]) +// expectEqual(try MinimalDecoder.decode(v2, as: OD.self), d2) +// +// let d3: OD = [0: 1, 2: 3] +// let v3: MinimalEncoder.Value = +// .array([.int(0), .int(1), .int(2), .int(3)]) +// expectEqual(try MinimalDecoder.decode(v3, as: OD.self), d3) +// +// let d4 = HashMap( +// uniqueKeys: 0 ..< 100, +// values: (0 ..< 100).map { 100 * $0 }) +// let v4: MinimalEncoder.Value = +// .array((0 ..< 100).flatMap { [.int($0), .int(100 * $0)] }) +// expectEqual(try MinimalDecoder.decode(v4, as: OD.self), d4) +// +// let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(2)]) +// expectThrows(try MinimalDecoder.decode(v5, as: OD.self)) { error in +// guard case DecodingError.dataCorrupted(let context) = error else { +// expectFailure("Unexpected error \(error)") +// return +// } +// expectEqual(context.debugDescription, +// "Unkeyed container reached end before value in key-value pair") +// +// } +// +// let v6: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0), .int(2)]) +// expectThrows(try MinimalDecoder.decode(v6, as: OD.self)) { error in +// guard case DecodingError.dataCorrupted(let context) = error else { +// expectFailure("Unexpected error \(error)") +// return +// } +// expectEqual(context.debugDescription, "Duplicate key at offset 2") +// } +// } + +// func test_swapAt() { +// withEvery("count", in: 0 ..< 20) { count in +// withEvery("i", in: 0 ..< count) { i in +// withEvery("j", in: 0 ..< count) { j in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// keys.swapAt(i, j) +// values.swapAt(i, j) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.swapAt(i, j) +// expectEqualElements(d.values, values) +// expectEqual(d[keys[i]], values[i]) +// expectEqual(d[keys[j]], values[j]) +// } +// } +// } +// } +// } +// } +// } + +// func test_partition() { +// withEvery("seed", in: 0 ..< 10) { seed in +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var rng = RepeatableRandomNumberGenerator(seed: seed) +// var (d, keys, values) = tracker.persistentDictionary( +// keys: (0 ..< count).shuffled(using: &rng)) +// var items = Array(zip(keys, values)) +// let expectedPivot = items.partition { $0.0.payload < count / 2 } +// withHiddenCopies(if: isShared, of: &d) { d in +// let actualPivot = d.partition { $0.key.payload < count / 2 } +// expectEqual(actualPivot, expectedPivot) +// expectEqualElements(d, items) +// } +// } +// } +// } +// } +// } + +// func test_sort() { +// withEvery("seed", in: 0 ..< 10) { seed in +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var rng = RepeatableRandomNumberGenerator(seed: seed) +// var (d, keys, values) = tracker.persistentDictionary( +// keys: (0 ..< count).shuffled(using: &rng)) +// var items = Array(zip(keys, values)) +// items.sort(by: { $0.0 < $1.0 }) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.sort() +// expectEqualElements(d, items) +// } +// } +// } +// } +// } +// } + +// func test_sort_by() { +// withEvery("seed", in: 0 ..< 10) { seed in +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var rng = RepeatableRandomNumberGenerator(seed: seed) +// var (d, keys, values) = tracker.persistentDictionary( +// keys: (0 ..< count).shuffled(using: &rng)) +// var items = Array(zip(keys, values)) +// items.sort(by: { $0.0 > $1.0 }) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.sort(by: { $0.key > $1.key }) +// expectEqualElements(d, items) +// } +// } +// } +// } +// } +// } + +// func test_shuffle() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withEvery("seed", in: 0 ..< 10) { seed in +// var d = HashMap( +// uniqueKeys: 0 ..< count, +// values: 100 ..< 100 + count) +// var items = (0 ..< count).map { (key: $0, value: 100 + $0) } +// withHiddenCopies(if: isShared, of: &d) { d in +// expectEqualElements(d, items) +// +// var rng1 = RepeatableRandomNumberGenerator(seed: seed) +// items.shuffle(using: &rng1) +// +// var rng2 = RepeatableRandomNumberGenerator(seed: seed) +// d.shuffle(using: &rng2) +// +// items.sort(by: { $0.key < $1.key }) +// d.sort() +// expectEqualElements(d, items) +// } +// } +// } +// if count >= 2 { +// // Check that shuffling with the system RNG does permute the elements. +// var d = HashMap( +// uniqueKeys: 0 ..< count, +// values: 100 ..< 100 + count) +// let original = d +// var success = false +// for _ in 0 ..< 1000 { +// d.shuffle() +// if !d.elementsEqual( +// original, +// by: { $0.key == $1.key && $0.value == $1.value} +// ) { +// success = true +// break +// } +// } +// expectTrue(success) +// } +// } +// } + +// func test_reverse() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// var items = Array(zip(keys, values)) +// withHiddenCopies(if: isShared, of: &d) { d in +// items.reverse() +// d.reverse() +// expectEqualElements(d, items) +// } +// } +// } +// } +// } + +// func test_removeAll() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeAll() +// expectEqual(d.keys.__unstable.scale, 0) +// expectEqualElements(d, []) +// } +// } +// } +// } +// } + +// func test_removeAll_keepCapacity() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) +// let origScale = d.keys.__unstable.scale +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeAll(keepingCapacity: true) +// expectEqual(d.keys.__unstable.scale, origScale) +// expectEqualElements(d, []) +// } +// } +// } +// } +// } + +// func test_remove_at() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("offset", in: 0 ..< count) { offset in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// let actual = d.remove(at: offset) +// let expectedKey = keys.remove(at: offset) +// let expectedValue = values.remove(at: offset) +// expectEqual(actual.key, expectedKey) +// expectEqual(actual.value, expectedValue) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } +// } + +// func test_removeSubrange() { +// withEvery("count", in: 0 ..< 30) { count in +// withEveryRange("range", in: 0 ..< count) { range in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeSubrange(range) +// keys.removeSubrange(range) +// values.removeSubrange(range) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } +// } + +// func test_removeSubrange_rangeExpression() { +// let d = HashMap(uniqueKeys: 0 ..< 30, values: 100 ..< 130) +// let item = (0 ..< 30).map { (key: $0, value: 100 + $0) } +// +// var d1 = d +// d1.removeSubrange(...10) +// expectEqualElements(d1, item[11...]) +// +// var d2 = d +// d2.removeSubrange(..<10) +// expectEqualElements(d2, item[10...]) +// +// var d3 = d +// d3.removeSubrange(10...) +// expectEqualElements(d3, item[0 ..< 10]) +// } +// +// func test_removeLast() { +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< 30) +// withEvery("i", in: 0 ..< d.count) { i in +// withHiddenCopies(if: isShared, of: &d) { d in +// let actual = d.removeLast() +// let expectedKey = keys.removeLast() +// let expectedValue = values.removeLast() +// expectEqual(actual.key, expectedKey) +// expectEqual(actual.value, expectedValue) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } + +// func test_removeFirst() { +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< 30) +// withEvery("i", in: 0 ..< d.count) { i in +// withHiddenCopies(if: isShared, of: &d) { d in +// let actual = d.removeFirst() +// let expectedKey = keys.removeFirst() +// let expectedValue = values.removeFirst() +// expectEqual(actual.key, expectedKey) +// expectEqual(actual.value, expectedValue) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } + +// func test_removeLast_n() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("suffix", in: 0 ..< count) { suffix in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeLast(suffix) +// keys.removeLast(suffix) +// values.removeLast(suffix) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } +// } + +// func test_removeFirst_n() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("prefix", in: 0 ..< count) { prefix in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeFirst(prefix) +// keys.removeFirst(prefix) +// values.removeFirst(prefix) +// expectEqualElements( +// d, +// zip(keys, values).map { (key: $0.0, value: $0.1) }) +// } +// } +// } +// } +// } +// } + +// func test_removeAll_where() { +// withEvery("count", in: 0 ..< 30) { count in +// withEvery("n", in: [2, 3, 4]) { n in +// withEvery("isShared", in: [false, true]) { isShared in +// withLifetimeTracking { tracker in +// var (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) +// var items = zip(keys, values).map { (key: $0.0, value: $0.1) } +// withHiddenCopies(if: isShared, of: &d) { d in +// d.removeAll(where: { !$0.key.payload.isMultiple(of: n) }) +// items.removeAll(where: { !$0.key.payload.isMultiple(of: n) }) +// expectEqualElements(d, items) +// } +// } +// } +// } +// } +// } + + func test_Sequence() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) + let items = zip(keys, values).map { (key: $0.0, value: $0.1) } + checkSequence( + { d }, + expectedContents: items, + by: { $0.key == $1.0 && $0.value == $1.1 }) + } + } + } + +} diff --git a/Tests/CapsuleTests/Capsule Utils.swift b/Tests/CapsuleTests/Capsule Utils.swift new file mode 100644 index 000000000..4fceb19d4 --- /dev/null +++ b/Tests/CapsuleTests/Capsule Utils.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import _CollectionsTestSupport +import Capsule + +extension LifetimeTracker { + func persistentDictionary( + keys: Keys + ) -> ( + dictionary: HashMap, LifetimeTracked>, + keys: [LifetimeTracked], + values: [LifetimeTracked] + ) + where Keys.Element == Int + { + let k = Array(keys) + let keys = self.instances(for: k) + let values = self.instances(for: k.map { $0 + 100 }) + let dictionary = HashMap(uniqueKeys: keys, values: values) + return (dictionary, keys, values) + } +} From bce54c64da07553908ae69e6a286bf71572a6402 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 09:51:23 +0200 Subject: [PATCH 073/176] [Capsule] Fix tests for unordered collections --- Tests/CapsuleTests/Capsule Tests.swift | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift index b80c301aa..4ee4da738 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -168,13 +168,13 @@ class HashMapTests: CollectionTestCase { expectTrue(d0.isEmpty) let d1: HashMap = [ - "one": 1, - "two": 2, - "three": 3, - "four": 4, + "1~one": 1, + "2~two": 2, + "3~three": 3, + "4~four": 4, ] - expectEqualElements(d1.map { $0.key }, ["one", "two", "three", "four"]) - expectEqualElements(d1.map { $0.value }, [1, 2, 3, 4]) + expectEqualElements(d1.map { $0.key }.sorted(), ["1~one", "2~two", "3~three", "4~four"]) + expectEqualElements(d1.map { $0.value }.sorted(), [1, 2, 3, 4]) } // func test_keys() { @@ -956,12 +956,8 @@ class HashMapTests: CollectionTestCase { [[100: 1], [100: 1]], [[1: 1], [1: 1]], [[100: 100], [100: 100]], - [[1: 100, 2: 200], [1: 100, 2: 200]], - [[2: 200, 1: 100], [2: 200, 1: 100]], - [[1: 100, 2: 200, 3: 300], [1: 100, 2: 200, 3: 300]], - [[2: 200, 1: 100, 3: 300], [2: 200, 1: 100, 3: 300]], - [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], - [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] + [[1: 100, 2: 200], [2: 200, 1: 100]], + [[1: 100, 2: 200, 3: 300], [1: 100, 3: 300, 2: 200], [2: 200, 1: 100, 3: 300], [2: 200, 3: 300, 1: 100], [3: 300, 1: 100, 2: 200], [3: 300, 2: 200, 1: 100]] ] checkHashable(equivalenceClasses: samples) } @@ -1369,7 +1365,7 @@ class HashMapTests: CollectionTestCase { let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) let items = zip(keys, values).map { (key: $0.0, value: $0.1) } checkSequence( - { d }, + { d.sorted(by: <) }, expectedContents: items, by: { $0.key == $1.0 && $0.value == $1.1 }) } From 186bc959d315a588b1615d24e4f4c3be4167faac Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 10:46:15 +0200 Subject: [PATCH 074/176] [Capsule] Add `CustomStringConvertible` conformance --- .../HashMap+CustomStringConvertible.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Sources/Capsule/HashMap+CustomStringConvertible.swift diff --git a/Sources/Capsule/HashMap+CustomStringConvertible.swift b/Sources/Capsule/HashMap+CustomStringConvertible.swift new file mode 100644 index 000000000..9acb9de7a --- /dev/null +++ b/Sources/Capsule/HashMap+CustomStringConvertible.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// 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 HashMap: 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 + } +} From 4a76bd954824d43d33eebeed77f3851813feaa70 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 11:01:27 +0200 Subject: [PATCH 075/176] [Capsule] Extract class `CollidableInt` from smoke tests --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 31 ---------------- Tests/CapsuleTests/CollidableInt.swift | 41 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 Tests/CapsuleTests/CollidableInt.swift diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 7d5d7ac8e..1435ad636 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -404,37 +404,6 @@ final class CapsuleSmokeTests: CollectionTestCase { } } -fileprivate final class CollidableInt: CustomStringConvertible, Equatable, Hashable { - let value: Int - let hashValue: Int - - fileprivate init(_ value: Int) { - self.value = value - self.hashValue = value - } - - fileprivate init(_ value: Int, _ hashValue: Int) { - self.value = value - self.hashValue = hashValue - } - - var description: String { - return "\(value) [hash = \(hashValue)]" - } - - func hash(into hasher: inout Hasher) { - hasher.combine(hashValue) - } - - static func == (lhs: CollidableInt, rhs: CollidableInt) -> Bool { - if lhs.value == rhs.value { - precondition(lhs.hashValue == rhs.hashValue) - return true - } - return false - } -} - final class BitmapSmokeTests: CollectionTestCase { func test_BitPartitionSize_isValid() { expectTrue(bitPartitionSize > 0) diff --git a/Tests/CapsuleTests/CollidableInt.swift b/Tests/CapsuleTests/CollidableInt.swift new file mode 100644 index 000000000..066b57252 --- /dev/null +++ b/Tests/CapsuleTests/CollidableInt.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +final class CollidableInt: CustomStringConvertible, Equatable, Hashable { + let value: Int + let hashValue: Int + + init(_ value: Int) { + self.value = value + self.hashValue = value + } + + init(_ value: Int, _ hashValue: Int) { + self.value = value + self.hashValue = hashValue + } + + var description: String { + return "\(value) [hash = \(hashValue)]" + } + + func hash(into hasher: inout Hasher) { + hasher.combine(hashValue) + } + + static func == (lhs: CollidableInt, rhs: CollidableInt) -> Bool { + if lhs.value == rhs.value { + precondition(lhs.hashValue == rhs.hashValue) + return true + } + return false + } +} From 77f6043c45ec36a853fbd176f163bdbf8addd20b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 11:12:15 +0200 Subject: [PATCH 076/176] [Capsule] Add `CustomDebugStringConvertible` conformance to `CollidableInt` --- Tests/CapsuleTests/CollidableInt.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/CapsuleTests/CollidableInt.swift b/Tests/CapsuleTests/CollidableInt.swift index 066b57252..c5114c3e7 100644 --- a/Tests/CapsuleTests/CollidableInt.swift +++ b/Tests/CapsuleTests/CollidableInt.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -final class CollidableInt: CustomStringConvertible, Equatable, Hashable { +final class CollidableInt: CustomStringConvertible, CustomDebugStringConvertible, Equatable, Hashable { let value: Int let hashValue: Int @@ -24,6 +24,10 @@ final class CollidableInt: CustomStringConvertible, Equatable, Hashable { } var description: String { + return "\(value)" + } + + var debugDescription: String { return "\(value) [hash = \(hashValue)]" } From ea6257fbb9052941e5b1113a0e064e89b2f9f898 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 11:13:46 +0200 Subject: [PATCH 077/176] [Capsule] Enable `test_CustomStringConvertible` --- Tests/CapsuleTests/Capsule Tests.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift index 4ee4da738..e4f04b790 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -909,16 +909,16 @@ class HashMapTests: CollectionTestCase { // }) // } -// func test_CustomStringConvertible() { -// let a: HashMap = [:] -// expectEqual(a.description, "[:]") -// -// let b: HashMap = [0: 1] -// expectEqual(b.description, "[0: 1]") -// -// let c: HashMap = [0: 1, 2: 3, 4: 5] -// expectEqual(c.description, "[0: 1, 2: 3, 4: 5]") -// } + func test_CustomStringConvertible() { + let a: HashMap = [:] + expectEqual(a.description, "[:]") + + let b: HashMap = [CollidableInt(0): 1] + expectEqual(b.description, "[0: 1]") + + let c: HashMap = [CollidableInt(0): 1, CollidableInt(2): 3, CollidableInt(4): 5] + expectEqual(c.description, "[0: 1, 2: 3, 4: 5]") + } // func test_CustomDebugStringConvertible() { // let a: HashMap = [:] From 0d91ef826a83242b9c621919c725dffc9a9c87c5 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 7 Jun 2021 20:54:23 +0200 Subject: [PATCH 078/176] [Capsule] Enable more initializer tests --- Sources/Capsule/HashMap.swift | 21 ++++++++++++ Tests/CapsuleTests/Capsule Tests.swift | 47 +++++++++++++------------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index a910934a0..2723f533b 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -28,16 +28,37 @@ public struct HashMap where Key: Hashable { self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) } + // TODO consider removing `unchecked` version, since it's only referenced from within the test suite + @inlinable + @inline(__always) + public init(uncheckedUniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { + var builder = Self() + keysAndValues.forEach { key, value in + builder.insert(key: key, value: value) + } + self.init(builder) + } + @inlinable @inline(__always) public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { var builder = Self() keysAndValues.forEach { key, value in + guard !builder.contains(key) else { + preconditionFailure("Duplicate key: '\(key)'") + } builder.insert(key: key, value: value) } self.init(builder) } + // TODO consider removing `unchecked` version, since it's only referenced from within the test suite + @inlinable + @inline(__always) + public init(uncheckedUniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { + self.init(uncheckedUniqueKeysWithValues: zip(keys, values)) + } + @inlinable @inline(__always) public init(uniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift index e4f04b790..81aa8506d 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -25,7 +25,7 @@ class HashMapTests: CollectionTestCase { // expectGreaterThanOrEqual(d.values.elements.capacity, 1000) // expectEqual(d.keys.__unstable.reservedScale, 0) // } -// + // func test_init_minimumCapacity_persistent() { // let d = HashMap(minimumCapacity: 1000, persistent: true) // expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) @@ -41,7 +41,7 @@ class HashMapTests: CollectionTestCase { // "three": 3, // ] // let d = HashMap(uncheckedUniqueKeysWithValues: items) -// expectEqualElements(d, items) +// expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } // func test_uniqueKeysWithValues_labeled_tuples() { @@ -52,31 +52,30 @@ class HashMapTests: CollectionTestCase { // "three": 3, // ] // let d = HashMap(uncheckedUniqueKeysWithValues: items) -// expectEqualElements(d, items) +// expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } -// func test_uniqueKeysWithValues_unlabeled_tuples() { -// let items: [(String, Int)] = [ -// ("zero", 0), -// ("one", 1), -// ("two", 2), -// ("three", 3), -// ] -// let d = HashMap(uncheckedUniqueKeysWithValues: items) -// expectEqualElements(d, items) -// } + func test_uniqueKeysWithValues_unlabeled_tuples() { + let items: [(String, Int)] = [ + ("zero", 0), + ("one", 1), + ("two", 2), + ("three", 3), + ] + let d = HashMap(uncheckedUniqueKeysWithValues: items) + expectEqualElements(d.sorted(by: <), items.sorted(by: <)) + } -// func test_uniqueKeys_values() { -// let d = HashMap( -// uncheckedUniqueKeys: ["zero", "one", "two", "three"], -// values: [0, 1, 2, 3]) -// expectEqualElements(d, [ -// (key: "zero", value: 0), -// (key: "one", value: 1), -// (key: "two", value: 2), -// (key: "three", value: 3), -// ]) -// } + func test_uniqueKeys_values() { + let items: [(key: String, value: Int)] = [ + (key: "zero", value: 0), + (key: "one", value: 1), + (key: "two", value: 2), + (key: "three", value: 3) + ] + let d = HashMap(uncheckedUniqueKeys: ["zero", "one", "two", "three"], values: [0, 1, 2, 3]) + expectEqualElements(d.sorted(by: <), items.sorted(by: <)) + } // func test_uniquing_initializer_labeled_tuples() { // let items: KeyValuePairs = [ From 3110518d5d8cb66d7ca6481ddcfe2d0bd681d2d6 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 8 Jun 2021 19:08:00 +0200 Subject: [PATCH 079/176] [Capsule] Extend copy-on-write optimizations --- Sources/Capsule/_BitmapIndexedMapNode.swift | 206 +++++++++++++++----- 1 file changed, 154 insertions(+), 52 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 03dc58ea0..a689198a6 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,15 +12,42 @@ fileprivate var tupleLength: Int { 2 } final class BitmapIndexedMapNode: MapNode where Key: Hashable { - let bitmap1: Bitmap - let bitmap2: Bitmap + var bitmap1: Bitmap + var bitmap2: Bitmap var content: [Any] - var dataMap: Bitmap { bitmap1 ^ collMap } + var dataMap: Bitmap { + get { + bitmap1 ^ collMap + } + set(dataMap) { + bitmap1 = dataMap ^ collMap + } + } - var nodeMap: Bitmap { bitmap2 ^ collMap } + var nodeMap: Bitmap { + get { + bitmap2 ^ collMap + } + set(nodeMap) { + bitmap2 = nodeMap ^ collMap + } + } + + var collMap: Bitmap { + get { + bitmap1 & bitmap2 + } + set(collMap) { + // be careful when referencing `dataMap` or `nodeMap`, since both have a dependency on `collMap` - var collMap: Bitmap { bitmap1 & bitmap2 } + bitmap1 ^= self.collMap + bitmap2 ^= self.collMap + + bitmap1 ^= collMap + bitmap2 ^= collMap + } + } init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: [Any]) { self.bitmap1 = dataMap ^ collMap @@ -116,11 +143,11 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if keyHash0 == keyHash { let subNodeNew = HashCollisionMapNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() - return copyAndMigrateFromInlineToCollisionNode(bitpos, subNodeNew) + return copyAndMigrateFromInlineToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() - return copyAndMigrateFromInlineToNode(bitpos, subNodeNew) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } } @@ -151,12 +178,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() - return copyAndMigrateFromCollisionNodeToNode(bitpos, subNodeNew) + return copyAndMigrateFromCollisionNodeToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } effect.setModified() - return copyAndInsertValue(bitpos, key, value) + return copyAndInsertValue(isStorageKnownUniquelyReferenced, bitpos, key, value) } func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { @@ -184,7 +211,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) - } else { return copyAndRemoveValue(bitpos) } + } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } guard (nodeMap & bitpos) == 0 else { @@ -207,7 +234,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return subNodeNew } else { // inline singleton - return copyAndMigrateFromNodeToInline(bitpos, subNodeNew.getPayload(0)) + return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } case .sizeMoreThanOne: @@ -219,7 +246,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndMigrateFromNodeToCollisionNode(bitpos, subNodeNew.getHashCollisionNode(0)) + return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0)) } } @@ -249,7 +276,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: subNodeNew.getPayload(0)) } else { // inline value - return copyAndMigrateFromCollisionNodeToInline(bitpos, subNodeNew.getPayload(0)) + return copyAndMigrateFromCollisionNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } case .sizeMoreThanOne: @@ -390,7 +417,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - self.nodeIndex(bitpos) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) + } func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { let idx = self.content.count - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) @@ -411,88 +439,162 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - func copyAndInsertValue(_ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { let idx = tupleLength * dataIndex(bitpos) - var dst = self.content - dst.insert(contentsOf: [key, value], at: idx) + if isStorageKnownUniquelyReferenced { + self.dataMap |= bitpos + self.content.insert(contentsOf: [key, value], at: idx) + + return self + } else { + var dst = self.content + dst.insert(contentsOf: [key, value], at: idx) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) + } } - func copyAndRemoveValue(_ bitpos: Bitmap) -> BitmapIndexedMapNode { + func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { let idx = tupleLength * dataIndex(bitpos) - var dst = self.content - dst.removeSubrange(idx..) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idxOld = tupleLength * dataIndex(bitpos) let idxNew = self.content.count - tupleLength - nodeIndex(bitpos) - var dst = self.content - dst.removeSubrange(idxOld..) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { let idxOld = tupleLength * dataIndex(bitpos) let idxNew = self.content.count - tupleLength - bitmapIndexedNodeArity - collIndex(bitpos) - var dst = self.content - dst.removeSubrange(idxOld.. BitmapIndexedMapNode { + func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) let idxNew = tupleLength * dataIndex(bitpos) - var dst = self.content - dst.remove(at: idxOld) - dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + if isStorageKnownUniquelyReferenced { + self.dataMap |= bitpos + self.nodeMap ^= bitpos + + self.content.remove(at: idxOld) + self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) + return self + } else { + var dst = self.content + dst.remove(at: idxOld) + dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) + } } - func copyAndMigrateFromCollisionNodeToInline(_ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { + func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) let idxNew = tupleLength * dataIndex(bitpos) - var dst = self.content - dst.remove(at: idxOld) - dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + if isStorageKnownUniquelyReferenced { + self.dataMap |= bitpos + self.collMap ^= bitpos + + self.content.remove(at: idxOld) + self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) + return self + } else { + var dst = self.content + dst.remove(at: idxOld) + dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + + return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) + } } - func copyAndMigrateFromCollisionNodeToNode(_ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) let idxNew = self.content.count - 1 - nodeIndex(bitpos) - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) + if isStorageKnownUniquelyReferenced { + self.nodeMap |= bitpos + self.collMap ^= bitpos + + self.content.remove(at: idxOld) + self.content.insert(node, at: idxNew) + + return self + } else { + var dst = self.content + dst.remove(at: idxOld) + dst.insert(node, at: idxNew) - return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) + return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) + } } - func copyAndMigrateFromNodeToCollisionNode(_ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) let idxNew = self.content.count - 1 - (bitmapIndexedNodeArity - 1) - collIndex(bitpos) - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) + if isStorageKnownUniquelyReferenced { + self.nodeMap ^= bitpos + self.collMap |= bitpos + + self.content.remove(at: idxOld) + self.content.insert(node, at: idxNew) + + return self + } else { + var dst = self.content + dst.remove(at: idxOld) + dst.insert(node, at: idxNew) - return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) + return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) + } } } From 0940b271d077fff54dce840f7b420dd890c66e5f Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 9 Jun 2021 19:58:16 +0200 Subject: [PATCH 080/176] [Capsule] Add benchmark helper script --- Utils/run-capsule-benchmarks.zsh | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 Utils/run-capsule-benchmarks.zsh diff --git a/Utils/run-capsule-benchmarks.zsh b/Utils/run-capsule-benchmarks.zsh new file mode 100755 index 000000000..e0ce850b4 --- /dev/null +++ b/Utils/run-capsule-benchmarks.zsh @@ -0,0 +1,44 @@ +#!/bin/zsh + +declare -a benchmarks +benchmarks=( + "init(uniqueKeysWithValues:)" + "sequential iteration" + "subscript, successful lookups" + "subscript, unsuccessful lookups" + "subscript, noop setter" + "subscript, set existing" + "subscript, _modify" + "subscript, insert" + "subscript, insert, reserving capacity" + "subscript, remove existing" + "subscript, remove missing" + "defaulted subscript, successful lookups" + "defaulted subscript, unsuccessful lookups" + "defaulted subscript, _modify existing" + "defaulted subscript, _modify missing" + "updateValue(_:forKey:), existing" + "updateValue(_:forKey:), insert" + "random removals (existing keys)" + "random removals (missing keys)" +) + +declare -a classes +classes=( + "Dictionary" + "HashMap" + # "OrderedDictionary" +) + +for benchmark in ${benchmarks[@]}; do + tasks_file=$(mktemp) + + for class in $classes; do + echo "$class $benchmark" >> $tasks_file + done + + rm "results-$benchmark" && rm "chart-$benchmark.png" + swift run -c release swift-collections-benchmark run "results-$benchmark" --tasks-file=$tasks_file --cycles=1 + swift run -c release swift-collections-benchmark render "results-$benchmark" "chart-$benchmark.png" + # open "chart-$benchmark.png" +done From 7f44f5439ac425e22a3a25d2c7428e2770251ac6 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 9 Jun 2021 20:41:46 +0200 Subject: [PATCH 081/176] [Capsule] Move allocation out of `isTrieNodeKnownUniquelyReferenced` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index a689198a6..21a24db2e 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -351,8 +351,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference // to pass into `isKnownUniquelyReferenced` private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let fakeNode = BitmapIndexedMapNode() - var realNode = content[slotIndex] as AnyObject content[slotIndex] = fakeNode @@ -598,6 +596,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } +fileprivate let fakeNode = BitmapIndexedMapNode() + extension BitmapIndexedMapNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { lhs === rhs || From 130e4a51cc32bb007d70c7d4f4100c232776ebe9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 9 Jun 2021 21:31:51 +0200 Subject: [PATCH 082/176] [Capsule] Experiment unifying node getter with uniqueness check --- Sources/Capsule/_BitmapIndexedMapNode.swift | 78 ++++++++++----------- Sources/Capsule/_Common.swift | 4 +- Sources/Capsule/_HashCollisionMapNode.swift | 4 +- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 21a24db2e..2d2df5082 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -92,12 +92,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) + return self.getBitmapIndexedNode(index).node.get(key, keyHash, shift + bitPartitionSize) } guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) + return self.getHashCollisionNode(index).node.get(key, keyHash, shift + bitPartitionSize) } return nil @@ -115,12 +115,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + return self.getBitmapIndexedNode(index).node.containsKey(key, keyHash, shift + bitPartitionSize) } guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + return self.getHashCollisionNode(index).node.containsKey(key, keyHash, shift + bitPartitionSize) } return false @@ -154,10 +154,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getBitmapIndexedNode(index) + let (subNode, isSubNodeKnownUniquelyReferenced) = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -165,13 +164,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getHashCollisionNode(index) + let (subNode, isSubNodeKnownUniquelyReferenced) = self.getHashCollisionNode(index) let collisionHash = subNode.hash if keyHash == collisionHash { - let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -209,17 +207,16 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // create potential new root: will a) become new root, or b) unwrapped on another level - let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) + let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).node.hash, 0)) + return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0).node) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getBitmapIndexedNode(index) + let (subNode, isSubNodeKnownUniquelyReferenced) = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { @@ -246,7 +243,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0)) + return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0).node) } } @@ -257,10 +254,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getHashCollisionNode(index) + let (subNode, isSubNodeKnownUniquelyReferenced) = self.getHashCollisionNode(index) - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { @@ -334,38 +330,36 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } - func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - content[content.count - 1 - index] as! BitmapIndexedMapNode - } - - private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + func getBitmapIndexedNode(_ index: Int) -> (node: BitmapIndexedMapNode, isKnownUniquelyReferenced: Bool) { let slotIndex = content.count - 1 - index - return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) - } - private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index - return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) - } - - // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference - // to pass into `isKnownUniquelyReferenced` - private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - var realNode = content[slotIndex] as AnyObject + // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference + // to pass into `isKnownUniquelyReferenced` + var realNode = content[slotIndex] as! BitmapIndexedMapNode content[slotIndex] = fakeNode let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) content[slotIndex] = realNode - return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced + return (realNode, isKnownUniquelyReferenced) } var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } - func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return content[content.count - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode + func getHashCollisionNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { + let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index + + // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference + // to pass into `isKnownUniquelyReferenced` + var realNode = content[slotIndex] as! HashCollisionMapNode + content[slotIndex] = fakeNode + + let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) + content[slotIndex] = realNode + + return (realNode, isKnownUniquelyReferenced) } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -374,9 +368,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { if index < bitmapIndexedNodeArity { - return .bitmapIndexed(getBitmapIndexedNode(index)) + return .bitmapIndexed(getBitmapIndexedNode(index).node) } else { - return .hashCollision(getHashCollisionNode(index)) + return .hashCollision(getHashCollisionNode(index).node) } } @@ -615,13 +609,13 @@ extension BitmapIndexedMapNode: Equatable where Value: Equatable { } for index in 0.. ReturnBitmapIndexedNode + func getBitmapIndexedNode(_ index: Int) -> (node: ReturnBitmapIndexedNode, isKnownUniquelyReferenced: Bool) var hasHashCollisionNodes: Bool { get } var hashCollisionNodeArity: Int { get } - func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode + func getHashCollisionNode(_ index: Int) -> (node: ReturnHashCollisionNode, isKnownUniquelyReferenced: Bool) var hasNodes: Bool { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 7d123e486..bb9663f3b 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -76,7 +76,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { 0 } - func getBitmapIndexedNode(_ index: Int) -> HashCollisionMapNode { + func getBitmapIndexedNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { preconditionFailure("No sub-nodes present in hash-collision leaf node") } @@ -84,7 +84,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { 0 } - func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { + func getHashCollisionNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From b2fe267423dfe65307ebd623887617c6e33b82b4 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 9 Jun 2021 21:32:58 +0200 Subject: [PATCH 083/176] Revert "[Capsule] Experiment unifying node getter with uniqueness check" This reverts commit 6861030778714080fb97e1b39021c173e99f4cd1. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 78 +++++++++++---------- Sources/Capsule/_Common.swift | 4 +- Sources/Capsule/_HashCollisionMapNode.swift | 4 +- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 2d2df5082..21a24db2e 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -92,12 +92,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).node.get(key, keyHash, shift + bitPartitionSize) + return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) } guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).node.get(key, keyHash, shift + bitPartitionSize) + return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) } return nil @@ -115,12 +115,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).node.containsKey(key, keyHash, shift + bitPartitionSize) + return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).node.containsKey(key, keyHash, shift + bitPartitionSize) + return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } return false @@ -154,9 +154,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - let (subNode, isSubNodeKnownUniquelyReferenced) = self.getBitmapIndexedNode(index) + let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.updateOrUpdating(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -164,12 +165,13 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - let (subNode, isSubNodeKnownUniquelyReferenced) = self.getHashCollisionNode(index) + let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getHashCollisionNode(index) let collisionHash = subNode.hash if keyHash == collisionHash { - let subNodeNew = subNode.updateOrUpdating(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, value, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) @@ -207,16 +209,17 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // create potential new root: will a) become new root, or b) unwrapped on another level - let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).node.hash, 0)) - return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0).node) + let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) + return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) - let (subNode, isSubNodeKnownUniquelyReferenced) = self.getBitmapIndexedNode(index) + let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getBitmapIndexedNode(index) - let subNodeNew = subNode.removeOrRemoving(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { @@ -243,7 +246,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0).node) + return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0)) } } @@ -254,9 +257,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) - let (subNode, isSubNodeKnownUniquelyReferenced) = self.getHashCollisionNode(index) + let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) + let subNode = self.getHashCollisionNode(index) - let subNodeNew = subNode.removeOrRemoving(isStorageKnownUniquelyReferenced && isSubNodeKnownUniquelyReferenced, key, keyHash, shift + bitPartitionSize, &effect) + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified else { return self } switch subNodeNew.sizePredicate { @@ -330,36 +334,38 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } - func getBitmapIndexedNode(_ index: Int) -> (node: BitmapIndexedMapNode, isKnownUniquelyReferenced: Bool) { + func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { + content[content.count - 1 - index] as! BitmapIndexedMapNode + } + + private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let slotIndex = content.count - 1 - index + return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) + } - // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference - // to pass into `isKnownUniquelyReferenced` - var realNode = content[slotIndex] as! BitmapIndexedMapNode + private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index + return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) + } + + // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference + // to pass into `isKnownUniquelyReferenced` + private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { + var realNode = content[slotIndex] as AnyObject content[slotIndex] = fakeNode let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) content[slotIndex] = realNode - return (realNode, isKnownUniquelyReferenced) + return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } - func getHashCollisionNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { - let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index - - // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference - // to pass into `isKnownUniquelyReferenced` - var realNode = content[slotIndex] as! HashCollisionMapNode - content[slotIndex] = fakeNode - - let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) - content[slotIndex] = realNode - - return (realNode, isKnownUniquelyReferenced) + func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { + return content[content.count - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -368,9 +374,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { if index < bitmapIndexedNodeArity { - return .bitmapIndexed(getBitmapIndexedNode(index).node) + return .bitmapIndexed(getBitmapIndexedNode(index)) } else { - return .hashCollision(getHashCollisionNode(index).node) + return .hashCollision(getHashCollisionNode(index)) } } @@ -609,13 +615,13 @@ extension BitmapIndexedMapNode: Equatable where Value: Equatable { } for index in 0.. (node: ReturnBitmapIndexedNode, isKnownUniquelyReferenced: Bool) + func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode var hasHashCollisionNodes: Bool { get } var hashCollisionNodeArity: Int { get } - func getHashCollisionNode(_ index: Int) -> (node: ReturnHashCollisionNode, isKnownUniquelyReferenced: Bool) + func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode var hasNodes: Bool { get } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index bb9663f3b..7d123e486 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -76,7 +76,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { 0 } - func getBitmapIndexedNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { + func getBitmapIndexedNode(_ index: Int) -> HashCollisionMapNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } @@ -84,7 +84,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { 0 } - func getHashCollisionNode(_ index: Int) -> (node: HashCollisionMapNode, isKnownUniquelyReferenced: Bool) { + func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From 18209103d4d6f935c2c043a5bcca02510b47b0c2 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 10 Jun 2021 09:11:57 +0200 Subject: [PATCH 084/176] [Capsule] Trim down bitmap size --- Sources/Capsule/_Common.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index e53dee04a..c82222b4d 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -15,7 +15,7 @@ func computeHash(_ value: T) -> Int { value.hashValue } -typealias Bitmap = Int64 +typealias Bitmap = Int32 let bitPartitionSize: Int = 5 From fbb72e063816fef8e4cdc893debc2dfc5bdb6220 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 10 Jun 2021 09:52:55 +0200 Subject: [PATCH 085/176] [Capsule] Use `count` as guidance for duplication checks --- Sources/Capsule/HashMap.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 2723f533b..a950a8e44 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -43,11 +43,14 @@ public struct HashMap where Key: Hashable { @inline(__always) public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { var builder = Self() + var expectedCount = 0 keysAndValues.forEach { key, value in - guard !builder.contains(key) else { + builder.insert(key: key, value: value) + expectedCount += 1 + + guard expectedCount == builder.count else { preconditionFailure("Duplicate key: '\(key)'") } - builder.insert(key: key, value: value) } self.init(builder) } From e688e368ac69b1bce5b4994a5403c73ffecd62e4 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 10 Jun 2021 10:28:46 +0200 Subject: [PATCH 086/176] [Capsule] Specialize initializers --- Sources/Capsule/_BitmapIndexedMapNode.swift | 49 +++++++++++++-------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 21a24db2e..4ecb44ffa 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -57,25 +57,35 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { assert(count - payloadArity >= 2 * nodeArity) } - convenience init(dataMap: Bitmap = 0, nodeMap: Bitmap = 0, collMap: Bitmap = 0, arrayLiteral content: Any...) { - self.init(dataMap, nodeMap, collMap, content) + convenience init() { + self.init(0, 0, 0, []) } - // TODO improve performance of variadic implementation or consider specializing for two key-value tuples - convenience init(dataMap: Bitmap, arrayLiteral elements: (Key, Value)...) { - self.init(dataMap, 0, 0, elements.flatMap { [$0.0, $0.1] }) + convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { + let elements: [Any] = [firstKey, firstValue] + self.init(dataMap, 0, 0, elements) } - // TODO improve performance of variadic implementation or consider specializing for singleton nodes - convenience init(nodeMap: Bitmap, arrayLiteral elements: BitmapIndexedMapNode...) { + convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { + let elements: [Any] = [firstKey, firstValue, secondKey, secondValue] + self.init(dataMap, 0, 0, elements) + } + + convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { + let elements = [firstNode] self.init(0, nodeMap, 0, elements) } - // TODO improve performance of variadic implementation or consider specializing for singleton nodes - convenience init(collMap: Bitmap, arrayLiteral elements: HashCollisionMapNode...) { + convenience init(collMap: Bitmap, firstNode: HashCollisionMapNode) { + let elements = [firstNode] self.init(0, 0, collMap, elements) } + convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { + let elements: [Any] = [firstKey, firstValue, firstNode] + self.init(dataMap, 0, collMap, elements) + } + var count: Int { self.reduce(0, { count, _ in count + 1 }) } @@ -201,16 +211,18 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if shift == 0 { // keep remaining pair on root level let newDataMap = (dataMap ^ bitpos) - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1 - index)) + let (remainingKey, remainingValue) = getPayload(1 - index) + return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // create potential new root: will a) become new root, or b) inlined on another level let newDataMap = bitposFrom(maskFrom(keyHash, 0)) - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: getPayload(1 - index)) + let (remainingKey, remainingValue) = getPayload(1 - index) + return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(collMap: newCollMap, arrayLiteral: getHashCollisionNode(0)) + return BitmapIndexedMapNode(collMap: newCollMap, firstNode: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -273,7 +285,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // escalate singleton // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) - return BitmapIndexedMapNode(dataMap: newDataMap, arrayLiteral: subNodeNew.getPayload(0)) + let (remainingKey, remainingValue) = subNodeNew.getPayload(0) + return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // inline value return copyAndMigrateFromCollisionNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) @@ -301,15 +314,15 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if mask0 != mask1 { // unique prefixes, payload fits on same level if mask0 < mask1 { - return BitmapIndexedMapNode(dataMap: bitposFrom(mask0) | bitposFrom(mask1), arrayLiteral: (key0, value0), (key1, value1)) + return BitmapIndexedMapNode(dataMap: bitposFrom(mask0) | bitposFrom(mask1), firstKey: key0, firstValue: value0, secondKey: key1, secondValue: value1) } else { - return BitmapIndexedMapNode(dataMap: bitposFrom(mask1) | bitposFrom(mask0), arrayLiteral: (key1, value1), (key0, value0)) + return BitmapIndexedMapNode(dataMap: bitposFrom(mask1) | bitposFrom(mask0), firstKey: key1, firstValue: value1, secondKey: key0, secondValue: value0) } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) + return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), firstNode: node) } } @@ -321,12 +334,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - return BitmapIndexedMapNode(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), arrayLiteral: key0, value0, node1) + return BitmapIndexedMapNode(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), arrayLiteral: node) + return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), firstNode: node) } } From c38d56d438afbda5adb493b545b8a0de9b550459 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 10 Jun 2021 10:56:45 +0200 Subject: [PATCH 087/176] [Capsule] Extract calls to `isKnownUniquelyReferenced` --- Sources/Capsule/HashMap.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index a950a8e44..16a11f207 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -90,9 +90,11 @@ public struct HashMap where Key: Hashable { } mutating set(optionalValue) { if let value = optionalValue { - insert(isKnownUniquelyReferenced(&self.rootNode), key: key, value: value) + let mutate = isKnownUniquelyReferenced(&self.rootNode) + insert(mutate, key: key, value: value) } else { - delete(isKnownUniquelyReferenced(&self.rootNode), key: key) + let mutate = isKnownUniquelyReferenced(&self.rootNode) + delete(mutate, key: key) } } } @@ -102,7 +104,8 @@ public struct HashMap where Key: Hashable { return get(key) ?? defaultValue() } mutating set(value) { - insert(isKnownUniquelyReferenced(&self.rootNode), key: key, value: value) + let mutate = isKnownUniquelyReferenced(&self.rootNode) + insert(mutate, key: key, value: value) } } From 35ccb9d9df6065e0c7cc45b2774f06b2b992641e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 10 Jun 2021 11:50:28 +0200 Subject: [PATCH 088/176] [Capsule] Add `typealias` for backend storage type --- Sources/Capsule/_BitmapIndexedMapNode.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 4ecb44ffa..dea3e5731 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,9 +12,11 @@ fileprivate var tupleLength: Int { 2 } final class BitmapIndexedMapNode: MapNode where Key: Hashable { + typealias Storage = [Any] + var bitmap1: Bitmap var bitmap2: Bitmap - var content: [Any] + var content: Storage var dataMap: Bitmap { get { @@ -49,7 +51,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: [Any]) { + init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: Storage) { self.bitmap1 = dataMap ^ collMap self.bitmap2 = nodeMap ^ collMap self.content = content @@ -62,27 +64,27 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { - let elements: [Any] = [firstKey, firstValue] + let elements: Storage = [firstKey, firstValue] self.init(dataMap, 0, 0, elements) } convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { - let elements: [Any] = [firstKey, firstValue, secondKey, secondValue] + let elements: Storage = [firstKey, firstValue, secondKey, secondValue] self.init(dataMap, 0, 0, elements) } convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { - let elements = [firstNode] + let elements: Storage = [firstNode] self.init(0, nodeMap, 0, elements) } convenience init(collMap: Bitmap, firstNode: HashCollisionMapNode) { - let elements = [firstNode] + let elements: Storage = [firstNode] self.init(0, 0, collMap, elements) } convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { - let elements: [Any] = [firstKey, firstValue, firstNode] + let elements: Storage = [firstKey, firstValue, firstNode] self.init(dataMap, 0, collMap, elements) } From 37a16d9ef5bdea58212d722d845b397eab2358b5 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 12 Jun 2021 16:42:04 +0200 Subject: [PATCH 089/176] [Capsule] Benchmark with `-Ounchecked` to ignore bounce checks hit, etcetera --- Utils/run-capsule-benchmarks.zsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Utils/run-capsule-benchmarks.zsh b/Utils/run-capsule-benchmarks.zsh index e0ce850b4..1f1b37afe 100755 --- a/Utils/run-capsule-benchmarks.zsh +++ b/Utils/run-capsule-benchmarks.zsh @@ -38,7 +38,7 @@ for benchmark in ${benchmarks[@]}; do done rm "results-$benchmark" && rm "chart-$benchmark.png" - swift run -c release swift-collections-benchmark run "results-$benchmark" --tasks-file=$tasks_file --cycles=1 - swift run -c release swift-collections-benchmark render "results-$benchmark" "chart-$benchmark.png" + swift run -Xswiftc -Ounchecked -c release swift-collections-benchmark run "results-$benchmark" --tasks-file=$tasks_file --cycles=1 + swift run -Xswiftc -Ounchecked -c release swift-collections-benchmark render "results-$benchmark" "chart-$benchmark.png" # open "chart-$benchmark.png" done From e32d9aa063f891168df616a47ed778e7f7eecff6 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 12 Jun 2021 18:39:22 +0200 Subject: [PATCH 090/176] [Capsule] Check content invariant --- Sources/Capsule/_BitmapIndexedMapNode.swift | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index dea3e5731..b5ae2b6a7 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -56,9 +56,26 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.bitmap2 = nodeMap ^ collMap self.content = content + assert(contentInvariant) assert(count - payloadArity >= 2 * nodeArity) } + var contentInvariant: Bool { + dataSliceInvariant && nodeSliceInvariant && collSliceInvariant + } + + var dataSliceInvariant: Bool { + (0 ..< payloadArity).allSatisfy { index in (content[tupleLength * index], content[tupleLength * index + 1]) is ReturnPayload } + } + + var nodeSliceInvariant: Bool { + (0 ..< bitmapIndexedNodeArity).allSatisfy { index in content[content.count - 1 - index] is ReturnBitmapIndexedNode } + } + + var collSliceInvariant: Bool { + (0 ..< hashCollisionNodeArity).allSatisfy { index in content[content.count - 1 - bitmapIndexedNodeArity - index] is ReturnHashCollisionNode } + } + convenience init() { self.init(0, 0, 0, []) } @@ -419,6 +436,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // no copying if already editable self.content[idx] = newValue + assert(contentInvariant) return self } else { var dst = self.content @@ -443,6 +461,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // no copying if already editable self.content[idx] = newNode + assert(contentInvariant) return self } else { var dst = self.content @@ -459,6 +478,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.dataMap |= bitpos self.content.insert(contentsOf: [key, value], at: idx) + assert(contentInvariant) return self } else { var dst = self.content @@ -475,6 +495,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.dataMap ^= bitpos self.content.removeSubrange(idx..: MapNode where Key: Hashable { self.content.removeSubrange(idxOld..: MapNode where Key: Hashable { self.content.removeSubrange(idxOld..: MapNode where Key: Hashable { self.content.remove(at: idxOld) self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + assert(contentInvariant) return self } else { var dst = self.content @@ -558,6 +582,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.content.remove(at: idxOld) self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + assert(contentInvariant) return self } else { var dst = self.content @@ -579,6 +604,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.content.remove(at: idxOld) self.content.insert(node, at: idxNew) + assert(contentInvariant) return self } else { var dst = self.content @@ -600,6 +626,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.content.remove(at: idxOld) self.content.insert(node, at: idxNew) + assert(contentInvariant) return self } else { var dst = self.content From 83716efa149fd6bc9c1f41283a6f0b1e66d587fc Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 12 Jun 2021 18:40:22 +0200 Subject: [PATCH 091/176] [Capsule] Fix indentation --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index b5ae2b6a7..5ea966ee8 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -315,7 +315,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // modify current node (set replacement node) return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } - } + } return self } From e3cda7682cddf442f53e29b696d10d2692fc0901 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 12 Jun 2021 18:42:23 +0200 Subject: [PATCH 092/176] [Capsule] Reduce to one content slot per key/value tuple --- Sources/Capsule/_BitmapIndexedMapNode.swift | 62 ++++++++++----------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 5ea966ee8..461e3cd7f 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,8 +9,6 @@ // //===----------------------------------------------------------------------===// -fileprivate var tupleLength: Int { 2 } - final class BitmapIndexedMapNode: MapNode where Key: Hashable { typealias Storage = [Any] @@ -65,7 +63,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } var dataSliceInvariant: Bool { - (0 ..< payloadArity).allSatisfy { index in (content[tupleLength * index], content[tupleLength * index + 1]) is ReturnPayload } + (0 ..< payloadArity).allSatisfy { index in content[index] is ReturnPayload } } var nodeSliceInvariant: Bool { @@ -81,12 +79,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { - let elements: Storage = [firstKey, firstValue] + let elements: Storage = [(firstKey, firstValue)] self.init(dataMap, 0, 0, elements) } convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { - let elements: Storage = [firstKey, firstValue, secondKey, secondValue] + let elements: Storage = [(firstKey, firstValue), (secondKey, secondValue)] self.init(dataMap, 0, 0, elements) } @@ -101,7 +99,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { - let elements: Storage = [firstKey, firstValue, firstNode] + let elements: Storage = [(firstKey, firstValue), firstNode] self.init(dataMap, 0, collMap, elements) } @@ -416,10 +414,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { - (content[tupleLength * index + 0] as! Key, - content[tupleLength * index + 1] as! Value) - } + func getPayload(_ index: Int) -> (key: Key, value: Value) { content[index] as! ReturnPayload } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -430,17 +425,18 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { - let idx = tupleLength * dataIndex(bitpos) + 1 + let idx = dataIndex(bitpos) + let (key, _) = self.content[idx] as! ReturnPayload if isStorageKnownUniquelyReferenced { // no copying if already editable - self.content[idx] = newValue + self.content[idx] = (key, newValue) assert(contentInvariant) return self } else { var dst = self.content - dst[idx] = newValue + dst[idx] = (key, newValue) return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) } @@ -472,55 +468,55 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { - let idx = tupleLength * dataIndex(bitpos) + let idx = dataIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap |= bitpos - self.content.insert(contentsOf: [key, value], at: idx) + self.content.insert((key, value), at: idx) assert(contentInvariant) return self } else { var dst = self.content - dst.insert(contentsOf: [key, value], at: idx) + dst.insert((key, value), at: idx) return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) } } func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { - let idx = tupleLength * dataIndex(bitpos) + let idx = dataIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap ^= bitpos - self.content.removeSubrange(idx..) -> BitmapIndexedMapNode { - let idxOld = tupleLength * dataIndex(bitpos) - let idxNew = self.content.count - tupleLength - nodeIndex(bitpos) + let idxOld = dataIndex(bitpos) + let idxNew = self.content.count - 1 /* tupleLength */ - nodeIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap ^= bitpos self.nodeMap |= bitpos - self.content.removeSubrange(idxOld..: MapNode where Key: Hashable { } func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idxOld = tupleLength * dataIndex(bitpos) - let idxNew = self.content.count - tupleLength - bitmapIndexedNodeArity - collIndex(bitpos) + let idxOld = dataIndex(bitpos) + let idxNew = self.content.count - 1 /* tupleLength */ - bitmapIndexedNodeArity - collIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap ^= bitpos self.collMap |= bitpos - self.content.removeSubrange(idxOld..: MapNode where Key: Hashable { func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - nodeIndex(bitpos) - let idxNew = tupleLength * dataIndex(bitpos) + let idxNew = dataIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap |= bitpos self.nodeMap ^= bitpos self.content.remove(at: idxOld) - self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + self.content.insert(tuple, at: idxNew) assert(contentInvariant) return self } else { var dst = self.content dst.remove(at: idxOld) - dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + dst.insert(tuple, at: idxNew) return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) } @@ -573,21 +569,21 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) - let idxNew = tupleLength * dataIndex(bitpos) + let idxNew = dataIndex(bitpos) if isStorageKnownUniquelyReferenced { self.dataMap |= bitpos self.collMap ^= bitpos self.content.remove(at: idxOld) - self.content.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + self.content.insert(tuple, at: idxNew) assert(contentInvariant) return self } else { var dst = self.content dst.remove(at: idxOld) - dst.insert(contentsOf: [tuple.key, tuple.value], at: idxNew) + dst.insert(tuple, at: idxNew) return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) } From b6b88b9fb2f4f82592fe7934c677c2e25aaecb18 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Sat, 12 Jun 2021 18:59:50 +0200 Subject: [PATCH 093/176] [Capsule] Simplify `isKnownUniquelyReferenced` logic --- Sources/Capsule/_BitmapIndexedMapNode.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 461e3cd7f..51fccb966 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -378,14 +378,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } - // TODO replace 'manual' move semantics with pointer arithmetic for obtaining reference - // to pass into `isKnownUniquelyReferenced` private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - var realNode = content[slotIndex] as AnyObject - content[slotIndex] = fakeNode - - let isKnownUniquelyReferenced = isKnownUniquelyReferenced(&realNode) - content[slotIndex] = realNode + let isKnownUniquelyReferenced = content.withUnsafeMutableBufferPointer { buffer in + buffer.baseAddress!.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in + Swift.isKnownUniquelyReferenced(&pointer.pointee) + } + } return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } @@ -634,8 +632,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } -fileprivate let fakeNode = BitmapIndexedMapNode() - extension BitmapIndexedMapNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { lhs === rhs || From 176c26ffed9b912368eb98bf4fe6c9c2ad4962ac Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 21 Jun 2021 10:47:39 +0200 Subject: [PATCH 094/176] [Capsule] Use `rounded(.up)` from standard library over `Foundation.ceil` --- Sources/Capsule/_Common.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index c82222b4d..7da1d8ac7 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -9,8 +9,6 @@ // //===----------------------------------------------------------------------===// -import func Foundation.ceil - func computeHash(_ value: T) -> Int { value.hashValue } @@ -23,7 +21,7 @@ let bitPartitionMask: Int = (1 << bitPartitionSize) - 1 let hashCodeLength: Int = Int.bitWidth -let maxDepth = Int(ceil(Double(hashCodeLength) / Double(bitPartitionSize))) +let maxDepth = Int((Double(hashCodeLength) / Double(bitPartitionSize)).rounded(.up)) func maskFrom(_ hash: Int, _ shift: Int) -> Int { (hash >> shift) & bitPartitionMask From cf34478437633c6d78e7f852ac2774b5049cad4b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 23 Jun 2021 08:59:08 +0200 Subject: [PATCH 095/176] [Capsule] Avoid `copyAndSetBitmapIndexedNode` on equal references when updating --- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 51fccb966..4b3287099 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -185,7 +185,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } + guard effect.modified && subNode !== subNodeNew else { return self } return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } @@ -199,7 +199,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if keyHash == collisionHash { let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } + guard effect.modified && subNode !== subNodeNew else { return self } return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { From 3e559bbe94caf8ae7e8986488d53146fe8f99192 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 14 Jun 2021 13:29:19 +0200 Subject: [PATCH 096/176] [Capsule] Hack minimal example for constructing a trie --- Sources/Capsule/HashMap.swift | 2 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 495 ++++++++++++-------- 2 files changed, 299 insertions(+), 198 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 16a11f207..733f29a53 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -21,7 +21,7 @@ public struct HashMap where Key: Hashable { } public init() { - self.init(BitmapIndexedMapNode(), 0, 0) + self.init(BitmapIndexedMapNode.create(), 0, 0) } public init(_ map: HashMap) { diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 4b3287099..f0bd431e4 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,53 +9,104 @@ // //===----------------------------------------------------------------------===// -final class BitmapIndexedMapNode: MapNode where Key: Hashable { - typealias Storage = [Any] +typealias Header = (bitmap1: Bitmap, bitmap2: Bitmap) +typealias Element = Any - var bitmap1: Bitmap - var bitmap2: Bitmap - var content: Storage +fileprivate let fixedCapacity = 32 + +final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { + // typealias Storage = [Any] + +// var bitmap1: Bitmap +// var bitmap2: Bitmap +// var content: Storage var dataMap: Bitmap { get { - bitmap1 ^ collMap + self.withUnsafeMutablePointerToHeader { + let (bitmap1, bitmap2) = $0.pointee + return bitmap1 ^ (bitmap1 & bitmap2) + } } set(dataMap) { - bitmap1 = dataMap ^ collMap + self.withUnsafeMutablePointerToHeader { + let (_, bitmap2) = $0.pointee + $0.initialize(to: (dataMap ^ collMap, bitmap2)) + } } } var nodeMap: Bitmap { get { - bitmap2 ^ collMap + self.withUnsafeMutablePointerToHeader { + let (bitmap1, bitmap2) = $0.pointee + return bitmap2 ^ (bitmap1 & bitmap2) + } } set(nodeMap) { - bitmap2 = nodeMap ^ collMap + self.withUnsafeMutablePointerToHeader { + let (bitmap1, _) = $0.pointee + $0.initialize(to: (bitmap1, nodeMap ^ collMap)) + } } } var collMap: Bitmap { get { - bitmap1 & bitmap2 + self.withUnsafeMutablePointerToHeader { + let (bitmap1, bitmap2) = $0.pointee + return bitmap1 & bitmap2 + } } set(collMap) { // be careful when referencing `dataMap` or `nodeMap`, since both have a dependency on `collMap` - bitmap1 ^= self.collMap - bitmap2 ^= self.collMap + self.withUnsafeMutablePointerToHeader { + var (bitmap1, bitmap2) = $0.pointee + + bitmap1 ^= self.collMap + bitmap2 ^= self.collMap - bitmap1 ^= collMap - bitmap2 ^= collMap + bitmap1 ^= collMap + bitmap2 ^= collMap + + $0.initialize(to: (bitmap1, bitmap2)) + } } } - init(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: Storage) { - self.bitmap1 = dataMap ^ collMap - self.bitmap2 = nodeMap ^ collMap - self.content = content +// static func create(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: Storage) -> Self { +// self.bitmap1 = dataMap ^ collMap +// self.bitmap2 = nodeMap ^ collMap +// self.content = content +// +//// assert(contentInvariant) +//// assert(count - payloadArity >= 2 * nodeArity) +// } + + + func clone() -> Self { +// Self.create(minimumCapacity: capacity). +// +// +// return Self.create(minimumCapacity: capacity) { newBuffer in +// newBuffer.withUnsafeMutablePointerToElements { newElements -> Void in +// newElements.initialize(from: elements, count: capacity) +// } +// } as! Self + +// return self.withUnsafeMutablePointerToElements { elements in +// return Self.create(minimumCapacity: capacity) { newBuffer in +// newBuffer.withUnsafeMutablePointerToElements { newElements -> Void in +// newElements.initialize(from: elements, count: capacity) +// } +// } as! Self +// } + return self + } - assert(contentInvariant) - assert(count - payloadArity >= 2 * nodeArity) + var content: UnsafeMutablePointer { + self.withUnsafeMutablePointerToElements { return $0 } } var contentInvariant: Bool { @@ -67,40 +118,66 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } var nodeSliceInvariant: Bool { - (0 ..< bitmapIndexedNodeArity).allSatisfy { index in content[content.count - 1 - index] is ReturnBitmapIndexedNode } + (0 ..< bitmapIndexedNodeArity).allSatisfy { index in content[capacity - 1 - index] is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - (0 ..< hashCollisionNodeArity).allSatisfy { index in content[content.count - 1 - bitmapIndexedNodeArity - index] is ReturnHashCollisionNode } + (0 ..< hashCollisionNodeArity).allSatisfy { index in content[capacity - 1 - bitmapIndexedNodeArity - index] is ReturnHashCollisionNode } } - convenience init() { - self.init(0, 0, 0, []) + // func capacity() -> Int { fixedCapacity } + + static func create() -> Self { + Self.create(minimumCapacity: fixedCapacity) { _ in (0, 0) } as! Self } - convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { - let elements: Storage = [(firstKey, firstValue)] - self.init(dataMap, 0, 0, elements) + static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } as! Self + result.withUnsafeMutablePointerToElements { +// $0[0] = (firstKey, firstValue) + + $0.initialize(to: (firstKey, firstValue)) + } + return result } - convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { - let elements: Storage = [(firstKey, firstValue), (secondKey, secondValue)] - self.init(dataMap, 0, 0, elements) + static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } + result.withUnsafeMutablePointerToElements { +// $0[0] = (firstKey, firstValue) +// $0[1] = (secondKey, secondValue) + + $0.initialize(to: (firstKey, firstValue)) + $0.successor().initialize(to: (secondKey, secondValue)) + } + return result as! Self } - convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { - let elements: Storage = [firstNode] - self.init(0, nodeMap, 0, elements) + static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (0, nodeMap) } as! Self + result.withUnsafeMutablePointerToElements { + // $0[result.capacity - 1] = firstNode + + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + } + return result } - convenience init(collMap: Bitmap, firstNode: HashCollisionMapNode) { - let elements: Storage = [firstNode] - self.init(0, 0, collMap, elements) + static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (collMap, collMap) } as! Self + result.withUnsafeMutablePointerToElements { + $0[result.capacity - 1] = firstNode + } + return result } - convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { - let elements: Storage = [(firstKey, firstValue), firstNode] - self.init(dataMap, 0, collMap, elements) + static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap | collMap, collMap) } as! Self + result.withUnsafeMutablePointerToElements { + $0[0] = (firstKey, firstValue) + $0[result.capacity - 1] = firstNode + } + return result } var count: Int { @@ -229,17 +306,17 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // keep remaining pair on root level let newDataMap = (dataMap ^ bitpos) let (remainingKey, remainingValue) = getPayload(1 - index) - return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // create potential new root: will a) become new root, or b) inlined on another level let newDataMap = bitposFrom(maskFrom(keyHash, 0)) let (remainingKey, remainingValue) = getPayload(1 - index) - return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return BitmapIndexedMapNode(collMap: newCollMap, firstNode: getHashCollisionNode(0)) + return Self.create(collMap: newCollMap, firstNode: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -303,7 +380,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (remainingKey, remainingValue) = subNodeNew.getPayload(0) - return BitmapIndexedMapNode(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // inline value return copyAndMigrateFromCollisionNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) @@ -331,15 +408,15 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if mask0 != mask1 { // unique prefixes, payload fits on same level if mask0 < mask1 { - return BitmapIndexedMapNode(dataMap: bitposFrom(mask0) | bitposFrom(mask1), firstKey: key0, firstValue: value0, secondKey: key1, secondValue: value1) + return Self.create(dataMap: bitposFrom(mask0) | bitposFrom(mask1), firstKey: key0, firstValue: value0, secondKey: key1, secondValue: value1) } else { - return BitmapIndexedMapNode(dataMap: bitposFrom(mask1) | bitposFrom(mask0), firstKey: key1, firstValue: value1, secondKey: key0, secondValue: value0) + return Self.create(dataMap: bitposFrom(mask1) | bitposFrom(mask0), firstKey: key1, firstValue: value1, secondKey: key0, secondValue: value0) } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), firstNode: node) + return Self.create(nodeMap: bitposFrom(mask0), firstNode: node) } } @@ -351,12 +428,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - return BitmapIndexedMapNode(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) + return Self.create(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - return BitmapIndexedMapNode(nodeMap: bitposFrom(mask0), firstNode: node) + return Self.create(nodeMap: bitposFrom(mask0), firstNode: node) } } @@ -365,22 +442,22 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - content[content.count - 1 - index] as! BitmapIndexedMapNode + content[capacity - 1 - index] as! BitmapIndexedMapNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = content.count - 1 - index + let slotIndex = capacity - 1 - index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = content.count - 1 - bitmapIndexedNodeArity - index + let slotIndex = capacity - 1 - bitmapIndexedNodeArity - index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = content.withUnsafeMutableBufferPointer { buffer in - buffer.baseAddress!.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in + let isKnownUniquelyReferenced = self.withUnsafeMutablePointerToElements { elements in + elements.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in Swift.isKnownUniquelyReferenced(&pointer.pointee) } } @@ -393,7 +470,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return content[content.count - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode + return content[capacity - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -426,209 +503,233 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let idx = dataIndex(bitpos) let (key, _) = self.content[idx] as! ReturnPayload - if isStorageKnownUniquelyReferenced { +// if isStorageKnownUniquelyReferenced { // no copying if already editable self.content[idx] = (key, newValue) assert(contentInvariant) return self - } else { - var dst = self.content - dst[idx] = (key, newValue) - - return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) - } +// } else { +// var dst = self.content +// dst[idx] = (key, newValue) +// +// return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) +// } } func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idx = self.content.count - 1 - self.nodeIndex(bitpos) + let idx = capacity - 1 - self.nodeIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idx = self.content.count - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) + let idx = capacity - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { - if isStorageKnownUniquelyReferenced { +// if isStorageKnownUniquelyReferenced { // no copying if already editable self.content[idx] = newNode assert(contentInvariant) return self - } else { - var dst = self.content - dst[idx] = newNode - - return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) - } +// } else { +// var dst = self.content +// dst[idx] = newNode +// +// return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) +// } } func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { let idx = dataIndex(bitpos) - if isStorageKnownUniquelyReferenced { +// if isStorageKnownUniquelyReferenced { self.dataMap |= bitpos - self.content.insert((key, value), at: idx) + // self.content.insert((key, value), at: idx) - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.insert((key, value), at: idx) - - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) - } - } + self.withUnsafeMutablePointerToElements { elements in + let elementsAtIdx = elements.advanced(by: idx) - func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { - let idx = dataIndex(bitpos) + // shift to right + elementsAtIdx.successor().moveInitialize(from: elementsAtIdx, count: payloadArity - idx) - if isStorageKnownUniquelyReferenced { - self.dataMap ^= bitpos - self.content.remove(at: idx) + // insert + elementsAtIdx.initialize(to: (key, value)) + } assert(contentInvariant) return self - } else { - var dst = self.content - dst.remove(at: idx) +// } else { +// var dst = self.content +// dst.insert((key, value), at: idx) +// +// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) +// } + } - return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap, dst) - } + func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { +// let idx = dataIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.dataMap ^= bitpos +// self.content.remove(at: idx) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idx) +// +// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap, dst) +// } + preconditionFailure() } func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { let idxOld = dataIndex(bitpos) - let idxNew = self.content.count - 1 /* tupleLength */ - nodeIndex(bitpos) + let idxNew = capacity - 1 /* tupleLength */ - nodeIndex(bitpos) - if isStorageKnownUniquelyReferenced { +// if isStorageKnownUniquelyReferenced { self.dataMap ^= bitpos self.nodeMap |= bitpos - self.content.remove(at: idxOld) - self.content.insert(node, at: idxNew) + self.withUnsafeMutablePointerToElements { elements in + let elementsAtIdx = elements.advanced(by: idxOld) - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) + // shift to left + elementsAtIdx.moveInitialize(from: elementsAtIdx.successor(), count: idxNew - idxOld) - return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap | bitpos, collMap, dst) - } - } - - func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idxOld = dataIndex(bitpos) - let idxNew = self.content.count - 1 /* tupleLength */ - bitmapIndexedNodeArity - collIndex(bitpos) - - if isStorageKnownUniquelyReferenced { - self.dataMap ^= bitpos - self.collMap |= bitpos - - self.content.remove(at: idxOld) - self.content.insert(node, at: idxNew) + // insert + // elementsAtIdx.initialize(to: (key, value)) + elements.advanced(by: idxNew).initialize(to: node) + } assert(contentInvariant) return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(node, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap | bitpos, collMap, dst) +// } + } - return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap | bitpos, dst) - } + func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { +// let idxOld = dataIndex(bitpos) +// let idxNew = capacity - 1 /* tupleLength */ - bitmapIndexedNodeArity - collIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.dataMap ^= bitpos +// self.collMap |= bitpos +// +// self.content.remove(at: idxOld) +// self.content.insert(node, at: idxNew) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(node, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap | bitpos, dst) +// } + preconditionFailure() } func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - nodeIndex(bitpos) - let idxNew = dataIndex(bitpos) - - if isStorageKnownUniquelyReferenced { - self.dataMap |= bitpos - self.nodeMap ^= bitpos - - self.content.remove(at: idxOld) - self.content.insert(tuple, at: idxNew) - - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(tuple, at: idxNew) - - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) - } +// let idxOld = capacity - 1 - nodeIndex(bitpos) +// let idxNew = dataIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.dataMap |= bitpos +// self.nodeMap ^= bitpos +// +// self.content.remove(at: idxOld) +// self.content.insert(tuple, at: idxNew) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(tuple, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) +// } + preconditionFailure() } func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) - let idxNew = dataIndex(bitpos) - - if isStorageKnownUniquelyReferenced { - self.dataMap |= bitpos - self.collMap ^= bitpos - - self.content.remove(at: idxOld) - self.content.insert(tuple, at: idxNew) - - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(tuple, at: idxNew) - - return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) - } +// let idxOld = capacity - 1 - bitmapIndexedNodeArity - collIndex(bitpos) +// let idxNew = dataIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.dataMap |= bitpos +// self.collMap ^= bitpos +// +// self.content.remove(at: idxOld) +// self.content.insert(tuple, at: idxNew) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(tuple, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) +// } + preconditionFailure() } func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - bitmapIndexedNodeArity - collIndex(bitpos) - let idxNew = self.content.count - 1 - nodeIndex(bitpos) - - if isStorageKnownUniquelyReferenced { - self.nodeMap |= bitpos - self.collMap ^= bitpos - - self.content.remove(at: idxOld) - self.content.insert(node, at: idxNew) - - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) - - return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) - } +// let idxOld = capacity - 1 - bitmapIndexedNodeArity - collIndex(bitpos) +// let idxNew = capacity - 1 - nodeIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.nodeMap |= bitpos +// self.collMap ^= bitpos +// +// self.content.remove(at: idxOld) +// self.content.insert(node, at: idxNew) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(node, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) +// } + preconditionFailure() } func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idxOld = self.content.count - 1 - nodeIndex(bitpos) - let idxNew = self.content.count - 1 - (bitmapIndexedNodeArity - 1) - collIndex(bitpos) - - if isStorageKnownUniquelyReferenced { - self.nodeMap ^= bitpos - self.collMap |= bitpos - - self.content.remove(at: idxOld) - self.content.insert(node, at: idxNew) - - assert(contentInvariant) - return self - } else { - var dst = self.content - dst.remove(at: idxOld) - dst.insert(node, at: idxNew) - - return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) - } +// let idxOld = capacity - 1 - nodeIndex(bitpos) +// let idxNew = capacity - 1 - (bitmapIndexedNodeArity - 1) - collIndex(bitpos) +// +// if isStorageKnownUniquelyReferenced { +// self.nodeMap ^= bitpos +// self.collMap |= bitpos +// +// self.content.remove(at: idxOld) +// self.content.insert(node, at: idxNew) +// +// assert(contentInvariant) +// return self +// } else { +// var dst = self.content +// dst.remove(at: idxOld) +// dst.insert(node, at: idxNew) +// +// return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) +// } + preconditionFailure() } } From cedc74bea678e1b5c9c2831cc20798fe2145a651 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 14 Jun 2021 14:28:37 +0200 Subject: [PATCH 097/176] [Capsule] Hack minimal (continued) --- Sources/Capsule/_BitmapIndexedMapNode.swift | 72 +++++++++++---------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index f0bd431e4..9d498f076 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,14 +12,9 @@ typealias Header = (bitmap1: Bitmap, bitmap2: Bitmap) typealias Element = Any -fileprivate let fixedCapacity = 32 +fileprivate let fixedCapacity = Bitmap.bitWidth final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { - // typealias Storage = [Any] - -// var bitmap1: Bitmap -// var bitmap2: Bitmap -// var content: Storage var dataMap: Bitmap { get { @@ -132,21 +127,16 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } as! Self + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } result.withUnsafeMutablePointerToElements { -// $0[0] = (firstKey, firstValue) - $0.initialize(to: (firstKey, firstValue)) } - return result + return result as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } result.withUnsafeMutablePointerToElements { -// $0[0] = (firstKey, firstValue) -// $0[1] = (secondKey, secondValue) - $0.initialize(to: (firstKey, firstValue)) $0.successor().initialize(to: (secondKey, secondValue)) } @@ -154,30 +144,28 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (0, nodeMap) } as! Self + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (0, nodeMap) } result.withUnsafeMutablePointerToElements { - // $0[result.capacity - 1] = firstNode - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (collMap, collMap) } as! Self + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (collMap, collMap) } result.withUnsafeMutablePointerToElements { - $0[result.capacity - 1] = firstNode + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap | collMap, collMap) } as! Self + let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap | collMap, collMap) } result.withUnsafeMutablePointerToElements { - $0[0] = (firstKey, firstValue) - $0[result.capacity - 1] = firstNode + $0.initialize(to: (firstKey, firstValue)) + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } var count: Int { @@ -234,6 +222,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) + let dataMap = self.dataMap guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, value0) = self.getPayload(index) @@ -256,6 +245,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } } + let nodeMap = self.nodeMap guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) @@ -267,6 +257,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } + let collMap = self.collMap guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) @@ -543,20 +534,24 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { - let idx = dataIndex(bitpos) - // if isStorageKnownUniquelyReferenced { - self.dataMap |= bitpos - // self.content.insert((key, value), at: idx) + self.withUnsafeMutablePointers { header, elements in + let (bitmap1, bitmap2) = header.pointee + let dataMap = bitmap1 ^ (bitmap1 & bitmap2) + + let idx = indexFrom(dataMap, bitpos) + let cnt = dataMap.nonzeroBitCount - self.withUnsafeMutablePointerToElements { elements in let elementsAtIdx = elements.advanced(by: idx) // shift to right - elementsAtIdx.successor().moveInitialize(from: elementsAtIdx, count: payloadArity - idx) + elementsAtIdx.successor().moveInitialize(from: elementsAtIdx, count: cnt - idx) // insert elementsAtIdx.initialize(to: (key, value)) + + // update metadata + header.initialize(to: (bitmap1 | bitpos, bitmap2)) } assert(contentInvariant) @@ -588,14 +583,18 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idxOld = dataIndex(bitpos) - let idxNew = capacity - 1 /* tupleLength */ - nodeIndex(bitpos) + // let idxOld = dataIndex(bitpos) + // let idxNew = capacity - 1 /* tupleLength */ - nodeIndex(bitpos) // if isStorageKnownUniquelyReferenced { - self.dataMap ^= bitpos - self.nodeMap |= bitpos + self.withUnsafeMutablePointers { header, elements in + let (bitmap1, bitmap2) = header.pointee + let dataMap = bitmap1 ^ (bitmap1 & bitmap2) + let nodeMap = bitmap2 ^ (bitmap1 & bitmap2) + + let idxOld = indexFrom(dataMap, bitpos) + let idxNew = capacity - 1 /* tupleLength */ - indexFrom(nodeMap, bitpos) - self.withUnsafeMutablePointerToElements { elements in let elementsAtIdx = elements.advanced(by: idxOld) // shift to left @@ -604,6 +603,9 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma // insert // elementsAtIdx.initialize(to: (key, value)) elements.advanced(by: idxNew).initialize(to: node) + + // update metadata + header.initialize(to: (bitmap1 ^ bitpos, bitmap2 | bitpos)) } assert(contentInvariant) From 83e40fcfd965705ba35f5484946a4440a5bc9bf3 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 10:22:32 +0200 Subject: [PATCH 098/176] [Capsule] Finalize `ManagedBuffer` rework Does exponentially grow buffer if it needs resizing, but does not shrink it on deletion yet. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 567 +++++++++++--------- 1 file changed, 308 insertions(+), 259 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 9d498f076..54f263d56 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -12,98 +12,89 @@ typealias Header = (bitmap1: Bitmap, bitmap2: Bitmap) typealias Element = Any -fileprivate let fixedCapacity = Bitmap.bitWidth +fileprivate let initialMinimumCapacity = 4 final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { + deinit { + self.withUnsafeMutablePointerToElements { elements in + let range = elements ..< elements.advanced(by: capacity) + + let dataRange = range.prefix(payloadArity) + dataRange.startIndex.deinitialize(count: dataRange.count) + + let nodeRange = range.suffix(nodeArity) + nodeRange.startIndex.deinitialize(count: nodeRange.count) + } + } + var dataMap: Bitmap { get { - self.withUnsafeMutablePointerToHeader { - let (bitmap1, bitmap2) = $0.pointee - return bitmap1 ^ (bitmap1 & bitmap2) - } + header.bitmap1 ^ (header.bitmap1 & header.bitmap2) } set(dataMap) { - self.withUnsafeMutablePointerToHeader { - let (_, bitmap2) = $0.pointee - $0.initialize(to: (dataMap ^ collMap, bitmap2)) - } + header.bitmap1 = dataMap ^ collMap } } var nodeMap: Bitmap { get { - self.withUnsafeMutablePointerToHeader { - let (bitmap1, bitmap2) = $0.pointee - return bitmap2 ^ (bitmap1 & bitmap2) - } + header.bitmap2 ^ (header.bitmap1 & header.bitmap2) } set(nodeMap) { - self.withUnsafeMutablePointerToHeader { - let (bitmap1, _) = $0.pointee - $0.initialize(to: (bitmap1, nodeMap ^ collMap)) - } + header.bitmap2 = nodeMap ^ collMap } } var collMap: Bitmap { get { - self.withUnsafeMutablePointerToHeader { - let (bitmap1, bitmap2) = $0.pointee - return bitmap1 & bitmap2 - } + header.bitmap1 & header.bitmap2 } set(collMap) { // be careful when referencing `dataMap` or `nodeMap`, since both have a dependency on `collMap` - self.withUnsafeMutablePointerToHeader { - var (bitmap1, bitmap2) = $0.pointee - - bitmap1 ^= self.collMap - bitmap2 ^= self.collMap + header.bitmap1 ^= self.collMap + header.bitmap2 ^= self.collMap - bitmap1 ^= collMap - bitmap2 ^= collMap - - $0.initialize(to: (bitmap1, bitmap2)) - } + header.bitmap1 ^= collMap + header.bitmap2 ^= collMap } } -// static func create(_ dataMap: Bitmap, _ nodeMap: Bitmap, _ collMap: Bitmap, _ content: Storage) -> Self { -// self.bitmap1 = dataMap ^ collMap -// self.bitmap2 = nodeMap ^ collMap -// self.content = content -// -//// assert(contentInvariant) -//// assert(count - payloadArity >= 2 * nodeArity) -// } + func copy(withCapacityFactor factor: Int = 1) -> Self { + let src = self + let dst = Self.create(minimumCapacity: capacity * factor) { _ in header } as! Self + src.withUnsafeMutablePointerToElements { srcElements in + dst.withUnsafeMutablePointerToElements { dstElements in + dstElements.initialize(from: srcElements, count: src.payloadArity) + dstElements.advanced(by: dst.capacity - dst.nodeArity).initialize(from: srcElements.advanced(by: src.capacity - src.nodeArity), count: src.nodeArity) + } + } - func clone() -> Self { -// Self.create(minimumCapacity: capacity). -// -// -// return Self.create(minimumCapacity: capacity) { newBuffer in -// newBuffer.withUnsafeMutablePointerToElements { newElements -> Void in -// newElements.initialize(from: elements, count: capacity) -// } -// } as! Self - -// return self.withUnsafeMutablePointerToElements { elements in -// return Self.create(minimumCapacity: capacity) { newBuffer in -// newBuffer.withUnsafeMutablePointerToElements { newElements -> Void in -// newElements.initialize(from: elements, count: capacity) -// } -// } as! Self -// } - return self + return dst } var content: UnsafeMutablePointer { self.withUnsafeMutablePointerToElements { return $0 } } + var invariant: Bool { + guard contentInvariant else { + return false + } + + guard recursiveCount - payloadArity >= 2 * nodeArity else { + return false + } + + guard count <= capacity else { + return false + } + + return true + } + var contentInvariant: Bool { dataSliceInvariant && nodeSliceInvariant && collSliceInvariant } @@ -120,14 +111,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma (0 ..< hashCollisionNodeArity).allSatisfy { index in content[capacity - 1 - bitmapIndexedNodeArity - index] is ReturnHashCollisionNode } } - // func capacity() -> Int { fixedCapacity } - static func create() -> Self { - Self.create(minimumCapacity: fixedCapacity) { _ in (0, 0) } as! Self + Self.create(minimumCapacity: initialMinimumCapacity) { _ in (0, 0) } as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap, 0) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) } @@ -135,7 +124,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap, 0) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap, 0) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) $0.successor().initialize(to: (secondKey, secondValue)) @@ -144,7 +133,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (0, nodeMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (0, nodeMap) } result.withUnsafeMutablePointerToElements { $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } @@ -152,7 +141,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (collMap, collMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (collMap, collMap) } result.withUnsafeMutablePointerToElements { $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } @@ -160,7 +149,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedCapacity) { _ in (dataMap | collMap, collMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap | collMap, collMap) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) $0.advanced(by: result.capacity - 1).initialize(to: firstNode) @@ -168,7 +157,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return result as! Self } - var count: Int { + var recursiveCount: Int { self.reduce(0, { count, _ in count + 1 }) } @@ -222,7 +211,6 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) - let dataMap = self.dataMap guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, value0) = self.getPayload(index) @@ -245,7 +233,6 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } } - let nodeMap = self.nodeMap guard (nodeMap & bitpos) == 0 else { let index = indexFrom(nodeMap, mask, bitpos) let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) @@ -257,7 +244,6 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } - let collMap = self.collMap guard (collMap & bitpos) == 0 else { let index = indexFrom(collMap, mask, bitpos) let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) @@ -490,22 +476,26 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } + /// The number of (non-contiguous) occupied buffer cells. + final var count: Int { (header.bitmap1 | header.bitmap2).nonzeroBitCount } + func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + let idx = dataIndex(bitpos) - let (key, _) = self.content[idx] as! ReturnPayload + let (key, _) = dst.content[idx] as! ReturnPayload -// if isStorageKnownUniquelyReferenced { - // no copying if already editable - self.content[idx] = (key, newValue) + dst.content[idx] = (key, newValue) - assert(contentInvariant) - return self -// } else { -// var dst = self.content -// dst[idx] = (key, newValue) -// -// return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) -// } + assert(dst.contentInvariant) + return dst } func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { @@ -519,219 +509,278 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { -// if isStorageKnownUniquelyReferenced { - // no copying if already editable - self.content[idx] = newNode - - assert(contentInvariant) - return self -// } else { -// var dst = self.content -// dst[idx] = newNode -// -// return BitmapIndexedMapNode(dataMap, nodeMap, collMap, dst) -// } + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.content[idx] = newNode + + assert(dst.contentInvariant) + return dst } - func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { -// if isStorageKnownUniquelyReferenced { - self.withUnsafeMutablePointers { header, elements in - let (bitmap1, bitmap2) = header.pointee - let dataMap = bitmap1 ^ (bitmap1 & bitmap2) + static func rangeInsert(_ element: Any, at index: Int, intoRange range: Range>) { + let seq = range.dropFirst(index) - let idx = indexFrom(dataMap, bitpos) - let cnt = dataMap.nonzeroBitCount + let src = seq.startIndex + let dst = src.successor() - let elementsAtIdx = elements.advanced(by: idx) + dst.moveInitialize(from: src, count: seq.count) - // shift to right - elementsAtIdx.successor().moveInitialize(from: elementsAtIdx, count: cnt - idx) + src.initialize(to: element) + } - // insert - elementsAtIdx.initialize(to: (key, value)) + // `index` is the logical index starting at the rear, indexing to the left + static func rangeInsertReversed(_ element: Any, at index: Int, intoRange range: Range>) { + let seq = range.dropLast(index) - // update metadata - header.initialize(to: (bitmap1 | bitpos, bitmap2)) - } + let src = seq.startIndex + let dst = src.predecessor() - assert(contentInvariant) - return self -// } else { -// var dst = self.content -// dst.insert((key, value), at: idx) -// -// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap, dst) -// } + dst.moveInitialize(from: src, count: seq.count) + + // requires call to predecessor on "past the end" position + seq.endIndex.predecessor().initialize(to: element) + } + + static func rangeRemove(at index: Int, fromRange range: Range>) { + let seq = range.dropFirst(index + 1) + + let src = seq.startIndex + let dst = src.predecessor() + + dst.deinitialize(count: 1) + dst.moveInitialize(from: src, count: seq.count) + } + + // `index` is the logical index starting at the rear, indexing to the left + static func rangeRemoveReversed(at index: Int, fromRange range: Range>) { + let seq = range.dropLast(index + 1) + + let src = seq.startIndex + let dst = src.successor() + + seq.endIndex.deinitialize(count: 1) + dst.moveInitialize(from: src, count: seq.count) + } + + func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced && count < capacity { + dst = src + } else { + dst = src.copy(withCapacityFactor: count < capacity ? 1 : 2) + } + + dst.withUnsafeMutablePointerToElements { elements in + let idx = indexFrom(dst.dataMap, bitpos) + let cnt = dst.dataMap.nonzeroBitCount + + Self.rangeInsert((key, value), at: idx, intoRange: elements ..< elements.advanced(by: cnt)) + } + // update metadata: `dataMap | bitpos, nodeMap, collMap` + dst.header.bitmap1 |= bitpos + + assert(dst.invariant) + return dst } func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { -// let idx = dataIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.dataMap ^= bitpos -// self.content.remove(at: idx) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idx) -// -// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let dataIdx = indexFrom(dst.dataMap, bitpos) + let dataCnt = dst.dataMap.nonzeroBitCount + + Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) + } + // update metadata: `dataMap ^ bitpos, nodeMap, collMap` + dst.header.bitmap1 ^= bitpos + + assert(dst.invariant) + return dst } func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - // let idxOld = dataIndex(bitpos) - // let idxNew = capacity - 1 /* tupleLength */ - nodeIndex(bitpos) - -// if isStorageKnownUniquelyReferenced { - self.withUnsafeMutablePointers { header, elements in - let (bitmap1, bitmap2) = header.pointee - let dataMap = bitmap1 ^ (bitmap1 & bitmap2) - let nodeMap = bitmap2 ^ (bitmap1 & bitmap2) + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode - let idxOld = indexFrom(dataMap, bitpos) - let idxNew = capacity - 1 /* tupleLength */ - indexFrom(nodeMap, bitpos) + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } - let elementsAtIdx = elements.advanced(by: idxOld) + dst.withUnsafeMutablePointerToElements { elements in + let dataIdx = indexFrom(dst.dataMap, bitpos) + let dataCnt = dst.dataMap.nonzeroBitCount - // shift to left - elementsAtIdx.moveInitialize(from: elementsAtIdx.successor(), count: idxNew - idxOld) + Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) - // insert - // elementsAtIdx.initialize(to: (key, value)) - elements.advanced(by: idxNew).initialize(to: node) + let trieIdx = indexFrom(dst.nodeMap, bitpos) // only nodeMap + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // but trieCnt - // update metadata - header.initialize(to: (bitmap1 ^ bitpos, bitmap2 | bitpos)) - } + Self.rangeInsertReversed(node, at: trieIdx, intoRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + } + // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` + dst.header.bitmap1 ^= bitpos + dst.header.bitmap2 |= bitpos - assert(contentInvariant) - return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(node, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap | bitpos, collMap, dst) -// } + assert(dst.invariant) + return dst } func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { -// let idxOld = dataIndex(bitpos) -// let idxNew = capacity - 1 /* tupleLength */ - bitmapIndexedNodeArity - collIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.dataMap ^= bitpos -// self.collMap |= bitpos -// -// self.content.remove(at: idxOld) -// self.content.insert(node, at: idxNew) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(node, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap ^ bitpos, nodeMap, collMap | bitpos, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let dataIdx = indexFrom(dst.dataMap, bitpos) + let dataCnt = dst.dataMap.nonzeroBitCount + + Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) + + let trieIdx = dst.nodeMap.nonzeroBitCount + indexFrom(dst.collMap, bitpos) // offset + collMap + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // trieCnt + + Self.rangeInsertReversed(node, at: trieIdx, intoRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + } + // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` + dst.header.bitmap2 |= bitpos + + assert(dst.invariant) + return dst } func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { -// let idxOld = capacity - 1 - nodeIndex(bitpos) -// let idxNew = dataIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.dataMap |= bitpos -// self.nodeMap ^= bitpos -// -// self.content.remove(at: idxOld) -// self.content.insert(tuple, at: idxNew) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(tuple, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap ^ bitpos, collMap, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let trieIdx = indexFrom(dst.nodeMap, bitpos) // only nodeMap + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // but trieCnt + + Self.rangeRemoveReversed(at: trieIdx, fromRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + + let dataIdx = indexFrom(dst.dataMap, bitpos) + let dataCnt = dst.dataMap.nonzeroBitCount + + Self.rangeInsert(tuple, at: dataIdx, intoRange: elements ..< elements.advanced(by: dataCnt)) + } + // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` + dst.header.bitmap1 |= bitpos + dst.header.bitmap2 ^= bitpos + + assert(dst.invariant) + return dst } func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { -// let idxOld = capacity - 1 - bitmapIndexedNodeArity - collIndex(bitpos) -// let idxNew = dataIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.dataMap |= bitpos -// self.collMap ^= bitpos -// -// self.content.remove(at: idxOld) -// self.content.insert(tuple, at: idxNew) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(tuple, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap | bitpos, nodeMap, collMap ^ bitpos, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let trieIdx = dst.nodeMap.nonzeroBitCount + indexFrom(dst.collMap, bitpos) // offset + collMap + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // trieCnt + + Self.rangeRemoveReversed(at: trieIdx, fromRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + + let dataIdx = indexFrom(dst.dataMap, bitpos) + let dataCnt = dst.dataMap.nonzeroBitCount + + Self.rangeInsert(tuple, at: dataIdx, intoRange: elements ..< elements.advanced(by: dataCnt)) + } + // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` + dst.header.bitmap2 ^= bitpos + + assert(dst.invariant) + return dst } func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { -// let idxOld = capacity - 1 - bitmapIndexedNodeArity - collIndex(bitpos) -// let idxNew = capacity - 1 - nodeIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.nodeMap |= bitpos -// self.collMap ^= bitpos -// -// self.content.remove(at: idxOld) -// self.content.insert(node, at: idxNew) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(node, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap, nodeMap | bitpos, collMap ^ bitpos, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let trieIdxOld = dst.nodeMap.nonzeroBitCount + dst.collIndex(bitpos) + let trieIdxNew = dst.nodeIndex(bitpos) + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount + + let trieRange = (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt) + + Self.rangeRemoveReversed(at: trieIdxOld, fromRange: trieRange) + Self.rangeInsertReversed(node, at: trieIdxNew, intoRange: trieRange) + } + // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` + dst.header.bitmap1 ^= bitpos + + assert(dst.invariant) + return dst } func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { -// let idxOld = capacity - 1 - nodeIndex(bitpos) -// let idxNew = capacity - 1 - (bitmapIndexedNodeArity - 1) - collIndex(bitpos) -// -// if isStorageKnownUniquelyReferenced { -// self.nodeMap ^= bitpos -// self.collMap |= bitpos -// -// self.content.remove(at: idxOld) -// self.content.insert(node, at: idxNew) -// -// assert(contentInvariant) -// return self -// } else { -// var dst = self.content -// dst.remove(at: idxOld) -// dst.insert(node, at: idxNew) -// -// return BitmapIndexedMapNode(dataMap, nodeMap ^ bitpos, collMap | bitpos, dst) -// } - preconditionFailure() + let src: ReturnBitmapIndexedNode = self + let dst: ReturnBitmapIndexedNode + + if isStorageKnownUniquelyReferenced { + dst = src + } else { + dst = src.copy() + } + + dst.withUnsafeMutablePointerToElements { elements in + let trieIdxOld = dst.nodeIndex(bitpos) + let trieIdxNew = dst.nodeMap.nonzeroBitCount - 1 + dst.collIndex(bitpos) + let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount + + let trieRange = (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt) + + Self.rangeRemoveReversed(at: trieIdxOld, fromRange: trieRange) + Self.rangeInsertReversed(node, at: trieIdxNew, intoRange: trieRange) + } + // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` + dst.header.bitmap1 |= bitpos + + assert(dst.invariant) + return dst } } From b065b318cc5f3d68583044e2bf16be186e537d74 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 10:27:20 +0200 Subject: [PATCH 099/176] [Capsule] Add header helpers for imploding/exploding --- Sources/Capsule/_BitmapIndexedMapNode.swift | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 54f263d56..ddae0ecdd 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -821,3 +821,29 @@ extension BitmapIndexedMapNode: Sequence { return MapKeyValueTupleIterator(rootNode: self) } } + +@inline(__always) +fileprivate func explode(header: Header) -> (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) { + let collMap = header.bitmap1 & header.bitmap2 + let dataMap = header.bitmap1 ^ collMap + let nodeMap = header.bitmap2 ^ collMap + + assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) + + return (dataMap, nodeMap, collMap) +} + +@inline(__always) +fileprivate func implode(dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) -> Header { + assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) + + let bitmap1 = dataMap ^ collMap + let bitmap2 = nodeMap ^ collMap + + return (bitmap1, bitmap2) +} + +@inline(__always) +fileprivate func map(header: (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap), _ transform: (Bitmap) -> T) -> (T, T, T) { + return (transform(header.dataMap), transform(header.nodeMap), transform(header.collMap)) +} From 81a9c0ef1311b487469fe375a91e263fab4cd770 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 11:34:39 +0200 Subject: [PATCH 100/176] [Capsule] Introduce `withUnsafeMutablePointerRanges` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 41 +++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index ddae0ecdd..c6c437d09 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -17,14 +17,9 @@ fileprivate let initialMinimumCapacity = 4 final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { deinit { - self.withUnsafeMutablePointerToElements { elements in - let range = elements ..< elements.advanced(by: capacity) - - let dataRange = range.prefix(payloadArity) + self.withUnsafeMutablePointerRanges { dataRange, trieRange in dataRange.startIndex.deinitialize(count: dataRange.count) - - let nodeRange = range.suffix(nodeArity) - nodeRange.startIndex.deinitialize(count: nodeRange.count) + trieRange.startIndex.deinitialize(count: trieRange.count) } } @@ -61,14 +56,28 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } } + @inline(__always) + func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { + self.withUnsafeMutablePointerToElements { elements in + let(dataMap, trieMap) = explode(header: header) + + let range = elements ..< elements.advanced(by: capacity) + + let dataRange = range.prefix(dataMap.nonzeroBitCount) + let trieRange = range.suffix(trieMap.nonzeroBitCount) + + return transform(dataRange, trieRange) + } + } + func copy(withCapacityFactor factor: Int = 1) -> Self { let src = self let dst = Self.create(minimumCapacity: capacity * factor) { _ in header } as! Self - src.withUnsafeMutablePointerToElements { srcElements in - dst.withUnsafeMutablePointerToElements { dstElements in - dstElements.initialize(from: srcElements, count: src.payloadArity) - dstElements.advanced(by: dst.capacity - dst.nodeArity).initialize(from: srcElements.advanced(by: src.capacity - src.nodeArity), count: src.nodeArity) + src.withUnsafeMutablePointerRanges { srcDataRange, srcTrieRange in + dst.withUnsafeMutablePointerRanges { dstDataRange, dstTrieRange in + dstDataRange.startIndex.initialize(from: srcDataRange.startIndex, count: srcDataRange.count) + dstTrieRange.startIndex.initialize(from: srcTrieRange.startIndex, count: srcTrieRange.count) } } @@ -833,6 +842,16 @@ fileprivate func explode(header: Header) -> (dataMap: Bitmap, nodeMap: Bitmap, c return (dataMap, nodeMap, collMap) } +@inline(__always) +fileprivate func explode(header: Header) -> (dataMap: Bitmap, trieMap: Bitmap) { + let dataMap = header.bitmap1 & ~header.bitmap2 + let trieMap = header.bitmap2 + + assert((dataMap | trieMap).nonzeroBitCount == dataMap.nonzeroBitCount + trieMap.nonzeroBitCount) + + return (dataMap, trieMap) +} + @inline(__always) fileprivate func implode(dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) -> Header { assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) From d49ec64899f778b8f159de4e598095f328be203b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 12:22:57 +0200 Subject: [PATCH 101/176] [Capsule] Simplify bitmap processing --- Sources/Capsule/_BitmapIndexedMapNode.swift | 34 +++++---------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index c6c437d09..ec9bf7694 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -23,37 +23,19 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } } + @inline(__always) var dataMap: Bitmap { - get { - header.bitmap1 ^ (header.bitmap1 & header.bitmap2) - } - set(dataMap) { - header.bitmap1 = dataMap ^ collMap - } + header.bitmap1 & ~header.bitmap2 } + @inline(__always) var nodeMap: Bitmap { - get { - header.bitmap2 ^ (header.bitmap1 & header.bitmap2) - } - set(nodeMap) { - header.bitmap2 = nodeMap ^ collMap - } + header.bitmap2 & ~header.bitmap1 } + @inline(__always) var collMap: Bitmap { - get { - header.bitmap1 & header.bitmap2 - } - set(collMap) { - // be careful when referencing `dataMap` or `nodeMap`, since both have a dependency on `collMap` - - header.bitmap1 ^= self.collMap - header.bitmap2 ^= self.collMap - - header.bitmap1 ^= collMap - header.bitmap2 ^= collMap - } + header.bitmap1 & header.bitmap2 } @inline(__always) @@ -834,8 +816,8 @@ extension BitmapIndexedMapNode: Sequence { @inline(__always) fileprivate func explode(header: Header) -> (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) { let collMap = header.bitmap1 & header.bitmap2 - let dataMap = header.bitmap1 ^ collMap - let nodeMap = header.bitmap2 ^ collMap + let dataMap = header.bitmap1 & ~header.bitmap2 + let nodeMap = header.bitmap2 & ~header.bitmap1 assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) From db398e85fa0a97ee6022859c4dbd1d4627bc6ac9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 14:01:05 +0200 Subject: [PATCH 102/176] [Capsule] Convert `Header` from tuple to struct --- Sources/Capsule/_BitmapIndexedMapNode.swift | 100 ++++++++++++-------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index ec9bf7694..da3df83fe 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -typealias Header = (bitmap1: Bitmap, bitmap2: Bitmap) typealias Element = Any fileprivate let initialMinimumCapacity = 4 @@ -25,28 +24,28 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma @inline(__always) var dataMap: Bitmap { - header.bitmap1 & ~header.bitmap2 + header.dataMap } @inline(__always) var nodeMap: Bitmap { - header.bitmap2 & ~header.bitmap1 + header.nodeMap } @inline(__always) var collMap: Bitmap { - header.bitmap1 & header.bitmap2 + header.collMap } @inline(__always) func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { self.withUnsafeMutablePointerToElements { elements in - let(dataMap, trieMap) = explode(header: header) + let(dataCnt, trieCnt) = header.explodedMap { bitmap in bitmap.nonzeroBitCount } let range = elements ..< elements.advanced(by: capacity) - let dataRange = range.prefix(dataMap.nonzeroBitCount) - let trieRange = range.suffix(trieMap.nonzeroBitCount) + let dataRange = range.prefix(dataCnt) + let trieRange = range.suffix(trieCnt) return transform(dataRange, trieRange) } @@ -103,11 +102,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create() -> Self { - Self.create(minimumCapacity: initialMinimumCapacity) { _ in (0, 0) } as! Self + Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap, 0) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) } @@ -115,7 +114,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap, 0) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) $0.successor().initialize(to: (secondKey, secondValue)) @@ -124,7 +123,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (0, nodeMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } result.withUnsafeMutablePointerToElements { $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } @@ -132,7 +131,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (collMap, collMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } result.withUnsafeMutablePointerToElements { $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } @@ -140,7 +139,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in (dataMap | collMap, collMap) } + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } result.withUnsafeMutablePointerToElements { $0.initialize(to: (firstKey, firstValue)) $0.advanced(by: result.capacity - 1).initialize(to: firstNode) @@ -813,38 +812,65 @@ extension BitmapIndexedMapNode: Sequence { } } -@inline(__always) -fileprivate func explode(header: Header) -> (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) { - let collMap = header.bitmap1 & header.bitmap2 - let dataMap = header.bitmap1 & ~header.bitmap2 - let nodeMap = header.bitmap2 & ~header.bitmap1 +struct Header { + var bitmap1: Bitmap + var bitmap2: Bitmap - assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) + @inline(__always) + var dataMap: Bitmap { + bitmap1 & ~bitmap2 + } + + @inline(__always) + var nodeMap: Bitmap { + bitmap2 & ~bitmap1 + } - return (dataMap, nodeMap, collMap) + @inline(__always) + var collMap: Bitmap { + bitmap1 & bitmap2 + } + + @inline(__always) + var trieMap: Bitmap { + bitmap2 + } } -@inline(__always) -fileprivate func explode(header: Header) -> (dataMap: Bitmap, trieMap: Bitmap) { - let dataMap = header.bitmap1 & ~header.bitmap2 - let trieMap = header.bitmap2 +extension Header { + @inline(__always) + func exploded() -> (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) { + assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) - assert((dataMap | trieMap).nonzeroBitCount == dataMap.nonzeroBitCount + trieMap.nonzeroBitCount) + return (dataMap, nodeMap, collMap) + } - return (dataMap, trieMap) -} + @inline(__always) + func exploded() -> (dataMap: Bitmap, trieMap: Bitmap) { + assert((dataMap | trieMap).nonzeroBitCount == dataMap.nonzeroBitCount + trieMap.nonzeroBitCount) -@inline(__always) -fileprivate func implode(dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) -> Header { - assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) + return (dataMap, trieMap) + } - let bitmap1 = dataMap ^ collMap - let bitmap2 = nodeMap ^ collMap + @inline(__always) + fileprivate func imploded(dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) -> Self { + assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) - return (bitmap1, bitmap2) -} + return Self(bitmap1: dataMap ^ collMap, bitmap2: nodeMap ^ collMap) + } + + @inline(__always) + func map(_ transform: (Self) -> T) -> T { + return transform(self) + } -@inline(__always) -fileprivate func map(header: (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap), _ transform: (Bitmap) -> T) -> (T, T, T) { - return (transform(header.dataMap), transform(header.nodeMap), transform(header.collMap)) + @inline(__always) + func explodedMap(_ transform: (Bitmap) -> T) -> (T, T) { + return (transform(dataMap), transform(trieMap)) + } + + @inline(__always) + func explodedMap(_ transform: (Bitmap) -> T) -> (T, T, T) { + return (transform(dataMap), transform(nodeMap), transform(collMap)) + } } From e66a39c100e9444c8506254d7adbc7cad1256f46 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 17 Jun 2021 15:07:03 +0200 Subject: [PATCH 103/176] [Capsule] Use `withUnsafeMutablePointerRanges` over alternative APIs --- Sources/Capsule/_BitmapIndexedMapNode.swift | 106 ++++++++------------ 1 file changed, 44 insertions(+), 62 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index da3df83fe..efd6fd14c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -569,12 +569,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy(withCapacityFactor: count < capacity ? 1 : 2) } - dst.withUnsafeMutablePointerToElements { elements in - let idx = indexFrom(dst.dataMap, bitpos) - let cnt = dst.dataMap.nonzeroBitCount - - Self.rangeInsert((key, value), at: idx, intoRange: elements ..< elements.advanced(by: cnt)) + dst.withUnsafeMutablePointerRanges { dataRange, _ in + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeInsert((key, value), at: dataIdx, intoRange: dataRange) } + // update metadata: `dataMap | bitpos, nodeMap, collMap` dst.header.bitmap1 |= bitpos @@ -592,12 +591,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let dataIdx = indexFrom(dst.dataMap, bitpos) - let dataCnt = dst.dataMap.nonzeroBitCount - - Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) + dst.withUnsafeMutablePointerRanges { dataRange, _ in + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeRemove(at: dataIdx, fromRange: dataRange) } + // update metadata: `dataMap ^ bitpos, nodeMap, collMap` dst.header.bitmap1 ^= bitpos @@ -615,17 +613,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let dataIdx = indexFrom(dst.dataMap, bitpos) - let dataCnt = dst.dataMap.nonzeroBitCount - - Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) + dst.withUnsafeMutablePointerRanges { dataRange, trieRange in + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeRemove(at: dataIdx, fromRange: dataRange) - let trieIdx = indexFrom(dst.nodeMap, bitpos) // only nodeMap - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // but trieCnt - - Self.rangeInsertReversed(node, at: trieIdx, intoRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + let nodeIdx = indexFrom(nodeMap, bitpos) + Self.rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } + // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` dst.header.bitmap1 ^= bitpos dst.header.bitmap2 |= bitpos @@ -644,17 +639,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let dataIdx = indexFrom(dst.dataMap, bitpos) - let dataCnt = dst.dataMap.nonzeroBitCount - - Self.rangeRemove(at: dataIdx, fromRange: elements ..< elements.advanced(by: dataCnt)) + dst.withUnsafeMutablePointerRanges { dataRange, trieRange in + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeRemove(at: dataIdx, fromRange: dataRange) - let trieIdx = dst.nodeMap.nonzeroBitCount + indexFrom(dst.collMap, bitpos) // offset + collMap - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // trieCnt - - Self.rangeInsertReversed(node, at: trieIdx, intoRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) + let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) + Self.rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } + // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` dst.header.bitmap2 |= bitpos @@ -672,17 +664,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let trieIdx = indexFrom(dst.nodeMap, bitpos) // only nodeMap - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // but trieCnt + dst.withUnsafeMutablePointerRanges { dataRange, trieRange in + let nodeIdx = indexFrom(nodeMap, bitpos) + Self.rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) - Self.rangeRemoveReversed(at: trieIdx, fromRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) - - let dataIdx = indexFrom(dst.dataMap, bitpos) - let dataCnt = dst.dataMap.nonzeroBitCount - - Self.rangeInsert(tuple, at: dataIdx, intoRange: elements ..< elements.advanced(by: dataCnt)) + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeInsert(tuple, at: dataIdx, intoRange: dataRange) } + // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` dst.header.bitmap1 |= bitpos dst.header.bitmap2 ^= bitpos @@ -701,17 +690,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let trieIdx = dst.nodeMap.nonzeroBitCount + indexFrom(dst.collMap, bitpos) // offset + collMap - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount // trieCnt - - Self.rangeRemoveReversed(at: trieIdx, fromRange: (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt)) - - let dataIdx = indexFrom(dst.dataMap, bitpos) - let dataCnt = dst.dataMap.nonzeroBitCount + dst.withUnsafeMutablePointerRanges { dataRange, trieRange in + let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) + Self.rangeRemoveReversed(at: collIdx, fromRange: trieRange) - Self.rangeInsert(tuple, at: dataIdx, intoRange: elements ..< elements.advanced(by: dataCnt)) + let dataIdx = indexFrom(dataMap, bitpos) + Self.rangeInsert(tuple, at: dataIdx, intoRange: dataRange) } + // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` dst.header.bitmap2 ^= bitpos @@ -729,16 +715,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let trieIdxOld = dst.nodeMap.nonzeroBitCount + dst.collIndex(bitpos) - let trieIdxNew = dst.nodeIndex(bitpos) - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount - - let trieRange = (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt) + dst.withUnsafeMutablePointerRanges { _, trieRange in + let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) + let nodeIdx = nodeIndex(bitpos) - Self.rangeRemoveReversed(at: trieIdxOld, fromRange: trieRange) - Self.rangeInsertReversed(node, at: trieIdxNew, intoRange: trieRange) + Self.rangeRemoveReversed(at: collIdx, fromRange: trieRange) + Self.rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } + // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` dst.header.bitmap1 ^= bitpos @@ -756,16 +740,14 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - let trieIdxOld = dst.nodeIndex(bitpos) - let trieIdxNew = dst.nodeMap.nonzeroBitCount - 1 + dst.collIndex(bitpos) - let trieCnt = (dst.nodeMap | dst.collMap).nonzeroBitCount + dst.withUnsafeMutablePointerRanges { _, trieRange in + let nodeIdx = nodeIndex(bitpos) + let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - let trieRange = (elements ..< elements.advanced(by: dst.capacity)).suffix(trieCnt) - - Self.rangeRemoveReversed(at: trieIdxOld, fromRange: trieRange) - Self.rangeInsertReversed(node, at: trieIdxNew, intoRange: trieRange) + Self.rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + Self.rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } + // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` dst.header.bitmap1 |= bitpos From 11f137a8da924cd3bc45ef58b274136a127ac2c8 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 21 Jun 2021 11:08:08 +0200 Subject: [PATCH 104/176] [Capsule] Relocate range copy and edit functions --- Sources/Capsule/_BitmapIndexedMapNode.swift | 73 ++++----------------- Sources/Capsule/_Common.swift | 45 +++++++++++++ 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index efd6fd14c..8af1f4371 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -514,51 +514,6 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return dst } - static func rangeInsert(_ element: Any, at index: Int, intoRange range: Range>) { - let seq = range.dropFirst(index) - - let src = seq.startIndex - let dst = src.successor() - - dst.moveInitialize(from: src, count: seq.count) - - src.initialize(to: element) - } - - // `index` is the logical index starting at the rear, indexing to the left - static func rangeInsertReversed(_ element: Any, at index: Int, intoRange range: Range>) { - let seq = range.dropLast(index) - - let src = seq.startIndex - let dst = src.predecessor() - - dst.moveInitialize(from: src, count: seq.count) - - // requires call to predecessor on "past the end" position - seq.endIndex.predecessor().initialize(to: element) - } - - static func rangeRemove(at index: Int, fromRange range: Range>) { - let seq = range.dropFirst(index + 1) - - let src = seq.startIndex - let dst = src.predecessor() - - dst.deinitialize(count: 1) - dst.moveInitialize(from: src, count: seq.count) - } - - // `index` is the logical index starting at the rear, indexing to the left - static func rangeRemoveReversed(at index: Int, fromRange range: Range>) { - let seq = range.dropLast(index + 1) - - let src = seq.startIndex - let dst = src.successor() - - seq.endIndex.deinitialize(count: 1) - dst.moveInitialize(from: src, count: seq.count) - } - func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -571,7 +526,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, _ in let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeInsert((key, value), at: dataIdx, intoRange: dataRange) + rangeInsert((key, value), at: dataIdx, intoRange: dataRange) } // update metadata: `dataMap | bitpos, nodeMap, collMap` @@ -593,7 +548,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, _ in let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeRemove(at: dataIdx, fromRange: dataRange) + rangeRemove(at: dataIdx, fromRange: dataRange) } // update metadata: `dataMap ^ bitpos, nodeMap, collMap` @@ -615,10 +570,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeRemove(at: dataIdx, fromRange: dataRange) + rangeRemove(at: dataIdx, fromRange: dataRange) let nodeIdx = indexFrom(nodeMap, bitpos) - Self.rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` @@ -641,10 +596,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeRemove(at: dataIdx, fromRange: dataRange) + rangeRemove(at: dataIdx, fromRange: dataRange) let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - Self.rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` @@ -666,10 +621,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let nodeIdx = indexFrom(nodeMap, bitpos) - Self.rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeInsert(tuple, at: dataIdx, intoRange: dataRange) + rangeInsert(tuple, at: dataIdx, intoRange: dataRange) } // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` @@ -692,10 +647,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - Self.rangeRemoveReversed(at: collIdx, fromRange: trieRange) + rangeRemoveReversed(at: collIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) - Self.rangeInsert(tuple, at: dataIdx, intoRange: dataRange) + rangeInsert(tuple, at: dataIdx, intoRange: dataRange) } // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` @@ -719,8 +674,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) let nodeIdx = nodeIndex(bitpos) - Self.rangeRemoveReversed(at: collIdx, fromRange: trieRange) - Self.rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeRemoveReversed(at: collIdx, fromRange: trieRange) + rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` @@ -744,8 +699,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let nodeIdx = nodeIndex(bitpos) let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - Self.rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) - Self.rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 7da1d8ac7..0ce8de417 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -290,3 +290,48 @@ struct ChampBaseReverseIterator= 0) || searchNextValueNode() } } + +func rangeInsert(_ element: Any, at index: Int, intoRange range: Range>) { + let seq = range.dropFirst(index) + + let src = seq.startIndex + let dst = src.successor() + + dst.moveInitialize(from: src, count: seq.count) + + src.initialize(to: element) +} + +// `index` is the logical index starting at the rear, indexing to the left +func rangeInsertReversed(_ element: Any, at index: Int, intoRange range: Range>) { + let seq = range.dropLast(index) + + let src = seq.startIndex + let dst = src.predecessor() + + dst.moveInitialize(from: src, count: seq.count) + + // requires call to predecessor on "past the end" position + seq.endIndex.predecessor().initialize(to: element) +} + +func rangeRemove(at index: Int, fromRange range: Range>) { + let seq = range.dropFirst(index + 1) + + let src = seq.startIndex + let dst = src.predecessor() + + dst.deinitialize(count: 1) + dst.moveInitialize(from: src, count: seq.count) +} + +// `index` is the logical index starting at the rear, indexing to the left +func rangeRemoveReversed(at index: Int, fromRange range: Range>) { + let seq = range.dropLast(index + 1) + + let src = seq.startIndex + let dst = src.successor() + + seq.endIndex.deinitialize(count: 1) + dst.moveInitialize(from: src, count: seq.count) +} From 22a7c6a7583a1fd958cebb43a0c00309963a6bc2 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 21 Jun 2021 11:29:53 +0200 Subject: [PATCH 105/176] [Capsule] Avoid returning closure pointer This change results in a slight performance hit. Mitigations need to be investigated. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 30 ++++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 8af1f4371..a70a6c32b 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -65,10 +65,6 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return dst } - var content: UnsafeMutablePointer { - self.withUnsafeMutablePointerToElements { return $0 } - } - var invariant: Bool { guard contentInvariant else { return false @@ -90,15 +86,15 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } var dataSliceInvariant: Bool { - (0 ..< payloadArity).allSatisfy { index in content[index] is ReturnPayload } + (0 ..< payloadArity).allSatisfy { index in getElement(index) is ReturnPayload } } var nodeSliceInvariant: Bool { - (0 ..< bitmapIndexedNodeArity).allSatisfy { index in content[capacity - 1 - index] is ReturnBitmapIndexedNode } + (0 ..< bitmapIndexedNodeArity).allSatisfy { index in getElement(capacity - 1 - index) is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - (0 ..< hashCollisionNodeArity).allSatisfy { index in content[capacity - 1 - bitmapIndexedNodeArity - index] is ReturnHashCollisionNode } + (0 ..< hashCollisionNodeArity).allSatisfy { index in getElement(capacity - 1 - bitmapIndexedNodeArity - index) is ReturnHashCollisionNode } } static func create() -> Self { @@ -409,7 +405,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - content[capacity - 1 - index] as! BitmapIndexedMapNode + getElement(capacity - 1 - index) as! BitmapIndexedMapNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { @@ -432,12 +428,16 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } + func getElement(_ index: Int) -> Element { + self.withUnsafeMutablePointerToElements { elements in elements[index] } + } + var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return content[capacity - 1 - bitmapIndexedNodeArity - index] as! HashCollisionMapNode + return getElement(capacity - 1 - bitmapIndexedNodeArity - index) as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -456,7 +456,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { content[index] as! ReturnPayload } + func getPayload(_ index: Int) -> (key: Key, value: Value) { getElement(index) as! ReturnPayload } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -480,9 +480,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } let idx = dataIndex(bitpos) - let (key, _) = dst.content[idx] as! ReturnPayload - dst.content[idx] = (key, newValue) + dst.withUnsafeMutablePointerToElements { elements in + let (key, _) = elements[idx] as! ReturnPayload + elements[idx] = (key, newValue) + } assert(dst.contentInvariant) return dst @@ -508,7 +510,9 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.content[idx] = newNode + dst.withUnsafeMutablePointerToElements { elements in + elements[idx] = newNode + } assert(dst.contentInvariant) return dst From e4d45eba7b628d283d07a56ff77aa8a8620f59bb Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 21 Jun 2021 12:48:30 +0200 Subject: [PATCH 106/176] [Capsule] Make range functions generic --- Sources/Capsule/_Common.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 0ce8de417..915d84da4 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -291,7 +291,7 @@ struct ChampBaseReverseIterator>) { +func rangeInsert(_ element: T, at index: Int, intoRange range: Range>) { let seq = range.dropFirst(index) let src = seq.startIndex @@ -303,7 +303,7 @@ func rangeInsert(_ element: Any, at index: Int, intoRange range: Range>) { +func rangeInsertReversed(_ element: T, at index: Int, intoRange range: Range>) { let seq = range.dropLast(index) let src = seq.startIndex @@ -315,7 +315,7 @@ func rangeInsertReversed(_ element: Any, at index: Int, intoRange range: Range>) { +func rangeRemove(at index: Int, fromRange range: Range>) { let seq = range.dropFirst(index + 1) let src = seq.startIndex @@ -326,7 +326,7 @@ func rangeRemove(at index: Int, fromRange range: Range } // `index` is the logical index starting at the rear, indexing to the left -func rangeRemoveReversed(at index: Int, fromRange range: Range>) { +func rangeRemoveReversed(at index: Int, fromRange range: Range>) { let seq = range.dropLast(index + 1) let src = seq.startIndex From 9331a01927b801d62d3018d3330f13dddbe16f51 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 22 Jun 2021 10:52:40 +0200 Subject: [PATCH 107/176] [Capsule] Use `fixedTrieCapacity` and rebind to `AnyObject` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 143 +++++++++++--------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index a70a6c32b..e0ed08067 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,7 +11,9 @@ typealias Element = Any -fileprivate let initialMinimumCapacity = 4 +fileprivate let fixedTrieCapacity = 8 + +fileprivate let initialDataCapacity = 4 final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { @@ -38,22 +40,23 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } @inline(__always) - func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { + func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { self.withUnsafeMutablePointerToElements { elements in let(dataCnt, trieCnt) = header.explodedMap { bitmap in bitmap.nonzeroBitCount } - let range = elements ..< elements.advanced(by: capacity) - - let dataRange = range.prefix(dataCnt) - let trieRange = range.suffix(trieCnt) + let dataElements = elements.advanced(by: fixedTrieCapacity) + let dataRange = dataElements ..< dataElements.advanced(by: dataCnt) - return transform(dataRange, trieRange) + return elements.withMemoryRebound(to: AnyObject.self, capacity: trieCnt) { trieElements in + let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) + return transform(dataRange, trieRange) + } } } func copy(withCapacityFactor factor: Int = 1) -> Self { let src = self - let dst = Self.create(minimumCapacity: capacity * factor) { _ in header } as! Self + let dst = Self.create(minimumCapacity: fixedTrieCapacity + (capacity - fixedTrieCapacity) * factor) { _ in header } as! Self src.withUnsafeMutablePointerRanges { srcDataRange, srcTrieRange in dst.withUnsafeMutablePointerRanges { dstDataRange, dstTrieRange in @@ -86,61 +89,67 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } var dataSliceInvariant: Bool { - (0 ..< payloadArity).allSatisfy { index in getElement(index) is ReturnPayload } + self.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.allSatisfy { pointer in pointer.pointee is ReturnPayload } + } } var nodeSliceInvariant: Bool { - (0 ..< bitmapIndexedNodeArity).allSatisfy { index in getElement(capacity - 1 - index) is ReturnBitmapIndexedNode } + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.prefix(bitmapIndexedNodeArity).allSatisfy { pointer in pointer.pointee is ReturnBitmapIndexedNode } + } } var collSliceInvariant: Bool { - (0 ..< hashCollisionNodeArity).allSatisfy { index in getElement(capacity - 1 - bitmapIndexedNodeArity - index) is ReturnHashCollisionNode } + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.suffix(hashCollisionNodeArity).allSatisfy { pointer in pointer.pointee is ReturnHashCollisionNode } + } } static func create() -> Self { - Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self + Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) } - return result as! Self + return result } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) - $0.successor().initialize(to: (secondKey, secondValue)) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) + dataRange.startIndex.successor().initialize(to: (secondKey, secondValue)) } - return result as! Self + return result } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } - result.withUnsafeMutablePointerToElements { - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + nodeRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } - result.withUnsafeMutablePointerToElements { - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + nodeRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) + nodeRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } var recursiveCount: Int { @@ -405,39 +414,37 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - getElement(capacity - 1 - index) as! BitmapIndexedMapNode + self.withUnsafeMutablePointerRanges { _, nodeRange in + nodeRange.startIndex[index] as! BitmapIndexedMapNode + } } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = capacity - 1 - index + let slotIndex = index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = capacity - 1 - bitmapIndexedNodeArity - index + let slotIndex = bitmapIndexedNodeArity + index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = self.withUnsafeMutablePointerToElements { elements in - elements.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in - Swift.isKnownUniquelyReferenced(&pointer.pointee) - } + let isKnownUniquelyReferenced = self.withUnsafeMutablePointerRanges { _, trieRange in + Swift.isKnownUniquelyReferenced(&trieRange.startIndex.advanced(by: slotIndex).pointee) } return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - func getElement(_ index: Int) -> Element { - self.withUnsafeMutablePointerToElements { elements in elements[index] } - } - var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return getElement(capacity - 1 - bitmapIndexedNodeArity - index) as! HashCollisionMapNode + self.withUnsafeMutablePointerRanges { _, nodeRange in + nodeRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode + } } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -456,7 +463,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { getElement(index) as! ReturnPayload } + func getPayload(_ index: Int) -> (key: Key, value: Value) { + self.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.startIndex[index] as! ReturnPayload + } + } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -479,11 +490,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - let idx = dataIndex(bitpos) + dst.withUnsafeMutablePointerRanges { dataRange, _ in + let idx = dataIndex(bitpos) - dst.withUnsafeMutablePointerToElements { elements in - let (key, _) = elements[idx] as! ReturnPayload - elements[idx] = (key, newValue) + let (key, _) = dataRange.startIndex[idx] as! ReturnPayload + dataRange.startIndex[idx] = (key, newValue) } assert(dst.contentInvariant) @@ -491,12 +502,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idx = capacity - 1 - self.nodeIndex(bitpos) + let idx = self.nodeIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idx = capacity - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) + let idx = bitmapIndexedNodeArity + self.collIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } @@ -510,8 +521,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - elements[idx] = newNode + dst.withUnsafeMutablePointerRanges { _, nodeRange in + nodeRange.startIndex[idx] = newNode as AnyObject } assert(dst.contentInvariant) @@ -522,10 +533,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced && count < capacity { + let hasRoomForData = dataMap.nonzeroBitCount < (capacity - fixedTrieCapacity) + + if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src } else { - dst = src.copy(withCapacityFactor: count < capacity ? 1 : 2) + dst = src.copy(withCapacityFactor: hasRoomForData ? 1 : 2) } dst.withUnsafeMutablePointerRanges { dataRange, _ in @@ -577,7 +590,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeInsert(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` @@ -603,7 +616,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeInsert(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` @@ -625,7 +638,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let nodeIdx = indexFrom(nodeMap, bitpos) - rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + rangeRemove(at: nodeIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -651,7 +664,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemoveReversed(at: collIdx, fromRange: trieRange) + rangeRemove(at: collIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -678,8 +691,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) let nodeIdx = nodeIndex(bitpos) - rangeRemoveReversed(at: collIdx, fromRange: trieRange) - rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeRemove(at: collIdx, fromRange: trieRange) + rangeInsert(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` @@ -703,8 +716,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let nodeIdx = nodeIndex(bitpos) let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) - rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeRemove(at: nodeIdx, fromRange: trieRange) + rangeInsert(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` From 84a47155fb58d4e6a74f7dc5b0976407e745e174 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 22 Jun 2021 11:37:30 +0200 Subject: [PATCH 108/176] [Capsule] Name correctly usages of `trieRange` --- Sources/Capsule/_BitmapIndexedMapNode.swift | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index e0ed08067..2c48d14d7 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -112,7 +112,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + result.withUnsafeMutablePointerRanges { dataRange, _ in dataRange.startIndex.initialize(to: (firstKey, firstValue)) } return result @@ -120,7 +120,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + result.withUnsafeMutablePointerRanges { dataRange, _ in dataRange.startIndex.initialize(to: (firstKey, firstValue)) dataRange.startIndex.successor().initialize(to: (secondKey, secondValue)) } @@ -129,25 +129,25 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, nodeRange in - nodeRange.startIndex.initialize(to: firstNode) + result.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex.initialize(to: firstNode) } return result } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, nodeRange in - nodeRange.startIndex.initialize(to: firstNode) + result.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex.initialize(to: firstNode) } return result } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, nodeRange in + result.withUnsafeMutablePointerRanges { dataRange, trieRange in dataRange.startIndex.initialize(to: (firstKey, firstValue)) - nodeRange.startIndex.initialize(to: firstNode) + trieRange.startIndex.initialize(to: firstNode) } return result } @@ -414,8 +414,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - self.withUnsafeMutablePointerRanges { _, nodeRange in - nodeRange.startIndex[index] as! BitmapIndexedMapNode + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[index] as! BitmapIndexedMapNode } } @@ -442,8 +442,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - self.withUnsafeMutablePointerRanges { _, nodeRange in - nodeRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode } } @@ -521,8 +521,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { _, nodeRange in - nodeRange.startIndex[idx] = newNode as AnyObject + dst.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[idx] = newNode as AnyObject } assert(dst.contentInvariant) From 2b0574ca99cec8592de286a2e7e382deda2c67ef Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 22 Jun 2021 11:39:11 +0200 Subject: [PATCH 109/176] [Capsule] Fix invariant references --- Sources/Capsule/_BitmapIndexedMapNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 2c48d14d7..fe8be380e 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -497,7 +497,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dataRange.startIndex[idx] = (key, newValue) } - assert(dst.contentInvariant) + assert(dst.invariant) return dst } @@ -525,7 +525,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma trieRange.startIndex[idx] = newNode as AnyObject } - assert(dst.contentInvariant) + assert(dst.invariant) return dst } From 2d9baf6b3ae6019e5992d525f942decbb83e1791 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 23 Jun 2021 09:05:45 +0200 Subject: [PATCH 110/176] Revert "[Capsule] Use `fixedTrieCapacity` and rebind to `AnyObject`" This reverts commit f9deeab824598b050bed44c879243fbf999e8b11. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 143 +++++++++----------- 1 file changed, 65 insertions(+), 78 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index fe8be380e..de21ff47a 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,9 +11,7 @@ typealias Element = Any -fileprivate let fixedTrieCapacity = 8 - -fileprivate let initialDataCapacity = 4 +fileprivate let initialMinimumCapacity = 4 final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { @@ -40,23 +38,22 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } @inline(__always) - func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { + func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { self.withUnsafeMutablePointerToElements { elements in let(dataCnt, trieCnt) = header.explodedMap { bitmap in bitmap.nonzeroBitCount } - let dataElements = elements.advanced(by: fixedTrieCapacity) - let dataRange = dataElements ..< dataElements.advanced(by: dataCnt) + let range = elements ..< elements.advanced(by: capacity) - return elements.withMemoryRebound(to: AnyObject.self, capacity: trieCnt) { trieElements in - let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) - return transform(dataRange, trieRange) - } + let dataRange = range.prefix(dataCnt) + let trieRange = range.suffix(trieCnt) + + return transform(dataRange, trieRange) } } func copy(withCapacityFactor factor: Int = 1) -> Self { let src = self - let dst = Self.create(minimumCapacity: fixedTrieCapacity + (capacity - fixedTrieCapacity) * factor) { _ in header } as! Self + let dst = Self.create(minimumCapacity: capacity * factor) { _ in header } as! Self src.withUnsafeMutablePointerRanges { srcDataRange, srcTrieRange in dst.withUnsafeMutablePointerRanges { dstDataRange, dstTrieRange in @@ -89,67 +86,61 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } var dataSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.allSatisfy { pointer in pointer.pointee is ReturnPayload } - } + (0 ..< payloadArity).allSatisfy { index in getElement(index) is ReturnPayload } } var nodeSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.prefix(bitmapIndexedNodeArity).allSatisfy { pointer in pointer.pointee is ReturnBitmapIndexedNode } - } + (0 ..< bitmapIndexedNodeArity).allSatisfy { index in getElement(capacity - 1 - index) is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.suffix(hashCollisionNodeArity).allSatisfy { pointer in pointer.pointee is ReturnHashCollisionNode } - } + (0 ..< hashCollisionNodeArity).allSatisfy { index in getElement(capacity - 1 - bitmapIndexedNodeArity - index) is ReturnHashCollisionNode } } static func create() -> Self { - Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self + Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } + result.withUnsafeMutablePointerToElements { + $0.initialize(to: (firstKey, firstValue)) } - return result + return result as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) - dataRange.startIndex.successor().initialize(to: (secondKey, secondValue)) + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } + result.withUnsafeMutablePointerToElements { + $0.initialize(to: (firstKey, firstValue)) + $0.successor().initialize(to: (secondKey, secondValue)) } - return result + return result as! Self } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } as! Self - result.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex.initialize(to: firstNode) + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } + result.withUnsafeMutablePointerToElements { + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex.initialize(to: firstNode) + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } + result.withUnsafeMutablePointerToElements { + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, trieRange in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) - trieRange.startIndex.initialize(to: firstNode) + let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } + result.withUnsafeMutablePointerToElements { + $0.initialize(to: (firstKey, firstValue)) + $0.advanced(by: result.capacity - 1).initialize(to: firstNode) } - return result + return result as! Self } var recursiveCount: Int { @@ -414,37 +405,39 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[index] as! BitmapIndexedMapNode - } + getElement(capacity - 1 - index) as! BitmapIndexedMapNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = index + let slotIndex = capacity - 1 - index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = bitmapIndexedNodeArity + index + let slotIndex = capacity - 1 - bitmapIndexedNodeArity - index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = self.withUnsafeMutablePointerRanges { _, trieRange in - Swift.isKnownUniquelyReferenced(&trieRange.startIndex.advanced(by: slotIndex).pointee) + let isKnownUniquelyReferenced = self.withUnsafeMutablePointerToElements { elements in + elements.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in + Swift.isKnownUniquelyReferenced(&pointer.pointee) + } } return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } + func getElement(_ index: Int) -> Element { + self.withUnsafeMutablePointerToElements { elements in elements[index] } + } + var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode - } + return getElement(capacity - 1 - bitmapIndexedNodeArity - index) as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -463,11 +456,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { - self.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex[index] as! ReturnPayload - } - } + func getPayload(_ index: Int) -> (key: Key, value: Value) { getElement(index) as! ReturnPayload } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -490,11 +479,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { dataRange, _ in - let idx = dataIndex(bitpos) + let idx = dataIndex(bitpos) - let (key, _) = dataRange.startIndex[idx] as! ReturnPayload - dataRange.startIndex[idx] = (key, newValue) + dst.withUnsafeMutablePointerToElements { elements in + let (key, _) = elements[idx] as! ReturnPayload + elements[idx] = (key, newValue) } assert(dst.invariant) @@ -502,12 +491,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idx = self.nodeIndex(bitpos) + let idx = capacity - 1 - self.nodeIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idx = bitmapIndexedNodeArity + self.collIndex(bitpos) + let idx = capacity - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } @@ -521,8 +510,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[idx] = newNode as AnyObject + dst.withUnsafeMutablePointerToElements { elements in + elements[idx] = newNode } assert(dst.invariant) @@ -533,12 +522,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForData = dataMap.nonzeroBitCount < (capacity - fixedTrieCapacity) - - if isStorageKnownUniquelyReferenced && hasRoomForData { + if isStorageKnownUniquelyReferenced && count < capacity { dst = src } else { - dst = src.copy(withCapacityFactor: hasRoomForData ? 1 : 2) + dst = src.copy(withCapacityFactor: count < capacity ? 1 : 2) } dst.withUnsafeMutablePointerRanges { dataRange, _ in @@ -590,7 +577,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsert(node, at: nodeIdx, intoRange: trieRange) + rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` @@ -616,7 +603,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsert(node, at: collIdx, intoRange: trieRange) + rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` @@ -638,7 +625,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let nodeIdx = indexFrom(nodeMap, bitpos) - rangeRemove(at: nodeIdx, fromRange: trieRange) + rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -664,7 +651,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemove(at: collIdx, fromRange: trieRange) + rangeRemoveReversed(at: collIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -691,8 +678,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) let nodeIdx = nodeIndex(bitpos) - rangeRemove(at: collIdx, fromRange: trieRange) - rangeInsert(node, at: nodeIdx, intoRange: trieRange) + rangeRemoveReversed(at: collIdx, fromRange: trieRange) + rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` @@ -716,8 +703,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let nodeIdx = nodeIndex(bitpos) let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - rangeRemove(at: nodeIdx, fromRange: trieRange) - rangeInsert(node, at: collIdx, intoRange: trieRange) + rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + rangeInsertReversed(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` From fdb2231cb1db6b7da4d99dbca489e73ad099680c Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 23 Jun 2021 09:07:48 +0200 Subject: [PATCH 111/176] Revert "Revert "[Capsule] Use `fixedTrieCapacity` and rebind to `AnyObject`"" This reverts commit 1117a89541c6294d89963c80b18d11296bcdc88f. --- Sources/Capsule/_BitmapIndexedMapNode.swift | 143 +++++++++++--------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index de21ff47a..fe8be380e 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,7 +11,9 @@ typealias Element = Any -fileprivate let initialMinimumCapacity = 4 +fileprivate let fixedTrieCapacity = 8 + +fileprivate let initialDataCapacity = 4 final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { @@ -38,22 +40,23 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } @inline(__always) - func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { + func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { self.withUnsafeMutablePointerToElements { elements in let(dataCnt, trieCnt) = header.explodedMap { bitmap in bitmap.nonzeroBitCount } - let range = elements ..< elements.advanced(by: capacity) - - let dataRange = range.prefix(dataCnt) - let trieRange = range.suffix(trieCnt) + let dataElements = elements.advanced(by: fixedTrieCapacity) + let dataRange = dataElements ..< dataElements.advanced(by: dataCnt) - return transform(dataRange, trieRange) + return elements.withMemoryRebound(to: AnyObject.self, capacity: trieCnt) { trieElements in + let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) + return transform(dataRange, trieRange) + } } } func copy(withCapacityFactor factor: Int = 1) -> Self { let src = self - let dst = Self.create(minimumCapacity: capacity * factor) { _ in header } as! Self + let dst = Self.create(minimumCapacity: fixedTrieCapacity + (capacity - fixedTrieCapacity) * factor) { _ in header } as! Self src.withUnsafeMutablePointerRanges { srcDataRange, srcTrieRange in dst.withUnsafeMutablePointerRanges { dstDataRange, dstTrieRange in @@ -86,61 +89,67 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } var dataSliceInvariant: Bool { - (0 ..< payloadArity).allSatisfy { index in getElement(index) is ReturnPayload } + self.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.allSatisfy { pointer in pointer.pointee is ReturnPayload } + } } var nodeSliceInvariant: Bool { - (0 ..< bitmapIndexedNodeArity).allSatisfy { index in getElement(capacity - 1 - index) is ReturnBitmapIndexedNode } + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.prefix(bitmapIndexedNodeArity).allSatisfy { pointer in pointer.pointee is ReturnBitmapIndexedNode } + } } var collSliceInvariant: Bool { - (0 ..< hashCollisionNodeArity).allSatisfy { index in getElement(capacity - 1 - bitmapIndexedNodeArity - index) is ReturnHashCollisionNode } + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.suffix(hashCollisionNodeArity).allSatisfy { pointer in pointer.pointee is ReturnHashCollisionNode } + } } static func create() -> Self { - Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self + Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) } - return result as! Self + return result } static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) - $0.successor().initialize(to: (secondKey, secondValue)) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) + dataRange.startIndex.successor().initialize(to: (secondKey, secondValue)) } - return result as! Self + return result } static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } - result.withUnsafeMutablePointerToElements { - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } as! Self + result.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } - result.withUnsafeMutablePointerToElements { - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } as! Self + result.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: initialMinimumCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } - result.withUnsafeMutablePointerToElements { - $0.initialize(to: (firstKey, firstValue)) - $0.advanced(by: result.capacity - 1).initialize(to: firstNode) + let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } as! Self + result.withUnsafeMutablePointerRanges { dataRange, trieRange in + dataRange.startIndex.initialize(to: (firstKey, firstValue)) + trieRange.startIndex.initialize(to: firstNode) } - return result as! Self + return result } var recursiveCount: Int { @@ -405,39 +414,37 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - getElement(capacity - 1 - index) as! BitmapIndexedMapNode + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[index] as! BitmapIndexedMapNode + } } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = capacity - 1 - index + let slotIndex = index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = capacity - 1 - bitmapIndexedNodeArity - index + let slotIndex = bitmapIndexedNodeArity + index return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = self.withUnsafeMutablePointerToElements { elements in - elements.advanced(by: slotIndex).withMemoryRebound(to: AnyObject.self, capacity: 1) { pointer in - Swift.isKnownUniquelyReferenced(&pointer.pointee) - } + let isKnownUniquelyReferenced = self.withUnsafeMutablePointerRanges { _, trieRange in + Swift.isKnownUniquelyReferenced(&trieRange.startIndex.advanced(by: slotIndex).pointee) } return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - func getElement(_ index: Int) -> Element { - self.withUnsafeMutablePointerToElements { elements in elements[index] } - } - var hasHashCollisionNodes: Bool { collMap != 0 } var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - return getElement(capacity - 1 - bitmapIndexedNodeArity - index) as! HashCollisionMapNode + self.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode + } } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -456,7 +463,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var payloadArity: Int { dataMap.nonzeroBitCount } - func getPayload(_ index: Int) -> (key: Key, value: Value) { getElement(index) as! ReturnPayload } + func getPayload(_ index: Int) -> (key: Key, value: Value) { + self.withUnsafeMutablePointerRanges { dataRange, _ in + dataRange.startIndex[index] as! ReturnPayload + } + } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -479,11 +490,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - let idx = dataIndex(bitpos) + dst.withUnsafeMutablePointerRanges { dataRange, _ in + let idx = dataIndex(bitpos) - dst.withUnsafeMutablePointerToElements { elements in - let (key, _) = elements[idx] as! ReturnPayload - elements[idx] = (key, newValue) + let (key, _) = dataRange.startIndex[idx] as! ReturnPayload + dataRange.startIndex[idx] = (key, newValue) } assert(dst.invariant) @@ -491,12 +502,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { - let idx = capacity - 1 - self.nodeIndex(bitpos) + let idx = self.nodeIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { - let idx = capacity - 1 - bitmapIndexedNodeArity - self.collIndex(bitpos) + let idx = bitmapIndexedNodeArity + self.collIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } @@ -510,8 +521,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerToElements { elements in - elements[idx] = newNode + dst.withUnsafeMutablePointerRanges { _, trieRange in + trieRange.startIndex[idx] = newNode as AnyObject } assert(dst.invariant) @@ -522,10 +533,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced && count < capacity { + let hasRoomForData = dataMap.nonzeroBitCount < (capacity - fixedTrieCapacity) + + if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src } else { - dst = src.copy(withCapacityFactor: count < capacity ? 1 : 2) + dst = src.copy(withCapacityFactor: hasRoomForData ? 1 : 2) } dst.withUnsafeMutablePointerRanges { dataRange, _ in @@ -577,7 +590,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeInsert(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` @@ -603,7 +616,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma rangeRemove(at: dataIdx, fromRange: dataRange) let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeInsert(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` @@ -625,7 +638,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let nodeIdx = indexFrom(nodeMap, bitpos) - rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) + rangeRemove(at: nodeIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -651,7 +664,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst.withUnsafeMutablePointerRanges { dataRange, trieRange in let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemoveReversed(at: collIdx, fromRange: trieRange) + rangeRemove(at: collIdx, fromRange: trieRange) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, intoRange: dataRange) @@ -678,8 +691,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) let nodeIdx = nodeIndex(bitpos) - rangeRemoveReversed(at: collIdx, fromRange: trieRange) - rangeInsertReversed(node, at: nodeIdx, intoRange: trieRange) + rangeRemove(at: collIdx, fromRange: trieRange) + rangeInsert(node, at: nodeIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` @@ -703,8 +716,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let nodeIdx = nodeIndex(bitpos) let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - rangeRemoveReversed(at: nodeIdx, fromRange: trieRange) - rangeInsertReversed(node, at: collIdx, intoRange: trieRange) + rangeRemove(at: nodeIdx, fromRange: trieRange) + rangeInsert(node, at: collIdx, intoRange: trieRange) } // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` From 3a3e078a4f7335f9105a887fb1d5c2df559cd4d8 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 23 Jun 2021 10:02:51 +0200 Subject: [PATCH 112/176] [Capsule] Bind `trieRange` with unsafe --- Sources/Capsule/_BitmapIndexedMapNode.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index fe8be380e..9c9b56cbc 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -47,10 +47,10 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let dataElements = elements.advanced(by: fixedTrieCapacity) let dataRange = dataElements ..< dataElements.advanced(by: dataCnt) - return elements.withMemoryRebound(to: AnyObject.self, capacity: trieCnt) { trieElements in - let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) - return transform(dataRange, trieRange) - } + let trieElements = UnsafeMutableRawPointer(elements).bindMemory(to: AnyObject.self, capacity: trieCnt) + let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) + + return transform(dataRange, trieRange) } } From fe7f34e0710a85bd10fcd3b739a05021cd977afc Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 23 Jun 2021 10:13:03 +0200 Subject: [PATCH 113/176] [Capsule] Make `fixedTrieCapacity` adjustable to bitmap format --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 9c9b56cbc..37eaecca5 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,7 +11,7 @@ typealias Element = Any -fileprivate let fixedTrieCapacity = 8 +fileprivate let fixedTrieCapacity = Bitmap.bitWidth * MemoryLayout.size / MemoryLayout.size fileprivate let initialDataCapacity = 4 From d3816c2fba1b74b179b47bcfccf1372c5d06e0f1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 28 Jun 2021 13:32:01 +0200 Subject: [PATCH 114/176] [Capsule] Rework buffer to use `UnsafeMutableRawPointer` --- Sources/Capsule/HashMap.swift | 2 +- Sources/Capsule/_BitmapIndexedMapNode.swift | 350 +++++++++++--------- Sources/Capsule/_Common.swift | 23 ++ 3 files changed, 223 insertions(+), 152 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 733f29a53..16a11f207 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -21,7 +21,7 @@ public struct HashMap where Key: Hashable { } public init() { - self.init(BitmapIndexedMapNode.create(), 0, 0) + self.init(BitmapIndexedMapNode(), 0, 0) } public init(_ map: HashMap) { diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 37eaecca5..9d7920483 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,19 +9,33 @@ // //===----------------------------------------------------------------------===// -typealias Element = Any +fileprivate let fixedTrieCapacity = Bitmap.bitWidth -fileprivate let fixedTrieCapacity = Bitmap.bitWidth * MemoryLayout.size / MemoryLayout.size +fileprivate let initialDataCapacity = 8 +fileprivate let initialTrieCapacity = 4 -fileprivate let initialDataCapacity = 4 +final class BitmapIndexedMapNode: MapNode where Key: Hashable { -final class BitmapIndexedMapNode: ManagedBuffer, MapNode where Key: Hashable { + typealias DataBufferElement = ReturnPayload // `ReturnPayload` or `Any` + typealias TrieBufferElement = AnyObject + + var header: Header + + var dataBuffer: UnsafeMutableBufferPointer + var trieBuffer: UnsafeMutableBufferPointer + + var dataBaseAddress: UnsafeMutablePointer { dataBuffer.baseAddress! } + var trieBaseAddress: UnsafeMutablePointer { trieBuffer.baseAddress! } + + private var rootBaseAddress: UnsafeMutableRawPointer { UnsafeMutableRawPointer(trieBuffer.baseAddress!) } deinit { - self.withUnsafeMutablePointerRanges { dataRange, trieRange in - dataRange.startIndex.deinitialize(count: dataRange.count) - trieRange.startIndex.deinitialize(count: trieRange.count) - } + // TODO use bitmaps since count is more or less capacity? + + dataBaseAddress.deinitialize(count: header.dataMap.nonzeroBitCount) + trieBaseAddress.deinitialize(count: header.trieMap.nonzeroBitCount) + + rootBaseAddress.deallocate() } @inline(__always) @@ -39,32 +53,43 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma header.collMap } - @inline(__always) - func withUnsafeMutablePointerRanges(transformRanges transform: (Range>, Range>) -> R) -> R { - self.withUnsafeMutablePointerToElements { elements in - let(dataCnt, trieCnt) = header.explodedMap { bitmap in bitmap.nonzeroBitCount } + @inlinable + static func _allocate(dataCapacity: Int, trieCapacity: Int) -> (dataBuffer: UnsafeMutableBufferPointer, trieBuffer: UnsafeMutableBufferPointer) { - let dataElements = elements.advanced(by: fixedTrieCapacity) - let dataRange = dataElements ..< dataElements.advanced(by: dataCnt) + let dataCapacityInBytes = dataCapacity * MemoryLayout.stride + let trieCapacityInBytes = trieCapacity * MemoryLayout.stride - let trieElements = UnsafeMutableRawPointer(elements).bindMemory(to: AnyObject.self, capacity: trieCnt) - let trieRange = trieElements ..< trieElements.advanced(by: trieCnt) + let memory = UnsafeMutableRawPointer.allocate( + byteCount: dataCapacityInBytes + trieCapacityInBytes, + alignment: 8) - return transform(dataRange, trieRange) - } + let dataBuffer = UnsafeMutableBufferPointer( + start: memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: dataCapacity), + count: dataCapacity) + + let trieBuffer = UnsafeMutableBufferPointer( + start: memory.bindMemory(to: TrieBufferElement.self, capacity: trieCapacity), + count: trieCapacity) + + return (dataBuffer, trieBuffer) } - func copy(withCapacityFactor factor: Int = 1) -> Self { + func copy(withDataCapacityFactor dataFactor: Int = 1, withTrieCapacityFactor trieFactor: Int = 1) -> Self { let src = self - let dst = Self.create(minimumCapacity: fixedTrieCapacity + (capacity - fixedTrieCapacity) * factor) { _ in header } as! Self - src.withUnsafeMutablePointerRanges { srcDataRange, srcTrieRange in - dst.withUnsafeMutablePointerRanges { dstDataRange, dstTrieRange in - dstDataRange.startIndex.initialize(from: srcDataRange.startIndex, count: srcDataRange.count) - dstTrieRange.startIndex.initialize(from: srcTrieRange.startIndex, count: srcTrieRange.count) - } - } + let (dstDataBuffer, dstTrieBuffer) = Self._allocate(dataCapacity: src.dataBuffer.count * dataFactor, trieCapacity: src.trieBuffer.count * trieFactor) + + let dst = Self() + dst.header = src.header + dst.dataBuffer = dstDataBuffer + dst.trieBuffer = dstTrieBuffer +// dst.dataBaseAddress = dstDataBuffer.baseAddress! +// dst.trieBaseAddress = dstTrieBuffer.baseAddress! + dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataMap.nonzeroBitCount) + dst.trieBaseAddress.initialize(from: src.trieBaseAddress, count: src.header.trieMap.nonzeroBitCount) + + assert(dst.invariant) return dst } @@ -77,9 +102,9 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma return false } - guard count <= capacity else { - return false - } +// guard count <= capacity else { +// return false +// } return true } @@ -89,67 +114,111 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } var dataSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.allSatisfy { pointer in pointer.pointee is ReturnPayload } - } + dataBuffer.prefix(payloadArity).allSatisfy { $0 is ReturnPayload } } var nodeSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.prefix(bitmapIndexedNodeArity).allSatisfy { pointer in pointer.pointee is ReturnBitmapIndexedNode } - } + trieBuffer.prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.suffix(hashCollisionNodeArity).allSatisfy { pointer in pointer.pointee is ReturnHashCollisionNode } - } + trieBuffer.dropFirst(bitmapIndexedNodeArity).prefix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } } - static func create() -> Self { - Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: 0) } as! Self + init() { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: 0, bitmap2: 0) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + + assert(self.invariant) } - static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) - } - return result + init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: dataMap, bitmap2: 0) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + + // dataBuffer[0] = (firstKey, firstValue) + + dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) + + assert(self.invariant) } - static func create(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap, bitmap2: 0) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) - dataRange.startIndex.successor().initialize(to: (secondKey, secondValue)) - } - return result + init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: dataMap, bitmap2: 0) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + +// dataBuffer[0] = (firstKey, firstValue) +// dataBuffer[1] = (secondKey, secondValue) + + dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) + dataBuffer.baseAddress!.successor().initialize(to: (secondKey, secondValue)) + + assert(self.invariant) } - static func create(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: 0, bitmap2: nodeMap) } as! Self - result.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex.initialize(to: firstNode) - } - return result + init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: 0, bitmap2: nodeMap) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + +// trieBuffer[0] = firstNode + + trieBuffer.baseAddress!.initialize(to: firstNode) + + assert(self.invariant) } - static func create(collMap: Bitmap, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex.initialize(to: firstNode) - } - return result + init(collMap: Bitmap, firstNode: HashCollisionMapNode) { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: collMap, bitmap2: collMap) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + +// trieBuffer[0] = firstNode + + trieBuffer.baseAddress!.initialize(to: firstNode) + + assert(self.invariant) } - static func create(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) -> Self { - let result = Self.create(minimumCapacity: fixedTrieCapacity + initialDataCapacity) { _ in Header(bitmap1: dataMap | collMap, bitmap2: collMap) } as! Self - result.withUnsafeMutablePointerRanges { dataRange, trieRange in - dataRange.startIndex.initialize(to: (firstKey, firstValue)) - trieRange.startIndex.initialize(to: firstNode) - } - return result + init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { + let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + + self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) + self.dataBuffer = dataBuffer + self.trieBuffer = trieBuffer +// self.dataBaseAddress = dataBuffer.baseAddress! +// self.trieBaseAddress = trieBuffer.baseAddress! + +// dataBuffer[0] = (firstKey, firstValue) +// trieBuffer[0] = firstNode + + dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) + trieBuffer.baseAddress!.initialize(to: firstNode) + + assert(self.invariant) } var recursiveCount: Int { @@ -278,17 +347,17 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma // keep remaining pair on root level let newDataMap = (dataMap ^ bitpos) let (remainingKey, remainingValue) = getPayload(1 - index) - return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // create potential new root: will a) become new root, or b) inlined on another level let newDataMap = bitposFrom(maskFrom(keyHash, 0)) let (remainingKey, remainingValue) = getPayload(1 - index) - return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return Self.create(collMap: newCollMap, firstNode: getHashCollisionNode(0)) + return Self(collMap: newCollMap, firstNode: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -352,7 +421,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (remainingKey, remainingValue) = subNodeNew.getPayload(0) - return Self.create(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } else { // inline value return copyAndMigrateFromCollisionNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) @@ -380,15 +449,15 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma if mask0 != mask1 { // unique prefixes, payload fits on same level if mask0 < mask1 { - return Self.create(dataMap: bitposFrom(mask0) | bitposFrom(mask1), firstKey: key0, firstValue: value0, secondKey: key1, secondValue: value1) + return Self(dataMap: bitposFrom(mask0) | bitposFrom(mask1), firstKey: key0, firstValue: value0, secondKey: key1, secondValue: value1) } else { - return Self.create(dataMap: bitposFrom(mask1) | bitposFrom(mask0), firstKey: key1, firstValue: value1, secondKey: key0, secondValue: value0) + return Self(dataMap: bitposFrom(mask1) | bitposFrom(mask0), firstKey: key1, firstValue: value1, secondKey: key0, secondValue: value0) } } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return Self.create(nodeMap: bitposFrom(mask0), firstNode: node) + return Self(nodeMap: bitposFrom(mask0), firstNode: node) } } @@ -400,12 +469,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - return Self.create(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) + return Self(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - return Self.create(nodeMap: bitposFrom(mask0), firstNode: node) + return Self(nodeMap: bitposFrom(mask0), firstNode: node) } } @@ -414,9 +483,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[index] as! BitmapIndexedMapNode - } + trieBuffer[index] as! BitmapIndexedMapNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { @@ -430,9 +497,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = self.withUnsafeMutablePointerRanges { _, trieRange in - Swift.isKnownUniquelyReferenced(&trieRange.startIndex.advanced(by: slotIndex).pointee) - } + let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBuffer[slotIndex]) return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } @@ -442,9 +507,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - self.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[bitmapIndexedNodeArity + index] as! HashCollisionMapNode - } + trieBuffer[bitmapIndexedNodeArity + index] as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -464,9 +527,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma var payloadArity: Int { dataMap.nonzeroBitCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { - self.withUnsafeMutablePointerRanges { dataRange, _ in - dataRange.startIndex[index] as! ReturnPayload - } + dataBuffer[index] // as! ReturnPayload } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -477,6 +538,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } + // TODO deprecate? /// The number of (non-contiguous) occupied buffer cells. final var count: Int { (header.bitmap1 | header.bitmap2).nonzeroBitCount } @@ -490,12 +552,12 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { dataRange, _ in - let idx = dataIndex(bitpos) + let idx = dataIndex(bitpos) - let (key, _) = dataRange.startIndex[idx] as! ReturnPayload - dataRange.startIndex[idx] = (key, newValue) - } +// let (key, _) = dst.dataBuffer[idx] // as! ReturnPayload +// dst.dataBuffer[idx] = (key, newValue) + + dst.dataBuffer[idx].value = newValue assert(dst.invariant) return dst @@ -521,9 +583,7 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { _, trieRange in - trieRange.startIndex[idx] = newNode as AnyObject - } + dst.trieBuffer[idx] = newNode as TrieBufferElement assert(dst.invariant) return dst @@ -533,18 +593,16 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForData = dataMap.nonzeroBitCount < (capacity - fixedTrieCapacity) + let hasRoomForData = dataMap.nonzeroBitCount < dataBuffer.count if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src } else { - dst = src.copy(withCapacityFactor: hasRoomForData ? 1 : 2) + dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } - dst.withUnsafeMutablePointerRanges { dataRange, _ in - let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert((key, value), at: dataIdx, intoRange: dataRange) - } + let dataIdx = indexFrom(dataMap, bitpos) + rangeInsert((key, value), at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) // update metadata: `dataMap | bitpos, nodeMap, collMap` dst.header.bitmap1 |= bitpos @@ -563,10 +621,8 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { dataRange, _ in - let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, fromRange: dataRange) - } + let dataIdx = indexFrom(dataMap, bitpos) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap` dst.header.bitmap1 ^= bitpos @@ -579,19 +635,19 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced { + let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieBuffer.count + + if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src } else { - dst = src.copy() + dst = src.copy(withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) } - dst.withUnsafeMutablePointerRanges { dataRange, trieRange in - let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, fromRange: dataRange) + let dataIdx = indexFrom(dataMap, bitpos) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) - let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsert(node, at: nodeIdx, intoRange: trieRange) - } + let nodeIdx = indexFrom(nodeMap, bitpos) + rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` dst.header.bitmap1 ^= bitpos @@ -605,19 +661,19 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced { + let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieBuffer.count + + if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src } else { - dst = src.copy() + dst = src.copy(withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) } - dst.withUnsafeMutablePointerRanges { dataRange, trieRange in - let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, fromRange: dataRange) + let dataIdx = indexFrom(dataMap, bitpos) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) - let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsert(node, at: collIdx, intoRange: trieRange) - } + let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) + rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` dst.header.bitmap2 |= bitpos @@ -636,13 +692,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { dataRange, trieRange in - let nodeIdx = indexFrom(nodeMap, bitpos) - rangeRemove(at: nodeIdx, fromRange: trieRange) + let nodeIdx = indexFrom(nodeMap, bitpos) + rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) - let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert(tuple, at: dataIdx, intoRange: dataRange) - } + let dataIdx = indexFrom(dataMap, bitpos) + rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` dst.header.bitmap1 |= bitpos @@ -662,13 +716,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { dataRange, trieRange in - let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemove(at: collIdx, fromRange: trieRange) + let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) + rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) - let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert(tuple, at: dataIdx, intoRange: dataRange) - } + let dataIdx = indexFrom(dataMap, bitpos) + rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` dst.header.bitmap2 ^= bitpos @@ -687,13 +739,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { _, trieRange in - let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) - let nodeIdx = nodeIndex(bitpos) + let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) + let nodeIdx = nodeIndex(bitpos) - rangeRemove(at: collIdx, fromRange: trieRange) - rangeInsert(node, at: nodeIdx, intoRange: trieRange) - } + rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount - 1) // TODO check, but moving one less should be accurate // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` dst.header.bitmap1 ^= bitpos @@ -712,13 +762,11 @@ final class BitmapIndexedMapNode: ManagedBuffer, Ma dst = src.copy() } - dst.withUnsafeMutablePointerRanges { _, trieRange in - let nodeIdx = nodeIndex(bitpos) - let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) + let nodeIdx = nodeIndex(bitpos) + let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - rangeRemove(at: nodeIdx, fromRange: trieRange) - rangeInsert(node, at: collIdx, intoRange: trieRange) - } + rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount - 1) // TODO check, but moving one less should be accurate // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` dst.header.bitmap1 |= bitpos diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 915d84da4..0b5e80d1b 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -302,6 +302,18 @@ func rangeInsert(_ element: T, at index: Int, intoRange range: Range(_ element: T, at index: Int, into baseAddress: UnsafeMutablePointer, count: Int) { + let src = baseAddress.advanced(by: index) + let dst = src.successor() + + dst.moveInitialize(from: src, count: count - index) + + src.initialize(to: element) +} + // `index` is the logical index starting at the rear, indexing to the left func rangeInsertReversed(_ element: T, at index: Int, intoRange range: Range>) { let seq = range.dropLast(index) @@ -325,6 +337,17 @@ func rangeRemove(at index: Int, fromRange range: Range(at index: Int, from baseAddress: UnsafeMutablePointer, count: Int) { + let src = baseAddress.advanced(by: index + 1) + let dst = src.predecessor() + + dst.deinitialize(count: 1) + dst.moveInitialize(from: src, count: count - index - 1) +} + // `index` is the logical index starting at the rear, indexing to the left func rangeRemoveReversed(at index: Int, fromRange range: Range>) { let seq = range.dropLast(index + 1) From 4805a864533c0d775f3b3ee98d15fb54b67dc92b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 28 Jun 2021 14:17:39 +0200 Subject: [PATCH 115/176] [Capsule] Replace `buffer` with `baseAddress` references --- Sources/Capsule/_BitmapIndexedMapNode.swift | 145 ++++++++------------ 1 file changed, 57 insertions(+), 88 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 9d7920483..ee50f6d81 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -11,8 +11,8 @@ fileprivate let fixedTrieCapacity = Bitmap.bitWidth -fileprivate let initialDataCapacity = 8 -fileprivate let initialTrieCapacity = 4 +fileprivate let initialDataCapacity = 4 +fileprivate let initialTrieCapacity = 1 final class BitmapIndexedMapNode: MapNode where Key: Hashable { @@ -21,13 +21,13 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var header: Header - var dataBuffer: UnsafeMutableBufferPointer - var trieBuffer: UnsafeMutableBufferPointer + let dataCapacity: Int // TODO constrain type + let trieCapacity: Int // TODO constrain type - var dataBaseAddress: UnsafeMutablePointer { dataBuffer.baseAddress! } - var trieBaseAddress: UnsafeMutablePointer { trieBuffer.baseAddress! } + let dataBaseAddress: UnsafeMutablePointer + let trieBaseAddress: UnsafeMutablePointer - private var rootBaseAddress: UnsafeMutableRawPointer { UnsafeMutableRawPointer(trieBuffer.baseAddress!) } + private var rootBaseAddress: UnsafeMutableRawPointer { UnsafeMutableRawPointer(trieBaseAddress) } deinit { // TODO use bitmaps since count is more or less capacity? @@ -54,8 +54,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } @inlinable - static func _allocate(dataCapacity: Int, trieCapacity: Int) -> (dataBuffer: UnsafeMutableBufferPointer, trieBuffer: UnsafeMutableBufferPointer) { - + static func _allocate(dataCapacity: Int, trieCapacity: Int) -> (dataBaseAddress: UnsafeMutablePointer, trieBaseAddress: UnsafeMutablePointer) { let dataCapacityInBytes = dataCapacity * MemoryLayout.stride let trieCapacityInBytes = trieCapacity * MemoryLayout.stride @@ -63,29 +62,17 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { byteCount: dataCapacityInBytes + trieCapacityInBytes, alignment: 8) - let dataBuffer = UnsafeMutableBufferPointer( - start: memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: dataCapacity), - count: dataCapacity) - - let trieBuffer = UnsafeMutableBufferPointer( - start: memory.bindMemory(to: TrieBufferElement.self, capacity: trieCapacity), - count: trieCapacity) + let dataBaseAddress = memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: dataCapacity) + let trieBaseAddress = memory.bindMemory(to: TrieBufferElement.self, capacity: trieCapacity) - return (dataBuffer, trieBuffer) + return (dataBaseAddress, trieBaseAddress) } func copy(withDataCapacityFactor dataFactor: Int = 1, withTrieCapacityFactor trieFactor: Int = 1) -> Self { let src = self + let dst = Self(dataCapacity: src.dataCapacity * dataFactor, trieCapacity: src.trieCapacity * trieFactor) - let (dstDataBuffer, dstTrieBuffer) = Self._allocate(dataCapacity: src.dataBuffer.count * dataFactor, trieCapacity: src.trieBuffer.count * trieFactor) - - let dst = Self() dst.header = src.header - dst.dataBuffer = dstDataBuffer - dst.trieBuffer = dstTrieBuffer -// dst.dataBaseAddress = dstDataBuffer.baseAddress! -// dst.trieBaseAddress = dstTrieBuffer.baseAddress! - dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataMap.nonzeroBitCount) dst.trieBaseAddress.initialize(from: src.trieBaseAddress, count: src.header.trieMap.nonzeroBitCount) @@ -102,121 +89,103 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return false } -// guard count <= capacity else { -// return false -// } - return true } var contentInvariant: Bool { - dataSliceInvariant && nodeSliceInvariant && collSliceInvariant - } - - var dataSliceInvariant: Bool { - dataBuffer.prefix(payloadArity).allSatisfy { $0 is ReturnPayload } + nodeSliceInvariant && collSliceInvariant } var nodeSliceInvariant: Bool { - trieBuffer.prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieMap.nonzeroBitCount).prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - trieBuffer.dropFirst(bitmapIndexedNodeArity).prefix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieMap.nonzeroBitCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } + } + + // default initializer + init(dataCapacity: Int, trieCapacity: Int) { + let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) + + self.header = Header(bitmap1: 0, bitmap2: 0) + self.dataBaseAddress = dataBaseAddress + self.trieBaseAddress = trieBaseAddress + + self.dataCapacity = dataCapacity + self.trieCapacity = trieCapacity + + assert(self.invariant) } - init() { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init() { + self.init(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) self.header = Header(bitmap1: 0, bitmap2: 0) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! assert(self.invariant) } - init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { + self.init() self.header = Header(bitmap1: dataMap, bitmap2: 0) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! // dataBuffer[0] = (firstKey, firstValue) - dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) + self.dataBaseAddress.initialize(to: (firstKey, firstValue)) assert(self.invariant) } - init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { + self.init() self.header = Header(bitmap1: dataMap, bitmap2: 0) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! // dataBuffer[0] = (firstKey, firstValue) // dataBuffer[1] = (secondKey, secondValue) - dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) - dataBuffer.baseAddress!.successor().initialize(to: (secondKey, secondValue)) + self.dataBaseAddress.initialize(to: (firstKey, firstValue)) + self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) assert(self.invariant) } - init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { + self.init() self.header = Header(bitmap1: 0, bitmap2: nodeMap) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! // trieBuffer[0] = firstNode - trieBuffer.baseAddress!.initialize(to: firstNode) + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } - init(collMap: Bitmap, firstNode: HashCollisionMapNode) { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init(collMap: Bitmap, firstNode: HashCollisionMapNode) { + self.init() self.header = Header(bitmap1: collMap, bitmap2: collMap) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! // trieBuffer[0] = firstNode - trieBuffer.baseAddress!.initialize(to: firstNode) + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } - init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { - let (dataBuffer, trieBuffer) = Self._allocate(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) + convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { + self.init() self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) - self.dataBuffer = dataBuffer - self.trieBuffer = trieBuffer -// self.dataBaseAddress = dataBuffer.baseAddress! -// self.trieBaseAddress = trieBuffer.baseAddress! // dataBuffer[0] = (firstKey, firstValue) // trieBuffer[0] = firstNode - dataBuffer.baseAddress!.initialize(to: (firstKey, firstValue)) - trieBuffer.baseAddress!.initialize(to: firstNode) + self.dataBaseAddress.initialize(to: (firstKey, firstValue)) + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } @@ -483,7 +452,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - trieBuffer[index] as! BitmapIndexedMapNode + trieBaseAddress[index] as! BitmapIndexedMapNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { @@ -497,7 +466,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBuffer[slotIndex]) + let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } @@ -507,7 +476,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - trieBuffer[bitmapIndexedNodeArity + index] as! HashCollisionMapNode + trieBaseAddress[bitmapIndexedNodeArity + index] as! HashCollisionMapNode } var hasNodes: Bool { (nodeMap | collMap) != 0 } @@ -527,7 +496,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var payloadArity: Int { dataMap.nonzeroBitCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { - dataBuffer[index] // as! ReturnPayload + dataBaseAddress[index] // as! ReturnPayload } var sizePredicate: SizePredicate { SizePredicate(self) } @@ -557,7 +526,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // let (key, _) = dst.dataBuffer[idx] // as! ReturnPayload // dst.dataBuffer[idx] = (key, newValue) - dst.dataBuffer[idx].value = newValue + dst.dataBaseAddress[idx].value = newValue assert(dst.invariant) return dst @@ -583,7 +552,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { dst = src.copy() } - dst.trieBuffer[idx] = newNode as TrieBufferElement + dst.trieBaseAddress[idx] = newNode as TrieBufferElement assert(dst.invariant) return dst @@ -593,7 +562,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForData = dataMap.nonzeroBitCount < dataBuffer.count + let hasRoomForData = dataMap.nonzeroBitCount < dataCapacity if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src @@ -635,7 +604,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieBuffer.count + let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieCapacity if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src @@ -661,7 +630,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieBuffer.count + let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieCapacity if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src From d7bda3b2e365f4b768c4016c0f7f21652cabd240 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 28 Jun 2021 14:28:46 +0200 Subject: [PATCH 116/176] [Capsule] Unclutter segment counts --- Sources/Capsule/_BitmapIndexedMapNode.swift | 76 +++++++++++---------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index ee50f6d81..6ca779b2e 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -32,8 +32,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { deinit { // TODO use bitmaps since count is more or less capacity? - dataBaseAddress.deinitialize(count: header.dataMap.nonzeroBitCount) - trieBaseAddress.deinitialize(count: header.trieMap.nonzeroBitCount) + dataBaseAddress.deinitialize(count: header.dataCount) + trieBaseAddress.deinitialize(count: header.trieCount) rootBaseAddress.deallocate() } @@ -73,8 +73,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let dst = Self(dataCapacity: src.dataCapacity * dataFactor, trieCapacity: src.trieCapacity * trieFactor) dst.header = src.header - dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataMap.nonzeroBitCount) - dst.trieBaseAddress.initialize(from: src.trieBaseAddress, count: src.header.trieMap.nonzeroBitCount) + dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataCount) + dst.trieBaseAddress.initialize(from: src.trieBaseAddress, count: src.header.trieCount) assert(dst.invariant) return dst @@ -97,11 +97,11 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } var nodeSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieMap.nonzeroBitCount).prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieMap.nonzeroBitCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } } // default initializer @@ -447,9 +447,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - var hasBitmapIndexedNodes: Bool { nodeMap != 0 } + var hasBitmapIndexedNodes: Bool { header.nodeMap != 0 } - var bitmapIndexedNodeArity: Int { nodeMap.nonzeroBitCount } + var bitmapIndexedNodeArity: Int { header.nodeCount } func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { trieBaseAddress[index] as! BitmapIndexedMapNode @@ -471,17 +471,19 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - var hasHashCollisionNodes: Bool { collMap != 0 } + var hasHashCollisionNodes: Bool { header.collMap != 0 } - var hashCollisionNodeArity: Int { collMap.nonzeroBitCount } + var hashCollisionNodeArity: Int { header.collCount } func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { trieBaseAddress[bitmapIndexedNodeArity + index] as! HashCollisionMapNode } - var hasNodes: Bool { (nodeMap | collMap) != 0 } + // TODO rename, not accurate any more + var hasNodes: Bool { header.trieMap != 0 } - var nodeArity: Int { (nodeMap | collMap).nonzeroBitCount } + // TODO rename, not accurate any more + var nodeArity: Int { header.trieCount } func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { if index < bitmapIndexedNodeArity { @@ -491,9 +493,9 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - var hasPayload: Bool { dataMap != 0 } + var hasPayload: Bool { header.dataMap != 0 } - var payloadArity: Int { dataMap.nonzeroBitCount } + var payloadArity: Int { header.dataCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { dataBaseAddress[index] // as! ReturnPayload @@ -507,10 +509,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } - // TODO deprecate? - /// The number of (non-contiguous) occupied buffer cells. - final var count: Int { (header.bitmap1 | header.bitmap2).nonzeroBitCount } - func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -562,7 +560,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForData = dataMap.nonzeroBitCount < dataCapacity + let hasRoomForData = header.dataCount < dataCapacity if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src @@ -571,7 +569,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert((key, value), at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeInsert((key, value), at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap, collMap` dst.header.bitmap1 |= bitpos @@ -591,7 +589,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap` dst.header.bitmap1 ^= bitpos @@ -604,7 +602,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieCapacity + let hasRoomForTrie = header.trieCount < trieCapacity if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src @@ -613,10 +611,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` dst.header.bitmap1 ^= bitpos @@ -630,7 +628,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - let hasRoomForTrie = header.trieMap.nonzeroBitCount < trieCapacity + let hasRoomForTrie = header.trieCount < trieCapacity if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src @@ -639,10 +637,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` dst.header.bitmap2 |= bitpos @@ -662,10 +660,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let nodeIdx = indexFrom(nodeMap, bitpos) - rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` dst.header.bitmap1 |= bitpos @@ -686,10 +684,10 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) + rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataMap.nonzeroBitCount) + rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` dst.header.bitmap2 ^= bitpos @@ -711,8 +709,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) let nodeIdx = nodeIndex(bitpos) - rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) - rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount - 1) // TODO check, but moving one less should be accurate + rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) + rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieCount - 1) // TODO check, but moving one less should be accurate // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` dst.header.bitmap1 ^= bitpos @@ -734,8 +732,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let nodeIdx = nodeIndex(bitpos) let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount) - rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieMap.nonzeroBitCount - 1) // TODO check, but moving one less should be accurate + rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) + rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieCount - 1) // TODO check, but moving one less should be accurate // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` dst.header.bitmap1 |= bitpos @@ -806,6 +804,14 @@ struct Header { var trieMap: Bitmap { bitmap2 } + + var dataCount: Int { dataMap.nonzeroBitCount } + + var nodeCount: Int { nodeMap.nonzeroBitCount } + + var collCount: Int { collMap.nonzeroBitCount } + + var trieCount: Int { trieMap.nonzeroBitCount } } extension Header { From b2808c45bbd067824746e7135476dd253e2f215a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 28 Jun 2021 15:06:24 +0200 Subject: [PATCH 117/176] [Capsule] Clean-up comments and unused constants --- Sources/Capsule/_BitmapIndexedMapNode.swift | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 6ca779b2e..1d83c13b7 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -9,8 +9,6 @@ // //===----------------------------------------------------------------------===// -fileprivate let fixedTrieCapacity = Bitmap.bitWidth - fileprivate let initialDataCapacity = 4 fileprivate let initialTrieCapacity = 1 @@ -30,8 +28,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { private var rootBaseAddress: UnsafeMutableRawPointer { UnsafeMutableRawPointer(trieBaseAddress) } deinit { - // TODO use bitmaps since count is more or less capacity? - dataBaseAddress.deinitialize(count: header.dataCount) trieBaseAddress.deinitialize(count: header.trieCount) @@ -104,7 +100,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } } - // default initializer init(dataCapacity: Int, trieCapacity: Int) { let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) @@ -131,7 +126,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap, bitmap2: 0) - // dataBuffer[0] = (firstKey, firstValue) +// self.dataBaseAddress[0] = (firstKey, firstValue) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) @@ -143,8 +138,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap, bitmap2: 0) -// dataBuffer[0] = (firstKey, firstValue) -// dataBuffer[1] = (secondKey, secondValue) +// self.dataBaseAddress[0] = (firstKey, firstValue) +// self.dataBaseAddress[1] = (secondKey, secondValue) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) @@ -157,7 +152,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: 0, bitmap2: nodeMap) -// trieBuffer[0] = firstNode +// self.trieBaseAddress[0] = firstNode self.trieBaseAddress.initialize(to: firstNode) @@ -169,7 +164,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: collMap, bitmap2: collMap) -// trieBuffer[0] = firstNode +// self.trieBaseAddress[0] = firstNode self.trieBaseAddress.initialize(to: firstNode) @@ -181,8 +176,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) -// dataBuffer[0] = (firstKey, firstValue) -// trieBuffer[0] = firstNode +// self.dataBaseAddress[0] = (firstKey, firstValue) +// self.trieBaseAddress[0] = firstNode self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) From cf5310624b8e07dffa540944debfacefef7ad9fd Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 12:50:01 +0100 Subject: [PATCH 118/176] [Capsule] Use provided `hasher` argument --- Sources/Capsule/HashMap+Hashable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index a597fe0b2..ee34e098a 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -14,7 +14,7 @@ extension HashMap: Hashable where Value: Hashable { public func hash(into hasher: inout Hasher) { var commutativeHash = 0 for (key, value) in self { - var elementHasher = Hasher() + var elementHasher = hasher elementHasher.combine(key) elementHasher.combine(value) commutativeHash ^= elementHasher.finalize() From d295c304596ef375cfaa6aac1ae6a9cdabb7db3e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 13:01:30 +0100 Subject: [PATCH 119/176] [Capsule] Remove unused key set hash code --- Sources/Capsule/HashMap+Equatable.swift | 1 - Sources/Capsule/HashMap+Hashable.swift | 1 - Sources/Capsule/HashMap.swift | 17 ++++++----------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/HashMap+Equatable.swift index a88c19ae4..77e88f844 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/HashMap+Equatable.swift @@ -13,7 +13,6 @@ extension HashMap: Equatable where Value: Equatable { public static func == (lhs: HashMap, rhs: HashMap) -> Bool { lhs.cachedSize == rhs.cachedSize && - lhs.cachedKeySetHashCode == rhs.cachedKeySetHashCode && (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) } } diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/HashMap+Hashable.swift index ee34e098a..670220e61 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/HashMap+Hashable.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -// TODO settle on (commutative) hash semantics that is reconcilable with `cachedKeySetHashCode` extension HashMap: Hashable where Value: Hashable { public func hash(into hasher: inout Hasher) { var commutativeHash = 0 diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 16a11f207..660e04bc1 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -11,21 +11,19 @@ public struct HashMap where Key: Hashable { var rootNode: BitmapIndexedMapNode - var cachedKeySetHashCode: Int var cachedSize: Int - fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedKeySetHashCode: Int, _ cachedSize: Int) { + fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedSize: Int) { self.rootNode = rootNode - self.cachedKeySetHashCode = cachedKeySetHashCode self.cachedSize = cachedSize } public init() { - self.init(BitmapIndexedMapNode(), 0, 0) + self.init(BitmapIndexedMapNode(), 0) } public init(_ map: HashMap) { - self.init(map.rootNode, map.cachedKeySetHashCode, map.cachedSize) + self.init(map.rootNode, map.cachedSize) } // TODO consider removing `unchecked` version, since it's only referenced from within the test suite @@ -131,11 +129,9 @@ public struct HashMap where Key: Hashable { if effect.modified { if effect.replacedValue { self.rootNode = newRootNode - // self.cachedKeySetHashCode = cachedKeySetHashCode // self.cachedSize = cachedSize } else { self.rootNode = newRootNode - self.cachedKeySetHashCode = cachedKeySetHashCode ^ keyHash self.cachedSize = cachedSize + 1 } } @@ -149,9 +145,9 @@ public struct HashMap where Key: Hashable { if effect.modified { if effect.replacedValue { - return Self(newRootNode, cachedKeySetHashCode, cachedSize) + return Self(newRootNode, cachedSize) } else { - return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize + 1) + return Self(newRootNode, cachedSize + 1) } } else { return self } } @@ -177,7 +173,6 @@ public struct HashMap where Key: Hashable { if effect.modified { self.rootNode = newRootNode - self.cachedKeySetHashCode = cachedKeySetHashCode ^ keyHash self.cachedSize = cachedSize - 1 } } @@ -189,7 +184,7 @@ public struct HashMap where Key: Hashable { let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) if effect.modified { - return Self(newRootNode, cachedKeySetHashCode ^ keyHash, cachedSize - 1) + return Self(newRootNode, cachedSize - 1) } else { return self } } From 285619778532f9c0e623c7ceed3b68cd0cebf242 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 13:07:25 +0100 Subject: [PATCH 120/176] [Capsule] Make alignment calculation robust --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 1d83c13b7..15116ca2d 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -56,7 +56,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let memory = UnsafeMutableRawPointer.allocate( byteCount: dataCapacityInBytes + trieCapacityInBytes, - alignment: 8) + alignment: Swift.max(MemoryLayout.alignment, MemoryLayout.alignment)) let dataBaseAddress = memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: dataCapacity) let trieBaseAddress = memory.bindMemory(to: TrieBufferElement.self, capacity: trieCapacity) From 7bb9cbad57f0cc170bd900e8fd910f17795b4bf9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 13:10:41 +0100 Subject: [PATCH 121/176] [Capsule] Ensure object layout of `Node` is class reference --- Sources/Capsule/_Common.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 0b5e80d1b..263bef8a9 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -105,7 +105,7 @@ extension SizePredicate { } } -protocol Node { +protocol Node: AnyObject { associatedtype ReturnPayload associatedtype ReturnBitmapIndexedNode: Node associatedtype ReturnHashCollisionNode: Node From 49c478877aa6d8b1d1a4262504fd2b0562fe2b97 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 13:18:52 +0100 Subject: [PATCH 122/176] [Capsule] Convert to unsigned bitmap type --- Sources/Capsule/_Common.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 263bef8a9..9ec99f590 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -13,7 +13,7 @@ func computeHash(_ value: T) -> Int { value.hashValue } -typealias Bitmap = Int32 +typealias Bitmap = UInt32 let bitPartitionSize: Int = 5 @@ -36,7 +36,7 @@ func indexFrom(_ bitmap: Bitmap, _ bitpos: Bitmap) -> Int { } func indexFrom(_ bitmap: Bitmap, _ mask: Int, _ bitpos: Bitmap) -> Int { - (bitmap == -1) ? mask : indexFrom(bitmap, bitpos) + (bitmap == Bitmap.max) ? mask : indexFrom(bitmap, bitpos) } enum TrieNode { From 5604bee3d262006e89b37617c98435e2ca6a2f85 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 13:37:31 +0100 Subject: [PATCH 123/176] [Capsule] Make `ReturnPayload` binding explicit --- Sources/Capsule/_BitmapIndexedMapNode.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 15116ca2d..0481b620a 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -14,6 +14,8 @@ fileprivate let initialTrieCapacity = 1 final class BitmapIndexedMapNode: MapNode where Key: Hashable { + typealias ReturnPayload = (key: Key, value: Value) + typealias DataBufferElement = ReturnPayload // `ReturnPayload` or `Any` typealias TrieBufferElement = AnyObject From f809ec1e795dba3923e5e179ef42b7431272b812 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 15:13:41 +0100 Subject: [PATCH 124/176] [Capsule] Remove alternatives that have undefined behavior --- Sources/Capsule/_BitmapIndexedMapNode.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index 0481b620a..e36a25207 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -128,8 +128,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap, bitmap2: 0) -// self.dataBaseAddress[0] = (firstKey, firstValue) - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) assert(self.invariant) @@ -140,9 +138,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap, bitmap2: 0) -// self.dataBaseAddress[0] = (firstKey, firstValue) -// self.dataBaseAddress[1] = (secondKey, secondValue) - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) @@ -154,8 +149,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: 0, bitmap2: nodeMap) -// self.trieBaseAddress[0] = firstNode - self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) @@ -166,8 +159,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: collMap, bitmap2: collMap) -// self.trieBaseAddress[0] = firstNode - self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) @@ -178,9 +169,6 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) -// self.dataBaseAddress[0] = (firstKey, firstValue) -// self.trieBaseAddress[0] = firstNode - self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) From 40e0f806a438f7baac5730e061727509d9f29ebf Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 15:36:35 +0100 Subject: [PATCH 125/176] [Capsule] Make `get` internal in favor of subscript --- Sources/Capsule/HashMap.swift | 2 +- Tests/CapsuleTests/CapsuleSmokeTests.swift | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index 660e04bc1..f6137e38d 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -111,7 +111,7 @@ public struct HashMap where Key: Hashable { rootNode.containsKey(key, computeHash(key), 0) } - public func get(_ key: Key) -> Value? { + func get(_ key: Key) -> Value? { rootNode.get(key, computeHash(key), 0) } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 1435ad636..47d004a7b 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -68,12 +68,12 @@ final class CapsuleSmokeTests: CollectionTestCase { res3[2] = "y" // in-place expectEqual(res2.count, 2) - expectEqual(res2.get(1), "a") - expectEqual(res2.get(2), "b") + expectEqual(res2[1], "a") + expectEqual(res2[2], "b") expectEqual(res3.count, 2) - expectEqual(res3.get(1), "x") - expectEqual(res3.get(2), "y") + expectEqual(res3[1], "x") + expectEqual(res3[2], "y") } func testTriggerOverwrite2() { @@ -95,13 +95,13 @@ final class CapsuleSmokeTests: CollectionTestCase { print("Yeah!") - expectEqual(res1.get(CollidableInt(10, 01)), "x") - expectEqual(res1.get(CollidableInt(11, 33)), "x") - expectEqual(res1.get(CollidableInt(20, 02)), "y") + expectEqual(res1[CollidableInt(10, 01)], "x") + expectEqual(res1[CollidableInt(11, 33)], "x") + expectEqual(res1[CollidableInt(20, 02)], "y") - expectEqual(res2.get(CollidableInt(10, 01)), "a") - expectEqual(res2.get(CollidableInt(11, 33)), "a") - expectEqual(res2.get(CollidableInt(20, 02)), "b") + expectEqual(res2[CollidableInt(10, 01)], "a") + expectEqual(res2[CollidableInt(11, 33)], "a") + expectEqual(res2[CollidableInt(20, 02)], "b") } From 108289e8fcfb44d4b54494406a85984015141fbe Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 20:39:42 +0100 Subject: [PATCH 126/176] [Capsule] Fix heap buffer overflow issue during deletion --- Sources/Capsule/_BitmapIndexedMapNode.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedMapNode.swift index e36a25207..443da5fb4 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedMapNode.swift @@ -638,10 +638,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced { + let hasRoomForData = header.dataCount < dataCapacity + + if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src } else { - dst = src.copy() + dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } let nodeIdx = indexFrom(nodeMap, bitpos) @@ -662,10 +664,12 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode - if isStorageKnownUniquelyReferenced { + let hasRoomForData = header.dataCount < dataCapacity + + if isStorageKnownUniquelyReferenced && hasRoomForData { dst = src } else { - dst = src.copy() + dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) From 1ffd819a0e7c7831547163a3cf78e6747973f197 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 1 Dec 2021 21:01:04 +0100 Subject: [PATCH 127/176] [Capsule] Draft benchmark configuration --- Benchmarks/Libraries/Capsule.json | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Benchmarks/Libraries/Capsule.json diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/Capsule.json new file mode 100644 index 000000000..929733c05 --- /dev/null +++ b/Benchmarks/Libraries/Capsule.json @@ -0,0 +1,87 @@ +{ + "kind": "group", + "title": "Capsule Benchmarks", + "directory": "Results", + "contents": [ + { + "kind": "group", + "title": "HashMap Operations", + "contents": [ + { + "kind": "chart", + "title": "all", + "tasks": [ + "HashMap init(uniqueKeysWithValues:)", + "HashMap sequential iteration", + "HashMap subscript, successful lookups", + "HashMap subscript, unsuccessful lookups", + "HashMap subscript, noop setter", + "HashMap subscript, set existing", + "HashMap subscript, _modify", + "HashMap subscript, insert", + "HashMap subscript, insert, reserving capacity", + "HashMap subscript, remove existing", + "HashMap subscript, remove missing", + "HashMap defaulted subscript, successful lookups", + "HashMap defaulted subscript, unsuccessful lookups", + "HashMap defaulted subscript, _modify existing", + "HashMap defaulted subscript, _modify missing", + "HashMap updateValue(_:forKey:), existing", + "HashMap updateValue(_:forKey:), insert", + "HashMap random removals (existing keys)", + "HashMap random removals (missing keys)", + ] + } + ] + }, + { + "kind": "group", + "title": "HashSet Operations", + "contents": [ + ] + }, + { + "kind": "group", + "title": "Comparisons against reference implementations", + "directory": "versus", + "contents": [ + { + "kind": "chart", + "title": "init(uniqueKeysWithValues:)", + "tasks": [ + "HashMap init(uniqueKeysWithValues:)", + "Dictionary init(uniqueKeysWithValues:)", + "OrderedDictionary init(uniqueKeysWithValues:)", + ] + }, + { + "kind": "chart", + "title": "sequential iteration", + "tasks": [ + "HashMap sequential iteration", + "Dictionary sequential iteration", + "OrderedDictionary sequential iteration", + ] + }, + { + "kind": "chart", + "title": "subscript, insert", + "tasks": [ + "HashMap subscript, insert", + "Dictionary subscript, insert", + "OrderedDictionary subscript, append", + ] + }, + { + "kind": "chart", + "title": "subscript, remove existing", + "tasks": [ + "HashMap subscript, remove existing", + "Dictionary subscript, remove existing", + "OrderedDictionary subscript, remove existing", + ] + }, + ] + }, + ] + } From 94505e600e1dd2a8881b625f570a73361f6f3ee4 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 6 Dec 2021 17:23:13 +0100 Subject: [PATCH 128/176] [Capsule] Make `ReturnPayload` binding explicit in hash collision node --- Sources/Capsule/_HashCollisionMapNode.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionMapNode.swift index 7d123e486..38e043645 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionMapNode.swift @@ -10,6 +10,9 @@ //===----------------------------------------------------------------------===// final class HashCollisionMapNode: MapNode where Key: Hashable { + + typealias ReturnPayload = (key: Key, value: Value) + let hash: Int let content: [(key: Key, value: Value)] From 624b245c7e9d02c2420e3857d584cd72bf80df81 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 11:27:35 +0100 Subject: [PATCH 129/176] [Capsule] Rework `MapKeyValueTupleIterator` --- Sources/Capsule/HashMap.swift | 76 +++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/HashMap.swift index f6137e38d..50a9dca75 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/HashMap.swift @@ -199,33 +199,77 @@ public struct HashMap where Key: Hashable { } } -public struct MapKeyValueTupleIterator { - private var baseIterator: ChampBaseIterator, HashCollisionMapNode> +/// +/// 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). +/// +public struct MapKeyValueTupleIterator: IteratorProtocol { + + private var payloadIterator: UnsafeBufferPointer<(key: Key, value: Value)>.Iterator? + + private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? + private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] init(rootNode: BitmapIndexedMapNode) { - self.baseIterator = ChampBaseIterator(rootNode: .bitmapIndexed(rootNode)) + trieIteratorStackRemainder = [] + trieIteratorStackRemainder.reserveCapacity(maxDepth) + + if rootNode.hasNodes { trieIteratorStackTop = makeTrieIterator(rootNode) } + if rootNode.hasPayload { payloadIterator = makePayloadIterator(rootNode) } } -} -extension MapKeyValueTupleIterator: IteratorProtocol { - public mutating func next() -> (key: Key, value: Value)? { - guard baseIterator.hasNext() else { return nil } + // TODO consider moving to `BitmapIndexedMapNode` + private func makePayloadIterator(_ node: BitmapIndexedMapNode) -> UnsafeBufferPointer<(key: Key, value: Value)>.Iterator { + UnsafeBufferPointer(start: node.dataBaseAddress, count: node.payloadArity).makeIterator() + } - let payload: (Key, Value) + // TODO consider moving to `BitmapIndexedMapNode` + private func makeTrieIterator(_ node: BitmapIndexedMapNode) -> UnsafeBufferPointer.Iterator { + UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() + } - // TODO remove duplication in specialization - switch baseIterator.currentValueNode! { - case .bitmapIndexed(let node): - payload = node.getPayload(baseIterator.currentValueCursor) - case .hashCollision(let node): - payload = node.getPayload(baseIterator.currentValueCursor) + public mutating func next() -> (key: Key, value: Value)? { + if let payload = payloadIterator?.next() { + return payload } - baseIterator.currentValueCursor += 1 - return payload + while trieIteratorStackTop != nil { + if let nextAnyObject = trieIteratorStackTop!.next() { + switch nextAnyObject { + case let nextNode as BitmapIndexedMapNode: + if nextNode.hasNodes { + trieIteratorStackRemainder.append(trieIteratorStackTop!) + trieIteratorStackTop = makeTrieIterator(nextNode) + } + if nextNode.hasPayload { + payloadIterator = makePayloadIterator(nextNode) + return payloadIterator?.next() + } + case let nextNode as HashCollisionMapNode: + payloadIterator = nextNode.content.withUnsafeBufferPointer { $0.makeIterator() } + return payloadIterator?.next() + default: + break + } + } else { + trieIteratorStackTop = trieIteratorStackRemainder.popLast() + } + } + + // Clean-up state + payloadIterator = nil + + assert(payloadIterator == nil) + assert(trieIteratorStackTop == nil) + assert(trieIteratorStackRemainder.isEmpty) + + return nil } } +// TODO consider reworking similar to `MapKeyValueTupleIterator` +// (would require a reversed variant of `UnsafeBufferPointer<(key: Key, value: Value)>.Iterator`) public struct MapKeyValueTupleReverseIterator { private var baseIterator: ChampBaseReverseIterator, HashCollisionMapNode> From 0f0c0bdc90e222b76a2c165be41b2d5796e2b627 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 17:10:14 +0100 Subject: [PATCH 130/176] [Capsule] Pin type to Capsule hash-map --- Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift index f8f8a16a0..0c88a47fe 100644 --- a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift +++ b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift @@ -138,7 +138,7 @@ extension Benchmark { title: "HashMap subscript, insert", input: [Int].self ) { input in - var d: [Int: Int] = [:] + var d: HashMap = [:] for i in input { d[i] = 2 * i } @@ -150,7 +150,7 @@ extension Benchmark { title: "HashMap subscript, insert, reserving capacity", input: [Int].self ) { input in - var d: [Int: Int] = [:] + var d: HashMap = [:] d.reserveCapacity(input.count) for i in input { d[i] = 2 * i From 5fad00e87bca7ccc224a949618076824d7e9c6a9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 17:11:45 +0100 Subject: [PATCH 131/176] [Capsule] Remove `reserveCapacity` test since it is not supported --- .../Benchmarks/CapsuleHashMapBenchmarks.swift | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift index 0c88a47fe..900aa2fbc 100644 --- a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift +++ b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift @@ -146,19 +146,6 @@ extension Benchmark { blackHole(d) } - self.addSimple( - title: "HashMap subscript, insert, reserving capacity", - input: [Int].self - ) { input in - var d: HashMap = [:] - d.reserveCapacity(input.count) - for i in input { - d[i] = 2 * i - } - precondition(d.count == input.count) - blackHole(d) - } - self.add( title: "HashMap subscript, remove existing", input: ([Int], [Int]).self From f956009ac241a215d18c75b85ab90a7ef8cc1b4c Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 17:16:31 +0100 Subject: [PATCH 132/176] [Capsule] Add `[COW] subscript, insert` benchmark --- .../Benchmarks/CapsuleHashMapBenchmarks.swift | 20 +++++++++++++++++++ .../Benchmarks/DictionaryBenchmarks.swift | 20 +++++++++++++++++++ .../OrderedDictionaryBenchmarks.swift | 20 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift index 900aa2fbc..e64102fa3 100644 --- a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift +++ b/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift @@ -146,6 +146,26 @@ extension Benchmark { blackHole(d) } + self.add( + title: "HashMap [COW] subscript, insert", + input: ([Int], [Int]).self + ) { input, insert in + return { timer in + let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in insert { + var e = d + e[c + i] = 2 * (c + i) + precondition(e.count == input.count + 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.add( title: "HashMap subscript, remove existing", input: ([Int], [Int]).self diff --git a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift index 8388fe610..5020a4ea0 100644 --- a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift @@ -145,6 +145,26 @@ extension Benchmark { blackHole(d) } + self.add( + title: "Dictionary [COW] subscript, insert", + input: ([Int], [Int]).self + ) { input, insert in + return { timer in + let d = Dictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in insert { + var e = d + e[c + i] = 2 * (c + i) + precondition(e.count == input.count + 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.addSimple( title: "Dictionary subscript, insert, reserving capacity", input: [Int].self diff --git a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift index f68c2aa86..bb3dff8ed 100644 --- a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift @@ -176,6 +176,26 @@ extension Benchmark { blackHole(d) } + self.add( + title: "OrderedDictionary [COW] subscript, append", + input: ([Int], [Int]).self + ) { input, insert in + return { timer in + let d = OrderedDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let c = input.count + timer.measure { + for i in insert { + var e = d + e[c + i] = 2 * (c + i) + precondition(e.count == input.count + 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.addSimple( title: "OrderedDictionary subscript, append, reserving capacity", input: [Int].self From fc60fae4d7d5e4c468fe7f08430b1bb515321125 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 17:39:21 +0100 Subject: [PATCH 133/176] [Capsule] Rename `HashMap` to `PersistentDictionary` --- ...t => PersistentDictionaryBenchmarks.swift} | 94 ++++----- Benchmarks/Libraries/Capsule.json | 180 +++++++++--------- Benchmarks/benchmark-tool/main.swift | 2 +- Package.swift | 2 +- ...tDictionary+CustomStringConvertible.swift} | 2 +- ...t => PersistentDictionary+Equatable.swift} | 4 +- ...nary+ExpressibleByDictionaryLiteral.swift} | 2 +- ...ft => PersistentDictionary+Hashable.swift} | 2 +- ...ft => PersistentDictionary+Sequence.swift} | 6 +- ...shMap.swift => PersistentDictionary.swift} | 44 ++--- ...ift => _BitmapIndexedDictionaryNode.swift} | 66 +++---- ...apEffect.swift => _DictionaryEffect.swift} | 2 +- .../{_MapNode.swift => _DictionaryNode.swift} | 6 +- ...ift => _HashCollisionDictionaryNode.swift} | 26 +-- Tests/CapsuleTests/Capsule Tests.swift | 102 +++++----- Tests/CapsuleTests/Capsule Utils.swift | 4 +- Tests/CapsuleTests/CapsuleSmokeTests.swift | 101 ++++++---- Utils/run-capsule-benchmarks.zsh | 2 +- 18 files changed, 341 insertions(+), 306 deletions(-) rename Benchmarks/Benchmarks/{CapsuleHashMapBenchmarks.swift => PersistentDictionaryBenchmarks.swift} (62%) rename Sources/Capsule/{HashMap+CustomStringConvertible.swift => PersistentDictionary+CustomStringConvertible.swift} (93%) rename Sources/Capsule/{HashMap+Equatable.swift => PersistentDictionary+Equatable.swift} (78%) rename Sources/Capsule/{HashMap+ExpressibleByDictionaryLiteral.swift => PersistentDictionary+ExpressibleByDictionaryLiteral.swift} (90%) rename Sources/Capsule/{HashMap+Hashable.swift => PersistentDictionary+Hashable.swift} (92%) rename Sources/Capsule/{HashMap+Sequence.swift => PersistentDictionary+Sequence.swift} (69%) rename Sources/Capsule/{HashMap.swift => PersistentDictionary.swift} (85%) rename Sources/Capsule/{_BitmapIndexedMapNode.swift => _BitmapIndexedDictionaryNode.swift} (91%) rename Sources/Capsule/{_MapEffect.swift => _DictionaryEffect.swift} (96%) rename Sources/Capsule/{_MapNode.swift => _DictionaryNode.swift} (81%) rename Sources/Capsule/{_HashCollisionMapNode.swift => _HashCollisionDictionaryNode.swift} (73%) diff --git a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift similarity index 62% rename from Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift rename to Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index e64102fa3..42448b196 100644 --- a/Benchmarks/Benchmarks/CapsuleHashMapBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -13,22 +13,22 @@ import CollectionsBenchmark import Capsule extension Benchmark { - public mutating func addCapsuleHashMapBenchmarks() { + public mutating func addPersistentDictionaryBenchmarks() { self.add( - title: "HashMap init(uniqueKeysWithValues:)", + title: "PersistentDictionary init(uniqueKeysWithValues:)", input: [Int].self ) { input in let keysAndValues = input.map { ($0, 2 * $0) } return { timer in - blackHole(HashMap(uniqueKeysWithValues: keysAndValues)) + blackHole(PersistentDictionary(uniqueKeysWithValues: keysAndValues)) } } self.add( - title: "HashMap sequential iteration", + title: "PersistentDictionary sequential iteration", input: [Int].self ) { input in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) return { timer in for item in d { blackHole(item) @@ -37,10 +37,10 @@ extension Benchmark { } // self.add( -// title: "HashMap.Keys sequential iteration", +// title: "PersistentDictionary.Keys sequential iteration", // input: [Int].self // ) { input in -// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) // return { timer in // for item in d.keys { // blackHole(item) @@ -49,10 +49,10 @@ extension Benchmark { // } // // self.add( -// title: "HashMap.Values sequential iteration", +// title: "PersistentDictionary.Values sequential iteration", // input: [Int].self // ) { input in -// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) // return { timer in // for item in d.values { // blackHole(item) @@ -61,10 +61,10 @@ extension Benchmark { // } self.add( - title: "HashMap subscript, successful lookups", + title: "PersistentDictionary subscript, successful lookups", input: ([Int], [Int]).self ) { input, lookups in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) return { timer in for i in lookups { precondition(d[i] == 2 * i) @@ -73,10 +73,10 @@ extension Benchmark { } self.add( - title: "HashMap subscript, unsuccessful lookups", + title: "PersistentDictionary subscript, unsuccessful lookups", input: ([Int], [Int]).self ) { input, lookups in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) let c = input.count return { timer in for i in lookups { @@ -86,11 +86,11 @@ extension Benchmark { } self.add( - title: "HashMap subscript, noop setter", + title: "PersistentDictionary subscript, noop setter", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) let c = input.count timer.measure { for i in lookups { @@ -103,11 +103,11 @@ extension Benchmark { } self.add( - title: "HashMap subscript, set existing", + title: "PersistentDictionary subscript, set existing", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { d[i] = 0 @@ -119,11 +119,11 @@ extension Benchmark { } self.add( - title: "HashMap subscript, _modify", + title: "PersistentDictionary subscript, _modify", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { d[i]! *= 2 @@ -135,10 +135,10 @@ extension Benchmark { } self.addSimple( - title: "HashMap subscript, insert", + title: "PersistentDictionary subscript, insert", input: [Int].self ) { input in - var d: HashMap = [:] + var d: PersistentDictionary = [:] for i in input { d[i] = 2 * i } @@ -147,11 +147,11 @@ extension Benchmark { } self.add( - title: "HashMap [COW] subscript, insert", + title: "PersistentDictionary [COW] subscript, insert", input: ([Int], [Int]).self ) { input, insert in return { timer in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) let c = input.count timer.measure { for i in insert { @@ -167,11 +167,11 @@ extension Benchmark { } self.add( - title: "HashMap subscript, remove existing", + title: "PersistentDictionary subscript, remove existing", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { d[i] = nil @@ -183,11 +183,11 @@ extension Benchmark { } self.add( - title: "HashMap subscript, remove missing", + title: "PersistentDictionary subscript, remove missing", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) let c = input.count timer.measure { for i in lookups { @@ -200,10 +200,10 @@ extension Benchmark { } self.add( - title: "HashMap defaulted subscript, successful lookups", + title: "PersistentDictionary defaulted subscript, successful lookups", input: ([Int], [Int]).self ) { input, lookups in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) return { timer in for i in lookups { precondition(d[i, default: -1] != -1) @@ -212,10 +212,10 @@ extension Benchmark { } self.add( - title: "HashMap defaulted subscript, unsuccessful lookups", + title: "PersistentDictionary defaulted subscript, unsuccessful lookups", input: ([Int], [Int]).self ) { input, lookups in - let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) return { timer in let c = d.count for i in lookups { @@ -225,11 +225,11 @@ extension Benchmark { } self.add( - title: "HashMap defaulted subscript, _modify existing", + title: "PersistentDictionary defaulted subscript, _modify existing", input: [Int].self ) { input in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in input { d[i, default: -1] *= 2 @@ -241,11 +241,11 @@ extension Benchmark { } self.add( - title: "HashMap defaulted subscript, _modify missing", + title: "PersistentDictionary defaulted subscript, _modify missing", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) let c = input.count timer.measure { for i in lookups { @@ -258,10 +258,10 @@ extension Benchmark { } // self.add( -// title: "HashMap successful index(forKey:)", +// title: "PersistentDictionary successful index(forKey:)", // input: ([Int], [Int]).self // ) { input, lookups in -// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) // return { timer in // for i in lookups { // precondition(d.index(forKey: i) != nil) @@ -270,10 +270,10 @@ extension Benchmark { // } // // self.add( -// title: "HashMap unsuccessful index(forKey:)", +// title: "PersistentDictionary unsuccessful index(forKey:)", // input: ([Int], [Int]).self // ) { input, lookups in -// let d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) +// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) // return { timer in // for i in lookups { // precondition(d.index(forKey: lookups.count + i) == nil) @@ -282,11 +282,11 @@ extension Benchmark { // } self.add( - title: "HashMap updateValue(_:forKey:), existing", + title: "PersistentDictionary updateValue(_:forKey:), existing", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { d.updateValue(0, forKey: i) @@ -298,11 +298,11 @@ extension Benchmark { } self.add( - title: "HashMap updateValue(_:forKey:), insert", + title: "PersistentDictionary updateValue(_:forKey:), insert", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { d.updateValue(0, forKey: input.count + i) @@ -314,11 +314,11 @@ extension Benchmark { } self.add( - title: "HashMap random removals (existing keys)", + title: "PersistentDictionary random removals (existing keys)", input: ([Int], [Int]).self ) { input, lookups in return { timer in - var d = HashMap(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) timer.measure { for i in lookups { precondition(d.removeValue(forKey: i) != nil) @@ -330,12 +330,12 @@ extension Benchmark { } self.add( - title: "HashMap random removals (missing keys)", + title: "PersistentDictionary random removals (missing keys)", input: ([Int], [Int]).self ) { input, lookups in return { timer in let c = input.count - var d = HashMap(uniqueKeysWithValues: input.lazy.map { (c + $0, 2 * $0) }) + var d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { (c + $0, 2 * $0) }) timer.measure { for i in lookups { precondition(d.removeValue(forKey: i) == nil) diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/Capsule.json index 929733c05..67326f1da 100644 --- a/Benchmarks/Libraries/Capsule.json +++ b/Benchmarks/Libraries/Capsule.json @@ -1,87 +1,95 @@ { - "kind": "group", - "title": "Capsule Benchmarks", - "directory": "Results", - "contents": [ - { - "kind": "group", - "title": "HashMap Operations", - "contents": [ - { - "kind": "chart", - "title": "all", - "tasks": [ - "HashMap init(uniqueKeysWithValues:)", - "HashMap sequential iteration", - "HashMap subscript, successful lookups", - "HashMap subscript, unsuccessful lookups", - "HashMap subscript, noop setter", - "HashMap subscript, set existing", - "HashMap subscript, _modify", - "HashMap subscript, insert", - "HashMap subscript, insert, reserving capacity", - "HashMap subscript, remove existing", - "HashMap subscript, remove missing", - "HashMap defaulted subscript, successful lookups", - "HashMap defaulted subscript, unsuccessful lookups", - "HashMap defaulted subscript, _modify existing", - "HashMap defaulted subscript, _modify missing", - "HashMap updateValue(_:forKey:), existing", - "HashMap updateValue(_:forKey:), insert", - "HashMap random removals (existing keys)", - "HashMap random removals (missing keys)", - ] - } - ] - }, - { - "kind": "group", - "title": "HashSet Operations", - "contents": [ - ] - }, - { - "kind": "group", - "title": "Comparisons against reference implementations", - "directory": "versus", - "contents": [ - { - "kind": "chart", - "title": "init(uniqueKeysWithValues:)", - "tasks": [ - "HashMap init(uniqueKeysWithValues:)", - "Dictionary init(uniqueKeysWithValues:)", - "OrderedDictionary init(uniqueKeysWithValues:)", - ] - }, - { - "kind": "chart", - "title": "sequential iteration", - "tasks": [ - "HashMap sequential iteration", - "Dictionary sequential iteration", - "OrderedDictionary sequential iteration", - ] - }, - { - "kind": "chart", - "title": "subscript, insert", - "tasks": [ - "HashMap subscript, insert", - "Dictionary subscript, insert", - "OrderedDictionary subscript, append", - ] - }, - { - "kind": "chart", - "title": "subscript, remove existing", - "tasks": [ - "HashMap subscript, remove existing", - "Dictionary subscript, remove existing", - "OrderedDictionary subscript, remove existing", - ] - }, - ] - }, - ] - } + "kind": "group", + "title": "Capsule Benchmarks", + "directory": "Results", + "contents": [ + { + "kind": "group", + "title": "PersistentDictionary Operations", + "contents": [ + { + "kind": "chart", + "title": "all", + "tasks": [ + "PersistentDictionary init(uniqueKeysWithValues:)", + "PersistentDictionary sequential iteration", + "PersistentDictionary subscript, successful lookups", + "PersistentDictionary subscript, unsuccessful lookups", + "PersistentDictionary subscript, noop setter", + "PersistentDictionary subscript, set existing", + "PersistentDictionary subscript, _modify", + "PersistentDictionary subscript, insert", + "PersistentDictionary subscript, insert, reserving capacity", + "PersistentDictionary subscript, remove existing", + "PersistentDictionary subscript, remove missing", + "PersistentDictionary defaulted subscript, successful lookups", + "PersistentDictionary defaulted subscript, unsuccessful lookups", + "PersistentDictionary defaulted subscript, _modify existing", + "PersistentDictionary defaulted subscript, _modify missing", + "PersistentDictionary updateValue(_:forKey:), existing", + "PersistentDictionary updateValue(_:forKey:), insert", + "PersistentDictionary random removals (existing keys)", + "PersistentDictionary random removals (missing keys)", + ] + } + ] + }, + { + "kind": "group", + "title": "HashSet Operations", + "contents": [] + }, + { + "kind": "group", + "title": "Comparisons against reference implementations", + "directory": "versus", + "contents": [ + { + "kind": "chart", + "title": "init(uniqueKeysWithValues:)", + "tasks": [ + "PersistentDictionary init(uniqueKeysWithValues:)", + "Dictionary init(uniqueKeysWithValues:)", + "OrderedDictionary init(uniqueKeysWithValues:)", + ] + }, + { + "kind": "chart", + "title": "sequential iteration", + "tasks": [ + "PersistentDictionary sequential iteration", + "Dictionary sequential iteration", + "OrderedDictionary sequential iteration", + ] + }, + { + "kind": "chart", + "title": "subscript, insert", + "tasks": [ + "PersistentDictionary subscript, insert", + "Dictionary subscript, insert", + "OrderedDictionary subscript, append", + ] + }, + { + "kind": "chart", + "title": "[COW] subscript, insert", + "tasks": [ + "PersistentDictionary [COW] subscript, insert", + "Dictionary [COW] subscript, insert", + "OrderedDictionary [COW] subscript, append", + ] + }, + { + "kind": "chart", + "title": "subscript, remove existing", + "tasks": [ + "PersistentDictionary subscript, remove existing", + "Dictionary subscript, remove existing", + "OrderedDictionary subscript, remove existing", + ] + }, + ] + }, + ] +} diff --git a/Benchmarks/benchmark-tool/main.swift b/Benchmarks/benchmark-tool/main.swift index 9e7c485e6..435277129 100644 --- a/Benchmarks/benchmark-tool/main.swift +++ b/Benchmarks/benchmark-tool/main.swift @@ -16,7 +16,7 @@ var benchmark = Benchmark(title: "Collection Benchmarks") benchmark.addArrayBenchmarks() benchmark.addSetBenchmarks() benchmark.addDictionaryBenchmarks() -benchmark.addCapsuleHashMapBenchmarks() +benchmark.addPersistentDictionaryBenchmarks() benchmark.addDequeBenchmarks() benchmark.addOrderedSetBenchmarks() benchmark.addOrderedDictionaryBenchmarks() diff --git a/Package.swift b/Package.swift index c0b61531a..9a290404f 100644 --- a/Package.swift +++ b/Package.swift @@ -116,7 +116,7 @@ let package = Package( dependencies: ["PriorityQueueModule"], swiftSettings: settings), - // HashMap + // PersistentDictionary .target( name: "Capsule", swiftSettings: settings), diff --git a/Sources/Capsule/HashMap+CustomStringConvertible.swift b/Sources/Capsule/PersistentDictionary+CustomStringConvertible.swift similarity index 93% rename from Sources/Capsule/HashMap+CustomStringConvertible.swift rename to Sources/Capsule/PersistentDictionary+CustomStringConvertible.swift index 9acb9de7a..7820e21b0 100644 --- a/Sources/Capsule/HashMap+CustomStringConvertible.swift +++ b/Sources/Capsule/PersistentDictionary+CustomStringConvertible.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -extension HashMap: CustomStringConvertible { +extension PersistentDictionary: CustomStringConvertible { public var description: String { guard count > 0 else { return "[:]" diff --git a/Sources/Capsule/HashMap+Equatable.swift b/Sources/Capsule/PersistentDictionary+Equatable.swift similarity index 78% rename from Sources/Capsule/HashMap+Equatable.swift rename to Sources/Capsule/PersistentDictionary+Equatable.swift index 77e88f844..b860cd3e7 100644 --- a/Sources/Capsule/HashMap+Equatable.swift +++ b/Sources/Capsule/PersistentDictionary+Equatable.swift @@ -10,8 +10,8 @@ //===----------------------------------------------------------------------===// // TODO check Dictionary semantics of Equatable (i.e., if it only compares keys or also values) -extension HashMap: Equatable where Value: Equatable { - public static func == (lhs: HashMap, rhs: HashMap) -> Bool { +extension PersistentDictionary: Equatable where Value: Equatable { + public static func == (lhs: PersistentDictionary, rhs: PersistentDictionary) -> Bool { lhs.cachedSize == rhs.cachedSize && (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) } diff --git a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift b/Sources/Capsule/PersistentDictionary+ExpressibleByDictionaryLiteral.swift similarity index 90% rename from Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift rename to Sources/Capsule/PersistentDictionary+ExpressibleByDictionaryLiteral.swift index a3de94db3..2efbde5e3 100644 --- a/Sources/Capsule/HashMap+ExpressibleByDictionaryLiteral.swift +++ b/Sources/Capsule/PersistentDictionary+ExpressibleByDictionaryLiteral.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -extension HashMap: ExpressibleByDictionaryLiteral { +extension PersistentDictionary: ExpressibleByDictionaryLiteral { @inlinable @inline(__always) public init(dictionaryLiteral elements: (Key, Value)...) { diff --git a/Sources/Capsule/HashMap+Hashable.swift b/Sources/Capsule/PersistentDictionary+Hashable.swift similarity index 92% rename from Sources/Capsule/HashMap+Hashable.swift rename to Sources/Capsule/PersistentDictionary+Hashable.swift index 670220e61..e7483d94c 100644 --- a/Sources/Capsule/HashMap+Hashable.swift +++ b/Sources/Capsule/PersistentDictionary+Hashable.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -extension HashMap: Hashable where Value: Hashable { +extension PersistentDictionary: Hashable where Value: Hashable { public func hash(into hasher: inout Hasher) { var commutativeHash = 0 for (key, value) in self { diff --git a/Sources/Capsule/HashMap+Sequence.swift b/Sources/Capsule/PersistentDictionary+Sequence.swift similarity index 69% rename from Sources/Capsule/HashMap+Sequence.swift rename to Sources/Capsule/PersistentDictionary+Sequence.swift index 144861ac4..e20284f5b 100644 --- a/Sources/Capsule/HashMap+Sequence.swift +++ b/Sources/Capsule/PersistentDictionary+Sequence.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -extension HashMap: Sequence { - public __consuming func makeIterator() -> MapKeyValueTupleIterator { - return MapKeyValueTupleIterator(rootNode: rootNode) +extension PersistentDictionary: Sequence { + public __consuming func makeIterator() -> DictionaryKeyValueTupleIterator { + return DictionaryKeyValueTupleIterator(rootNode: rootNode) } } diff --git a/Sources/Capsule/HashMap.swift b/Sources/Capsule/PersistentDictionary.swift similarity index 85% rename from Sources/Capsule/HashMap.swift rename to Sources/Capsule/PersistentDictionary.swift index 50a9dca75..9667e5eb6 100644 --- a/Sources/Capsule/HashMap.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -9,20 +9,20 @@ // //===----------------------------------------------------------------------===// -public struct HashMap where Key: Hashable { - var rootNode: BitmapIndexedMapNode +public struct PersistentDictionary where Key: Hashable { + var rootNode: BitmapIndexedDictionaryNode var cachedSize: Int - fileprivate init(_ rootNode: BitmapIndexedMapNode, _ cachedSize: Int) { + fileprivate init(_ rootNode: BitmapIndexedDictionaryNode, _ cachedSize: Int) { self.rootNode = rootNode self.cachedSize = cachedSize } public init() { - self.init(BitmapIndexedMapNode(), 0) + self.init(BitmapIndexedDictionaryNode(), 0) } - public init(_ map: HashMap) { + public init(_ map: PersistentDictionary) { self.init(map.rootNode, map.cachedSize) } @@ -122,7 +122,7 @@ public struct HashMap where Key: Hashable { // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` mutating func insert(_ isStorageKnownUniquelyReferenced: Bool, key: Key, value: Value) { - var effect = MapEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updateOrUpdating(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) @@ -139,7 +139,7 @@ public struct HashMap where Key: Hashable { // fluid/immutable API public func inserting(key: Key, value: Value) -> Self { - var effect = MapEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updateOrUpdating(false, key, value, keyHash, 0, &effect) @@ -167,7 +167,7 @@ public struct HashMap where Key: Hashable { // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` mutating func delete(_ isStorageKnownUniquelyReferenced: Bool, key: Key) { - var effect = MapEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removeOrRemoving(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) @@ -179,7 +179,7 @@ public struct HashMap where Key: Hashable { // fluid/immutable API public func deleting(key: Key) -> Self { - var effect = MapEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) @@ -204,14 +204,14 @@ public struct HashMap where Key: Hashable { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -public struct MapKeyValueTupleIterator: IteratorProtocol { +public struct DictionaryKeyValueTupleIterator: IteratorProtocol { private var payloadIterator: UnsafeBufferPointer<(key: Key, value: Value)>.Iterator? private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] - init(rootNode: BitmapIndexedMapNode) { + init(rootNode: BitmapIndexedDictionaryNode) { trieIteratorStackRemainder = [] trieIteratorStackRemainder.reserveCapacity(maxDepth) @@ -219,13 +219,13 @@ public struct MapKeyValueTupleIterator: IteratorProtocol { if rootNode.hasPayload { payloadIterator = makePayloadIterator(rootNode) } } - // TODO consider moving to `BitmapIndexedMapNode` - private func makePayloadIterator(_ node: BitmapIndexedMapNode) -> UnsafeBufferPointer<(key: Key, value: Value)>.Iterator { + // TODO consider moving to `BitmapIndexedDictionaryNode` + private func makePayloadIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer<(key: Key, value: Value)>.Iterator { UnsafeBufferPointer(start: node.dataBaseAddress, count: node.payloadArity).makeIterator() } - // TODO consider moving to `BitmapIndexedMapNode` - private func makeTrieIterator(_ node: BitmapIndexedMapNode) -> UnsafeBufferPointer.Iterator { + // TODO consider moving to `BitmapIndexedDictionaryNode` + private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() } @@ -237,7 +237,7 @@ public struct MapKeyValueTupleIterator: IteratorProtocol { while trieIteratorStackTop != nil { if let nextAnyObject = trieIteratorStackTop!.next() { switch nextAnyObject { - case let nextNode as BitmapIndexedMapNode: + case let nextNode as BitmapIndexedDictionaryNode: if nextNode.hasNodes { trieIteratorStackRemainder.append(trieIteratorStackTop!) trieIteratorStackTop = makeTrieIterator(nextNode) @@ -246,7 +246,7 @@ public struct MapKeyValueTupleIterator: IteratorProtocol { payloadIterator = makePayloadIterator(nextNode) return payloadIterator?.next() } - case let nextNode as HashCollisionMapNode: + case let nextNode as HashCollisionDictionaryNode: payloadIterator = nextNode.content.withUnsafeBufferPointer { $0.makeIterator() } return payloadIterator?.next() default: @@ -268,17 +268,17 @@ public struct MapKeyValueTupleIterator: IteratorProtocol { } } -// TODO consider reworking similar to `MapKeyValueTupleIterator` +// TODO consider reworking similar to `DictionaryKeyValueTupleIterator` // (would require a reversed variant of `UnsafeBufferPointer<(key: Key, value: Value)>.Iterator`) -public struct MapKeyValueTupleReverseIterator { - private var baseIterator: ChampBaseReverseIterator, HashCollisionMapNode> +public struct DictionaryKeyValueTupleReverseIterator { + private var baseIterator: ChampBaseReverseIterator, HashCollisionDictionaryNode> - init(rootNode: BitmapIndexedMapNode) { + init(rootNode: BitmapIndexedDictionaryNode) { self.baseIterator = ChampBaseReverseIterator(rootNode: .bitmapIndexed(rootNode)) } } -extension MapKeyValueTupleReverseIterator: IteratorProtocol { +extension DictionaryKeyValueTupleReverseIterator: IteratorProtocol { public mutating func next() -> (key: Key, value: Value)? { guard baseIterator.hasNext() else { return nil } diff --git a/Sources/Capsule/_BitmapIndexedMapNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift similarity index 91% rename from Sources/Capsule/_BitmapIndexedMapNode.swift rename to Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 443da5fb4..23fd4608c 100644 --- a/Sources/Capsule/_BitmapIndexedMapNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -12,7 +12,7 @@ fileprivate let initialDataCapacity = 4 fileprivate let initialTrieCapacity = 1 -final class BitmapIndexedMapNode: MapNode where Key: Hashable { +final class BitmapIndexedDictionaryNode: DictionaryNode where Key: Hashable { typealias ReturnPayload = (key: Key, value: Value) @@ -144,7 +144,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { assert(self.invariant) } - convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedMapNode) { + convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedDictionaryNode) { self.init() self.header = Header(bitmap1: 0, bitmap2: nodeMap) @@ -154,7 +154,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { assert(self.invariant) } - convenience init(collMap: Bitmap, firstNode: HashCollisionMapNode) { + convenience init(collMap: Bitmap, firstNode: HashCollisionDictionaryNode) { self.init() self.header = Header(bitmap1: collMap, bitmap2: collMap) @@ -164,7 +164,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { assert(self.invariant) } - convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionMapNode) { + convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionDictionaryNode) { self.init() self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) @@ -225,7 +225,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return false } - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -240,7 +240,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { let keyHash0 = computeHash(key0) if keyHash0 == keyHash { - let subNodeNew = HashCollisionMapNode(keyHash0, [(key0, value0), (key, value)]) + let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() return copyAndMigrateFromInlineToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { @@ -285,7 +285,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return copyAndInsertValue(isStorageKnownUniquelyReferenced, bitpos, key, value) } - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout MapEffect) -> BitmapIndexedMapNode { + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -372,7 +372,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // TODO simplify hash-collision compaction (if feasible) if self.isCandiateForCompaction { // escalate singleton - // convert `HashCollisionMapNode` to `BitmapIndexedMapNode` (logic moved/inlined from `HashCollisionMapNode`) + // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) let (remainingKey, remainingValue) = subNodeNew.getPayload(0) return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) @@ -394,7 +394,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && bitmapIndexedNodeArity == 0 && hashCollisionNodeArity == 1 } - func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { + func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != keyHash1) let mask0 = maskFrom(keyHash0, shift) @@ -415,7 +415,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } - func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionMapNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedMapNode { + func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionDictionaryNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != nodeHash1) let mask0 = maskFrom(keyHash0, shift) @@ -436,8 +436,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { header.nodeCount } - func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedMapNode { - trieBaseAddress[index] as! BitmapIndexedMapNode + func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedDictionaryNode { + trieBaseAddress[index] as! BitmapIndexedDictionaryNode } private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { @@ -460,8 +460,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { header.collCount } - func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { - trieBaseAddress[bitmapIndexedNodeArity + index] as! HashCollisionMapNode + func getHashCollisionNode(_ index: Int) -> HashCollisionDictionaryNode { + trieBaseAddress[bitmapIndexedNodeArity + index] as! HashCollisionDictionaryNode } // TODO rename, not accurate any more @@ -470,7 +470,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { // TODO rename, not accurate any more var nodeArity: Int { header.trieCount } - func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { if index < bitmapIndexedNodeArity { return .bitmapIndexed(getBitmapIndexedNode(index)) } else { @@ -494,7 +494,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } - func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedMapNode { + func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -515,17 +515,17 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { let idx = self.nodeIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } - func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionMapNode) -> BitmapIndexedMapNode { + func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { let idx = bitmapIndexedNodeArity + self.collIndex(bitpos) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) } - private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedMapNode { + private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -541,7 +541,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedMapNode { + func copyAndInsertValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ key: Key, _ value: Value) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -563,7 +563,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedMapNode { + func copyAndRemoveValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -583,7 +583,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -609,7 +609,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -634,7 +634,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { + func copyAndMigrateFromNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -660,7 +660,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedMapNode { + func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -685,7 +685,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -708,7 +708,7 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { return dst } - func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionMapNode) -> BitmapIndexedMapNode { + func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -732,8 +732,8 @@ final class BitmapIndexedMapNode: MapNode where Key: Hashable { } } -extension BitmapIndexedMapNode: Equatable where Value: Equatable { - static func == (lhs: BitmapIndexedMapNode, rhs: BitmapIndexedMapNode) -> Bool { +extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { + static func == (lhs: BitmapIndexedDictionaryNode, rhs: BitmapIndexedDictionaryNode) -> Bool { lhs === rhs || lhs.nodeMap == rhs.nodeMap && lhs.dataMap == rhs.dataMap && @@ -741,7 +741,7 @@ extension BitmapIndexedMapNode: Equatable where Value: Equatable { deepContentEquality(lhs, rhs) } - private static func deepContentEquality(_ lhs: BitmapIndexedMapNode, _ rhs: BitmapIndexedMapNode) -> Bool { + private static func deepContentEquality(_ lhs: BitmapIndexedDictionaryNode, _ rhs: BitmapIndexedDictionaryNode) -> Bool { for index in 0.. MapKeyValueTupleIterator { - return MapKeyValueTupleIterator(rootNode: self) +extension BitmapIndexedDictionaryNode: Sequence { + public __consuming func makeIterator() -> DictionaryKeyValueTupleIterator { + return DictionaryKeyValueTupleIterator(rootNode: self) } } diff --git a/Sources/Capsule/_MapEffect.swift b/Sources/Capsule/_DictionaryEffect.swift similarity index 96% rename from Sources/Capsule/_MapEffect.swift rename to Sources/Capsule/_DictionaryEffect.swift index 71e81732b..ba8bc9454 100644 --- a/Sources/Capsule/_MapEffect.swift +++ b/Sources/Capsule/_DictionaryEffect.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -struct MapEffect { +struct DictionaryEffect { var modified: Bool = false var replacedValue: Bool = false diff --git a/Sources/Capsule/_MapNode.swift b/Sources/Capsule/_DictionaryNode.swift similarity index 81% rename from Sources/Capsule/_MapNode.swift rename to Sources/Capsule/_DictionaryNode.swift index 406ab0662..92c96ae25 100644 --- a/Sources/Capsule/_MapNode.swift +++ b/Sources/Capsule/_DictionaryNode.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -protocol MapNode: Node { +protocol DictionaryNode: Node { associatedtype Key: Hashable associatedtype Value @@ -17,7 +17,7 @@ protocol MapNode: Node { func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> ReturnBitmapIndexedNode + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode } diff --git a/Sources/Capsule/_HashCollisionMapNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift similarity index 73% rename from Sources/Capsule/_HashCollisionMapNode.swift rename to Sources/Capsule/_HashCollisionDictionaryNode.swift index 38e043645..e7ed554f8 100644 --- a/Sources/Capsule/_HashCollisionMapNode.swift +++ b/Sources/Capsule/_HashCollisionDictionaryNode.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -final class HashCollisionMapNode: MapNode where Key: Hashable { +final class HashCollisionDictionaryNode: DictionaryNode where Key: Hashable { typealias ReturnPayload = (key: Key, value: Value) @@ -39,7 +39,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { // return self.hash == hash && content.contains(where: { key == $0.key && value == $0.value }) // } - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) if self.containsKey(key, hash, shift) { @@ -48,16 +48,16 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { effect.setReplacedValue() // TODO check (performance of) slicing and materialization of array content - return HashCollisionMapNode(hash, Array(updatedContent)) + return HashCollisionDictionaryNode(hash, Array(updatedContent)) } else { effect.setModified() - return HashCollisionMapNode(hash, content + [(key, value)]) + return HashCollisionDictionaryNode(hash, content + [(key, value)]) } } // TODO rethink such that `precondition(content.count >= 2)` holds - // TODO consider returning either type of `BitmapIndexedMapNode` and `HashCollisionMapNode` - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout MapEffect) -> HashCollisionMapNode { + // TODO consider returning either type of `BitmapIndexedDictionaryNode` and `HashCollisionDictionaryNode` + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { if !self.containsKey(key, hash, shift) { return self } else { @@ -68,9 +68,9 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { // switch updatedContent.count { // case 1: // let (k, v) = updatedContent[0].self -// return BitmapIndexedMapNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) +// return BitmapIndexedDictionaryNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) // default: - return HashCollisionMapNode(hash, updatedContent) + return HashCollisionDictionaryNode(hash, updatedContent) // } } } @@ -79,7 +79,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var bitmapIndexedNodeArity: Int { 0 } - func getBitmapIndexedNode(_ index: Int) -> HashCollisionMapNode { + func getBitmapIndexedNode(_ index: Int) -> HashCollisionDictionaryNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } @@ -87,7 +87,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var hashCollisionNodeArity: Int { 0 } - func getHashCollisionNode(_ index: Int) -> HashCollisionMapNode { + func getHashCollisionNode(_ index: Int) -> HashCollisionDictionaryNode { preconditionFailure("No sub-nodes present in hash-collision leaf node") } @@ -95,7 +95,7 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var nodeArity: Int { 0 } - func getNode(_ index: Int) -> TrieNode, HashCollisionMapNode> { + func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { preconditionFailure("No sub-nodes present in hash-collision leaf node") } @@ -108,8 +108,8 @@ final class HashCollisionMapNode: MapNode where Key: Hashable { var sizePredicate: SizePredicate { SizePredicate(self) } } -extension HashCollisionMapNode: Equatable where Value: Equatable { - static func == (lhs: HashCollisionMapNode, rhs: HashCollisionMapNode) -> Bool { +extension HashCollisionDictionaryNode: Equatable where Value: Equatable { + static func == (lhs: HashCollisionDictionaryNode, rhs: HashCollisionDictionaryNode) -> Bool { Dictionary.init(uniqueKeysWithValues: lhs.content) == Dictionary.init(uniqueKeysWithValues: rhs.content) } } diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift index 81aa8506d..6bcfcdaef 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -12,22 +12,22 @@ import _CollectionsTestSupport @testable import Capsule -class HashMapTests: CollectionTestCase { +class PersistentDictionaryTests: CollectionTestCase { func test_empty() { - let d = HashMap() + let d = PersistentDictionary() expectEqualElements(d, []) expectEqual(d.count, 0) } // func test_init_minimumCapacity() { -// let d = HashMap(minimumCapacity: 1000) +// let d = PersistentDictionary(minimumCapacity: 1000) // expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) // expectGreaterThanOrEqual(d.values.elements.capacity, 1000) // expectEqual(d.keys.__unstable.reservedScale, 0) // } // func test_init_minimumCapacity_persistent() { -// let d = HashMap(minimumCapacity: 1000, persistent: true) +// let d = PersistentDictionary(minimumCapacity: 1000, persistent: true) // expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) // expectGreaterThanOrEqual(d.values.elements.capacity, 1000) // expectNotEqual(d.keys.__unstable.reservedScale, 0) @@ -40,7 +40,7 @@ class HashMapTests: CollectionTestCase { // "two": 2, // "three": 3, // ] -// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) // expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } @@ -51,7 +51,7 @@ class HashMapTests: CollectionTestCase { // "two": 2, // "three": 3, // ] -// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) // expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } @@ -62,7 +62,7 @@ class HashMapTests: CollectionTestCase { ("two", 2), ("three", 3), ] - let d = HashMap(uncheckedUniqueKeysWithValues: items) + let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) expectEqualElements(d.sorted(by: <), items.sorted(by: <)) } @@ -73,7 +73,7 @@ class HashMapTests: CollectionTestCase { (key: "two", value: 2), (key: "three", value: 3) ] - let d = HashMap(uncheckedUniqueKeys: ["zero", "one", "two", "three"], values: [0, 1, 2, 3]) + let d = PersistentDictionary(uncheckedUniqueKeys: ["zero", "one", "two", "three"], values: [0, 1, 2, 3]) expectEqualElements(d.sorted(by: <), items.sorted(by: <)) } @@ -87,7 +87,7 @@ class HashMapTests: CollectionTestCase { // "b": 1, // "d": 3, // ] -// let d = HashMap(items, uniquingKeysWith: +) +// let d = PersistentDictionary(items, uniquingKeysWith: +) // expectEqualElements(d, [ // (key: "a", value: 5), // (key: "b", value: 2), @@ -106,7 +106,7 @@ class HashMapTests: CollectionTestCase { // ("b", 1), // ("d", 3), // ] -// let d = HashMap(items, uniquingKeysWith: +) +// let d = PersistentDictionary(items, uniquingKeysWith: +) // expectEqualElements(d, [ // (key: "a", value: 5), // (key: "b", value: 2), @@ -120,7 +120,7 @@ class HashMapTests: CollectionTestCase { // "one", "two", "three", "four", "five", // "six", "seven", "eight", "nine", "ten" // ] -// let d = HashMap(grouping: items, by: { $0.count }) +// let d = PersistentDictionary(grouping: items, by: { $0.count }) // expectEqualElements(d, [ // (key: 3, value: ["one", "two", "six", "ten"]), // (key: 5, value: ["three", "seven", "eight"]), @@ -135,7 +135,7 @@ class HashMapTests: CollectionTestCase { // "two": 2, // "three": 3, // ] -// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) // expectEqualElements(d, items) // } @@ -146,12 +146,12 @@ class HashMapTests: CollectionTestCase { // ("two", 2), // ("three", 3), // ] -// let d = HashMap(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) // expectEqualElements(d, items) // } // func test_uncheckedUniqueKeys_values() { -// let d = HashMap( +// let d = PersistentDictionary( // uncheckedUniqueKeys: ["zero", "one", "two", "three"], // values: [0, 1, 2, 3]) // expectEqualElements(d, [ @@ -163,10 +163,10 @@ class HashMapTests: CollectionTestCase { // } func test_ExpressibleByDictionaryLiteral() { - let d0: HashMap = [:] + let d0: PersistentDictionary = [:] expectTrue(d0.isEmpty) - let d1: HashMap = [ + let d1: PersistentDictionary = [ "1~one": 1, "2~two": 2, "3~three": 3, @@ -177,7 +177,7 @@ class HashMapTests: CollectionTestCase { } // func test_keys() { -// let d: HashMap = [ +// let d: PersistentDictionary = [ // "one": 1, // "two": 2, // "three": 3, @@ -286,7 +286,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in // d[keys[offset]] = values[offset] @@ -382,7 +382,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in // mutate(&d[keys[offset]]) { v in @@ -478,7 +478,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // let fallback = tracker.instance(for: -1) // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in @@ -530,7 +530,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in // let key = keys[offset] @@ -581,7 +581,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in // let key = keys[count - 1 - offset] @@ -636,7 +636,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // let fallback = tracker.instance(for: -2) // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in @@ -692,7 +692,7 @@ class HashMapTests: CollectionTestCase { // withLifetimeTracking { tracker in // let keys = tracker.instances(for: 0 ..< count) // let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) -// var d: HashMap, LifetimeTracked> = [:] +// var d: PersistentDictionary, LifetimeTracked> = [:] // let fallback = tracker.instance(for: -2) // withEvery("offset", in: 0 ..< count) { offset in // withHiddenCopies(if: isShared, of: &d) { d in @@ -742,7 +742,7 @@ class HashMapTests: CollectionTestCase { // } // func test_merge_labeled_tuple() { -// var d: HashMap = [ +// var d: PersistentDictionary = [ // "one": 1, // "two": 1, // "three": 1, @@ -767,7 +767,7 @@ class HashMapTests: CollectionTestCase { // } // func test_merge_unlabeled_tuple() { -// var d: HashMap = [ +// var d: PersistentDictionary = [ // "one": 1, // "two": 1, // "three": 1, @@ -792,7 +792,7 @@ class HashMapTests: CollectionTestCase { // } // func test_merging_labeled_tuple() { -// let d: HashMap = [ +// let d: PersistentDictionary = [ // "one": 1, // "two": 1, // "three": 1, @@ -823,7 +823,7 @@ class HashMapTests: CollectionTestCase { // } // func test_merging_unlabeled_tuple() { -// let d: HashMap = [ +// let d: PersistentDictionary = [ // "one": 1, // "two": 1, // "three": 1, @@ -855,7 +855,7 @@ class HashMapTests: CollectionTestCase { // func test_filter() { // let items = (0 ..< 100).map { ($0, 100 * $0) } -// let d = HashMap(uniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // // var c = 0 // let d2 = d.filter { item in @@ -873,7 +873,7 @@ class HashMapTests: CollectionTestCase { // func test_mapValues() { // let items = (0 ..< 100).map { ($0, 100 * $0) } -// let d = HashMap(uniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // // var c = 0 // let d2 = d.mapValues { value -> String in @@ -891,7 +891,7 @@ class HashMapTests: CollectionTestCase { // func test_compactMapValue() { // let items = (0 ..< 100).map { ($0, 100 * $0) } -// let d = HashMap(uniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // // var c = 0 // let d2 = d.compactMapValues { value -> String? in @@ -909,33 +909,33 @@ class HashMapTests: CollectionTestCase { // } func test_CustomStringConvertible() { - let a: HashMap = [:] + let a: PersistentDictionary = [:] expectEqual(a.description, "[:]") - let b: HashMap = [CollidableInt(0): 1] + let b: PersistentDictionary = [CollidableInt(0): 1] expectEqual(b.description, "[0: 1]") - let c: HashMap = [CollidableInt(0): 1, CollidableInt(2): 3, CollidableInt(4): 5] + let c: PersistentDictionary = [CollidableInt(0): 1, CollidableInt(2): 3, CollidableInt(4): 5] expectEqual(c.description, "[0: 1, 2: 3, 4: 5]") } // func test_CustomDebugStringConvertible() { -// let a: HashMap = [:] +// let a: PersistentDictionary = [:] // expectEqual(a.debugDescription, -// "HashMap([:])") +// "PersistentDictionary([:])") // -// let b: HashMap = [0: 1] +// let b: PersistentDictionary = [0: 1] // expectEqual(b.debugDescription, -// "HashMap([0: 1])") +// "PersistentDictionary([0: 1])") // -// let c: HashMap = [0: 1, 2: 3, 4: 5] +// let c: PersistentDictionary = [0: 1, 2: 3, 4: 5] // expectEqual(c.debugDescription, -// "HashMap([0: 1, 2: 3, 4: 5])") +// "PersistentDictionary([0: 1, 2: 3, 4: 5])") // } // func test_customReflectable() { // do { -// let d: HashMap = [1: 2, 3: 4, 5: 6] +// let d: PersistentDictionary = [1: 2, 3: 4, 5: 6] // let mirror = Mirror(reflecting: d) // expectEqual(mirror.displayStyle, .dictionary) // expectNil(mirror.superclassMirror) @@ -947,7 +947,7 @@ class HashMapTests: CollectionTestCase { // } func test_Equatable_Hashable() { - let samples: [[HashMap]] = [ + let samples: [[PersistentDictionary]] = [ [[:], [:]], [[1: 100], [1: 100]], [[2: 200], [2: 200]], @@ -962,20 +962,20 @@ class HashMapTests: CollectionTestCase { } // func test_Encodable() throws { -// let d1: HashMap = [:] +// let d1: PersistentDictionary = [:] // let v1: MinimalEncoder.Value = .array([]) // expectEqual(try MinimalEncoder.encode(d1), v1) // -// let d2: HashMap = [0: 1] +// let d2: PersistentDictionary = [0: 1] // let v2: MinimalEncoder.Value = .array([.int(0), .int(1)]) // expectEqual(try MinimalEncoder.encode(d2), v2) // -// let d3: HashMap = [0: 1, 2: 3] +// let d3: PersistentDictionary = [0: 1, 2: 3] // let v3: MinimalEncoder.Value = // .array([.int(0), .int(1), .int(2), .int(3)]) // expectEqual(try MinimalEncoder.encode(d3), v3) // -// let d4 = HashMap( +// let d4 = PersistentDictionary( // uniqueKeys: 0 ..< 100, // values: (0 ..< 100).map { 100 * $0 }) // let v4: MinimalEncoder.Value = @@ -984,7 +984,7 @@ class HashMapTests: CollectionTestCase { // } // func test_Decodable() throws { -// typealias OD = HashMap +// typealias OD = PersistentDictionary // let d1: OD = [:] // let v1: MinimalEncoder.Value = .array([]) // expectEqual(try MinimalDecoder.decode(v1, as: OD.self), d1) @@ -998,7 +998,7 @@ class HashMapTests: CollectionTestCase { // .array([.int(0), .int(1), .int(2), .int(3)]) // expectEqual(try MinimalDecoder.decode(v3, as: OD.self), d3) // -// let d4 = HashMap( +// let d4 = PersistentDictionary( // uniqueKeys: 0 ..< 100, // values: (0 ..< 100).map { 100 * $0 }) // let v4: MinimalEncoder.Value = @@ -1113,7 +1113,7 @@ class HashMapTests: CollectionTestCase { // withEvery("count", in: 0 ..< 30) { count in // withEvery("isShared", in: [false, true]) { isShared in // withEvery("seed", in: 0 ..< 10) { seed in -// var d = HashMap( +// var d = PersistentDictionary( // uniqueKeys: 0 ..< count, // values: 100 ..< 100 + count) // var items = (0 ..< count).map { (key: $0, value: 100 + $0) } @@ -1134,7 +1134,7 @@ class HashMapTests: CollectionTestCase { // } // if count >= 2 { // // Check that shuffling with the system RNG does permute the elements. -// var d = HashMap( +// var d = PersistentDictionary( // uniqueKeys: 0 ..< count, // values: 100 ..< 100 + count) // let original = d @@ -1244,7 +1244,7 @@ class HashMapTests: CollectionTestCase { // } // func test_removeSubrange_rangeExpression() { -// let d = HashMap(uniqueKeys: 0 ..< 30, values: 100 ..< 130) +// let d = PersistentDictionary(uniqueKeys: 0 ..< 30, values: 100 ..< 130) // let item = (0 ..< 30).map { (key: $0, value: 100 + $0) } // // var d1 = d diff --git a/Tests/CapsuleTests/Capsule Utils.swift b/Tests/CapsuleTests/Capsule Utils.swift index 4fceb19d4..900235aff 100644 --- a/Tests/CapsuleTests/Capsule Utils.swift +++ b/Tests/CapsuleTests/Capsule Utils.swift @@ -16,7 +16,7 @@ extension LifetimeTracker { func persistentDictionary( keys: Keys ) -> ( - dictionary: HashMap, LifetimeTracked>, + dictionary: PersistentDictionary, LifetimeTracked>, keys: [LifetimeTracked], values: [LifetimeTracked] ) @@ -25,7 +25,7 @@ extension LifetimeTracker { let k = Array(keys) let keys = self.instances(for: k) let values = self.instances(for: k.map { $0 + 100 }) - let dictionary = HashMap(uniqueKeys: keys, values: values) + let dictionary = PersistentDictionary(uniqueKeys: keys, values: values) return (dictionary, keys, values) } } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 47d004a7b..a69a5f505 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -14,7 +14,7 @@ import _CollectionsTestSupport final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { - var map: HashMap = [1: "a", 2: "b"] + var map: PersistentDictionary = [1: "a", 2: "b"] map[3] = "x" map[4] = "y" @@ -27,7 +27,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testSubscriptOverwrite() { - var map: HashMap = [1: "a", 2: "b"] + var map: PersistentDictionary = [1: "a", 2: "b"] map[1] = "x" map[2] = "y" @@ -38,7 +38,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testSubscriptRemove() { - var map: HashMap = [1: "a", 2: "b"] + var map: PersistentDictionary = [1: "a", 2: "b"] map[1] = nil map[2] = nil @@ -49,21 +49,21 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testTriggerOverwrite1() { - let map: HashMap = [1: "a", 2: "b"] + let map: PersistentDictionary = [1: "a", 2: "b"] _ = map .inserting(key: 1, value: "x") // triggers COW .inserting(key: 2, value: "y") // triggers COW - var res1: HashMap = [:] + var res1: PersistentDictionary = [:] res1.insert(key: 1, value: "a") // in-place res1.insert(key: 2, value: "b") // in-place - var res2: HashMap = [:] + var res2: PersistentDictionary = [:] res2[1] = "a" // in-place res2[2] = "b" // in-place - var res3: HashMap = res2 + var res3: PersistentDictionary = res2 res3[1] = "x" // triggers COW res3[2] = "y" // in-place @@ -77,7 +77,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testTriggerOverwrite2() { - var res1: HashMap = [:] + var res1: PersistentDictionary = [:] res1.insert(key: CollidableInt(10, 01), value: "a") // in-place res1.insert(key: CollidableInt(11, 33), value: "a") // in-place res1.insert(key: CollidableInt(20, 02), value: "b") // in-place @@ -88,7 +88,7 @@ final class CapsuleSmokeTests: CollectionTestCase { print("Yeah!") - var res2: HashMap = res1 + var res2: PersistentDictionary = res1 res2.insert(key: CollidableInt(10, 01), value: "a") // triggers COW res2.insert(key: CollidableInt(11, 33), value: "a") // in-place res2.insert(key: CollidableInt(20, 02), value: "b") // in-place @@ -108,14 +108,14 @@ final class CapsuleSmokeTests: CollectionTestCase { func testTriggerOverwrite3() { let upperBound = 1_000 - var map1: HashMap = [:] + var map1: PersistentDictionary = [:] for index in 0.. = map1 + var map2: PersistentDictionary = map1 for index in 0.. = map2 + var map3: PersistentDictionary = map2 for index in 0.. = [1: "a", 2: "b"] + let map: PersistentDictionary = [1: "a", 2: "b"] let hashPair1 = hashPair(1, "a") let hashPair2 = hashPair(2, "b") @@ -175,7 +175,7 @@ final class CapsuleSmokeTests: CollectionTestCase { } func testCompactionWhenDeletingFromHashCollisionNode1() { - let map: HashMap = [:] + let map: PersistentDictionary = [:] var res1 = map @@ -186,7 +186,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(12, 1))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), res1) + expectEqual(PersistentDictionary.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), res1) var res2 = res1 @@ -196,7 +196,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectFalse(res2.contains(CollidableInt(12, 1))) expectEqual(res2.count, 1) - expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1)]), res2) + expectEqual(PersistentDictionary.init([CollidableInt(11, 1): CollidableInt(11, 1)]), res2) var res3 = res1 @@ -206,7 +206,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(12, 1))) expectEqual(res3.count, 1) - expectEqual(HashMap.init([CollidableInt(12, 1): CollidableInt(12, 1)]), res3) + expectEqual(PersistentDictionary.init([CollidableInt(12, 1): CollidableInt(12, 1)]), res3) var resX = res1 @@ -218,7 +218,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(resX.contains(CollidableInt(32769))) expectEqual(resX.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(32769): CollidableInt(32769)]), resX) + expectEqual(PersistentDictionary.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(32769): CollidableInt(32769)]), resX) var resY = res1 @@ -230,11 +230,11 @@ final class CapsuleSmokeTests: CollectionTestCase { expectFalse(resY.contains(CollidableInt(32769))) expectEqual(resY.count, 2) - expectEqual(HashMap.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), resY) + expectEqual(PersistentDictionary.init([CollidableInt(11, 1): CollidableInt(11, 1), CollidableInt(12, 1): CollidableInt(12, 1)]), resY) } func testCompactionWhenDeletingFromHashCollisionNode2() { - let map: HashMap = [:] + let map: PersistentDictionary = [:] var res1 = map @@ -245,7 +245,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) + expectEqual(PersistentDictionary.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -256,7 +256,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) + expectEqual(PersistentDictionary.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -266,11 +266,11 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_1, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769)]), res3) + expectEqual(PersistentDictionary.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769)]), res3) } func testCompactionWhenDeletingFromHashCollisionNode3() { - let map: HashMap = [:] + let map: PersistentDictionary = [:] var res1 = map @@ -281,7 +281,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) + expectEqual(PersistentDictionary.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -292,7 +292,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) + expectEqual(PersistentDictionary.init([CollidableInt(1): CollidableInt(1), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -302,14 +302,14 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_2, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) + expectEqual(PersistentDictionary.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) expectEqual(res1, res3) } func testCompactionWhenDeletingFromHashCollisionNode4() { - let map: HashMap = [:] + let map: PersistentDictionary = [:] var res1 = map @@ -320,7 +320,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32769_2, 32769))) expectEqual(res1.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) + expectEqual(PersistentDictionary.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res1) var res2 = res1 @@ -331,7 +331,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32769_2, 32769))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(5): CollidableInt(5), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) + expectEqual(PersistentDictionary.init([CollidableInt(5): CollidableInt(5), CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res2) var res3 = res2 @@ -341,14 +341,14 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res3.contains(CollidableInt(32769_2, 32769))) expectEqual(res3.count, 2) - expectEqual(HashMap.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) + expectEqual(PersistentDictionary.init([CollidableInt(32769_1, 32769): CollidableInt(32769_1, 32769), CollidableInt(32769_2, 32769): CollidableInt(32769_2, 32769)]), res3) expectEqual(res1, res3) } func testCompactionWhenDeletingFromHashCollisionNode5() { - let map: HashMap = [:] + let map: PersistentDictionary = [:] var res1 = map @@ -363,7 +363,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res1.contains(CollidableInt(32770_2, 32770))) expectEqual(res1.count, 4) - expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(1026): CollidableInt(1026), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res1) + expectEqual(PersistentDictionary.init([CollidableInt(1): CollidableInt(1), CollidableInt(1026): CollidableInt(1026), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res1) var res2 = res1 @@ -375,10 +375,10 @@ final class CapsuleSmokeTests: CollectionTestCase { expectTrue(res2.contains(CollidableInt(32770_2, 32770))) expectEqual(res2.count, 3) - expectEqual(HashMap.init([CollidableInt(1): CollidableInt(1), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res2) + expectEqual(PersistentDictionary.init([CollidableInt(1): CollidableInt(1), CollidableInt(32770_1, 32770): CollidableInt(32770_1, 32770), CollidableInt(32770_2, 32770): CollidableInt(32770_2, 32770)]), res2) } - func inferSize(_ map: HashMap) -> Int { + func inferSize(_ map: PersistentDictionary) -> Int { var size = 0 for _ in map { @@ -388,14 +388,41 @@ final class CapsuleSmokeTests: CollectionTestCase { return size } + func testIteratorEnumeratesAllIfNoCollision() { + let upperBound = 1_000_000 + + var map1: PersistentDictionary = [:] + for index in 0..) { + var count = 0 + for _ in map1 { + count = count + 1 + } + expectEqual(map1.count, count) + } + func testIteratorEnumeratesAll() { - let map1: HashMap = [ + let map1: PersistentDictionary = [ CollidableInt(11, 1): "a", CollidableInt(12, 1): "a", CollidableInt(32769): "b" ] - var map2: HashMap = [:] + var map2: PersistentDictionary = [:] for (key, value) in map1 { map2[key] = value } diff --git a/Utils/run-capsule-benchmarks.zsh b/Utils/run-capsule-benchmarks.zsh index 1f1b37afe..40747afef 100755 --- a/Utils/run-capsule-benchmarks.zsh +++ b/Utils/run-capsule-benchmarks.zsh @@ -26,7 +26,7 @@ benchmarks=( declare -a classes classes=( "Dictionary" - "HashMap" + "PersistentDictionary" # "OrderedDictionary" ) From 60f0b6d109d98c8e78770aeaedd651dacbca4c24 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 17:40:08 +0100 Subject: [PATCH 134/176] [Capsule] Remove outdated Capsule benchmark helper --- Utils/run-capsule-benchmarks.zsh | 44 -------------------------------- 1 file changed, 44 deletions(-) delete mode 100755 Utils/run-capsule-benchmarks.zsh diff --git a/Utils/run-capsule-benchmarks.zsh b/Utils/run-capsule-benchmarks.zsh deleted file mode 100755 index 40747afef..000000000 --- a/Utils/run-capsule-benchmarks.zsh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/zsh - -declare -a benchmarks -benchmarks=( - "init(uniqueKeysWithValues:)" - "sequential iteration" - "subscript, successful lookups" - "subscript, unsuccessful lookups" - "subscript, noop setter" - "subscript, set existing" - "subscript, _modify" - "subscript, insert" - "subscript, insert, reserving capacity" - "subscript, remove existing" - "subscript, remove missing" - "defaulted subscript, successful lookups" - "defaulted subscript, unsuccessful lookups" - "defaulted subscript, _modify existing" - "defaulted subscript, _modify missing" - "updateValue(_:forKey:), existing" - "updateValue(_:forKey:), insert" - "random removals (existing keys)" - "random removals (missing keys)" -) - -declare -a classes -classes=( - "Dictionary" - "PersistentDictionary" - # "OrderedDictionary" -) - -for benchmark in ${benchmarks[@]}; do - tasks_file=$(mktemp) - - for class in $classes; do - echo "$class $benchmark" >> $tasks_file - done - - rm "results-$benchmark" && rm "chart-$benchmark.png" - swift run -Xswiftc -Ounchecked -c release swift-collections-benchmark run "results-$benchmark" --tasks-file=$tasks_file --cycles=1 - swift run -Xswiftc -Ounchecked -c release swift-collections-benchmark render "results-$benchmark" "chart-$benchmark.png" - # open "chart-$benchmark.png" -done From 92d07b3910c8135dec880b61b59dcde75ee85590 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 9 Dec 2021 18:14:52 +0100 Subject: [PATCH 135/176] [Capsule] Add `[COW] subscript, remove existing` benchmark --- .../Benchmarks/DictionaryBenchmarks.swift | 19 +++++++++++++++++++ .../OrderedDictionaryBenchmarks.swift | 19 +++++++++++++++++++ .../PersistentDictionaryBenchmarks.swift | 19 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift index 5020a4ea0..5256c5059 100644 --- a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift @@ -194,6 +194,25 @@ extension Benchmark { } } + self.add( + title: "Dictionary [COW] subscript, remove existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let d = Dictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + var e = d + e[i] = nil + precondition(e.count == input.count - 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.add( title: "Dictionary subscript, remove missing", input: ([Int], [Int]).self diff --git a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift index bb3dff8ed..5aab23081 100644 --- a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift @@ -226,6 +226,25 @@ extension Benchmark { } } + self.add( + title: "OrderedDictionary [COW] subscript, remove existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let d = OrderedDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + var e = d + e[i] = nil + precondition(e.count == input.count - 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.add( title: "OrderedDictionary subscript, remove missing", input: ([Int], [Int]).self diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index 42448b196..c4c334fa0 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -182,6 +182,25 @@ extension Benchmark { } } + self.add( + title: "PersistentDictionary [COW] subscript, remove existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + timer.measure { + for i in lookups { + var e = d + e[i] = nil + precondition(e.count == input.count - 1) + blackHole(e) + } + } + precondition(d.count == input.count) + blackHole(d) + } + } + self.add( title: "PersistentDictionary subscript, remove missing", input: ([Int], [Int]).self From ce70ede82c8370f5e45133e68927225efd95d2c9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 9 May 2022 16:02:39 +0200 Subject: [PATCH 136/176] [Capsule] Constrain capacity type --- .../_BitmapIndexedDictionaryNode.swift | 24 +++++++++---------- Sources/Capsule/_Common.swift | 2 ++ Tests/CapsuleTests/CapsuleSmokeTests.swift | 4 ++++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 23fd4608c..82f92e224 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -fileprivate let initialDataCapacity = 4 -fileprivate let initialTrieCapacity = 1 +fileprivate let initialDataCapacity: Capacity = 4 +fileprivate let initialTrieCapacity: Capacity = 1 final class BitmapIndexedDictionaryNode: DictionaryNode where Key: Hashable { @@ -21,8 +21,8 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var header: Header - let dataCapacity: Int // TODO constrain type - let trieCapacity: Int // TODO constrain type + let dataCapacity: Capacity + let trieCapacity: Capacity let dataBaseAddress: UnsafeMutablePointer let trieBaseAddress: UnsafeMutablePointer @@ -52,23 +52,23 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } @inlinable - static func _allocate(dataCapacity: Int, trieCapacity: Int) -> (dataBaseAddress: UnsafeMutablePointer, trieBaseAddress: UnsafeMutablePointer) { - let dataCapacityInBytes = dataCapacity * MemoryLayout.stride - let trieCapacityInBytes = trieCapacity * MemoryLayout.stride + static func _allocate(dataCapacity: Capacity, trieCapacity: Capacity) -> (dataBaseAddress: UnsafeMutablePointer, trieBaseAddress: UnsafeMutablePointer) { + let dataCapacityInBytes = Int(dataCapacity) * MemoryLayout.stride + let trieCapacityInBytes = Int(trieCapacity) * MemoryLayout.stride let memory = UnsafeMutableRawPointer.allocate( byteCount: dataCapacityInBytes + trieCapacityInBytes, alignment: Swift.max(MemoryLayout.alignment, MemoryLayout.alignment)) - let dataBaseAddress = memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: dataCapacity) - let trieBaseAddress = memory.bindMemory(to: TrieBufferElement.self, capacity: trieCapacity) + let dataBaseAddress = memory.advanced(by: trieCapacityInBytes).bindMemory(to: DataBufferElement.self, capacity: Int(dataCapacity)) + let trieBaseAddress = memory.bindMemory(to: TrieBufferElement.self, capacity: Int(trieCapacity)) return (dataBaseAddress, trieBaseAddress) } - func copy(withDataCapacityFactor dataFactor: Int = 1, withTrieCapacityFactor trieFactor: Int = 1) -> Self { + func copy(withDataCapacityFactor dataCapacityFactor: Capacity = 1, withTrieCapacityFactor trieCapacityFactor: Capacity = 1) -> Self { let src = self - let dst = Self(dataCapacity: src.dataCapacity * dataFactor, trieCapacity: src.trieCapacity * trieFactor) + let dst = Self(dataCapacity: src.dataCapacity &* dataCapacityFactor, trieCapacity: src.trieCapacity &* trieCapacityFactor) dst.header = src.header dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataCount) @@ -102,7 +102,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } } - init(dataCapacity: Int, trieCapacity: Int) { + init(dataCapacity: Capacity, trieCapacity: Capacity) { let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) self.header = Header(bitmap1: 0, bitmap2: 0) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 9ec99f590..fc4adc305 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -19,6 +19,8 @@ let bitPartitionSize: Int = 5 let bitPartitionMask: Int = (1 << bitPartitionSize) - 1 +typealias Capacity = UInt8 + let hashCodeLength: Int = Int.bitWidth let maxDepth = Int((Double(hashCodeLength) / Double(bitPartitionSize)).rounded(.up)) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index a69a5f505..710f0b400 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -437,4 +437,8 @@ final class BitmapSmokeTests: CollectionTestCase { expectTrue((2 << (bitPartitionSize - 1)) != 0) expectTrue((2 << (bitPartitionSize - 1)) <= Bitmap.bitWidth) } + + func test_Capacity_isValid() { + expectTrue(Int(2 << bitPartitionSize) <= Int(Capacity.max)) + } } From 93054561f61f4038c56b415e060e826c646411d1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 10 Jun 2022 19:41:16 -0700 Subject: [PATCH 137/176] [Capsule] Add preliminary support for shrinking --- .../Capsule/_BitmapIndexedDictionaryNode.swift | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 82f92e224..d2ff179c7 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -66,9 +66,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return (dataBaseAddress, trieBaseAddress) } - func copy(withDataCapacityFactor dataCapacityFactor: Capacity = 1, withTrieCapacityFactor trieCapacityFactor: Capacity = 1) -> Self { + func copy(withDataCapacityFactor dataCapacityFactor: Capacity = 1, + withDataCapacityShrinkFactor dataCapacityShrinkFactor: Capacity = 1, + withTrieCapacityFactor trieCapacityFactor: Capacity = 1, + withTrieCapacityShrinkFactor trieCapacityShrinkFactor: Capacity = 1) -> Self { let src = self - let dst = Self(dataCapacity: src.dataCapacity &* dataCapacityFactor, trieCapacity: src.trieCapacity &* trieCapacityFactor) + let dst = Self(dataCapacity: src.dataCapacity &* dataCapacityFactor / dataCapacityShrinkFactor, trieCapacity: src.trieCapacity &* trieCapacityFactor / trieCapacityShrinkFactor) dst.header = src.header dst.dataBaseAddress.initialize(from: src.dataBaseAddress, count: src.header.dataCount) @@ -592,7 +595,15 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if isStorageKnownUniquelyReferenced && hasRoomForTrie { dst = src } else { - dst = src.copy(withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) + // TODO reconsider the details of the heuristic + // + // Since copying is necessary, check if the data section can be reduced. + // Keep at mininum the initial capacity. + // + // 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 + + dst = src.copy(withDataCapacityShrinkFactor: tooMuchForData ? 2 : 1, withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) } let dataIdx = indexFrom(dataMap, bitpos) From fadd0541a4a93bfe26d26b79e29adeb5061f9eec Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 23 Jun 2022 12:54:42 +0200 Subject: [PATCH 138/176] [Capsule] Add placeholders for missing `Dictionary` protocols --- .../PersistentDictionary+CVarArg.swift | 16 +++++++++ .../PersistentDictionary+Collection.swift | 36 +++++++++++++++++++ ...tionary+CustomDebugStringConvertible.swift | 16 +++++++++ ...rsistentDictionary+CustomReflectible.swift | 16 +++++++++ .../PersistentDictionary+Decodable.swift | 16 +++++++++ .../PersistentDictionary+Encodable.swift | 16 +++++++++ .../PersistentDictionary+Sequence.swift | 2 ++ 7 files changed, 118 insertions(+) create mode 100644 Sources/Capsule/PersistentDictionary+CVarArg.swift create mode 100644 Sources/Capsule/PersistentDictionary+Collection.swift create mode 100644 Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift create mode 100644 Sources/Capsule/PersistentDictionary+CustomReflectible.swift create mode 100644 Sources/Capsule/PersistentDictionary+Decodable.swift create mode 100644 Sources/Capsule/PersistentDictionary+Encodable.swift diff --git a/Sources/Capsule/PersistentDictionary+CVarArg.swift b/Sources/Capsule/PersistentDictionary+CVarArg.swift new file mode 100644 index 000000000..1be31a6ef --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+CVarArg.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +//extension PersistentDictionary: CVarArg { +// public var _cVarArgEncoding: [Int] { +// <#code#> +// } +//} diff --git a/Sources/Capsule/PersistentDictionary+Collection.swift b/Sources/Capsule/PersistentDictionary+Collection.swift new file mode 100644 index 000000000..7b2feff28 --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+Collection.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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: Collection { +// public typealias Index = Int +// +// /// +// /// Manipulating Indices +// /// +// +// public var startIndex: Self.Index { +// <#code#> +// } +// +// public var endIndex: Self.Index { +// <#code#> +// } +// +// public func index(after i: Self.Index) -> Self.Index { +// <#code#> +// } +// +// public subscript(position: Self.Index) -> Self.Element { +// _read { +// <#code#> +// } +// } +//} diff --git a/Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift b/Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift new file mode 100644 index 000000000..4e86b71e8 --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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/Capsule/PersistentDictionary+CustomReflectible.swift b/Sources/Capsule/PersistentDictionary+CustomReflectible.swift new file mode 100644 index 000000000..393908f09 --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+CustomReflectible.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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: CustomReflectable { +// public var customMirror: Mirror { +// <#code#> +// } +//} diff --git a/Sources/Capsule/PersistentDictionary+Decodable.swift b/Sources/Capsule/PersistentDictionary+Decodable.swift new file mode 100644 index 000000000..511fb846e --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+Decodable.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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/Capsule/PersistentDictionary+Encodable.swift b/Sources/Capsule/PersistentDictionary+Encodable.swift new file mode 100644 index 000000000..c1362ab72 --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+Encodable.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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/Capsule/PersistentDictionary+Sequence.swift b/Sources/Capsule/PersistentDictionary+Sequence.swift index e20284f5b..5b053baf1 100644 --- a/Sources/Capsule/PersistentDictionary+Sequence.swift +++ b/Sources/Capsule/PersistentDictionary+Sequence.swift @@ -10,6 +10,8 @@ //===----------------------------------------------------------------------===// extension PersistentDictionary: Sequence { + public typealias Element = DictionaryKeyValueTupleIterator.Element + public __consuming func makeIterator() -> DictionaryKeyValueTupleIterator { return DictionaryKeyValueTupleIterator(rootNode: rootNode) } From d9e22947f7c5c77e7d05d10f80f2410670172208 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 9 Aug 2022 16:30:24 +0200 Subject: [PATCH 139/176] [Capsule] Provide skeletons for `keys` and `values` views These are simplistic approximations that will require rework. --- .../Capsule/PersistentDictionary+Keys.swift | 25 +++++++++++++++++++ .../Capsule/PersistentDictionary+Values.swift | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 Sources/Capsule/PersistentDictionary+Keys.swift create mode 100644 Sources/Capsule/PersistentDictionary+Values.swift diff --git a/Sources/Capsule/PersistentDictionary+Keys.swift b/Sources/Capsule/PersistentDictionary+Keys.swift new file mode 100644 index 000000000..7d014fedf --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+Keys.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// TODO: implement a custom `Keys` view rather than relying on an array representation +extension PersistentDictionary { + /// + /// A view of a dictionary’s keys. + /// + public typealias Keys = [Key] + + /// + /// A collection containing just the keys of the dictionary. + /// + public var keys: Self.Keys /* { get } */ { + self.map { $0.key } + } +} diff --git a/Sources/Capsule/PersistentDictionary+Values.swift b/Sources/Capsule/PersistentDictionary+Values.swift new file mode 100644 index 000000000..83e356556 --- /dev/null +++ b/Sources/Capsule/PersistentDictionary+Values.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// TODO: implement a custom `Values` view rather than relying on an array representation +extension PersistentDictionary { + /// + /// A view of a dictionary’s values. + /// + public typealias Values = [Value] + + /// + /// A collection containing just the values of the dictionary. + /// + public var values: Self.Values /* { get set } */ { + self.map { $0.value } + } +} From c7905a5153445e1b9a1dcdbae53d34db7fe17f77 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 9 Aug 2022 17:10:39 +0200 Subject: [PATCH 140/176] [Capsule] Add basic implementation of `PersistentDictionary.Index` type --- .../PersistentDictionary+Collection.swift | 81 +++++++++++++------ Tests/CapsuleTests/Capsule Tests.swift | 50 ++++++------ 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary+Collection.swift b/Sources/Capsule/PersistentDictionary+Collection.swift index 7b2feff28..2a0d4e90b 100644 --- a/Sources/Capsule/PersistentDictionary+Collection.swift +++ b/Sources/Capsule/PersistentDictionary+Collection.swift @@ -9,28 +9,59 @@ // //===----------------------------------------------------------------------===// -//extension PersistentDictionary: Collection { -// public typealias Index = Int -// -// /// -// /// Manipulating Indices -// /// -// -// public var startIndex: Self.Index { -// <#code#> -// } -// -// public var endIndex: Self.Index { -// <#code#> -// } -// -// public func index(after i: Self.Index) -> Self.Index { -// <#code#> -// } -// -// public subscript(position: Self.Index) -> Self.Element { -// _read { -// <#code#> -// } -// } -//} +extension PersistentDictionary: Collection { + public typealias Index = PersistentDictionaryIndex + + /// + /// Manipulating Indices + /// + + public var startIndex: Self.Index { PersistentDictionaryIndex(value: 0) } + + public var endIndex: Self.Index { PersistentDictionaryIndex(value: count) } + + public func index(after i: Self.Index) -> Self.Index { + return i + 1 + } + + /// + /// Returns the index for the given key. + /// + // TODO: implement specialized method in `BitmapIndexedDictionaryNode` + public func index(forKey key: Key) -> Self.Index? { + guard self.contains(key) else { return nil } + + var intIndex = 0 + var iterator = makeIterator() + + while iterator.next()?.key != key { + intIndex += 1 + } + + return PersistentDictionaryIndex(value: intIndex) + } + + /// + /// Accesses the key-value pair at the specified position. + /// + // TODO: implement specialized method in `BitmapIndexedDictionaryNode` (may require cached size on node for efficient skipping) + public subscript(position: Self.Index) -> Self.Element { + var iterator = makeIterator() + for _ in 0 ..< position.value { + let _ = iterator.next() + } + return iterator.next()! + } +} + +public struct PersistentDictionaryIndex: Comparable { + let value: Int + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.value < rhs.value + } + + public static func +(lhs: Self, rhs: Int) -> Self { + return PersistentDictionaryIndex(value: lhs.value + rhs) + } +} diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/CapsuleTests/Capsule Tests.swift index 6bcfcdaef..bcfdce24d 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/CapsuleTests/Capsule Tests.swift @@ -197,31 +197,33 @@ class PersistentDictionaryTests: CollectionTestCase { } } -// func test_index_forKey() { -// withEvery("count", in: 0 ..< 30) { count in -// withLifetimeTracking { tracker in -// let (d, keys, _) = tracker.persistentDictionary(keys: 0 ..< count) -// withEvery("offset", in: 0 ..< count) { offset in -// expectEqual(d.index(forKey: keys[offset]), offset) -// } -// expectNil(d.index(forKey: tracker.instance(for: -1))) -// expectNil(d.index(forKey: tracker.instance(for: count))) -// } -// } -// } + // TODO: determine how to best calculate the expected order of the hash-trie for testing purposes, without relying on the actual implementation + func test_index_forKey() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + expectEqual(d.index(forKey: d.keys[offset]), PersistentDictionaryIndex(value: offset)) // NOTE: uses the actual order `d.keys` + } + expectNil(d.index(forKey: tracker.instance(for: -1))) + expectNil(d.index(forKey: tracker.instance(for: count))) + } + } + } -// func test_subscript_offset() { -// withEvery("count", in: 0 ..< 30) { count in -// withLifetimeTracking { tracker in -// let (d, keys, values) = tracker.persistentDictionary(keys: 0 ..< count) -// withEvery("offset", in: 0 ..< count) { offset in -// let item = d[offset: offset] -// expectEqual(item.key, keys[offset]) -// expectEqual(item.value, values[offset]) -// } -// } -// } -// } + // TODO: determine how to best calculate the expected order of the hash-trie for testing purposes, without relying on the actual implementation + func test_subscript_offset() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + let item = d[PersistentDictionaryIndex(value: offset)] + expectEqual(item.key, d.keys[offset]) // NOTE: uses the actual order `d.keys` + expectEqual(item.value, d.values[offset]) // NOTE: uses the actual order `d.values` + } + } + } + } func test_subscript_getter() { withEvery("count", in: 0 ..< 30) { count in From 0c192fd2e6a1dd4daf56575a8154a67232a91ae4 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 12 Aug 2022 15:29:59 +0200 Subject: [PATCH 141/176] [Capsule] Add bitmap to sequence converters --- Sources/Capsule/_Common.swift | 31 ++++++++++++++++++++++ Tests/CapsuleTests/CapsuleSmokeTests.swift | 22 +++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index fc4adc305..87b3877ad 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -15,6 +15,37 @@ func computeHash(_ value: T) -> Int { typealias Bitmap = UInt32 +extension Bitmap { + public func nonzeroBits() -> NonzeroBits { + return NonzeroBits(from: self) + } + + public func zeroBits() -> NonzeroBits { + return NonzeroBits(from: ~self) + } +} + +public struct NonzeroBits: Sequence, IteratorProtocol, CustomStringConvertible where Bitmap: BinaryInteger { + var bitmap: Bitmap + + init(from bitmap: Bitmap) { + self.bitmap = bitmap + } + + public mutating func next() -> Int? { + guard bitmap != 0 else { return nil } + + let index = bitmap.trailingZeroBitCount + bitmap ^= 1 << index + + return index + } + + public var description: String { + "[\(self.map { $0.description }.joined(separator: ", "))]" + } +} + let bitPartitionSize: Int = 5 let bitPartitionMask: Int = (1 << bitPartitionSize) - 1 diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 710f0b400..1cc802dfc 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -441,4 +441,26 @@ final class BitmapSmokeTests: CollectionTestCase { func test_Capacity_isValid() { expectTrue(Int(2 << bitPartitionSize) <= Int(Capacity.max)) } + + func test_Bitmap_nonzeroBits() { + let bitmap: Bitmap = 0b0100_0000_0000_1011 + + expectEqual(Array(bitmap.nonzeroBits()), [0, 1, 3, 14]) + expectEqual(Array(bitmap.zeroBits()), (0 ..< Bitmap.bitWidth).filter { ![0, 1, 3, 14].contains($0) }) + } + + func test_Bitmap_nonzeroBitsToArray() { + let bitmap: Bitmap = 0b0100_0000_0000_1011 + + let counts = bitmap.nonzeroBits().reduce( + into: Array(repeating: 0, count: Bitmap.bitWidth), { counts, index in counts[index] = 1 }) + + expectEqual(counts.count, Bitmap.bitWidth) + expectEqual(counts.reduce(0, +), bitmap.nonzeroBitCount) + expectEqual(counts.reduce(0, +), 4) + expectEqual(counts[0], 1) + expectEqual(counts[1], 1) + expectEqual(counts[3], 1) + expectEqual(counts[14], 1) + } } From 8563a916a88bc80bf966ed8ef5392f9a9e6f0db4 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 19 Aug 2022 14:18:12 +0200 Subject: [PATCH 142/176] [Capsule] Exemplify enumeration of compacted array --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 1cc802dfc..a18928a39 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -463,4 +463,17 @@ final class BitmapSmokeTests: CollectionTestCase { expectEqual(counts[3], 1) expectEqual(counts[14], 1) } + + func test_Bitmap_enumerateCompactedArray() { + let bitmap: Bitmap = 0b0100_0000_0000_1011 + let elements: [String] = ["zero", "one", "three", "fourteen"] + + var zipIterator = zip(bitmap.nonzeroBits(), elements).makeIterator() + + expectEqual(zipIterator.next()!, (0, "zero")) + expectEqual(zipIterator.next()!, (1, "one")) + expectEqual(zipIterator.next()!, (3, "three")) + expectEqual(zipIterator.next()!, (14, "fourteen")) + expectNil(zipIterator.next()) + } } From 810f22d976a9eaaa69001f6ed9ec8a31de5a967e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 19 Aug 2022 14:30:48 +0200 Subject: [PATCH 143/176] [Capsule] Add sequence representations for slices --- .../_BitmapIndexedDictionaryNode.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index d2ff179c7..16fb72943 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -98,11 +98,26 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } var nodeSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(bitmapIndexedNodeArity).allSatisfy { $0 is ReturnBitmapIndexedNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(header.nodeCount).allSatisfy { $0 is ReturnBitmapIndexedNode } } var collSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(hashCollisionNodeArity).allSatisfy { $0 is ReturnHashCollisionNode } + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(header.collCount).allSatisfy { $0 is ReturnHashCollisionNode } + } + + // TODO: should not materialize as `Array` for performance reasons + var _dataSlice: [ReturnPayload] { + UnsafeMutableBufferPointer(start: dataBaseAddress, count: header.dataCount).map { $0 } + } + + // TODO: should not materialize as `Array` for performance reasons + var _nodeSlice: [ReturnBitmapIndexedNode] { + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(header.nodeCount).map { $0 as! ReturnBitmapIndexedNode } + } + + // TODO: should not materialize as `Array` for performance reasons + var _collSlice: [ReturnHashCollisionNode] { + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(header.collCount).map { $0 as! ReturnHashCollisionNode } } init(dataCapacity: Capacity, trieCapacity: Capacity) { From afcd915044e05ae72157e87ff34497a0b601784d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Wed, 24 Aug 2022 17:49:17 +0200 Subject: [PATCH 144/176] [Capsule] Add persistent dictionary `index` benchmarks --- .../PersistentDictionaryBenchmarks.swift | 46 +++++++++---------- Benchmarks/Libraries/Capsule.json | 20 ++++++++ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index c4c334fa0..d386f52b9 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -276,29 +276,29 @@ extension Benchmark { } } -// self.add( -// title: "PersistentDictionary successful index(forKey:)", -// input: ([Int], [Int]).self -// ) { input, lookups in -// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) -// return { timer in -// for i in lookups { -// precondition(d.index(forKey: i) != nil) -// } -// } -// } -// -// self.add( -// title: "PersistentDictionary unsuccessful index(forKey:)", -// input: ([Int], [Int]).self -// ) { input, lookups in -// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) -// return { timer in -// for i in lookups { -// precondition(d.index(forKey: lookups.count + i) == nil) -// } -// } -// } + self.add( + title: "PersistentDictionary successful index(forKey:)", + input: ([Int], [Int]).self + ) { input, lookups in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for i in lookups { + precondition(d.index(forKey: i) != nil) + } + } + } + + self.add( + title: "PersistentDictionary unsuccessful index(forKey:)", + input: ([Int], [Int]).self + ) { input, lookups in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for i in lookups { + precondition(d.index(forKey: lookups.count + i) == nil) + } + } + } self.add( title: "PersistentDictionary updateValue(_:forKey:), existing", diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/Capsule.json index 67326f1da..5548cbc4e 100644 --- a/Benchmarks/Libraries/Capsule.json +++ b/Benchmarks/Libraries/Capsule.json @@ -30,6 +30,8 @@ "PersistentDictionary updateValue(_:forKey:), insert", "PersistentDictionary random removals (existing keys)", "PersistentDictionary random removals (missing keys)", + "PersistentDictionary successful index(forKey:)", + "PersistentDictionary unsuccessful index(forKey:)", ] } ] @@ -89,6 +91,24 @@ "OrderedDictionary subscript, remove existing", ] }, + { + "kind": "chart", + "title": "index(forKey:), successful index(forKey:)", + "tasks": [ + "PersistentDictionary successful index(forKey:)", + "Dictionary successful index(forKey:)", + "OrderedDictionary successful index(forKey:)", + ] + }, + { + "kind": "chart", + "title": "index(forKey:), unsuccessful index(forKey:)", + "tasks": [ + "PersistentDictionary unsuccessful index(forKey:)", + "Dictionary unsuccessful index(forKey:)", + "OrderedDictionary unsuccessful index(forKey:)", + ] + }, ] }, ] From 3e3e3cc5f1ac04b59d89b77ed5a6cc426fa0eaf1 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 25 Aug 2022 20:27:40 +0200 Subject: [PATCH 145/176] [Capsule] Add `Equatable` smoke test for a special case --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index a18928a39..7a1dc42e1 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -174,6 +174,26 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(inoutHasher.finalize(), expectedHashValue) } + func testCollisionNodeNotEqual() { + let map: PersistentDictionary = [:] + + var res12 = map + res12[CollidableInt(1, 1)] = CollidableInt(1, 1) + res12[CollidableInt(2, 1)] = CollidableInt(2, 1) + + var res13 = map + res13[CollidableInt(1, 1)] = CollidableInt(1, 1) + res13[CollidableInt(3, 1)] = CollidableInt(3, 1) + + var res31 = map + res31[CollidableInt(3, 1)] = CollidableInt(3, 1) + res31[CollidableInt(1, 1)] = CollidableInt(1, 1) + + expectEqual(res13, res31) + expectNotEqual(res13, res12) + expectNotEqual(res31, res12) + } + func testCompactionWhenDeletingFromHashCollisionNode1() { let map: PersistentDictionary = [:] From 3b2c061ecf4d2f0833baefbee9901b5ac58e9f43 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 25 Aug 2022 20:31:03 +0200 Subject: [PATCH 146/176] [Capsule] Backout explicit collision bitmap to simplify codebase --- .../_BitmapIndexedDictionaryNode.swift | 493 ++++++------------ Sources/Capsule/_Common.swift | 19 +- 2 files changed, 161 insertions(+), 351 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 16fb72943..80812820d 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -42,13 +42,8 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } @inline(__always) - var nodeMap: Bitmap { - header.nodeMap - } - - @inline(__always) - var collMap: Bitmap { - header.collMap + var trieMap: Bitmap { + header.trieMap } @inlinable @@ -86,44 +81,52 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } + let recursiveCount = self.reduce(0, { count, _ in count + 1 }) guard recursiveCount - payloadArity >= 2 * nodeArity else { return false } + guard (header.dataMap & header.trieMap) == 0 else { + return false + } + return true } var contentInvariant: Bool { - nodeSliceInvariant && collSliceInvariant + trieSliceInvariant } - var nodeSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(header.nodeCount).allSatisfy { $0 is ReturnBitmapIndexedNode } + var trieSliceInvariant: Bool { + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).allSatisfy { $0 is ReturnBitmapIndexedNode || $0 is ReturnHashCollisionNode } } - var collSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(header.collCount).allSatisfy { $0 is ReturnHashCollisionNode } + // TODO: should not materialize as `Array` for performance reasons + var _dataSlice: [DataBufferElement] { + UnsafeMutableBufferPointer(start: dataBaseAddress, count: header.dataCount).map { $0 } } // TODO: should not materialize as `Array` for performance reasons - var _dataSlice: [ReturnPayload] { - UnsafeMutableBufferPointer(start: dataBaseAddress, count: header.dataCount).map { $0 } + var _trieSlice: [TrieBufferElement] { + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).map { $0 } } // TODO: should not materialize as `Array` for performance reasons + // TODO: consider if of interest var _nodeSlice: [ReturnBitmapIndexedNode] { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(header.nodeCount).map { $0 as! ReturnBitmapIndexedNode } + _trieSlice.filter { $0 is ReturnBitmapIndexedNode }.map { $0 as! ReturnBitmapIndexedNode } } // TODO: should not materialize as `Array` for performance reasons + // TODO: consider if of interest var _collSlice: [ReturnHashCollisionNode] { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).suffix(header.collCount).map { $0 as! ReturnHashCollisionNode } + _trieSlice.filter { $0 is ReturnHashCollisionNode }.map { $0 as! ReturnHashCollisionNode } } init(dataCapacity: Capacity, trieCapacity: Capacity) { let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) - self.header = Header(bitmap1: 0, bitmap2: 0) + self.header = Header(dataMap: 0, trieMap: 0) self.dataBaseAddress = dataBaseAddress self.trieBaseAddress = trieBaseAddress @@ -136,7 +139,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init() { self.init(dataCapacity: initialDataCapacity, trieCapacity: initialTrieCapacity) - self.header = Header(bitmap1: 0, bitmap2: 0) + self.header = Header(dataMap: 0, trieMap: 0) assert(self.invariant) } @@ -144,7 +147,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value) { self.init() - self.header = Header(bitmap1: dataMap, bitmap2: 0) + self.header = Header(dataMap: dataMap, trieMap: 0) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) @@ -154,7 +157,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init(dataMap: Bitmap, firstKey: Key, firstValue: Value, secondKey: Key, secondValue: Value) { self.init() - self.header = Header(bitmap1: dataMap, bitmap2: 0) + self.header = Header(dataMap: dataMap, trieMap: 0) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) @@ -165,7 +168,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedDictionaryNode) { self.init() - self.header = Header(bitmap1: 0, bitmap2: nodeMap) + self.header = Header(dataMap: 0, trieMap: nodeMap) self.trieBaseAddress.initialize(to: firstNode) @@ -175,7 +178,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init(collMap: Bitmap, firstNode: HashCollisionDictionaryNode) { self.init() - self.header = Header(bitmap1: collMap, bitmap2: collMap) + self.header = Header(dataMap: 0, trieMap: collMap) self.trieBaseAddress.initialize(to: firstNode) @@ -185,7 +188,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionDictionaryNode) { self.init() - self.header = Header(bitmap1: dataMap | collMap, bitmap2: collMap) + self.header = Header(dataMap: dataMap, trieMap: collMap) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) @@ -193,10 +196,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - var recursiveCount: Int { - self.reduce(0, { count, _ in count + 1 }) - } - func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -207,14 +206,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return key == payload.key ? payload.value : nil } - guard (nodeMap & bitpos) == 0 else { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) - } + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) - guard (collMap & bitpos) == 0 else { - let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) + if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { + return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) + } else { + return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) + } } return nil @@ -230,14 +229,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return key == payload.key } - guard (nodeMap & bitpos) == 0 else { - let index = indexFrom(nodeMap, mask, bitpos) - return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) - } + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) - guard (collMap & bitpos) == 0 else { - let index = indexFrom(collMap, mask, bitpos) - return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { + return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + } else { + return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + } } return false @@ -260,7 +259,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if keyHash0 == keyHash { let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() - return copyAndMigrateFromInlineToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() @@ -269,33 +268,32 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } } - guard (nodeMap & bitpos) == 0 else { - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getBitmapIndexedNode(index) - - let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { return self } - - return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } - - guard (collMap & bitpos) == 0 else { - let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getHashCollisionNode(index) + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) + let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let collisionHash = subNode.hash + if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { + let subNode = self.getBitmapIndexedNode(index) - if keyHash == collisionHash { let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { return self } - return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) } else { - let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) - effect.setModified() - return copyAndMigrateFromCollisionNodeToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + let subNode = self.getHashCollisionNode(index) + + let collisionHash = subNode.hash + + if keyHash == collisionHash { + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified && subNode !== subNodeNew else { return self } + + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + } else { + let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) + effect.setModified() + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + } } } @@ -314,7 +312,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H effect.setModified() // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` - if self.payloadArity == 2 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 0 { + if self.payloadArity == 2 && self.nodeArity == 0 /* rename */ { if shift == 0 { // keep remaining pair on root level let newDataMap = (dataMap ^ bitpos) @@ -326,82 +324,81 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (remainingKey, remainingValue) = getPayload(1 - index) return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } - } else if self.payloadArity == 1 && self.bitmapIndexedNodeArity == 0 && self.hashCollisionNodeArity == 1 { + } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1 && self.getTrieNode(0) is HashCollisionDictionaryNode { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) return Self(collMap: newCollMap, firstNode: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } - guard (nodeMap & bitpos) == 0 else { - let index = indexFrom(nodeMap, mask, bitpos) - let subNodeModifyInPlace = self.isBitmapIndexedNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getBitmapIndexedNode(index) + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) + let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } + if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { + let subNode = self.getBitmapIndexedNode(index) - switch subNodeNew.sizePredicate { - case .sizeEmpty: - preconditionFailure("Sub-node must have at least one element.") + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified else { return self } - case .sizeOne: - assert(self.bitmapIndexedNodeArity >= 1) + switch subNodeNew.sizePredicate { + case .sizeEmpty: + preconditionFailure("Sub-node must have at least one element.") - if self.isCandiateForCompaction { - // escalate singleton - return subNodeNew - } else { - // inline singleton - return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) - } - - case .sizeMoreThanOne: - assert(self.bitmapIndexedNodeArity >= 1) + case .sizeOne: + assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) - if (subNodeNew.isWrappingSingleHashCollisionNode) { if self.isCandiateForCompaction { - // escalate node that has only a single hash-collision sub-node + // escalate singleton return subNodeNew } else { - // unwrap hash-collision sub-node - return copyAndMigrateFromNodeToCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getHashCollisionNode(0)) + // inline singleton + return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } - } - // modify current node (set replacement node) - return copyAndSetBitmapIndexedNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - } - } + case .sizeMoreThanOne: + assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) + + if (subNodeNew.isWrappingSingleHashCollisionNode) { + if self.isCandiateForCompaction { + // escalate node that has only a single hash-collision sub-node + return subNodeNew + } else { + // unwrap hash-collision sub-node + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getHashCollisionNode(0)) + } + } - guard (collMap & bitpos) == 0 else { - let index = indexFrom(collMap, mask, bitpos) - let subNodeModifyInPlace = self.isHashCollisionNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - let subNode = self.getHashCollisionNode(index) - - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } - - switch subNodeNew.sizePredicate { - case .sizeEmpty: - preconditionFailure("Sub-node must have at least one element.") - - case .sizeOne: - // TODO simplify hash-collision compaction (if feasible) - if self.isCandiateForCompaction { - // escalate singleton - // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) - let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) - let (remainingKey, remainingValue) = subNodeNew.getPayload(0) - return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) - } else { - // inline value - return copyAndMigrateFromCollisionNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) + // modify current node (set replacement node) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) } + } else { + let subNode = self.getHashCollisionNode(index) + + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified else { return self } + + switch subNodeNew.sizePredicate { + case .sizeEmpty: + preconditionFailure("Sub-node must have at least one element.") + + case .sizeOne: + // TODO simplify hash-collision compaction (if feasible) + if self.isCandiateForCompaction { + // escalate singleton + // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) + let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) + let (remainingKey, remainingValue) = subNodeNew.getPayload(0) + return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + } else { + // inline value + return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) + } - case .sizeMoreThanOne: - // modify current node (set replacement node) - return copyAndSetHashCollisionNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + case .sizeMoreThanOne: + // modify current node (set replacement node) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + } } } @@ -410,7 +407,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } - var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && bitmapIndexedNodeArity == 0 && hashCollisionNodeArity == 1 } + var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && self.nodeArity /* rename */ == 1 && self.getTrieNode(0) is HashCollisionDictionaryNode } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != keyHash1) @@ -450,36 +447,18 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } } - var hasBitmapIndexedNodes: Bool { header.nodeMap != 0 } - - var bitmapIndexedNodeArity: Int { header.nodeCount } - func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedDictionaryNode { trieBaseAddress[index] as! BitmapIndexedDictionaryNode } - private func isBitmapIndexedNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = index - return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) - } - - private func isHashCollisionNodeKnownUniquelyReferenced(_ index: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let slotIndex = bitmapIndexedNodeArity + index - return isTrieNodeKnownUniquelyReferenced(slotIndex, isParentNodeKnownUniquelyReferenced) - } - private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - var hasHashCollisionNodes: Bool { header.collMap != 0 } - - var hashCollisionNodeArity: Int { header.collCount } - func getHashCollisionNode(_ index: Int) -> HashCollisionDictionaryNode { - trieBaseAddress[bitmapIndexedNodeArity + index] as! HashCollisionDictionaryNode + trieBaseAddress[index] as! HashCollisionDictionaryNode } // TODO rename, not accurate any more @@ -488,14 +467,20 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // TODO rename, not accurate any more var nodeArity: Int { header.trieCount } + // TODO rename, not accurate any more func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { - if index < bitmapIndexedNodeArity { + if trieBaseAddress[index] is BitmapIndexedDictionaryNode { return .bitmapIndexed(getBitmapIndexedNode(index)) } else { return .hashCollision(getHashCollisionNode(index)) } } + // TODO rename, not accurate any more + func getTrieNode(_ index: Int) -> TrieBufferElement { + trieBaseAddress[index] + } + var hasPayload: Bool { header.dataMap != 0 } var payloadArity: Int { header.dataCount } @@ -508,9 +493,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H func dataIndex(_ bitpos: Bitmap) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } - func nodeIndex(_ bitpos: Bitmap) -> Int { (nodeMap & (bitpos &- 1)).nonzeroBitCount } - - func collIndex(_ bitpos: Bitmap) -> Int { (collMap & (bitpos &- 1)).nonzeroBitCount } + func trieIndex(_ bitpos: Bitmap) -> Int { (trieMap & (bitpos &- 1)).nonzeroBitCount } func copyAndSetValue(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newValue: Value) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self @@ -524,25 +507,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let idx = dataIndex(bitpos) -// let (key, _) = dst.dataBuffer[idx] // as! ReturnPayload -// dst.dataBuffer[idx] = (key, newValue) - dst.dataBaseAddress[idx].value = newValue assert(dst.invariant) return dst } - func copyAndSetBitmapIndexedNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { - let idx = self.nodeIndex(bitpos) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) - } - - func copyAndSetHashCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ newNode: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { - let idx = bitmapIndexedNodeArity + self.collIndex(bitpos) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, idx, newNode) - } - private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -575,7 +545,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H rangeInsert((key, value), at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap, collMap` - dst.header.bitmap1 |= bitpos + dst.header.dataMap |= bitpos assert(dst.invariant) return dst @@ -595,13 +565,13 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap ^ bitpos, nodeMap, collMap` - dst.header.bitmap1 ^= bitpos + dst.header.dataMap ^= bitpos assert(dst.invariant) return dst } - func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { + func copyAndMigrateFromInlineToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: TrieBufferElement) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -624,37 +594,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let dataIdx = indexFrom(dataMap, bitpos) rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) - let nodeIdx = indexFrom(nodeMap, bitpos) - rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) + let trieIdx = indexFrom(trieMap, bitpos) + rangeInsert(node, at: trieIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` - dst.header.bitmap1 ^= bitpos - dst.header.bitmap2 |= bitpos - - assert(dst.invariant) - return dst - } - - func copyAndMigrateFromInlineToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode - - let hasRoomForTrie = header.trieCount < trieCapacity - - if isStorageKnownUniquelyReferenced && hasRoomForTrie { - dst = src - } else { - dst = src.copy(withTrieCapacityFactor: hasRoomForTrie ? 1 : 2) - } - - let dataIdx = indexFrom(dataMap, bitpos) - rangeRemove(at: dataIdx, from: dst.dataBaseAddress, count: dst.header.dataCount) - - let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieCount) - - // update metadata: `dataMap ^ bitpos, nodeMap, collMap | bitpos` - dst.header.bitmap2 |= bitpos + dst.header.dataMap ^= bitpos + dst.header.trieMap |= bitpos assert(dst.invariant) return dst @@ -672,116 +617,45 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) } - let nodeIdx = indexFrom(nodeMap, bitpos) + let nodeIdx = indexFrom(trieMap, bitpos) rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) let dataIdx = indexFrom(dataMap, bitpos) rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` - dst.header.bitmap1 |= bitpos - dst.header.bitmap2 ^= bitpos - - assert(dst.invariant) - return dst - } - - func copyAndMigrateFromCollisionNodeToInline(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ tuple: (key: Key, value: Value)) -> BitmapIndexedDictionaryNode { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode - - let hasRoomForData = header.dataCount < dataCapacity - - if isStorageKnownUniquelyReferenced && hasRoomForData { - dst = src - } else { - dst = src.copy(withDataCapacityFactor: hasRoomForData ? 1 : 2) - } - - let collIdx = nodeMap.nonzeroBitCount + indexFrom(collMap, bitpos) - rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) - - let dataIdx = indexFrom(dataMap, bitpos) - rangeInsert(tuple, at: dataIdx, into: dst.dataBaseAddress, count: dst.header.dataCount) - - // update metadata: `dataMap | bitpos, nodeMap, collMap ^ bitpos` - dst.header.bitmap2 ^= bitpos - - assert(dst.invariant) - return dst - } - - func copyAndMigrateFromCollisionNodeToNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: BitmapIndexedDictionaryNode) -> BitmapIndexedDictionaryNode { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode - - if isStorageKnownUniquelyReferenced { - dst = src - } else { - dst = src.copy() - } - - let collIdx = nodeMap.nonzeroBitCount + collIndex(bitpos) - let nodeIdx = nodeIndex(bitpos) - - rangeRemove(at: collIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) - rangeInsert(node, at: nodeIdx, into: dst.trieBaseAddress, count: dst.header.trieCount - 1) // TODO check, but moving one less should be accurate - - // update metadata: `dataMap, nodeMap | bitpos, collMap ^ bitpos` - dst.header.bitmap1 ^= bitpos - - assert(dst.invariant) - return dst - } - - func copyAndMigrateFromNodeToCollisionNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ node: HashCollisionDictionaryNode) -> BitmapIndexedDictionaryNode { - let src: ReturnBitmapIndexedNode = self - let dst: ReturnBitmapIndexedNode - - if isStorageKnownUniquelyReferenced { - dst = src - } else { - dst = src.copy() - } - - let nodeIdx = nodeIndex(bitpos) - let collIdx = nodeMap.nonzeroBitCount - 1 + collIndex(bitpos) - - rangeRemove(at: nodeIdx, from: dst.trieBaseAddress, count: dst.header.trieCount) - rangeInsert(node, at: collIdx, into: dst.trieBaseAddress, count: dst.header.trieCount - 1) // TODO check, but moving one less should be accurate - - // update metadata: `dataMap, nodeMap ^ bitpos, collMap | bitpos` - dst.header.bitmap1 |= bitpos + dst.header.dataMap |= bitpos + dst.header.trieMap ^= bitpos assert(dst.invariant) return dst } } +// TODO: `Equatable` needs more test coverage, apart from hash-collision smoke test extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedDictionaryNode, rhs: BitmapIndexedDictionaryNode) -> Bool { lhs === rhs || - lhs.nodeMap == rhs.nodeMap && - lhs.dataMap == rhs.dataMap && - lhs.collMap == rhs.collMap && + lhs.header == rhs.header && deepContentEquality(lhs, rhs) } private static func deepContentEquality(_ lhs: BitmapIndexedDictionaryNode, _ rhs: BitmapIndexedDictionaryNode) -> Bool { + guard lhs.header == rhs.header else { return false } + for index in 0.. (dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) { - assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) - - return (dataMap, nodeMap, collMap) - } - - @inline(__always) - func exploded() -> (dataMap: Bitmap, trieMap: Bitmap) { - assert((dataMap | trieMap).nonzeroBitCount == dataMap.nonzeroBitCount + trieMap.nonzeroBitCount) - - return (dataMap, trieMap) - } - @inline(__always) - fileprivate func imploded(dataMap: Bitmap, nodeMap: Bitmap, collMap: Bitmap) -> Self { - assert((dataMap | nodeMap | collMap).nonzeroBitCount == dataMap.nonzeroBitCount + nodeMap.nonzeroBitCount + collMap.nonzeroBitCount) - - return Self(bitmap1: dataMap ^ collMap, bitmap2: nodeMap ^ collMap) - } - - @inline(__always) - func map(_ transform: (Self) -> T) -> T { - return transform(self) - } - - @inline(__always) - func explodedMap(_ transform: (Bitmap) -> T) -> (T, T) { - return (transform(dataMap), transform(trieMap)) - } - - @inline(__always) - func explodedMap(_ transform: (Bitmap) -> T) -> (T, T, T) { - return (transform(dataMap), transform(nodeMap), transform(collMap)) + static func == (lhs: Header, rhs: Header) -> Bool { + lhs.dataMap == rhs.dataMap && lhs.trieMap == rhs.trieMap } } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 87b3877ad..e2354a0d4 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -143,24 +143,19 @@ protocol Node: AnyObject { associatedtype ReturnBitmapIndexedNode: Node associatedtype ReturnHashCollisionNode: Node - var hasBitmapIndexedNodes: Bool { get } - - var bitmapIndexedNodeArity: Int { get } - - func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode - - var hasHashCollisionNodes: Bool { get } - - var hashCollisionNodeArity: Int { get } - - func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode - var hasNodes: Bool { get } var nodeArity: Int { get } + @available(*, deprecated) func getNode(_ index: Int) -> TrieNode + @available(*, deprecated) + func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode + + @available(*, deprecated) + func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode + var hasPayload: Bool { get } var payloadArity: Int { get } From c8a880794d9554cebd68454916e56e07c8658878 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Thu, 25 Aug 2022 20:46:52 +0200 Subject: [PATCH 147/176] [Capsule] Simplify initializers --- .../_BitmapIndexedDictionaryNode.swift | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 80812820d..3d5eb8817 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -165,30 +165,20 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - convenience init(nodeMap: Bitmap, firstNode: BitmapIndexedDictionaryNode) { + convenience init(trieMap: Bitmap, firstNode: T) { self.init() - self.header = Header(dataMap: 0, trieMap: nodeMap) + self.header = Header(dataMap: 0, trieMap: trieMap) self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } - convenience init(collMap: Bitmap, firstNode: HashCollisionDictionaryNode) { + convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: T) { self.init() - self.header = Header(dataMap: 0, trieMap: collMap) - - self.trieBaseAddress.initialize(to: firstNode) - - assert(self.invariant) - } - - convenience init(dataMap: Bitmap, collMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: HashCollisionDictionaryNode) { - self.init() - - self.header = Header(dataMap: dataMap, trieMap: collMap) + self.header = Header(dataMap: dataMap, trieMap: trieMap) self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) @@ -327,7 +317,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1 && self.getTrieNode(0) is HashCollisionDictionaryNode { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ // create potential new root: will a) become new root, or b) unwrapped on another level let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return Self(collMap: newCollMap, firstNode: getHashCollisionNode(0)) + return Self(trieMap: newCollMap, firstNode: getHashCollisionNode(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -426,7 +416,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return Self(nodeMap: bitposFrom(mask0), firstNode: node) + return Self(trieMap: bitposFrom(mask0), firstNode: node) } } @@ -438,12 +428,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - return Self(dataMap: bitposFrom(mask0), collMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) + return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - return Self(nodeMap: bitposFrom(mask0), firstNode: node) + return Self(trieMap: bitposFrom(mask0), firstNode: node) } } From c6b971910fbe4a02541fa58b611163c9dad37301 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 29 Aug 2022 13:38:00 +0200 Subject: [PATCH 148/176] [Capsule] Replace top-level `cachedSize` with per node `count` --- .../PersistentDictionary+Equatable.swift | 3 +- Sources/Capsule/PersistentDictionary.swift | 33 +++----- .../_BitmapIndexedDictionaryNode.swift | 81 ++++++++++++++++--- Tests/CapsuleTests/CapsuleSmokeTests.swift | 25 +++++- 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary+Equatable.swift b/Sources/Capsule/PersistentDictionary+Equatable.swift index b860cd3e7..4055aeeef 100644 --- a/Sources/Capsule/PersistentDictionary+Equatable.swift +++ b/Sources/Capsule/PersistentDictionary+Equatable.swift @@ -12,7 +12,6 @@ // TODO check Dictionary semantics of Equatable (i.e., if it only compares keys or also values) extension PersistentDictionary: Equatable where Value: Equatable { public static func == (lhs: PersistentDictionary, rhs: PersistentDictionary) -> Bool { - lhs.cachedSize == rhs.cachedSize && - (lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode) + lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode } } diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index 9667e5eb6..0ef4621a4 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -11,19 +11,17 @@ public struct PersistentDictionary where Key: Hashable { var rootNode: BitmapIndexedDictionaryNode - var cachedSize: Int - fileprivate init(_ rootNode: BitmapIndexedDictionaryNode, _ cachedSize: Int) { + fileprivate init(_ rootNode: BitmapIndexedDictionaryNode) { self.rootNode = rootNode - self.cachedSize = cachedSize } public init() { - self.init(BitmapIndexedDictionaryNode(), 0) + self.init(BitmapIndexedDictionaryNode()) } public init(_ map: PersistentDictionary) { - self.init(map.rootNode, map.cachedSize) + self.init(map.rootNode) } // TODO consider removing `unchecked` version, since it's only referenced from within the test suite @@ -70,13 +68,13 @@ public struct PersistentDictionary where Key: Hashable { /// Inspecting a Dictionary /// - public var isEmpty: Bool { cachedSize == 0 } + public var isEmpty: Bool { rootNode.count == 0 } - public var count: Int { cachedSize } + public var count: Int { rootNode.count } - public var underestimatedCount: Int { cachedSize } + public var underestimatedCount: Int { rootNode.count } - public var capacity: Int { count } + public var capacity: Int { rootNode.count } /// /// Accessing Keys and Values @@ -127,13 +125,7 @@ public struct PersistentDictionary where Key: Hashable { let newRootNode = rootNode.updateOrUpdating(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) if effect.modified { - if effect.replacedValue { - self.rootNode = newRootNode - // self.cachedSize = cachedSize - } else { - self.rootNode = newRootNode - self.cachedSize = cachedSize + 1 - } + self.rootNode = newRootNode } } @@ -144,11 +136,7 @@ public struct PersistentDictionary where Key: Hashable { let newRootNode = rootNode.updateOrUpdating(false, key, value, keyHash, 0, &effect) if effect.modified { - if effect.replacedValue { - return Self(newRootNode, cachedSize) - } else { - return Self(newRootNode, cachedSize + 1) - } + return Self(newRootNode) } else { return self } } @@ -173,7 +161,6 @@ public struct PersistentDictionary where Key: Hashable { if effect.modified { self.rootNode = newRootNode - self.cachedSize = cachedSize - 1 } } @@ -184,7 +171,7 @@ public struct PersistentDictionary where Key: Hashable { let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) if effect.modified { - return Self(newRootNode, cachedSize - 1) + return Self(newRootNode) } else { return self } } diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 3d5eb8817..6914b6ff7 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -20,6 +20,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H typealias TrieBufferElement = AnyObject var header: Header + var count: Int let dataCapacity: Capacity let trieCapacity: Capacity @@ -69,30 +70,50 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let dst = Self(dataCapacity: src.dataCapacity &* dataCapacityFactor / dataCapacityShrinkFactor, trieCapacity: src.trieCapacity &* trieCapacityFactor / trieCapacityShrinkFactor) 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) + assert(src.invariant) assert(dst.invariant) return dst } var invariant: Bool { + guard headerInvariant else { + return false + } + guard contentInvariant else { return false } let recursiveCount = self.reduce(0, { count, _ in count + 1 }) + + guard recursiveCount == count else { + return false + } + guard recursiveCount - payloadArity >= 2 * nodeArity else { return false } - guard (header.dataMap & header.trieMap) == 0 else { + guard _nodeSlice.allSatisfy({ $0.invariant }) else { + return false + } + + guard _collSlice.allSatisfy({ _ in true /* does not have explicit invariant yet */ }) else { return false } return true } + var headerInvariant: Bool { + (header.dataMap & header.trieMap) == 0 + } + var contentInvariant: Bool { trieSliceInvariant } @@ -127,6 +148,8 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) self.header = Header(dataMap: 0, trieMap: 0) + self.count = 0 + self.dataBaseAddress = dataBaseAddress self.trieBaseAddress = trieBaseAddress @@ -148,6 +171,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H self.init() self.header = Header(dataMap: dataMap, trieMap: 0) + self.count = 1 self.dataBaseAddress.initialize(to: (firstKey, firstValue)) @@ -158,6 +182,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H self.init() self.header = Header(dataMap: dataMap, trieMap: 0) + self.count = 2 self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.dataBaseAddress.successor().initialize(to: (secondKey, secondValue)) @@ -170,6 +195,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H self.header = Header(dataMap: 0, trieMap: trieMap) + switch firstNode { + case let firstNode as BitmapIndexedDictionaryNode: + self.count = firstNode.count + case let firstNode as HashCollisionDictionaryNode: + self.count = firstNode.content.count + default: break + } + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) @@ -180,6 +213,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H self.header = Header(dataMap: dataMap, trieMap: trieMap) + switch firstNode { + case let firstNode as BitmapIndexedDictionaryNode: + self.count = 1 + firstNode.count + case let firstNode as HashCollisionDictionaryNode: + self.count = 1 + firstNode.content.count + default: break + } + self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) @@ -266,9 +307,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { return self } + guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) } else { let subNode = self.getHashCollisionNode(index) @@ -276,13 +317,13 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if keyHash == collisionHash { let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { return self } + guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) } } } @@ -298,7 +339,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let (key0, _) = self.getPayload(index) - guard key0 == key else { return self } + guard key0 == key else { assert(self.invariant) ; return self } effect.setModified() // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` @@ -329,7 +370,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNode = self.getBitmapIndexedNode(index) let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } + guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } switch subNodeNew.sizePredicate { case .sizeEmpty: @@ -355,18 +396,18 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getHashCollisionNode(0)) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getHashCollisionNode(0), updateCount: { $0 -= 1 }) } } // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } } else { let subNode = self.getHashCollisionNode(index) let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified else { return self } + guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } switch subNodeNew.sizePredicate { case .sizeEmpty: @@ -387,7 +428,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H case .sizeMoreThanOne: // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } } } @@ -499,11 +540,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dst.dataBaseAddress[idx].value = newValue + assert(src.invariant) assert(dst.invariant) return dst } - private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T) -> BitmapIndexedDictionaryNode { + private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T, updateCount: (inout Int) -> Void) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -515,6 +557,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dst.trieBaseAddress[idx] = newNode as TrieBufferElement + // update metadata: `dataMap, nodeMap, collMap` + updateCount(&dst.count) + + assert(src.invariant) assert(dst.invariant) return dst } @@ -536,7 +582,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // update metadata: `dataMap | bitpos, nodeMap, collMap` dst.header.dataMap |= bitpos + dst.count += 1 + assert(src.invariant) assert(dst.invariant) return dst } @@ -556,7 +604,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // update metadata: `dataMap ^ bitpos, nodeMap, collMap` dst.header.dataMap ^= bitpos + dst.count -= 1 + assert(src.invariant) assert(dst.invariant) return dst } @@ -590,7 +640,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // update metadata: `dataMap ^ bitpos, nodeMap | bitpos, collMap` dst.header.dataMap ^= bitpos dst.header.trieMap |= bitpos + dst.count += 1 // assuming that `node.count == 2` + assert(src.invariant) assert(dst.invariant) return dst } @@ -616,7 +668,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // update metadata: `dataMap | bitpos, nodeMap ^ bitpos, collMap` dst.header.dataMap |= bitpos dst.header.trieMap ^= bitpos + dst.count -= 1 // assuming that updated `node.count == 1` + assert(src.invariant) assert(dst.invariant) return dst } @@ -627,6 +681,7 @@ extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedDictionaryNode, rhs: BitmapIndexedDictionaryNode) -> Bool { lhs === rhs || lhs.header == rhs.header && + lhs.count == rhs.count && deepContentEquality(lhs, rhs) } diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 7a1dc42e1..2ee10c06d 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -136,8 +136,8 @@ final class CapsuleSmokeTests: CollectionTestCase { for index in 0.. = [:] + + map[CollidableInt(32769)] = CollidableInt(32769) + map[CollidableInt(11, 1)] = CollidableInt(11, 1) + map[CollidableInt(12, 1)] = CollidableInt(12, 1) + 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) + } + func testCompactionWhenDeletingFromHashCollisionNode1() { let map: PersistentDictionary = [:] From b879788d8a6ccf13bbbd6ef794a4c37c18dbf297 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 29 Aug 2022 13:56:50 +0200 Subject: [PATCH 149/176] [Capsule] Replace `sizePredicate` usage by `count` --- .../_BitmapIndexedDictionaryNode.swift | 18 ++++++------- Sources/Capsule/_Common.swift | 25 ------------------- .../_HashCollisionDictionaryNode.swift | 2 +- 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 6914b6ff7..f899c7a31 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -372,11 +372,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } - switch subNodeNew.sizePredicate { - case .sizeEmpty: + switch subNodeNew.count { + case 0: preconditionFailure("Sub-node must have at least one element.") - case .sizeOne: + case 1: assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) if self.isCandiateForCompaction { @@ -387,7 +387,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } - case .sizeMoreThanOne: + case _: assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) if (subNodeNew.isWrappingSingleHashCollisionNode) { @@ -409,11 +409,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } - switch subNodeNew.sizePredicate { - case .sizeEmpty: + switch subNodeNew.count { + case 0: preconditionFailure("Sub-node must have at least one element.") - case .sizeOne: + case 1: // TODO simplify hash-collision compaction (if feasible) if self.isCandiateForCompaction { // escalate singleton @@ -426,7 +426,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } - case .sizeMoreThanOne: + case _: // modify current node (set replacement node) return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } @@ -520,8 +520,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dataBaseAddress[index] // as! ReturnPayload } - var sizePredicate: SizePredicate { SizePredicate(self) } - func dataIndex(_ bitpos: Bitmap) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } func trieIndex(_ bitpos: Bitmap) -> Int { (trieMap & (bitpos &- 1)).nonzeroBitCount } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index e2354a0d4..505b14ca1 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -115,29 +115,6 @@ enum TrieNode { } } -enum SizePredicate { - case sizeEmpty - case sizeOne - case sizeMoreThanOne -} - -extension SizePredicate { - init(_ node: T) { - if node.nodeArity == 0 { - switch node.payloadArity { - case 0: - self = .sizeEmpty - case 1: - self = .sizeOne - case _: - self = .sizeMoreThanOne - } - } else { - self = .sizeMoreThanOne - } - } -} - protocol Node: AnyObject { associatedtype ReturnPayload associatedtype ReturnBitmapIndexedNode: Node @@ -161,8 +138,6 @@ protocol Node: AnyObject { var payloadArity: Int { get } func getPayload(_ index: Int) -> ReturnPayload - - var sizePredicate: SizePredicate { get } } /// diff --git a/Sources/Capsule/_HashCollisionDictionaryNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift index e7ed554f8..a530d566d 100644 --- a/Sources/Capsule/_HashCollisionDictionaryNode.swift +++ b/Sources/Capsule/_HashCollisionDictionaryNode.swift @@ -105,7 +105,7 @@ final class HashCollisionDictionaryNode: DictionaryNode where Key: H func getPayload(_ index: Int) -> (key: Key, value: Value) { content[index] } - var sizePredicate: SizePredicate { SizePredicate(self) } + var count: Int { content.count } } extension HashCollisionDictionaryNode: Equatable where Value: Equatable { From fa8afba05a0cb653e56526d2a26ee071f0741c0d Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 29 Aug 2022 21:59:25 +0200 Subject: [PATCH 150/176] [Capsule] Convert to `TrieNode` content and add specialized `index` Note that this is an intermediate step towards generic heterogeneous `any` type content. --- .../PersistentDictionary+Collection.swift | 12 +- Sources/Capsule/PersistentDictionary.swift | 14 +- .../_BitmapIndexedDictionaryNode.swift | 203 +++++++++--------- Sources/Capsule/_Common.swift | 26 ++- Sources/Capsule/_DictionaryNode.swift | 2 + .../_HashCollisionDictionaryNode.swift | 4 + Tests/CapsuleTests/CapsuleSmokeTests.swift | 13 ++ 7 files changed, 149 insertions(+), 125 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary+Collection.swift b/Sources/Capsule/PersistentDictionary+Collection.swift index 2a0d4e90b..140d235e4 100644 --- a/Sources/Capsule/PersistentDictionary+Collection.swift +++ b/Sources/Capsule/PersistentDictionary+Collection.swift @@ -27,18 +27,8 @@ extension PersistentDictionary: Collection { /// /// Returns the index for the given key. /// - // TODO: implement specialized method in `BitmapIndexedDictionaryNode` public func index(forKey key: Key) -> Self.Index? { - guard self.contains(key) else { return nil } - - var intIndex = 0 - var iterator = makeIterator() - - while iterator.next()?.key != key { - intIndex += 1 - } - - return PersistentDictionaryIndex(value: intIndex) + return rootNode.index(key, computeHash(key), 0, 0) } /// diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index 0ef4621a4..f50a7b22c 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -193,10 +193,12 @@ public struct PersistentDictionary where Key: Hashable { /// public struct DictionaryKeyValueTupleIterator: IteratorProtocol { + typealias TrieBufferElement = TrieNode, HashCollisionDictionaryNode> + private var payloadIterator: UnsafeBufferPointer<(key: Key, value: Value)>.Iterator? - private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? - private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] + private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? + private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] init(rootNode: BitmapIndexedDictionaryNode) { trieIteratorStackRemainder = [] @@ -212,7 +214,7 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro } // TODO consider moving to `BitmapIndexedDictionaryNode` - private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { + private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() } @@ -224,7 +226,7 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro while trieIteratorStackTop != nil { if let nextAnyObject = trieIteratorStackTop!.next() { switch nextAnyObject { - case let nextNode as BitmapIndexedDictionaryNode: + case .bitmapIndexed(let nextNode): if nextNode.hasNodes { trieIteratorStackRemainder.append(trieIteratorStackTop!) trieIteratorStackTop = makeTrieIterator(nextNode) @@ -233,11 +235,9 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro payloadIterator = makePayloadIterator(nextNode) return payloadIterator?.next() } - case let nextNode as HashCollisionDictionaryNode: + case .hashCollision(let nextNode): payloadIterator = nextNode.content.withUnsafeBufferPointer { $0.makeIterator() } return payloadIterator?.next() - default: - break } } else { trieIteratorStackTop = trieIteratorStackRemainder.popLast() diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index f899c7a31..020be9f2c 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -17,7 +17,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H typealias ReturnPayload = (key: Key, value: Value) typealias DataBufferElement = ReturnPayload // `ReturnPayload` or `Any` - typealias TrieBufferElement = AnyObject + typealias TrieBufferElement = TrieNode var header: Header var count: Int @@ -85,10 +85,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } - guard contentInvariant else { - return false - } - let recursiveCount = self.reduce(0, { count, _ in count + 1 }) guard recursiveCount == count else { @@ -99,11 +95,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } - guard _nodeSlice.allSatisfy({ $0.invariant }) else { + guard _trieSlice.allSatisfy({ if case .bitmapIndexed(let node) = $0 { return node.invariant } else { return true } }) else { return false } - guard _collSlice.allSatisfy({ _ in true /* does not have explicit invariant yet */ }) else { + guard _trieSlice.allSatisfy({ if case .hashCollision(_) = $0 { return true } else { return true /* does not have explicit invariant yet */ } }) else { return false } @@ -114,14 +110,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H (header.dataMap & header.trieMap) == 0 } - var contentInvariant: Bool { - trieSliceInvariant - } - - var trieSliceInvariant: Bool { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).allSatisfy { $0 is ReturnBitmapIndexedNode || $0 is ReturnHashCollisionNode } - } - // TODO: should not materialize as `Array` for performance reasons var _dataSlice: [DataBufferElement] { UnsafeMutableBufferPointer(start: dataBaseAddress, count: header.dataCount).map { $0 } @@ -132,18 +120,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).map { $0 } } - // TODO: should not materialize as `Array` for performance reasons - // TODO: consider if of interest - var _nodeSlice: [ReturnBitmapIndexedNode] { - _trieSlice.filter { $0 is ReturnBitmapIndexedNode }.map { $0 as! ReturnBitmapIndexedNode } - } - - // TODO: should not materialize as `Array` for performance reasons - // TODO: consider if of interest - var _collSlice: [ReturnHashCollisionNode] { - _trieSlice.filter { $0 is ReturnHashCollisionNode }.map { $0 as! ReturnHashCollisionNode } - } - init(dataCapacity: Capacity, trieCapacity: Capacity) { let (dataBaseAddress, trieBaseAddress) = Self._allocate(dataCapacity: dataCapacity, trieCapacity: trieCapacity) @@ -190,36 +166,22 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - convenience init(trieMap: Bitmap, firstNode: T) { + convenience init(trieMap: Bitmap, firstNode: TrieBufferElement) { self.init() self.header = Header(dataMap: 0, trieMap: trieMap) - - switch firstNode { - case let firstNode as BitmapIndexedDictionaryNode: - self.count = firstNode.count - case let firstNode as HashCollisionDictionaryNode: - self.count = firstNode.content.count - default: break - } + self.count = firstNode.count self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } - convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: T) { + convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: TrieBufferElement) { self.init() self.header = Header(dataMap: dataMap, trieMap: trieMap) - - switch firstNode { - case let firstNode as BitmapIndexedDictionaryNode: - self.count = 1 + firstNode.count - case let firstNode as HashCollisionDictionaryNode: - self.count = 1 + firstNode.content.count - default: break - } + self.count = 1 + firstNode.count self.dataBaseAddress.initialize(to: (firstKey, firstValue)) self.trieBaseAddress.initialize(to: firstNode) @@ -240,10 +202,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { - return self.getBitmapIndexedNode(index).get(key, keyHash, shift + bitPartitionSize) - } else { - return self.getHashCollisionNode(index).get(key, keyHash, shift + bitPartitionSize) + switch self.getNodeEnum(index) { + case .bitmapIndexed(let node): + return node.get(key, keyHash, shift + bitPartitionSize) + case .hashCollision(let node): + return node.get(key, keyHash, shift + bitPartitionSize) } } @@ -263,16 +226,45 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { - return self.getBitmapIndexedNode(index).containsKey(key, keyHash, shift + bitPartitionSize) - } else { - return self.getHashCollisionNode(index).containsKey(key, keyHash, shift + bitPartitionSize) + switch self.getNodeEnum(index) { + case .bitmapIndexed(let node): + return node.containsKey(key, keyHash, shift + bitPartitionSize) + case .hashCollision(let node): + return node.containsKey(key, keyHash, shift + bitPartitionSize) } } return false } + func index(_ key: Key, _ keyHash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? { + let mask = maskFrom(keyHash, shift) + let bitpos = bitposFrom(mask) + + let skipped = self.counts.prefix(upTo: mask).reduce(0, +) + + guard (dataMap & bitpos) == 0 else { + let index = indexFrom(dataMap, mask, bitpos) + let payload = self.getPayload(index) + guard key == payload.key else { return nil } + + return PersistentDictionaryIndex(value: skippedBefore + skipped) + } + + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) + + switch self.getNodeEnum(index) { + case .bitmapIndexed(let node): + return node.index(key, keyHash, shift + bitPartitionSize, skippedBefore + skipped) + case .hashCollision(let node): + return node.index(key, keyHash, shift + bitPartitionSize, skippedBefore + skipped) + } + } + + return nil + } + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -290,11 +282,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if keyHash0 == keyHash { let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() - return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, .hashCollision(subNodeNew)) } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() - return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, .bitmapIndexed(subNodeNew)) } } } @@ -303,27 +295,24 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let index = indexFrom(trieMap, mask, bitpos) let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { - let subNode = self.getBitmapIndexedNode(index) - + switch self.getNodeEnum(index) { + case .bitmapIndexed(let subNode): let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) - } else { - let subNode = self.getHashCollisionNode(index) - + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) + case .hashCollision(let subNode): let collisionHash = subNode.hash if keyHash == collisionHash { let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .hashCollision(subNodeNew), updateCount: { $0 += 1 }) } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 += 1 }) } } } @@ -355,10 +344,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (remainingKey, remainingValue) = getPayload(1 - index) return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } - } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1 && self.getTrieNode(0) is HashCollisionDictionaryNode { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ + } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1, case .hashCollision(let __node) = self.getNodeEnum(0) { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ // create potential new root: will a) become new root, or b) unwrapped on another level - let newCollMap: Bitmap = bitposFrom(maskFrom(getHashCollisionNode(0).hash, 0)) - return Self(trieMap: newCollMap, firstNode: getHashCollisionNode(0)) + let newCollMap: Bitmap = bitposFrom(maskFrom(__node.hash, 0)) + return Self(trieMap: newCollMap, firstNode: getNodeEnum(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -366,9 +355,8 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let index = indexFrom(trieMap, mask, bitpos) let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - if /*shift <= Bitmap.bitWidth*/ self.getTrieNode(index) is BitmapIndexedDictionaryNode { - let subNode = self.getBitmapIndexedNode(index) - + switch self.getNodeEnum(index) { + case .bitmapIndexed(let subNode): let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } @@ -396,16 +384,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getHashCollisionNode(0), updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNodeEnum(0), updateCount: { $0 -= 1 }) } } // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 -= 1 }) } - } else { - let subNode = self.getHashCollisionNode(index) - + case .hashCollision(let subNode): let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } @@ -428,7 +414,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H case _: // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .hashCollision(subNodeNew), updateCount: { $0 -= 1 }) } } } @@ -438,7 +424,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } - var isWrappingSingleHashCollisionNode: Bool { payloadArity == 0 && self.nodeArity /* rename */ == 1 && self.getTrieNode(0) is HashCollisionDictionaryNode } + var isWrappingSingleHashCollisionNode: Bool { if payloadArity == 0 && self.nodeArity /* rename */ == 1, case .hashCollision(_) = self.getNodeEnum(0) { return true } else { return false } } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != keyHash1) @@ -457,7 +443,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return Self(trieMap: bitposFrom(mask0), firstNode: node) + return Self(trieMap: bitposFrom(mask0), firstNode: .bitmapIndexed(node)) } } @@ -469,29 +455,23 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if mask0 != mask1 { // unique prefixes, payload and collision node fit on same level - return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) + return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: .hashCollision(node1)) } else { // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - return Self(trieMap: bitposFrom(mask0), firstNode: node) + return Self(trieMap: bitposFrom(mask0), firstNode: .bitmapIndexed(node)) } } - func getBitmapIndexedNode(_ index: Int) -> BitmapIndexedDictionaryNode { - trieBaseAddress[index] as! BitmapIndexedDictionaryNode - } - + // TODO: verify using a `var` does void `isKnownUniquelyReferenced` check private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) + var node = trieBaseAddress[slotIndex].object + let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&node) return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - func getHashCollisionNode(_ index: Int) -> HashCollisionDictionaryNode { - trieBaseAddress[index] as! HashCollisionDictionaryNode - } - // TODO rename, not accurate any more var hasNodes: Bool { header.trieMap != 0 } @@ -499,16 +479,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var nodeArity: Int { header.trieCount } // TODO rename, not accurate any more - func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { - if trieBaseAddress[index] is BitmapIndexedDictionaryNode { - return .bitmapIndexed(getBitmapIndexedNode(index)) - } else { - return .hashCollision(getHashCollisionNode(index)) - } + final func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { + return trieBaseAddress[index] } - // TODO rename, not accurate any more - func getTrieNode(_ index: Int) -> TrieBufferElement { + final func getNodeEnum(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { trieBaseAddress[index] } @@ -517,7 +492,33 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var payloadArity: Int { header.dataCount } func getPayload(_ index: Int) -> (key: Key, value: Value) { - dataBaseAddress[index] // as! ReturnPayload + dataBaseAddress[index] + } + + private final var counts: [Int] { + var counts = Array(repeating: 0, count: Bitmap.bitWidth) + + zip(header.dataMap.nonzeroBits(), _dataSlice).forEach { (index, _) in + counts[index] = 1 + } + + zip(header.trieMap.nonzeroBits(), _trieSlice).forEach { (index, trieNode) in + counts[index] = trieNode.count + } + + return counts + } + + private final func count(upTo mask: Int) -> Int { + let bitpos = bitposFrom(mask) + + let dataIndex = indexFrom(dataMap, mask, bitpos) + let trieIndex = indexFrom(trieMap, mask, bitpos) + + let count = dataIndex + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(upTo: trieIndex).map { $0.count }.reduce(0, +) + + assert(count == counts.prefix(upTo: mask).reduce(0, +)) + return count } func dataIndex(_ bitpos: Bitmap) -> Int { (dataMap & (bitpos &- 1)).nonzeroBitCount } @@ -543,7 +544,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return dst } - private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: T, updateCount: (inout Int) -> Void) -> BitmapIndexedDictionaryNode { + private func copyAndSetTrieNode(_ isStorageKnownUniquelyReferenced: Bool, _ bitpos: Bitmap, _ idx: Int, _ newNode: TrieBufferElement, updateCount: (inout Int) -> Void) -> BitmapIndexedDictionaryNode { let src: ReturnBitmapIndexedNode = self let dst: ReturnBitmapIndexedNode @@ -553,7 +554,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dst = src.copy() } - dst.trieBaseAddress[idx] = newNode as TrieBufferElement + dst.trieBaseAddress[idx] = newNode // as TrieBufferElement // update metadata: `dataMap, nodeMap, collMap` updateCount(&dst.count) @@ -693,7 +694,7 @@ extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { } for index in 0.. { /// The convenience computed properties below are used in the base iterator implementations. + var object: AnyObject { + switch self { + case .bitmapIndexed(let node): + return node + case .hashCollision(let node): + return node + } + } + + var count: Int { + switch self { + case .bitmapIndexed(let node): + return node.count + case .hashCollision(let node): + return node.count + } + } + var hasPayload: Bool { switch self { case .bitmapIndexed(let node): @@ -127,17 +145,13 @@ protocol Node: AnyObject { @available(*, deprecated) func getNode(_ index: Int) -> TrieNode - @available(*, deprecated) - func getBitmapIndexedNode(_ index: Int) -> ReturnBitmapIndexedNode - - @available(*, deprecated) - func getHashCollisionNode(_ index: Int) -> ReturnHashCollisionNode - var hasPayload: Bool { get } var payloadArity: Int { get } func getPayload(_ index: Int) -> ReturnPayload + + var count: Int { get } } /// diff --git a/Sources/Capsule/_DictionaryNode.swift b/Sources/Capsule/_DictionaryNode.swift index 92c96ae25..dc7d92bc3 100644 --- a/Sources/Capsule/_DictionaryNode.swift +++ b/Sources/Capsule/_DictionaryNode.swift @@ -17,6 +17,8 @@ protocol DictionaryNode: Node { func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool + func index(_ key: Key, _ hash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode diff --git a/Sources/Capsule/_HashCollisionDictionaryNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift index a530d566d..1bcb7501d 100644 --- a/Sources/Capsule/_HashCollisionDictionaryNode.swift +++ b/Sources/Capsule/_HashCollisionDictionaryNode.swift @@ -39,6 +39,10 @@ final class HashCollisionDictionaryNode: DictionaryNode where Key: H // return self.hash == hash && content.contains(where: { key == $0.key && value == $0.value }) // } + func index(_ key: Key, _ keyHash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? { + content.firstIndex(where: { _key, _ in _key == key }).map { PersistentDictionaryIndex(value: $0) } + } + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 2ee10c06d..44275d640 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -470,6 +470,19 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map1, map2) } + + func testIndexForKey() { + let map: PersistentDictionary = [ + CollidableInt(11, 1): "a", + CollidableInt(12, 1): "a", + CollidableInt(32769): "b" + ] + + expectEqual(map.index(forKey: CollidableInt(11, 1)), PersistentDictionaryIndex(value: 0)) + expectEqual(map.index(forKey: CollidableInt(12, 1)), PersistentDictionaryIndex(value: 1)) + expectEqual(map.index(forKey: CollidableInt(32769)), PersistentDictionaryIndex(value: 2)) + expectNil(map.index(forKey: CollidableInt(13, 1))) + } } final class BitmapSmokeTests: CollectionTestCase { From cc0f051b740dc394ce9350c63a72e25ee2d06832 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 30 Aug 2022 17:09:25 +0200 Subject: [PATCH 151/176] [Capsule] Revert to `AnyObject` node representation Fixes an issue with `isTrieNodeKnownUniquelyReferenced` and existential types. --- Sources/Capsule/PersistentDictionary.swift | 14 ++-- .../_BitmapIndexedDictionaryNode.swift | 67 ++++++++++++------- Sources/Capsule/_Common.swift | 5 +- .../_HashCollisionDictionaryNode.swift | 6 +- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index f50a7b22c..a98cf8f0e 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -193,12 +193,10 @@ public struct PersistentDictionary where Key: Hashable { /// public struct DictionaryKeyValueTupleIterator: IteratorProtocol { - typealias TrieBufferElement = TrieNode, HashCollisionDictionaryNode> - private var payloadIterator: UnsafeBufferPointer<(key: Key, value: Value)>.Iterator? - private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? - private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] + private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? + private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] init(rootNode: BitmapIndexedDictionaryNode) { trieIteratorStackRemainder = [] @@ -214,7 +212,7 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro } // TODO consider moving to `BitmapIndexedDictionaryNode` - private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { + private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() } @@ -226,7 +224,7 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro while trieIteratorStackTop != nil { if let nextAnyObject = trieIteratorStackTop!.next() { switch nextAnyObject { - case .bitmapIndexed(let nextNode): + case let nextNode as BitmapIndexedDictionaryNode: if nextNode.hasNodes { trieIteratorStackRemainder.append(trieIteratorStackTop!) trieIteratorStackTop = makeTrieIterator(nextNode) @@ -235,9 +233,11 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro payloadIterator = makePayloadIterator(nextNode) return payloadIterator?.next() } - case .hashCollision(let nextNode): + case let nextNode as HashCollisionDictionaryNode: payloadIterator = nextNode.content.withUnsafeBufferPointer { $0.makeIterator() } return payloadIterator?.next() + default: + fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause } } else { trieIteratorStackTop = trieIteratorStackRemainder.popLast() diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 020be9f2c..611116a80 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -17,7 +17,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H typealias ReturnPayload = (key: Key, value: Value) typealias DataBufferElement = ReturnPayload // `ReturnPayload` or `Any` - typealias TrieBufferElement = TrieNode + typealias TrieBufferElement = AnyObject var header: Header var count: Int @@ -95,11 +95,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } - guard _trieSlice.allSatisfy({ if case .bitmapIndexed(let node) = $0 { return node.invariant } else { return true } }) else { - return false - } - - guard _trieSlice.allSatisfy({ if case .hashCollision(_) = $0 { return true } else { return true /* does not have explicit invariant yet */ } }) else { + guard _trieSlice.allSatisfy({ _ in true /* TODO */ }) else { return false } @@ -166,25 +162,25 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - convenience init(trieMap: Bitmap, firstNode: TrieBufferElement) { + convenience init(trieMap: Bitmap, firstNode: TrieNode, HashCollisionDictionaryNode>) { self.init() self.header = Header(dataMap: 0, trieMap: trieMap) self.count = firstNode.count - self.trieBaseAddress.initialize(to: firstNode) + self.trieBaseAddress.initialize(to: firstNode.object) assert(self.invariant) } - convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: TrieBufferElement) { + convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: TrieNode, HashCollisionDictionaryNode>) { self.init() self.header = Header(dataMap: dataMap, trieMap: trieMap) self.count = 1 + firstNode.count self.dataBaseAddress.initialize(to: (firstKey, firstValue)) - self.trieBaseAddress.initialize(to: firstNode) + self.trieBaseAddress.initialize(to: firstNode.object) assert(self.invariant) } @@ -282,11 +278,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if keyHash0 == keyHash { let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) effect.setModified() - return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, .hashCollision(subNodeNew)) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() - return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, .bitmapIndexed(subNodeNew)) + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } } } @@ -300,7 +296,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) case .hashCollision(let subNode): let collisionHash = subNode.hash @@ -308,11 +304,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .hashCollision(subNodeNew), updateCount: { $0 += 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) } else { let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) effect.setModified() - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 += 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) } } } @@ -384,12 +380,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return subNodeNew } else { // unwrap hash-collision sub-node - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNodeEnum(0), updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNode(0), updateCount: { $0 -= 1 }) } } // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .bitmapIndexed(subNodeNew), updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } case .hashCollision(let subNode): let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) @@ -414,7 +410,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H case _: // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, .hashCollision(subNodeNew), updateCount: { $0 -= 1 }) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } } } @@ -464,10 +460,8 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } } - // TODO: verify using a `var` does void `isKnownUniquelyReferenced` check private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { - var node = trieBaseAddress[slotIndex].object - let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&node) + let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } @@ -479,12 +473,19 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var nodeArity: Int { header.trieCount } // TODO rename, not accurate any more - final func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { + func getNode(_ index: Int) -> AnyObject { return trieBaseAddress[index] } final func getNodeEnum(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { - trieBaseAddress[index] + switch trieBaseAddress[index] { + case let node as BitmapIndexedDictionaryNode: + return .bitmapIndexed(node) + case let node as HashCollisionDictionaryNode: + return .hashCollision(node) + default: + fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause + } } var hasPayload: Bool { header.dataMap != 0 } @@ -503,7 +504,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } zip(header.trieMap.nonzeroBits(), _trieSlice).forEach { (index, trieNode) in - counts[index] = trieNode.count + switch trieNode { + case let trieNode as BitmapIndexedDictionaryNode: + counts[index] = trieNode.count + case let trieNode as HashCollisionDictionaryNode: + counts[index] = trieNode.count + default: + fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause + } } return counts @@ -515,7 +523,16 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let dataIndex = indexFrom(dataMap, mask, bitpos) let trieIndex = indexFrom(trieMap, mask, bitpos) - let count = dataIndex + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(upTo: trieIndex).map { $0.count }.reduce(0, +) + let count = dataIndex + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(upTo: trieIndex).map { + switch $0 { + case let trieNode as BitmapIndexedDictionaryNode: + return trieNode.count + case let trieNode as BitmapIndexedDictionaryNode: + return trieNode.count + default: + fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause + } + }.reduce(0, +) assert(count == counts.prefix(upTo: mask).reduce(0, +)) return count diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index f050b9ca8..0aec453fe 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -142,8 +142,9 @@ protocol Node: AnyObject { var nodeArity: Int { get } - @available(*, deprecated) - func getNode(_ index: Int) -> TrieNode + func getNode(_ index: Int) -> AnyObject + + func getNodeEnum(_ index: Int) -> TrieNode var hasPayload: Bool { get } diff --git a/Sources/Capsule/_HashCollisionDictionaryNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift index 1bcb7501d..b416776f2 100644 --- a/Sources/Capsule/_HashCollisionDictionaryNode.swift +++ b/Sources/Capsule/_HashCollisionDictionaryNode.swift @@ -99,7 +99,11 @@ final class HashCollisionDictionaryNode: DictionaryNode where Key: H var nodeArity: Int { 0 } - func getNode(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { + func getNode(_ index: Int) -> AnyObject { + preconditionFailure("No sub-nodes present in hash-collision leaf node") + } + + func getNodeEnum(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { preconditionFailure("No sub-nodes present in hash-collision leaf node") } From 3d4b802ef755555743376f885f689c2246034ac0 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 2 Sep 2022 08:58:17 +0200 Subject: [PATCH 152/176] [Capsule] Add specialized `subscript(position: Self.Index)` method --- .../PersistentDictionary+Collection.swift | 8 +--- .../_BitmapIndexedDictionaryNode.swift | 41 +++++++++++++++++++ .../_HashCollisionDictionaryNode.swift | 4 ++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary+Collection.swift b/Sources/Capsule/PersistentDictionary+Collection.swift index 140d235e4..658ca4f59 100644 --- a/Sources/Capsule/PersistentDictionary+Collection.swift +++ b/Sources/Capsule/PersistentDictionary+Collection.swift @@ -34,13 +34,9 @@ extension PersistentDictionary: Collection { /// /// Accesses the key-value pair at the specified position. /// - // TODO: implement specialized method in `BitmapIndexedDictionaryNode` (may require cached size on node for efficient skipping) + // TODO: add benchmark for this method across all dictionary types public subscript(position: Self.Index) -> Self.Element { - var iterator = makeIterator() - for _ in 0 ..< position.value { - let _ = iterator.next() - } - return iterator.next()! + return rootNode.get(position: position, 0, position.value) } } diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 611116a80..9f7e37e98 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -261,6 +261,47 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return nil } + func get(position: PersistentDictionaryIndex, _ shift: Int, _ stillToSkip: Int) -> ReturnPayload { + var cumulativeCounts = self.counts + + for i in 1 ..< cumulativeCounts.count { + cumulativeCounts[i] += cumulativeCounts[i - 1] + } + + var mask = 0 + + for i in 0 ..< cumulativeCounts.count { + if cumulativeCounts[i] <= stillToSkip { + mask = i + } else { + mask = i + break + } + } + + let skipped = (mask == 0) ? 0 : cumulativeCounts[mask - 1] + + let bitpos = bitposFrom(mask) + + guard (dataMap & bitpos) == 0 else { + let index = indexFrom(dataMap, mask, bitpos) + return self.getPayload(index) + } + + guard (trieMap & bitpos) == 0 else { + let index = indexFrom(trieMap, mask, bitpos) + + switch self.getNodeEnum(index) { + case .bitmapIndexed(let node): + return node.get(position: position, shift + bitPartitionSize, stillToSkip - skipped) + case .hashCollision(let node): + return node.get(position: position, shift + bitPartitionSize, stillToSkip - skipped) + } + } + + fatalError("Should not reach here.") + } + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) diff --git a/Sources/Capsule/_HashCollisionDictionaryNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift index b416776f2..43b6f21d4 100644 --- a/Sources/Capsule/_HashCollisionDictionaryNode.swift +++ b/Sources/Capsule/_HashCollisionDictionaryNode.swift @@ -43,6 +43,10 @@ final class HashCollisionDictionaryNode: DictionaryNode where Key: H content.firstIndex(where: { _key, _ in _key == key }).map { PersistentDictionaryIndex(value: $0) } } + func get(position: PersistentDictionaryIndex, _ shift: Int, _ stillToSkip: Int) -> ReturnPayload { + content[stillToSkip] + } + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { // TODO check if key/value-pair check should be added (requires value to be Equitable) From 5eb679dff722d54945babfecae8f26c7c801a415 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 2 Sep 2022 10:13:57 +0200 Subject: [PATCH 153/176] [Capsule] Reduce expensive invariant check that slow down testing --- .../Capsule/_BitmapIndexedDictionaryNode.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 9f7e37e98..72dc5c8cc 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -85,19 +85,19 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } - let recursiveCount = self.reduce(0, { count, _ in count + 1 }) - - guard recursiveCount == count else { - return false - } +// let recursiveCount = self.reduce(0, { count, _ in count + 1 }) +// +// guard recursiveCount == count else { +// return false +// } - guard recursiveCount - payloadArity >= 2 * nodeArity else { + guard count - payloadArity >= 2 * nodeArity else { return false } - guard _trieSlice.allSatisfy({ _ in true /* TODO */ }) else { - return false - } +// guard _trieSlice.allSatisfy({ _ in true /* TODO */ }) else { +// return false +// } return true } From 26893b17125d54a454d32871c57cc5c1f7dcdbda Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Fri, 2 Sep 2022 12:19:26 +0200 Subject: [PATCH 154/176] [Capsule][WIP] Return to monomorphic spine --- Sources/Capsule/PersistentDictionary.swift | 42 +-- .../_BitmapIndexedDictionaryNode.swift | 293 ++++++++---------- Sources/Capsule/_Common.swift | 102 +----- .../_HashCollisionDictionaryNode.swift | 127 -------- 4 files changed, 146 insertions(+), 418 deletions(-) delete mode 100644 Sources/Capsule/_HashCollisionDictionaryNode.swift diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index a98cf8f0e..d76c0a9c6 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -195,8 +195,8 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro private var payloadIterator: UnsafeBufferPointer<(key: Key, value: Value)>.Iterator? - private var trieIteratorStackTop: UnsafeBufferPointer.Iterator? - private var trieIteratorStackRemainder: [UnsafeBufferPointer.Iterator] + private var trieIteratorStackTop: UnsafeBufferPointer>.Iterator? + private var trieIteratorStackRemainder: [UnsafeBufferPointer>.Iterator] init(rootNode: BitmapIndexedDictionaryNode) { trieIteratorStackRemainder = [] @@ -212,7 +212,7 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro } // TODO consider moving to `BitmapIndexedDictionaryNode` - private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer.Iterator { + private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer>.Iterator { UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() } @@ -222,22 +222,14 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro } while trieIteratorStackTop != nil { - if let nextAnyObject = trieIteratorStackTop!.next() { - switch nextAnyObject { - case let nextNode as BitmapIndexedDictionaryNode: - if nextNode.hasNodes { - trieIteratorStackRemainder.append(trieIteratorStackTop!) - trieIteratorStackTop = makeTrieIterator(nextNode) - } - if nextNode.hasPayload { - payloadIterator = makePayloadIterator(nextNode) - return payloadIterator?.next() - } - case let nextNode as HashCollisionDictionaryNode: - payloadIterator = nextNode.content.withUnsafeBufferPointer { $0.makeIterator() } + if let nextNode = trieIteratorStackTop!.next() { + if nextNode.hasNodes { + trieIteratorStackRemainder.append(trieIteratorStackTop!) + trieIteratorStackTop = makeTrieIterator(nextNode) + } + if nextNode.hasPayload { + payloadIterator = makePayloadIterator(nextNode) return payloadIterator?.next() - default: - fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause } } else { trieIteratorStackTop = trieIteratorStackRemainder.popLast() @@ -258,10 +250,10 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro // TODO consider reworking similar to `DictionaryKeyValueTupleIterator` // (would require a reversed variant of `UnsafeBufferPointer<(key: Key, value: Value)>.Iterator`) public struct DictionaryKeyValueTupleReverseIterator { - private var baseIterator: ChampBaseReverseIterator, HashCollisionDictionaryNode> + private var baseIterator: ChampBaseReverseIterator> init(rootNode: BitmapIndexedDictionaryNode) { - self.baseIterator = ChampBaseReverseIterator(rootNode: .bitmapIndexed(rootNode)) + self.baseIterator = ChampBaseReverseIterator(rootNode: rootNode) } } @@ -269,15 +261,7 @@ extension DictionaryKeyValueTupleReverseIterator: IteratorProtocol { public mutating func next() -> (key: Key, value: Value)? { guard baseIterator.hasNext() else { return nil } - let payload: (Key, Value) - - // TODO remove duplication in specialization - switch baseIterator.currentValueNode! { - case .bitmapIndexed(let node): - payload = node.getPayload(baseIterator.currentValueCursor) - case .hashCollision(let node): - payload = node.getPayload(baseIterator.currentValueCursor) - } + let payload = baseIterator.currentValueNode!.getPayload(baseIterator.currentValueCursor) baseIterator.currentValueCursor -= 1 return payload diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 72dc5c8cc..4b0043591 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -15,9 +15,10 @@ fileprivate let initialTrieCapacity: Capacity = 1 final class BitmapIndexedDictionaryNode: DictionaryNode where Key: Hashable { typealias ReturnPayload = (key: Key, value: Value) + typealias ReturnBitmapIndexedNode = BitmapIndexedDictionaryNode - typealias DataBufferElement = ReturnPayload // `ReturnPayload` or `Any` - typealias TrieBufferElement = AnyObject + typealias DataBufferElement = ReturnPayload + typealias TrieBufferElement = ReturnBitmapIndexedNode var header: Header var count: Int @@ -162,25 +163,27 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - convenience init(trieMap: Bitmap, firstNode: TrieNode, HashCollisionDictionaryNode>) { + @available(*, deprecated) + convenience init(trieMap: Bitmap, firstNode: BitmapIndexedDictionaryNode) { self.init() self.header = Header(dataMap: 0, trieMap: trieMap) self.count = firstNode.count - self.trieBaseAddress.initialize(to: firstNode.object) + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } - convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: TrieNode, HashCollisionDictionaryNode>) { + @available(*, deprecated) + convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: BitmapIndexedDictionaryNode) { self.init() self.header = Header(dataMap: dataMap, trieMap: trieMap) self.count = 1 + firstNode.count self.dataBaseAddress.initialize(to: (firstKey, firstValue)) - self.trieBaseAddress.initialize(to: firstNode.object) + self.trieBaseAddress.initialize(to: firstNode) assert(self.invariant) } @@ -197,13 +200,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - - switch self.getNodeEnum(index) { - case .bitmapIndexed(let node): - return node.get(key, keyHash, shift + bitPartitionSize) - case .hashCollision(let node): - return node.get(key, keyHash, shift + bitPartitionSize) - } + return self.getNode(index).get(key, keyHash, shift + bitPartitionSize) } return nil @@ -221,13 +218,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - - switch self.getNodeEnum(index) { - case .bitmapIndexed(let node): - return node.containsKey(key, keyHash, shift + bitPartitionSize) - case .hashCollision(let node): - return node.containsKey(key, keyHash, shift + bitPartitionSize) - } + return self.getNode(index).containsKey(key, keyHash, shift + bitPartitionSize) } return false @@ -249,13 +240,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - - switch self.getNodeEnum(index) { - case .bitmapIndexed(let node): - return node.index(key, keyHash, shift + bitPartitionSize, skippedBefore + skipped) - case .hashCollision(let node): - return node.index(key, keyHash, shift + bitPartitionSize, skippedBefore + skipped) - } + return self.getNode(index).index(key, keyHash, shift + bitPartitionSize, skippedBefore + skipped) } return nil @@ -290,13 +275,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (trieMap & bitpos) == 0 else { let index = indexFrom(trieMap, mask, bitpos) - - switch self.getNodeEnum(index) { - case .bitmapIndexed(let node): - return node.get(position: position, shift + bitPartitionSize, stillToSkip - skipped) - case .hashCollision(let node): - return node.get(position: position, shift + bitPartitionSize, stillToSkip - skipped) - } + return self.getNode(index).get(position: position, shift + bitPartitionSize, stillToSkip - skipped) } fatalError("Should not reach here.") @@ -317,9 +296,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let keyHash0 = computeHash(key0) if keyHash0 == keyHash { - let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) - effect.setModified() - return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) +// let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) +// effect.setModified() +// return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + return self // TODO dummy } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) effect.setModified() @@ -332,26 +312,26 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let index = indexFrom(trieMap, mask, bitpos) let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - switch self.getNodeEnum(index) { - case .bitmapIndexed(let subNode): - let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } + let subNode = self.getNode(index) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) - case .hashCollision(let subNode): - let collisionHash = subNode.hash + let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } - if keyHash == collisionHash { - let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) - } else { - let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) - effect.setModified() - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) - } - } +// case .hashCollision(let subNode): +// let collisionHash = subNode.hash +// +// if keyHash == collisionHash { +// let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) +// guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } +// +// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) +// } else { +// let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) +// effect.setModified() +// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) +// } } effect.setModified() @@ -381,10 +361,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (remainingKey, remainingValue) = getPayload(1 - index) return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } - } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1, case .hashCollision(let __node) = self.getNodeEnum(0) { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ - // create potential new root: will a) become new root, or b) unwrapped on another level - let newCollMap: Bitmap = bitposFrom(maskFrom(__node.hash, 0)) - return Self(trieMap: newCollMap, firstNode: getNodeEnum(0)) +// } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1, case .hashCollision(let __node) = self.getNodeEnum(0) { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ +// // create potential new root: will a) become new root, or b) unwrapped on another level +// let newCollMap: Bitmap = bitposFrom(maskFrom(__node.hash, 0)) +// return Self(trieMap: newCollMap, firstNode: getNodeEnum(0)) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -392,76 +372,76 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let index = indexFrom(trieMap, mask, bitpos) let subNodeModifyInPlace = self.isTrieNodeKnownUniquelyReferenced(index, isStorageKnownUniquelyReferenced) - switch self.getNodeEnum(index) { - case .bitmapIndexed(let subNode): - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } - - switch subNodeNew.count { - case 0: - preconditionFailure("Sub-node must have at least one element.") - - case 1: - assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) - - if self.isCandiateForCompaction { - // escalate singleton - return subNodeNew - } else { - // inline singleton - return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) - } - - case _: - assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) - - if (subNodeNew.isWrappingSingleHashCollisionNode) { - if self.isCandiateForCompaction { - // escalate node that has only a single hash-collision sub-node - return subNodeNew - } else { - // unwrap hash-collision sub-node - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNode(0), updateCount: { $0 -= 1 }) - } - } - - // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) - } - case .hashCollision(let subNode): - let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } - - switch subNodeNew.count { - case 0: - preconditionFailure("Sub-node must have at least one element.") - - case 1: - // TODO simplify hash-collision compaction (if feasible) - if self.isCandiateForCompaction { - // escalate singleton - // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) - let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) - let (remainingKey, remainingValue) = subNodeNew.getPayload(0) - return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) - } else { - // inline value - return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) - } - - case _: - // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) + let subNode = self.getNode(index) + + let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) + guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } + + switch subNodeNew.count { + case 0: + preconditionFailure("Sub-node must have at least one element.") + + case 1: + assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) + + if self.isCandiateForCompaction { + // escalate singleton + return subNodeNew + } else { + // inline singleton + return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) } + + case _: + assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) + +// if (subNodeNew.isWrappingSingleHashCollisionNode) { +// if self.isCandiateForCompaction { +// // escalate node that has only a single hash-collision sub-node +// return subNodeNew +// } else { +// // unwrap hash-collision sub-node +// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNode(0), updateCount: { $0 -= 1 }) +// } +// } + + // modify current node (set replacement node) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) } } +// case .hashCollision(let subNode): +// let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) +// guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } +// +// switch subNodeNew.count { +// case 0: +// preconditionFailure("Sub-node must have at least one element.") +// +// case 1: +// // TODO simplify hash-collision compaction (if feasible) +// if self.isCandiateForCompaction { +// // escalate singleton +// // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) +// let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) +// let (remainingKey, remainingValue) = subNodeNew.getPayload(0) +// return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) +// } else { +// // inline value +// return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) +// } +// +// case _: +// // modify current node (set replacement node) +// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) +// } + return self } var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } - var isWrappingSingleHashCollisionNode: Bool { if payloadArity == 0 && self.nodeArity /* rename */ == 1, case .hashCollision(_) = self.getNodeEnum(0) { return true } else { return false } } +// var isWrappingSingleHashCollisionNode: Bool { if payloadArity == 0 && self.nodeArity /* rename */ == 1, case .hashCollision(_) = self.getNodeEnum(0) { return true } else { return false } } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != keyHash1) @@ -480,26 +460,26 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // recurse: identical prefixes, payload must be disambiguated deeper in the trie let node = mergeTwoKeyValPairs(key0, value0, keyHash0, key1, value1, keyHash1, shift + bitPartitionSize) - return Self(trieMap: bitposFrom(mask0), firstNode: .bitmapIndexed(node)) + return Self(trieMap: bitposFrom(mask0), firstNode: node) } } - func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionDictionaryNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { - assert(keyHash0 != nodeHash1) - - let mask0 = maskFrom(keyHash0, shift) - let mask1 = maskFrom(nodeHash1, shift) - - if mask0 != mask1 { - // unique prefixes, payload and collision node fit on same level - return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: .hashCollision(node1)) - } else { - // recurse: identical prefixes, payload must be disambiguated deeper in the trie - let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) - - return Self(trieMap: bitposFrom(mask0), firstNode: .bitmapIndexed(node)) - } - } +// func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionDictionaryNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { +// assert(keyHash0 != nodeHash1) +// +// let mask0 = maskFrom(keyHash0, shift) +// let mask1 = maskFrom(nodeHash1, shift) +// +// if mask0 != mask1 { +// // unique prefixes, payload and collision node fit on same level +// return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: .hashCollision(node1)) +// } else { +// // recurse: identical prefixes, payload must be disambiguated deeper in the trie +// let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) +// +// return Self(trieMap: bitposFrom(mask0), firstNode: node) +// } +// } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) @@ -514,21 +494,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var nodeArity: Int { header.trieCount } // TODO rename, not accurate any more - func getNode(_ index: Int) -> AnyObject { + func getNode(_ index: Int) -> BitmapIndexedDictionaryNode { return trieBaseAddress[index] } - final func getNodeEnum(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { - switch trieBaseAddress[index] { - case let node as BitmapIndexedDictionaryNode: - return .bitmapIndexed(node) - case let node as HashCollisionDictionaryNode: - return .hashCollision(node) - default: - fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause - } - } - var hasPayload: Bool { header.dataMap != 0 } var payloadArity: Int { header.dataCount } @@ -545,14 +514,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } zip(header.trieMap.nonzeroBits(), _trieSlice).forEach { (index, trieNode) in - switch trieNode { - case let trieNode as BitmapIndexedDictionaryNode: - counts[index] = trieNode.count - case let trieNode as HashCollisionDictionaryNode: - counts[index] = trieNode.count - default: - fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause - } + counts[index] = trieNode.count } return counts @@ -564,16 +526,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let dataIndex = indexFrom(dataMap, mask, bitpos) let trieIndex = indexFrom(trieMap, mask, bitpos) - let count = dataIndex + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(upTo: trieIndex).map { - switch $0 { - case let trieNode as BitmapIndexedDictionaryNode: - return trieNode.count - case let trieNode as BitmapIndexedDictionaryNode: - return trieNode.count - default: - fatalError("Should not reach here.") // TODO: rework to remove 'dummy' default clause - } - }.reduce(0, +) + let count = dataIndex + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).prefix(upTo: trieIndex).map { $0.count }.reduce(0, +) assert(count == counts.prefix(upTo: mask).reduce(0, +)) return count @@ -752,12 +705,8 @@ extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { } for index in 0.. Int { (bitmap == Bitmap.max) ? mask : indexFrom(bitmap, bitpos) } -enum TrieNode { - case bitmapIndexed(BitmapIndexedNode) - case hashCollision(HashCollisionNode) - - /// The convenience computed properties below are used in the base iterator implementations. - - var object: AnyObject { - switch self { - case .bitmapIndexed(let node): - return node - case .hashCollision(let node): - return node - } - } - - var count: Int { - switch self { - case .bitmapIndexed(let node): - return node.count - case .hashCollision(let node): - return node.count - } - } - - var hasPayload: Bool { - switch self { - case .bitmapIndexed(let node): - return node.hasPayload - case .hashCollision(let node): - return node.hasPayload - } - } - - var payloadArity: Int { - switch self { - case .bitmapIndexed(let node): - return node.payloadArity - case .hashCollision(let node): - return node.payloadArity - } - } - - var hasNodes: Bool { - switch self { - case .bitmapIndexed(let node): - return node.hasNodes - case .hashCollision(let node): - return node.hasNodes - } - } - - var nodeArity: Int { - switch self { - case .bitmapIndexed(let node): - return node.nodeArity - case .hashCollision(let node): - return node.nodeArity - } - } -} - protocol Node: AnyObject { associatedtype ReturnPayload associatedtype ReturnBitmapIndexedNode: Node - associatedtype ReturnHashCollisionNode: Node var hasNodes: Bool { get } var nodeArity: Int { get } - func getNode(_ index: Int) -> AnyObject - - func getNodeEnum(_ index: Int) -> TrieNode + func getNode(_ index: Int) -> Self var hasPayload: Bool { get } @@ -160,8 +96,8 @@ protocol Node: AnyObject { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -struct ChampBaseIterator { - typealias T = TrieNode +struct ChampBaseIterator { + typealias T = BitmapIndexedNode var currentValueCursor: Int = 0 var currentValueLength: Int = 0 @@ -212,19 +148,11 @@ struct ChampBaseIterator { if nodeCursor < nodeLength { nodeCursorsAndLengths[cursorIndex] += 1 - // TODO remove duplication in specialization - switch nodes[currentStackLevel]! { - case .bitmapIndexed(let currentNode): - let nextNode = currentNode.getNode(nodeCursor) as! T - - if nextNode.hasNodes { pushNode(nextNode) } - if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } - case .hashCollision(let currentNode): - let nextNode = currentNode.getNode(nodeCursor) as! T + let currentNode = nodes[currentStackLevel]! + let nextNode = currentNode.getNode(nodeCursor) - if nextNode.hasNodes { pushNode(nextNode) } - if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } - } + if nextNode.hasNodes { pushNode(nextNode) } + if nextNode.hasPayload { setupPayloadNode(nextNode) ; return true } } else { popNode() } @@ -243,8 +171,8 @@ struct ChampBaseIterator { /// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// -struct ChampBaseReverseIterator { - typealias T = TrieNode +struct ChampBaseReverseIterator { + typealias T = BitmapIndexedNode var currentValueCursor: Int = -1 var currentValueNode: T? = nil @@ -284,15 +212,9 @@ struct ChampBaseReverseIterator= 0 { - // TODO remove duplication in specialization - switch nodeStack[currentStackLevel]! { - case .bitmapIndexed(let currentNode): - let nextNode = currentNode.getNode(nodeCursor) as! T - pushNode(nextNode) - case .hashCollision(let currentNode): - let nextNode = currentNode.getNode(nodeCursor) as! T - pushNode(nextNode) - } + let currentNode = nodeStack[currentStackLevel]! + let nextNode = currentNode.getNode(nodeCursor) + pushNode(nextNode) } else { let currNode = nodeStack[currentStackLevel]! popNode() diff --git a/Sources/Capsule/_HashCollisionDictionaryNode.swift b/Sources/Capsule/_HashCollisionDictionaryNode.swift deleted file mode 100644 index 43b6f21d4..000000000 --- a/Sources/Capsule/_HashCollisionDictionaryNode.swift +++ /dev/null @@ -1,127 +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 -// -//===----------------------------------------------------------------------===// - -final class HashCollisionDictionaryNode: DictionaryNode where Key: Hashable { - - typealias ReturnPayload = (key: Key, value: Value) - - let hash: Int - let content: [(key: Key, value: Value)] - - init(_ hash: Int, _ content: [(key: Key, value: Value)]) { - // precondition(content.count >= 2) - precondition(content.map { $0.key }.allSatisfy {$0.hashValue == hash}) - - self.hash = hash - self.content = content - } - - func get(_ key: Key, _ hash: Int, _ shift: Int) -> Value? { - if self.hash == hash { - return content.first(where: { key == $0.key }).map { $0.value } - } else { return nil } - } - - func containsKey(_ key: Key, _ hash: Int, _ shift: Int) -> Bool { - return self.hash == hash && content.contains(where: { key == $0.key }) - } - -// // TODO requires Value to be Equatable -// func contains(_ key: Key, _ value: Value, _ hash: Int, _ shift: Int) -> Bool { -// return self.hash == hash && content.contains(where: { key == $0.key && value == $0.value }) -// } - - func index(_ key: Key, _ keyHash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? { - content.firstIndex(where: { _key, _ in _key == key }).map { PersistentDictionaryIndex(value: $0) } - } - - func get(position: PersistentDictionaryIndex, _ shift: Int, _ stillToSkip: Int) -> ReturnPayload { - content[stillToSkip] - } - - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { - - // TODO check if key/value-pair check should be added (requires value to be Equitable) - if self.containsKey(key, hash, shift) { - let index = content.firstIndex(where: { key == $0.key })! - let updatedContent = content[0..= 2)` holds - // TODO consider returning either type of `BitmapIndexedDictionaryNode` and `HashCollisionDictionaryNode` - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> HashCollisionDictionaryNode { - if !self.containsKey(key, hash, shift) { - return self - } else { - effect.setModified() - let updatedContent = content.filter { $0.key != key } - assert(updatedContent.count == content.count - 1) - -// switch updatedContent.count { -// case 1: -// let (k, v) = updatedContent[0].self -// return BitmapIndexedDictionaryNode(bitposFrom(maskFrom(hash, 0)), 0, Array(arrayLiteral: k, v)) -// default: - return HashCollisionDictionaryNode(hash, updatedContent) -// } - } - } - - var hasBitmapIndexedNodes: Bool { false } - - var bitmapIndexedNodeArity: Int { 0 } - - func getBitmapIndexedNode(_ index: Int) -> HashCollisionDictionaryNode { - preconditionFailure("No sub-nodes present in hash-collision leaf node") - } - - var hasHashCollisionNodes: Bool { false } - - var hashCollisionNodeArity: Int { 0 } - - func getHashCollisionNode(_ index: Int) -> HashCollisionDictionaryNode { - preconditionFailure("No sub-nodes present in hash-collision leaf node") - } - - var hasNodes: Bool { false } - - var nodeArity: Int { 0 } - - func getNode(_ index: Int) -> AnyObject { - preconditionFailure("No sub-nodes present in hash-collision leaf node") - } - - func getNodeEnum(_ index: Int) -> TrieNode, HashCollisionDictionaryNode> { - preconditionFailure("No sub-nodes present in hash-collision leaf node") - } - - var hasPayload: Bool { true } - - var payloadArity: Int { content.count } - - func getPayload(_ index: Int) -> (key: Key, value: Value) { content[index] } - - var count: Int { content.count } -} - -extension HashCollisionDictionaryNode: Equatable where Value: Equatable { - static func == (lhs: HashCollisionDictionaryNode, rhs: HashCollisionDictionaryNode) -> Bool { - Dictionary.init(uniqueKeysWithValues: lhs.content) == Dictionary.init(uniqueKeysWithValues: rhs.content) - } -} From 0550c27fca6d7de0ac2fc4b2cd6b0b8e6b0d4c99 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 12:15:09 +0200 Subject: [PATCH 155/176] [Capsule] Add `BitmapIndexedDictionaryNode: CustomDebugStringConvertible` skeleton --- ...ionaryNode+CustomDebugStringConvertible.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift new file mode 100644 index 000000000..5ba56bba3 --- /dev/null +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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 BitmapIndexedDictionaryNode: CustomDebugStringConvertible { +// public var debugDescription: String { +// <#code#> +// } +//} From 81a38b39cae3ccec963ac463a8be85fc89b5911a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 12:17:18 +0200 Subject: [PATCH 156/176] [Capsule] Add `BitmapIndexedDictionaryNode: CustomStringConvertible` skeleton --- ...dDictionaryNode+CustomStringConvertible.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift new file mode 100644 index 000000000..91128467c --- /dev/null +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// 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 BitmapIndexedDictionaryNode: CustomStringConvertible { +// public var description: String { +// <#code#> +// } +//} From ef523a9f5758846972c99873f8ff88b383676725 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 12:22:09 +0200 Subject: [PATCH 157/176] [Capsule] Add smoke test for larger hash-collision nodes --- Tests/CapsuleTests/CapsuleSmokeTests.swift | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 44275d640..6842d7f6c 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -429,6 +429,39 @@ final class CapsuleSmokeTests: CollectionTestCase { return size } + func testIteratorEnumeratesAllIfCollision() { + let upperBound = 1_000 + + // '+' prefixed values + var map1: PersistentDictionary = [:] + for index in 0.. = map1 + for index in 0.. = map1 + for index in 0..) { + func doIterate(_ map1: PersistentDictionary) { var count = 0 for _ in map1 { count = count + 1 From bf224619a52eb8d8aec846ef12864d1bb0241051 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 20:24:13 +0200 Subject: [PATCH 158/176] [Capsule][WIP] Return to monomorphic spine, continued --- .../_BitmapIndexedDictionaryNode.swift | 268 +++++++++++------- Sources/Capsule/_Common.swift | 2 +- Tests/CapsuleTests/CapsuleSmokeTests.swift | 1 - 3 files changed, 168 insertions(+), 103 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 4b0043591..2d747976c 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -23,6 +23,15 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H var header: Header var count: Int + + var collisionFree: Bool { + !hashCollision + } + + var hashCollision: Bool { + header.hashCollision + } + let dataCapacity: Capacity let trieCapacity: Capacity @@ -96,15 +105,19 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return false } -// guard _trieSlice.allSatisfy({ _ in true /* TODO */ }) else { -// return false -// } + if hashCollision { + let hash = computeHash(_dataSlice.first!.key) + + guard _dataSlice.allSatisfy({ computeHash($0.key) == hash }) else { + return false + } + } return true } var headerInvariant: Bool { - (header.dataMap & header.trieMap) == 0 + (header.dataMap & header.trieMap) == 0 || (header.dataMap == header.trieMap) } // TODO: should not materialize as `Array` for performance reasons @@ -188,10 +201,32 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } + convenience init(collisions: [ReturnPayload]) { + self.init(dataCapacity: Capacity(collisions.count), trieCapacity: 0) + + self.header = Header(dataMap: Bitmap(collisions.count), trieMap: Bitmap(collisions.count)) + self.count = collisions.count + + self.dataBaseAddress.initialize(from: collisions, count: collisions.count) + + assert(self.invariant) + } + func get(_ key: Key, _ keyHash: Int, _ shift: Int) -> Value? { let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) + guard collisionFree else { + let content: [ReturnPayload] = Array(self) + let hash = computeHash(content.first!.key) + + guard keyHash == hash else { + return nil + } + + return content.first(where: { key == $0.key }).map { $0.value } + } + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) @@ -210,6 +245,17 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) + guard collisionFree else { + let content: [ReturnPayload] = Array(self) + let hash = computeHash(content.first!.key) + + guard keyHash == hash else { + return false + } + + return content.contains(where: { key == $0.key }) + } + guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) let payload = self.getPayload(index) @@ -225,6 +271,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } func index(_ key: Key, _ keyHash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? { + guard collisionFree else { + let content: [ReturnPayload] = Array(self) + let hash = computeHash(content.first!.key) + + assert(keyHash == hash) + return content.firstIndex(where: { _key, _ in _key == key }).map { PersistentDictionaryIndex(value: $0) } + } + let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -281,7 +335,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H fatalError("Should not reach here.") } - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + final func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + + guard collisionFree else { + return updateOrUpdatingCollision(isStorageKnownUniquelyReferenced, key, value, keyHash, shift, &effect) + } + let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -296,12 +355,17 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let keyHash0 = computeHash(key0) if keyHash0 == keyHash { -// let subNodeNew = HashCollisionDictionaryNode(keyHash0, [(key0, value0), (key, value)]) -// effect.setModified() -// return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) - return self // TODO dummy + let subNodeNew = Self(/* hash, */ collisions: [(key0, value0), (key, value)]) + + effect.setModified() + if self.count == 1 { + return subNodeNew + } else { + return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) + } } else { let subNodeNew = mergeTwoKeyValPairs(key0, value0, keyHash0, key, value, keyHash, shift + bitPartitionSize) + effect.setModified() return copyAndMigrateFromInlineToNode(isStorageKnownUniquelyReferenced, bitpos, subNodeNew) } @@ -318,27 +382,41 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) - -// case .hashCollision(let subNode): -// let collisionHash = subNode.hash -// -// if keyHash == collisionHash { -// let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) -// guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } -// -// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) -// } else { -// let subNodeNew = mergeKeyValPairAndCollisionNode(key, value, keyHash, subNode, collisionHash, shift + bitPartitionSize) -// effect.setModified() -// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 += 1 }) -// } } effect.setModified() return copyAndInsertValue(isStorageKnownUniquelyReferenced, bitpos, key, value) } - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + @inline(never) + final func updateOrUpdatingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + assert(hashCollision) + + let content: [ReturnPayload] = Array(self) + let hash = computeHash(content.first!.key) + + guard keyHash == hash else { + effect.setModified() + return mergeKeyValPairAndCollisionNode(key, value, keyHash, self, hash, shift) + } + + if let index = content.firstIndex(where: { key == $0.key }) { + let updatedContent: [ReturnPayload] = content[0.. BitmapIndexedDictionaryNode { + + guard collisionFree else { + return removeOrRemovingCollision(isStorageKnownUniquelyReferenced, key, keyHash, shift, &effect) + } + let mask = maskFrom(keyHash, shift) let bitpos = bitposFrom(mask) @@ -348,8 +426,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard key0 == key else { assert(self.invariant) ; return self } effect.setModified() - // TODO check globally usage of `bitmapIndexedNodeArity` and `hashCollisionNodeArity` - if self.payloadArity == 2 && self.nodeArity == 0 /* rename */ { + if self.payloadArity == 2 && self.nodeArity == 0 { if shift == 0 { // keep remaining pair on root level let newDataMap = (dataMap ^ bitpos) @@ -361,10 +438,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (remainingKey, remainingValue) = getPayload(1 - index) return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) } -// } else if self.payloadArity == 1 && self.nodeArity /* rename */ == 1, case .hashCollision(let __node) = self.getNodeEnum(0) { /* TODO: is similar to `isWrappingSingleHashCollisionNode`? */ -// // create potential new root: will a) become new root, or b) unwrapped on another level -// let newCollMap: Bitmap = bitposFrom(maskFrom(__node.hash, 0)) -// return Self(trieMap: newCollMap, firstNode: getNodeEnum(0)) + } else if self.payloadArity == 1 && self.nodeArity == 1, self.getNode(0).hashCollision { + // escalate hash-collision node + return getNode(0) } else { return copyAndRemoveValue(isStorageKnownUniquelyReferenced, bitpos) } } @@ -377,13 +453,9 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } + assert(subNodeNew.count > 0, "Sub-node must have at least one element.") switch subNodeNew.count { - case 0: - preconditionFailure("Sub-node must have at least one element.") - case 1: - assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) - if self.isCandiateForCompaction { // escalate singleton return subNodeNew @@ -393,55 +465,45 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } case _: - assert(self.nodeArity /*bitmapIndexedNodeArity???*/ >= 1) - -// if (subNodeNew.isWrappingSingleHashCollisionNode) { -// if self.isCandiateForCompaction { -// // escalate node that has only a single hash-collision sub-node -// return subNodeNew -// } else { -// // unwrap hash-collision sub-node -// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew.getNode(0), updateCount: { $0 -= 1 }) -// } -// } - - // modify current node (set replacement node) - return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) + if subNodeNew.hashCollision && self.isCandiateForCompaction { + // escalate singleton + return subNodeNew + } else { + // modify current node (set replacement node) + return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) + } } } -// case .hashCollision(let subNode): -// let subNodeNew = subNode.removeOrRemoving(subNodeModifyInPlace, key, keyHash, shift + bitPartitionSize, &effect) -// guard effect.modified && subNode !== subNodeNew else { if effect.modified { count -= 1 } ; assert(self.invariant) ; return self } -// -// switch subNodeNew.count { -// case 0: -// preconditionFailure("Sub-node must have at least one element.") -// -// case 1: -// // TODO simplify hash-collision compaction (if feasible) -// if self.isCandiateForCompaction { -// // escalate singleton -// // convert `HashCollisionDictionaryNode` to `BitmapIndexedDictionaryNode` (logic moved/inlined from `HashCollisionDictionaryNode`) -// let newDataMap: Bitmap = bitposFrom(maskFrom(subNodeNew.hash, 0)) -// let (remainingKey, remainingValue) = subNodeNew.getPayload(0) -// return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) -// } else { -// // inline value -// return copyAndMigrateFromNodeToInline(isStorageKnownUniquelyReferenced, bitpos, subNodeNew.getPayload(0)) -// } -// -// case _: -// // modify current node (set replacement node) -// return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= 1 }) -// } - return self } - var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } + @inline(never) + final func removeOrRemovingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + assert(hashCollision) + + let content: [ReturnPayload] = Array(self) + let _ = computeHash(content.first!.key) + + if let index = content.firstIndex(where: { key == $0.key }) { + effect.setModified() + var updatedContent = content; updatedContent.remove(at: index) + assert(updatedContent.count == content.count - 1) + + if updatedContent.count == 1 { + // create potential new root: will a) become new root, or b) inlined on another level + let newDataMap = bitposFrom(maskFrom(keyHash, 0)) + let (remainingKey, remainingValue) = updatedContent.first! + return Self(dataMap: newDataMap, firstKey: remainingKey, firstValue: remainingValue) + } else { + return Self(/* hash, */ collisions: updatedContent) + } + } else { + return self + } + } -// var isWrappingSingleHashCollisionNode: Bool { if payloadArity == 0 && self.nodeArity /* rename */ == 1, case .hashCollision(_) = self.getNodeEnum(0) { return true } else { return false } } + var isCandiateForCompaction: Bool { payloadArity == 0 && nodeArity == 1 } func mergeTwoKeyValPairs(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ key1: Key, _ value1: Value, _ keyHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { assert(keyHash0 != keyHash1) @@ -464,22 +526,22 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } } -// func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: HashCollisionDictionaryNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { -// assert(keyHash0 != nodeHash1) -// -// let mask0 = maskFrom(keyHash0, shift) -// let mask1 = maskFrom(nodeHash1, shift) -// -// if mask0 != mask1 { -// // unique prefixes, payload and collision node fit on same level -// return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: .hashCollision(node1)) -// } else { -// // recurse: identical prefixes, payload must be disambiguated deeper in the trie -// let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) -// -// return Self(trieMap: bitposFrom(mask0), firstNode: node) -// } -// } + func mergeKeyValPairAndCollisionNode(_ key0: Key, _ value0: Value, _ keyHash0: Int, _ node1: BitmapIndexedDictionaryNode, _ nodeHash1: Int, _ shift: Int) -> BitmapIndexedDictionaryNode { + assert(keyHash0 != nodeHash1) + + let mask0 = maskFrom(keyHash0, shift) + let mask1 = maskFrom(nodeHash1, shift) + + if mask0 != mask1 { + // unique prefixes, payload and collision node fit on same level + return Self(dataMap: bitposFrom(mask0), trieMap: bitposFrom(mask1), firstKey: key0, firstValue: value0, firstNode: node1) + } else { + // recurse: identical prefixes, payload must be disambiguated deeper in the trie + let node = mergeKeyValPairAndCollisionNode(key0, value0, keyHash0, node1, nodeHash1, shift + bitPartitionSize) + + return Self(trieMap: bitposFrom(mask0), firstNode: node) + } + } private func isTrieNodeKnownUniquelyReferenced(_ slotIndex: Int, _ isParentNodeKnownUniquelyReferenced: Bool) -> Bool { let isKnownUniquelyReferenced = Swift.isKnownUniquelyReferenced(&trieBaseAddress[slotIndex]) @@ -487,13 +549,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H return isParentNodeKnownUniquelyReferenced && isKnownUniquelyReferenced } - // TODO rename, not accurate any more var hasNodes: Bool { header.trieMap != 0 } - // TODO rename, not accurate any more var nodeArity: Int { header.trieCount } - // TODO rename, not accurate any more func getNode(_ index: Int) -> BitmapIndexedDictionaryNode { return trieBaseAddress[index] } @@ -565,7 +624,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H dst = src.copy() } - dst.trieBaseAddress[idx] = newNode // as TrieBufferElement + dst.trieBaseAddress[idx] = newNode // update metadata: `dataMap, nodeMap, collMap` updateCount(&dst.count) @@ -689,7 +748,11 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H // TODO: `Equatable` needs more test coverage, apart from hash-collision smoke test extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { static func == (lhs: BitmapIndexedDictionaryNode, rhs: BitmapIndexedDictionaryNode) -> Bool { - lhs === rhs || + if lhs.hashCollision && rhs.hashCollision { + return Dictionary.init(uniqueKeysWithValues: Array(lhs)) == Dictionary.init(uniqueKeysWithValues: Array(rhs)) + } + + return lhs === rhs || lhs.header == rhs.header && lhs.count == rhs.count && deepContentEquality(lhs, rhs) @@ -705,7 +768,6 @@ extension BitmapIndexedDictionaryNode: Equatable where Value: Equatable { } for index in 0.. Bool { lhs.dataMap == rhs.dataMap && lhs.trieMap == rhs.trieMap diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index fc2404902..262d91590 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -50,7 +50,7 @@ let bitPartitionSize: Int = 5 let bitPartitionMask: Int = (1 << bitPartitionSize) - 1 -typealias Capacity = UInt8 +typealias Capacity = UInt32 // TODO: restore type to `UInt8` after reworking hash-collisions to grow in depth instead of width let hashCodeLength: Int = Int.bitWidth diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/CapsuleTests/CapsuleSmokeTests.swift index 6842d7f6c..4608c4b08 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/CapsuleTests/CapsuleSmokeTests.swift @@ -459,7 +459,6 @@ final class CapsuleSmokeTests: CollectionTestCase { } expectEqual(map3.count, 0) doIterate(map3) - } func testIteratorEnumeratesAllIfNoCollision() { From 8bc5e20b2961aa0628ddf9452f50853f35281c92 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 20:53:41 +0200 Subject: [PATCH 159/176] [Capsule] Enable `Keys` and `Values` view benchmarks --- .../PersistentDictionaryBenchmarks.swift | 46 +++++++++---------- Benchmarks/Libraries/Capsule.json | 18 ++++++++ 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index d386f52b9..859ba4538 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -36,29 +36,29 @@ extension Benchmark { } } -// self.add( -// title: "PersistentDictionary.Keys sequential iteration", -// input: [Int].self -// ) { input in -// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) -// return { timer in -// for item in d.keys { -// blackHole(item) -// } -// } -// } -// -// self.add( -// title: "PersistentDictionary.Values sequential iteration", -// input: [Int].self -// ) { input in -// let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) -// return { timer in -// for item in d.values { -// blackHole(item) -// } -// } -// } + self.add( + title: "PersistentDictionary.Keys sequential iteration", + input: [Int].self + ) { input in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for item in d.keys { + blackHole(item) + } + } + } + + self.add( + title: "PersistentDictionary.Values sequential iteration", + input: [Int].self + ) { input in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + return { timer in + for item in d.values { + blackHole(item) + } + } + } self.add( title: "PersistentDictionary subscript, successful lookups", diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/Capsule.json index 5548cbc4e..e0b879dfb 100644 --- a/Benchmarks/Libraries/Capsule.json +++ b/Benchmarks/Libraries/Capsule.json @@ -64,6 +64,24 @@ "OrderedDictionary sequential iteration", ] }, + { + "kind": "chart", + "title": "sequential iteration [Keys]", + "tasks": [ + "PersistentDictionary.Keys sequential iteration", + "Dictionary.Keys sequential iteration", + "OrderedDictionary.Keys sequential iteration", + ] + }, + { + "kind": "chart", + "title": "sequential iteration [Values]", + "tasks": [ + "PersistentDictionary.Values sequential iteration", + "Dictionary.Values sequential iteration", + "OrderedDictionary.Values sequential iteration", + ] + }, { "kind": "chart", "title": "subscript, insert", From 45b9d8db26b09edb0c35719d62ac733bd77afa2e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 21:11:20 +0200 Subject: [PATCH 160/176] [Capsule] Use non-materializing data/trie slice views in iterator --- Sources/Capsule/PersistentDictionary.swift | 18 ++++-------------- .../Capsule/_BitmapIndexedDictionaryNode.swift | 10 ++++------ 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index d76c0a9c6..302f7fe8b 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -202,18 +202,8 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro trieIteratorStackRemainder = [] trieIteratorStackRemainder.reserveCapacity(maxDepth) - if rootNode.hasNodes { trieIteratorStackTop = makeTrieIterator(rootNode) } - if rootNode.hasPayload { payloadIterator = makePayloadIterator(rootNode) } - } - - // TODO consider moving to `BitmapIndexedDictionaryNode` - private func makePayloadIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer<(key: Key, value: Value)>.Iterator { - UnsafeBufferPointer(start: node.dataBaseAddress, count: node.payloadArity).makeIterator() - } - - // TODO consider moving to `BitmapIndexedDictionaryNode` - private func makeTrieIterator(_ node: BitmapIndexedDictionaryNode) -> UnsafeBufferPointer>.Iterator { - UnsafeBufferPointer(start: node.trieBaseAddress, count: node.nodeArity).makeIterator() + if rootNode.hasNodes { trieIteratorStackTop = rootNode._trieSlice.makeIterator() } + if rootNode.hasPayload { payloadIterator = rootNode._dataSlice.makeIterator() } } public mutating func next() -> (key: Key, value: Value)? { @@ -225,10 +215,10 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro if let nextNode = trieIteratorStackTop!.next() { if nextNode.hasNodes { trieIteratorStackRemainder.append(trieIteratorStackTop!) - trieIteratorStackTop = makeTrieIterator(nextNode) + trieIteratorStackTop = nextNode._trieSlice.makeIterator() } if nextNode.hasPayload { - payloadIterator = makePayloadIterator(nextNode) + payloadIterator = nextNode._dataSlice.makeIterator() return payloadIterator?.next() } } else { diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift index 2d747976c..99403f915 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode.swift @@ -120,14 +120,12 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H (header.dataMap & header.trieMap) == 0 || (header.dataMap == header.trieMap) } - // TODO: should not materialize as `Array` for performance reasons - var _dataSlice: [DataBufferElement] { - UnsafeMutableBufferPointer(start: dataBaseAddress, count: header.dataCount).map { $0 } + var _dataSlice: UnsafeBufferPointer { + UnsafeBufferPointer(start: dataBaseAddress, count: header.dataCount) } - // TODO: should not materialize as `Array` for performance reasons - var _trieSlice: [TrieBufferElement] { - UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount).map { $0 } + var _trieSlice: UnsafeMutableBufferPointer { + UnsafeMutableBufferPointer(start: trieBaseAddress, count: header.trieCount) } init(dataCapacity: Capacity, trieCapacity: Capacity) { From e9dec67cc24c1ebe448b724fba926c1776bbe14b Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 21:21:22 +0200 Subject: [PATCH 161/176] [Capsule] Rename base iterators --- Sources/Capsule/PersistentDictionary.swift | 4 ++-- Sources/Capsule/_Common.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index 302f7fe8b..804a08711 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -240,10 +240,10 @@ public struct DictionaryKeyValueTupleIterator: IteratorPro // TODO consider reworking similar to `DictionaryKeyValueTupleIterator` // (would require a reversed variant of `UnsafeBufferPointer<(key: Key, value: Value)>.Iterator`) public struct DictionaryKeyValueTupleReverseIterator { - private var baseIterator: ChampBaseReverseIterator> + private var baseIterator: BaseReverseIterator> init(rootNode: BitmapIndexedDictionaryNode) { - self.baseIterator = ChampBaseReverseIterator(rootNode: rootNode) + self.baseIterator = BaseReverseIterator(rootNode: rootNode) } } diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 262d91590..7d7489c33 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -96,7 +96,7 @@ protocol Node: AnyObject { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -struct ChampBaseIterator { +struct BaseIterator { typealias T = BitmapIndexedNode var currentValueCursor: Int = 0 @@ -171,7 +171,7 @@ struct ChampBaseIterator { /// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// -struct ChampBaseReverseIterator { +struct BaseReverseIterator { typealias T = BitmapIndexedNode var currentValueCursor: Int = -1 From 870e49d67a714cb191f5b6680030a9f4dab2c03e Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Mon, 5 Sep 2022 21:25:03 +0200 Subject: [PATCH 162/176] [Capsule] Remove not necessary type alias --- Sources/Capsule/_Common.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/Capsule/_Common.swift b/Sources/Capsule/_Common.swift index 7d7489c33..2cc979d1a 100644 --- a/Sources/Capsule/_Common.swift +++ b/Sources/Capsule/_Common.swift @@ -96,9 +96,7 @@ protocol Node: AnyObject { /// depth-first pre-order traversal, which yields first all payload elements of the current /// node before traversing sub-nodes (left to right). /// -struct BaseIterator { - typealias T = BitmapIndexedNode - +struct BaseIterator { var currentValueCursor: Int = 0 var currentValueLength: Int = 0 var currentValueNode: T? = nil @@ -164,16 +162,13 @@ struct BaseIterator { mutating func hasNext() -> Bool { return (currentValueCursor < currentValueLength) || searchNextValueNode() } - } /// /// Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base /// iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). /// -struct BaseReverseIterator { - typealias T = BitmapIndexedNode - +struct BaseReverseIterator { var currentValueCursor: Int = -1 var currentValueNode: T? = nil From f6fe3c06581fd1076a6dc6025eeefb874b1dc469 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 09:38:36 +0200 Subject: [PATCH 163/176] [Capsule] Add `init(uncheckedUniqueKeysWithValues:)` benchmark Further add missing items to the benchmark library. --- .../Benchmarks/PersistentDictionaryBenchmarks.swift | 10 ++++++++++ Benchmarks/Libraries/Capsule.json | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index 859ba4538..099c99dd9 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -24,6 +24,16 @@ extension Benchmark { } } + self.add( + title: "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", + input: [Int].self + ) { input in + let keysAndValues = input.map { ($0, 2 * $0) } + return { timer in + blackHole(PersistentDictionary(uncheckedUniqueKeysWithValues: keysAndValues)) + } + } + self.add( title: "PersistentDictionary sequential iteration", input: [Int].self diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/Capsule.json index e0b879dfb..1be423512 100644 --- a/Benchmarks/Libraries/Capsule.json +++ b/Benchmarks/Libraries/Capsule.json @@ -12,7 +12,10 @@ "title": "all", "tasks": [ "PersistentDictionary init(uniqueKeysWithValues:)", + "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", "PersistentDictionary sequential iteration", + "PersistentDictionary.Keys sequential iteration", + "PersistentDictionary.Values sequential iteration", "PersistentDictionary subscript, successful lookups", "PersistentDictionary subscript, unsuccessful lookups", "PersistentDictionary subscript, noop setter", @@ -55,6 +58,14 @@ "OrderedDictionary init(uniqueKeysWithValues:)", ] }, + { + "kind": "chart", + "title": "init(uncheckedUniqueKeysWithValues:)", + "tasks": [ + "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", + "OrderedDictionary init(uncheckedUniqueKeysWithValues:)", + ] + }, { "kind": "chart", "title": "sequential iteration", From a4bce51f58c726924e383111f42c419670602554 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 09:43:21 +0200 Subject: [PATCH 164/176] [Capsule] Fix TODO comment --- Sources/Capsule/PersistentDictionary.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/Capsule/PersistentDictionary.swift index 804a08711..13fd7828f 100644 --- a/Sources/Capsule/PersistentDictionary.swift +++ b/Sources/Capsule/PersistentDictionary.swift @@ -24,7 +24,7 @@ public struct PersistentDictionary where Key: Hashable { self.init(map.rootNode) } - // TODO consider removing `unchecked` version, since it's only referenced from within the test suite + // TODO: consider removing `unchecked` version, since it's only referenced from within the test suite @inlinable @inline(__always) public init(uncheckedUniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { @@ -51,7 +51,7 @@ public struct PersistentDictionary where Key: Hashable { self.init(builder) } - // TODO consider removing `unchecked` version, since it's only referenced from within the test suite + // TODO: consider removing `unchecked` version, since it's only referenced from within the test suite @inlinable @inline(__always) public init(uncheckedUniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { From 6b905eb928db84490d8174da8981cdd87d914506 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 10:01:25 +0200 Subject: [PATCH 165/176] [Capsule] Implement `BitmapIndexedDictionaryNode: CustomStringConvertible` --- ...ctionaryNode+CustomStringConvertible.swift | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift index 91128467c..80b97f323 100644 --- a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift +++ b/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift @@ -9,8 +9,31 @@ // //===----------------------------------------------------------------------===// -//extension BitmapIndexedDictionaryNode: CustomStringConvertible { -// public var description: String { -// <#code#> -// } -//} +extension BitmapIndexedDictionaryNode: 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 + } +} From 5bc17ffe84a882e98074faf3ebdc8504aaad190f Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 13:22:10 +0200 Subject: [PATCH 166/176] [Capsule] Rename package to `PersistentCollections` --- .../PersistentDictionaryBenchmarks.swift | 2 +- ...apsule.json => PersistentCollections.json} | 2 +- Package.swift | 22 +++++++++---------- Sources/Collections/Collections.swift | 2 +- .../PersistentDictionary+CVarArg.swift | 0 .../PersistentDictionary+Collection.swift | 0 ...tionary+CustomDebugStringConvertible.swift | 0 ...rsistentDictionary+CustomReflectible.swift | 0 ...ntDictionary+CustomStringConvertible.swift | 0 .../PersistentDictionary+Decodable.swift | 0 .../PersistentDictionary+Encodable.swift | 0 .../PersistentDictionary+Equatable.swift | 0 ...onary+ExpressibleByDictionaryLiteral.swift | 0 .../PersistentDictionary+Hashable.swift | 0 .../PersistentDictionary+Keys.swift | 0 .../PersistentDictionary+Sequence.swift | 0 .../PersistentDictionary+Values.swift | 0 .../PersistentDictionary.swift | 0 ...aryNode+CustomDebugStringConvertible.swift | 0 ...ctionaryNode+CustomStringConvertible.swift | 0 .../_BitmapIndexedDictionaryNode.swift | 0 .../_Common.swift | 0 .../_DictionaryEffect.swift | 0 .../_DictionaryNode.swift | 0 .../PersistentCollections Smoke Tests.swift} | 4 ++-- .../PersistentCollections Tests.swift} | 4 ++-- .../PersistentCollections Utils.swift} | 2 +- .../_CollidableInt.swift} | 0 28 files changed, 19 insertions(+), 19 deletions(-) rename Benchmarks/Libraries/{Capsule.json => PersistentCollections.json} (99%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+CVarArg.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Collection.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+CustomDebugStringConvertible.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+CustomReflectible.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+CustomStringConvertible.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Decodable.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Encodable.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Equatable.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+ExpressibleByDictionaryLiteral.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Hashable.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Keys.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Sequence.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary+Values.swift (100%) rename Sources/{Capsule => PersistentCollections}/PersistentDictionary.swift (100%) rename Sources/{Capsule => PersistentCollections}/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift (100%) rename Sources/{Capsule => PersistentCollections}/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift (100%) rename Sources/{Capsule => PersistentCollections}/_BitmapIndexedDictionaryNode.swift (100%) rename Sources/{Capsule => PersistentCollections}/_Common.swift (100%) rename Sources/{Capsule => PersistentCollections}/_DictionaryEffect.swift (100%) rename Sources/{Capsule => PersistentCollections}/_DictionaryNode.swift (100%) rename Tests/{CapsuleTests/CapsuleSmokeTests.swift => PersistentCollectionsTests/PersistentCollections Smoke Tests.swift} (99%) rename Tests/{CapsuleTests/Capsule Tests.swift => PersistentCollectionsTests/PersistentCollections Tests.swift} (99%) rename Tests/{CapsuleTests/Capsule Utils.swift => PersistentCollectionsTests/PersistentCollections Utils.swift} (97%) rename Tests/{CapsuleTests/CollidableInt.swift => PersistentCollectionsTests/_CollidableInt.swift} (100%) diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index 099c99dd9..938cebd0a 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import CollectionsBenchmark -import Capsule +import PersistentCollections extension Benchmark { public mutating func addPersistentDictionaryBenchmarks() { diff --git a/Benchmarks/Libraries/Capsule.json b/Benchmarks/Libraries/PersistentCollections.json similarity index 99% rename from Benchmarks/Libraries/Capsule.json rename to Benchmarks/Libraries/PersistentCollections.json index 1be423512..23c37a4d8 100644 --- a/Benchmarks/Libraries/Capsule.json +++ b/Benchmarks/Libraries/PersistentCollections.json @@ -1,6 +1,6 @@ { "kind": "group", - "title": "Capsule Benchmarks", + "title": "PersistentCollections Benchmarks", "directory": "Results", "contents": [ { diff --git a/Package.swift b/Package.swift index 9a290404f..ccb80d957 100644 --- a/Package.swift +++ b/Package.swift @@ -54,8 +54,8 @@ let package = Package( .library(name: "Collections", targets: ["Collections"]), .library(name: "DequeModule", targets: ["DequeModule"]), .library(name: "OrderedCollections", targets: ["OrderedCollections"]), + .library(name: "PersistentCollections", targets: ["PersistentCollections"]), .library(name: "PriorityQueueModule", targets: ["PriorityQueueModule"]), - .library(name: "Capsule", targets: ["Capsule"]), ], targets: [ .target( @@ -63,8 +63,8 @@ let package = Package( dependencies: [ "DequeModule", "OrderedCollections", + "PersistentCollections", "PriorityQueueModule", - "Capsule", ], path: "Sources/Collections", exclude: ["CMakeLists.txt"], @@ -106,6 +106,15 @@ let package = Package( dependencies: ["OrderedCollections", "_CollectionsTestSupport"], swiftSettings: settings), + // PersistentDictionary + .target( + name: "PersistentCollections", + swiftSettings: settings), + .testTarget( + name: "PersistentCollectionsTests", + dependencies: ["PersistentCollections", "_CollectionsTestSupport"], + swiftSettings: settings), + // PriorityQueue .target( name: "PriorityQueueModule", @@ -115,14 +124,5 @@ let package = Package( name: "PriorityQueueTests", dependencies: ["PriorityQueueModule"], swiftSettings: settings), - - // PersistentDictionary - .target( - name: "Capsule", - swiftSettings: settings), - .testTarget( - name: "CapsuleTests", - dependencies: ["Capsule", "_CollectionsTestSupport"], - swiftSettings: settings), ] ) diff --git a/Sources/Collections/Collections.swift b/Sources/Collections/Collections.swift index 806cc8871..ec989b309 100644 --- a/Sources/Collections/Collections.swift +++ b/Sources/Collections/Collections.swift @@ -11,5 +11,5 @@ @_exported import DequeModule @_exported import OrderedCollections +@_exported import PersistentCollections @_exported import PriorityQueueModule -@_exported import Capsule diff --git a/Sources/Capsule/PersistentDictionary+CVarArg.swift b/Sources/PersistentCollections/PersistentDictionary+CVarArg.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+CVarArg.swift rename to Sources/PersistentCollections/PersistentDictionary+CVarArg.swift diff --git a/Sources/Capsule/PersistentDictionary+Collection.swift b/Sources/PersistentCollections/PersistentDictionary+Collection.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Collection.swift rename to Sources/PersistentCollections/PersistentDictionary+Collection.swift diff --git a/Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift b/Sources/PersistentCollections/PersistentDictionary+CustomDebugStringConvertible.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+CustomDebugStringConvertible.swift rename to Sources/PersistentCollections/PersistentDictionary+CustomDebugStringConvertible.swift diff --git a/Sources/Capsule/PersistentDictionary+CustomReflectible.swift b/Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+CustomReflectible.swift rename to Sources/PersistentCollections/PersistentDictionary+CustomReflectible.swift diff --git a/Sources/Capsule/PersistentDictionary+CustomStringConvertible.swift b/Sources/PersistentCollections/PersistentDictionary+CustomStringConvertible.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+CustomStringConvertible.swift rename to Sources/PersistentCollections/PersistentDictionary+CustomStringConvertible.swift diff --git a/Sources/Capsule/PersistentDictionary+Decodable.swift b/Sources/PersistentCollections/PersistentDictionary+Decodable.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Decodable.swift rename to Sources/PersistentCollections/PersistentDictionary+Decodable.swift diff --git a/Sources/Capsule/PersistentDictionary+Encodable.swift b/Sources/PersistentCollections/PersistentDictionary+Encodable.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Encodable.swift rename to Sources/PersistentCollections/PersistentDictionary+Encodable.swift diff --git a/Sources/Capsule/PersistentDictionary+Equatable.swift b/Sources/PersistentCollections/PersistentDictionary+Equatable.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Equatable.swift rename to Sources/PersistentCollections/PersistentDictionary+Equatable.swift diff --git a/Sources/Capsule/PersistentDictionary+ExpressibleByDictionaryLiteral.swift b/Sources/PersistentCollections/PersistentDictionary+ExpressibleByDictionaryLiteral.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+ExpressibleByDictionaryLiteral.swift rename to Sources/PersistentCollections/PersistentDictionary+ExpressibleByDictionaryLiteral.swift diff --git a/Sources/Capsule/PersistentDictionary+Hashable.swift b/Sources/PersistentCollections/PersistentDictionary+Hashable.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Hashable.swift rename to Sources/PersistentCollections/PersistentDictionary+Hashable.swift diff --git a/Sources/Capsule/PersistentDictionary+Keys.swift b/Sources/PersistentCollections/PersistentDictionary+Keys.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Keys.swift rename to Sources/PersistentCollections/PersistentDictionary+Keys.swift diff --git a/Sources/Capsule/PersistentDictionary+Sequence.swift b/Sources/PersistentCollections/PersistentDictionary+Sequence.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Sequence.swift rename to Sources/PersistentCollections/PersistentDictionary+Sequence.swift diff --git a/Sources/Capsule/PersistentDictionary+Values.swift b/Sources/PersistentCollections/PersistentDictionary+Values.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary+Values.swift rename to Sources/PersistentCollections/PersistentDictionary+Values.swift diff --git a/Sources/Capsule/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift similarity index 100% rename from Sources/Capsule/PersistentDictionary.swift rename to Sources/PersistentCollections/PersistentDictionary.swift diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift similarity index 100% rename from Sources/Capsule/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift rename to Sources/PersistentCollections/_BitmapIndexedDictionaryNode+CustomDebugStringConvertible.swift diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift similarity index 100% rename from Sources/Capsule/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift rename to Sources/PersistentCollections/_BitmapIndexedDictionaryNode+CustomStringConvertible.swift diff --git a/Sources/Capsule/_BitmapIndexedDictionaryNode.swift b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift similarity index 100% rename from Sources/Capsule/_BitmapIndexedDictionaryNode.swift rename to Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift diff --git a/Sources/Capsule/_Common.swift b/Sources/PersistentCollections/_Common.swift similarity index 100% rename from Sources/Capsule/_Common.swift rename to Sources/PersistentCollections/_Common.swift diff --git a/Sources/Capsule/_DictionaryEffect.swift b/Sources/PersistentCollections/_DictionaryEffect.swift similarity index 100% rename from Sources/Capsule/_DictionaryEffect.swift rename to Sources/PersistentCollections/_DictionaryEffect.swift diff --git a/Sources/Capsule/_DictionaryNode.swift b/Sources/PersistentCollections/_DictionaryNode.swift similarity index 100% rename from Sources/Capsule/_DictionaryNode.swift rename to Sources/PersistentCollections/_DictionaryNode.swift diff --git a/Tests/CapsuleTests/CapsuleSmokeTests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift similarity index 99% rename from Tests/CapsuleTests/CapsuleSmokeTests.swift rename to Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift index 4608c4b08..9b669bdda 100644 --- a/Tests/CapsuleTests/CapsuleSmokeTests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import _CollectionsTestSupport -@testable import Capsule +@testable import PersistentCollections final class CapsuleSmokeTests: CollectionTestCase { func testSubscriptAdd() { diff --git a/Tests/CapsuleTests/Capsule Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift similarity index 99% rename from Tests/CapsuleTests/Capsule Tests.swift rename to Tests/PersistentCollectionsTests/PersistentCollections Tests.swift index bcfdce24d..69c97632c 100644 --- a/Tests/CapsuleTests/Capsule Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift @@ -2,7 +2,7 @@ // // 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 @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import _CollectionsTestSupport -@testable import Capsule +@testable import PersistentCollections class PersistentDictionaryTests: CollectionTestCase { func test_empty() { diff --git a/Tests/CapsuleTests/Capsule Utils.swift b/Tests/PersistentCollectionsTests/PersistentCollections Utils.swift similarity index 97% rename from Tests/CapsuleTests/Capsule Utils.swift rename to Tests/PersistentCollectionsTests/PersistentCollections Utils.swift index 900235aff..78f815a89 100644 --- a/Tests/CapsuleTests/Capsule Utils.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Utils.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// import _CollectionsTestSupport -import Capsule +import PersistentCollections extension LifetimeTracker { func persistentDictionary( diff --git a/Tests/CapsuleTests/CollidableInt.swift b/Tests/PersistentCollectionsTests/_CollidableInt.swift similarity index 100% rename from Tests/CapsuleTests/CollidableInt.swift rename to Tests/PersistentCollectionsTests/_CollidableInt.swift From 4e213be00af47565efab8ec87c28c657841929de Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 13:53:58 +0200 Subject: [PATCH 167/176] [Capsule] Simplify insertion methods --- .../PersistentCollections/PersistentDictionary.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index 13fd7828f..6bf715127 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -86,8 +86,7 @@ public struct PersistentDictionary where Key: Hashable { } mutating set(optionalValue) { if let value = optionalValue { - let mutate = isKnownUniquelyReferenced(&self.rootNode) - insert(mutate, key: key, value: value) + insert(key: key, value: value) } else { let mutate = isKnownUniquelyReferenced(&self.rootNode) delete(mutate, key: key) @@ -100,8 +99,7 @@ public struct PersistentDictionary where Key: Hashable { return get(key) ?? defaultValue() } mutating set(value) { - let mutate = isKnownUniquelyReferenced(&self.rootNode) - insert(mutate, key: key, value: value) + insert(key: key, value: value) } } @@ -114,12 +112,8 @@ public struct PersistentDictionary where Key: Hashable { } public mutating func insert(key: Key, value: Value) { - let mutate = isKnownUniquelyReferenced(&self.rootNode) - insert(mutate, key: key, value: value) - } + let isStorageKnownUniquelyReferenced = isKnownUniquelyReferenced(&self.rootNode) - // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` - mutating func insert(_ isStorageKnownUniquelyReferenced: Bool, key: Key, value: Value) { var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updateOrUpdating(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) From 7ea47d711f85bd331c9616c84ac545f23a784c05 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 14:02:54 +0200 Subject: [PATCH 168/176] [Capsule] Simplify deletion methods --- .../PersistentDictionary.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index 6bf715127..313576911 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -88,8 +88,7 @@ public struct PersistentDictionary where Key: Hashable { if let value = optionalValue { insert(key: key, value: value) } else { - let mutate = isKnownUniquelyReferenced(&self.rootNode) - delete(mutate, key: key) + delete(key: key) } } } @@ -134,7 +133,7 @@ public struct PersistentDictionary where Key: Hashable { } else { return self } } - // TODO signature adopted from `Dictionary`, unify with API + // TODO: signature adopted from `Dictionary`, unify with API @discardableResult public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { let oldValue = get(key) @@ -142,13 +141,9 @@ public struct PersistentDictionary where Key: Hashable { return oldValue } - public mutating func delete(_ key: Key) { - let mutate = isKnownUniquelyReferenced(&self.rootNode) - delete(mutate, key: key) - } + public mutating func delete(key: Key) { + let isStorageKnownUniquelyReferenced = isKnownUniquelyReferenced(&self.rootNode) - // querying `isKnownUniquelyReferenced(&self.rootNode)` from within the body of the function always yields `false` - mutating func delete(_ isStorageKnownUniquelyReferenced: Bool, key: Key) { var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removeOrRemoving(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) @@ -169,11 +164,11 @@ public struct PersistentDictionary where Key: Hashable { } else { return self } } - // TODO signature adopted from `Dictionary`, unify with API + // TODO: signature adopted from `Dictionary`, unify with API @discardableResult public mutating func removeValue(forKey key: Key) -> Value? { if let value = get(key) { - delete(key) + delete(key: key) return value } return nil From 26b97f95d2c9f0751fb442d3c9d6a82a0715cc2a Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 16:11:17 +0200 Subject: [PATCH 169/176] [Capsule] Unify insertion/deletion methods Note, always tracking discardable result negatively impacts batch use cases. --- .../Libraries/PersistentCollections.json | 16 ++++++ .../PersistentDictionary.swift | 52 ++++++++----------- .../_BitmapIndexedDictionaryNode.swift | 20 +++---- .../_DictionaryEffect.swift | 15 ++++-- .../_DictionaryNode.swift | 6 +-- .../PersistentCollections Smoke Tests.swift | 42 ++++++--------- 6 files changed, 77 insertions(+), 74 deletions(-) diff --git a/Benchmarks/Libraries/PersistentCollections.json b/Benchmarks/Libraries/PersistentCollections.json index 23c37a4d8..bb86f69c8 100644 --- a/Benchmarks/Libraries/PersistentCollections.json +++ b/Benchmarks/Libraries/PersistentCollections.json @@ -138,6 +138,22 @@ "OrderedDictionary unsuccessful index(forKey:)", ] }, + { + "kind": "chart", + "title": "updateValue(_:forKey:), existing", + "tasks": [ + "PersistentDictionary updateValue(_:forKey:), existing", + "Dictionary updateValue(_:forKey:), existing", + ] + }, + { + "kind": "chart", + "title": "updateValue(_:forKey:), insert", + "tasks": [ + "PersistentDictionary updateValue(_:forKey:), insert", + "Dictionary updateValue(_:forKey:), insert", + ] + }, ] }, ] diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index 313576911..530b4daf7 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 @@ -30,7 +30,7 @@ public struct PersistentDictionary where Key: Hashable { public init(uncheckedUniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { var builder = Self() keysAndValues.forEach { key, value in - builder.insert(key: key, value: value) + builder.updateValue(value, forKey: key) } self.init(builder) } @@ -41,7 +41,7 @@ public struct PersistentDictionary where Key: Hashable { var builder = Self() var expectedCount = 0 keysAndValues.forEach { key, value in - builder.insert(key: key, value: value) + builder.updateValue(value, forKey: key) expectedCount += 1 guard expectedCount == builder.count else { @@ -86,9 +86,9 @@ public struct PersistentDictionary where Key: Hashable { } mutating set(optionalValue) { if let value = optionalValue { - insert(key: key, value: value) + updateValue(value, forKey: key) } else { - delete(key: key) + removeValue(forKey: key) } } } @@ -98,7 +98,7 @@ public struct PersistentDictionary where Key: Hashable { return get(key) ?? defaultValue() } mutating set(value) { - insert(key: key, value: value) + updateValue(value, forKey: key) } } @@ -110,21 +110,25 @@ public struct PersistentDictionary where Key: Hashable { rootNode.get(key, computeHash(key), 0) } - public mutating func insert(key: Key, value: Value) { + @discardableResult + public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { let isStorageKnownUniquelyReferenced = isKnownUniquelyReferenced(&self.rootNode) - var effect = DictionaryEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updateOrUpdating(isStorageKnownUniquelyReferenced, key, value, keyHash, 0, &effect) if effect.modified { self.rootNode = newRootNode } + + // Note, always tracking discardable result negatively impacts batch use cases + return effect.previousValue } // fluid/immutable API - public func inserting(key: Key, value: Value) -> Self { - var effect = DictionaryEffect() + public func updatingValue(_ value: Value, forKey key: Key) -> Self { + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.updateOrUpdating(false, key, value, keyHash, 0, &effect) @@ -133,29 +137,25 @@ public struct PersistentDictionary where Key: Hashable { } else { return self } } - // TODO: signature adopted from `Dictionary`, unify with API @discardableResult - public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { - let oldValue = get(key) - insert(key: key, value: value) - return oldValue - } - - public mutating func delete(key: Key) { + public mutating func removeValue(forKey key: Key) -> Value? { let isStorageKnownUniquelyReferenced = isKnownUniquelyReferenced(&self.rootNode) - var effect = DictionaryEffect() + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removeOrRemoving(isStorageKnownUniquelyReferenced, key, keyHash, 0, &effect) if effect.modified { self.rootNode = newRootNode } + + // Note, always tracking discardable result negatively impacts batch use cases + return effect.previousValue } // fluid/immutable API - public func deleting(key: Key) -> Self { - var effect = DictionaryEffect() + public func removingValue(forKey key: Key) -> Self { + var effect = DictionaryEffect() let keyHash = computeHash(key) let newRootNode = rootNode.removeOrRemoving(false, key, keyHash, 0, &effect) @@ -163,16 +163,6 @@ public struct PersistentDictionary where Key: Hashable { return Self(newRootNode) } else { return self } } - - // TODO: signature adopted from `Dictionary`, unify with API - @discardableResult - public mutating func removeValue(forKey key: Key) -> Value? { - if let value = get(key) { - delete(key: key) - return value - } - return nil - } } /// diff --git a/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift index 99403f915..d6f7b10ae 100644 --- a/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift +++ b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift @@ -333,7 +333,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H fatalError("Should not reach here.") } - final func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + final func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { guard collisionFree else { return updateOrUpdatingCollision(isStorageKnownUniquelyReferenced, key, value, keyHash, shift, &effect) @@ -347,7 +347,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let (key0, value0) = self.getPayload(index) if key0 == key { - effect.setReplacedValue() + effect.setReplacedValue(previousValue: value0) return copyAndSetValue(isStorageKnownUniquelyReferenced, bitpos, value) } else { let keyHash0 = computeHash(key0) @@ -377,7 +377,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H let subNode = self.getNode(index) let subNodeNew = subNode.updateOrUpdating(subNodeModifyInPlace, key, value, keyHash, shift + bitPartitionSize, &effect) - guard effect.modified && subNode !== subNodeNew else { if !effect.replacedValue { count += 1 } ; assert(self.invariant) ; return self } + guard effect.modified && subNode !== subNodeNew else { if effect.previousValue == nil { count += 1 } ; assert(self.invariant) ; return self } return copyAndSetTrieNode(isStorageKnownUniquelyReferenced, bitpos, index, subNodeNew, updateCount: { $0 -= subNode.count ; $0 += subNodeNew.count }) } @@ -387,7 +387,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } @inline(never) - final func updateOrUpdatingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + final func updateOrUpdatingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { assert(hashCollision) let content: [ReturnPayload] = Array(self) @@ -401,7 +401,7 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H if let index = content.firstIndex(where: { key == $0.key }) { let updatedContent: [ReturnPayload] = content[0..: DictionaryNode where Key: H } } - final func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + final func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { guard collisionFree else { return removeOrRemovingCollision(isStorageKnownUniquelyReferenced, key, keyHash, shift, &effect) @@ -420,10 +420,10 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H guard (dataMap & bitpos) == 0 else { let index = indexFrom(dataMap, mask, bitpos) - let (key0, _) = self.getPayload(index) + let (key0, value0) = self.getPayload(index) guard key0 == key else { assert(self.invariant) ; return self } - effect.setModified() + effect.setModified(previousValue: value0) if self.payloadArity == 2 && self.nodeArity == 0 { if shift == 0 { // keep remaining pair on root level @@ -477,14 +477,14 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H } @inline(never) - final func removeOrRemovingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { + final func removeOrRemovingCollision(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ keyHash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> BitmapIndexedDictionaryNode { assert(hashCollision) let content: [ReturnPayload] = Array(self) let _ = computeHash(content.first!.key) if let index = content.firstIndex(where: { key == $0.key }) { - effect.setModified() + effect.setModified(previousValue: content[index].value) var updatedContent = content; updatedContent.remove(at: index) assert(updatedContent.count == content.count - 1) diff --git a/Sources/PersistentCollections/_DictionaryEffect.swift b/Sources/PersistentCollections/_DictionaryEffect.swift index ba8bc9454..c4f0541d9 100644 --- a/Sources/PersistentCollections/_DictionaryEffect.swift +++ b/Sources/PersistentCollections/_DictionaryEffect.swift @@ -2,23 +2,28 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 // //===----------------------------------------------------------------------===// -struct DictionaryEffect { +struct DictionaryEffect { var modified: Bool = false - var replacedValue: Bool = false + var previousValue: Value? mutating func setModified() { self.modified = true } - mutating func setReplacedValue() { + mutating func setModified(previousValue: Value) { self.modified = true - self.replacedValue = true + self.previousValue = previousValue + } + + mutating func setReplacedValue(previousValue: Value) { + self.modified = true + self.previousValue = previousValue } } diff --git a/Sources/PersistentCollections/_DictionaryNode.swift b/Sources/PersistentCollections/_DictionaryNode.swift index dc7d92bc3..c87727020 100644 --- a/Sources/PersistentCollections/_DictionaryNode.swift +++ b/Sources/PersistentCollections/_DictionaryNode.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2019 - 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2019 - 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 @@ -19,7 +19,7 @@ protocol DictionaryNode: Node { func index(_ key: Key, _ hash: Int, _ shift: Int, _ skippedBefore: Int) -> PersistentDictionaryIndex? - func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode + func updateOrUpdating(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ value: Value, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode - func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode + func removeOrRemoving(_ isStorageKnownUniquelyReferenced: Bool, _ key: Key, _ hash: Int, _ shift: Int, _ effect: inout DictionaryEffect) -> ReturnBitmapIndexedNode } diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift index 9b669bdda..14223a734 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift @@ -52,12 +52,12 @@ final class CapsuleSmokeTests: CollectionTestCase { let map: PersistentDictionary = [1: "a", 2: "b"] _ = map - .inserting(key: 1, value: "x") // triggers COW - .inserting(key: 2, value: "y") // triggers COW + .updatingValue("x", forKey: 1) // triggers COW + .updatingValue("y", forKey: 2) // triggers COW var res1: PersistentDictionary = [:] - res1.insert(key: 1, value: "a") // in-place - res1.insert(key: 2, value: "b") // in-place + res1.updateValue("a", forKey: 1) // in-place + res1.updateValue("b", forKey: 2) // in-place var res2: PersistentDictionary = [:] res2[1] = "a" // in-place @@ -78,22 +78,18 @@ final class CapsuleSmokeTests: CollectionTestCase { func testTriggerOverwrite2() { var res1: PersistentDictionary = [:] - res1.insert(key: CollidableInt(10, 01), value: "a") // in-place - res1.insert(key: CollidableInt(11, 33), value: "a") // in-place - res1.insert(key: CollidableInt(20, 02), value: "b") // in-place + res1.updateValue("a", forKey: CollidableInt(10, 01)) // in-place + res1.updateValue("a", forKey: CollidableInt(11, 33)) // in-place + res1.updateValue("b", forKey: CollidableInt(20, 02)) // in-place - res1.insert(key: CollidableInt(10, 01), value: "x") // in-place - res1.insert(key: CollidableInt(11, 33), value: "x") // in-place - res1.insert(key: CollidableInt(20, 02), value: "y") // in-place - - print("Yeah!") + res1.updateValue("x", forKey: CollidableInt(10, 01)) // in-place + res1.updateValue("x", forKey: CollidableInt(11, 33)) // in-place + res1.updateValue("y", forKey: CollidableInt(20, 02)) // in-place var res2: PersistentDictionary = res1 - res2.insert(key: CollidableInt(10, 01), value: "a") // triggers COW - res2.insert(key: CollidableInt(11, 33), value: "a") // in-place - res2.insert(key: CollidableInt(20, 02), value: "b") // in-place - - print("Yeah!") + res2.updateValue("a", forKey: CollidableInt(10, 01)) // triggers COW + res2.updateValue("a", forKey: CollidableInt(11, 33)) // in-place + res2.updateValue("b", forKey: CollidableInt(20, 02)) // in-place expectEqual(res1[CollidableInt(10, 01)], "x") expectEqual(res1[CollidableInt(11, 33)], "x") @@ -108,20 +104,19 @@ final class CapsuleSmokeTests: CollectionTestCase { func testTriggerOverwrite3() { let upperBound = 1_000 + // Populating `map1` var map1: PersistentDictionary = [:] for index in 0.. = map1 for index in 0.. = map2 for index in 0.. Date: Tue, 6 Sep 2022 16:37:23 +0200 Subject: [PATCH 170/176] [Capsule] Remove unchecked variant of initializer --- .../PersistentDictionaryBenchmarks.swift | 10 ---------- .../Libraries/PersistentCollections.json | 11 +---------- .../PersistentDictionary.swift | 18 ------------------ .../PersistentCollections Tests.swift | 14 +++++++------- 4 files changed, 8 insertions(+), 45 deletions(-) diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index 938cebd0a..d3d75cba0 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -24,16 +24,6 @@ extension Benchmark { } } - self.add( - title: "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", - input: [Int].self - ) { input in - let keysAndValues = input.map { ($0, 2 * $0) } - return { timer in - blackHole(PersistentDictionary(uncheckedUniqueKeysWithValues: keysAndValues)) - } - } - self.add( title: "PersistentDictionary sequential iteration", input: [Int].self diff --git a/Benchmarks/Libraries/PersistentCollections.json b/Benchmarks/Libraries/PersistentCollections.json index bb86f69c8..ec1544442 100644 --- a/Benchmarks/Libraries/PersistentCollections.json +++ b/Benchmarks/Libraries/PersistentCollections.json @@ -12,7 +12,6 @@ "title": "all", "tasks": [ "PersistentDictionary init(uniqueKeysWithValues:)", - "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", "PersistentDictionary sequential iteration", "PersistentDictionary.Keys sequential iteration", "PersistentDictionary.Values sequential iteration", @@ -58,14 +57,6 @@ "OrderedDictionary init(uniqueKeysWithValues:)", ] }, - { - "kind": "chart", - "title": "init(uncheckedUniqueKeysWithValues:)", - "tasks": [ - "PersistentDictionary init(uncheckedUniqueKeysWithValues:)", - "OrderedDictionary init(uncheckedUniqueKeysWithValues:)", - ] - }, { "kind": "chart", "title": "sequential iteration", @@ -144,7 +135,7 @@ "tasks": [ "PersistentDictionary updateValue(_:forKey:), existing", "Dictionary updateValue(_:forKey:), existing", - ] + ] }, { "kind": "chart", diff --git a/Sources/PersistentCollections/PersistentDictionary.swift b/Sources/PersistentCollections/PersistentDictionary.swift index 530b4daf7..355838ef0 100644 --- a/Sources/PersistentCollections/PersistentDictionary.swift +++ b/Sources/PersistentCollections/PersistentDictionary.swift @@ -24,17 +24,6 @@ public struct PersistentDictionary where Key: Hashable { self.init(map.rootNode) } - // TODO: consider removing `unchecked` version, since it's only referenced from within the test suite - @inlinable - @inline(__always) - public init(uncheckedUniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { - var builder = Self() - keysAndValues.forEach { key, value in - builder.updateValue(value, forKey: key) - } - self.init(builder) - } - @inlinable @inline(__always) public init(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) { @@ -51,13 +40,6 @@ public struct PersistentDictionary where Key: Hashable { self.init(builder) } - // TODO: consider removing `unchecked` version, since it's only referenced from within the test suite - @inlinable - @inline(__always) - public init(uncheckedUniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { - self.init(uncheckedUniqueKeysWithValues: zip(keys, values)) - } - @inlinable @inline(__always) public init(uniqueKeys keys: Keys, values: Values) where Keys.Element == Key, Values.Element == Value { diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift index 69c97632c..38b16c4df 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift @@ -40,7 +40,7 @@ class PersistentDictionaryTests: CollectionTestCase { // "two": 2, // "three": 3, // ] -// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } @@ -51,7 +51,7 @@ class PersistentDictionaryTests: CollectionTestCase { // "two": 2, // "three": 3, // ] -// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // expectEqualElements(d.sorted(by: <), items.sorted(by: <)) // } @@ -62,7 +62,7 @@ class PersistentDictionaryTests: CollectionTestCase { ("two", 2), ("three", 3), ] - let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) + let d = PersistentDictionary(uniqueKeysWithValues: items) expectEqualElements(d.sorted(by: <), items.sorted(by: <)) } @@ -128,25 +128,25 @@ class PersistentDictionaryTests: CollectionTestCase { // ]) // } -// func test_uncheckedUniqueKeysWithValues_labeled_tuples() { +// func test_uniqueKeysWithValues_labeled_tuples() { // let items: KeyValuePairs = [ // "zero": 0, // "one": 1, // "two": 2, // "three": 3, // ] -// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // expectEqualElements(d, items) // } -// func test_uncheckedUniqueKeysWithValues_unlabeled_tuples() { +// func test_uniqueKeysWithValues_unlabeled_tuples() { // let items: [(String, Int)] = [ // ("zero", 0), // ("one", 1), // ("two", 2), // ("three", 3), // ] -// let d = PersistentDictionary(uncheckedUniqueKeysWithValues: items) +// let d = PersistentDictionary(uniqueKeysWithValues: items) // expectEqualElements(d, items) // } From 868eb2fdcca848f11bafbec52e46e06c3ceb9d84 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 16:43:19 +0200 Subject: [PATCH 171/176] [Capsule] Remove comment after check --- .../PersistentCollections/PersistentDictionary+Equatable.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/PersistentCollections/PersistentDictionary+Equatable.swift b/Sources/PersistentCollections/PersistentDictionary+Equatable.swift index 4055aeeef..9578a47f6 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Equatable.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Equatable.swift @@ -9,7 +9,6 @@ // //===----------------------------------------------------------------------===// -// TODO check Dictionary semantics of Equatable (i.e., if it only compares keys or also values) extension PersistentDictionary: Equatable where Value: Equatable { public static func == (lhs: PersistentDictionary, rhs: PersistentDictionary) -> Bool { lhs.rootNode === rhs.rootNode || lhs.rootNode == rhs.rootNode From 031b7620534b7861ae1a01d0a4250c12e0027318 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 18:57:10 +0200 Subject: [PATCH 172/176] [Capsule] Fix test case argument label Necessary due to removal of unchecked initializer variants. --- .../PersistentCollections Tests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift index 38b16c4df..d288361db 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift @@ -73,7 +73,7 @@ class PersistentDictionaryTests: CollectionTestCase { (key: "two", value: 2), (key: "three", value: 3) ] - let d = PersistentDictionary(uncheckedUniqueKeys: ["zero", "one", "two", "three"], values: [0, 1, 2, 3]) + let d = PersistentDictionary(uniqueKeys: ["zero", "one", "two", "three"], values: [0, 1, 2, 3]) expectEqualElements(d.sorted(by: <), items.sorted(by: <)) } From e2f53441d6eab7497ce9aea32d523613d4d901a9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 20:39:38 +0200 Subject: [PATCH 173/176] [Capsule] Add `subscript(position:)` benchmarks --- Benchmarks/Benchmarks/DictionaryBenchmarks.swift | 13 +++++++++++++ .../Benchmarks/OrderedDictionaryBenchmarks.swift | 13 +++++++++++++ .../Benchmarks/PersistentDictionaryBenchmarks.swift | 13 +++++++++++++ Benchmarks/Libraries/PersistentCollections.json | 9 +++++++++ .../PersistentDictionary+Collection.swift | 1 - 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift index 5256c5059..aa01ed4d1 100644 --- a/Benchmarks/Benchmarks/DictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/DictionaryBenchmarks.swift @@ -230,6 +230,19 @@ extension Benchmark { } } + self.add( + title: "Dictionary subscript(position:)", + input: ([Int], [Int]).self + ) { input, lookups in + let d = Dictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let indices = lookups.map { d.index(forKey: $0)! } + return { timer in + for i in indices { + blackHole(d[i]) + } + } + } + self.add( title: "Dictionary defaulted subscript, successful lookups", input: ([Int], [Int]).self diff --git a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift index 5aab23081..8ac1e5bd9 100644 --- a/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/OrderedDictionaryBenchmarks.swift @@ -85,6 +85,19 @@ extension Benchmark { } } + self.add( + title: "OrderedDictionary subscript(position:)", + input: ([Int], [Int]).self + ) { input, lookups in + let d = OrderedDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let indices = lookups.map { d.index(forKey: $0)! } + return { timer in + for i in indices { + blackHole(d.elements[indices[i]]) // uses `elements` random-access collection view + } + } + } + self.add( title: "OrderedDictionary subscript, successful lookups", input: ([Int], [Int]).self diff --git a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift index d3d75cba0..4c1a8135a 100644 --- a/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift +++ b/Benchmarks/Benchmarks/PersistentDictionaryBenchmarks.swift @@ -60,6 +60,19 @@ extension Benchmark { } } + self.add( + title: "PersistentDictionary subscript(position:)", + input: ([Int], [Int]).self + ) { input, lookups in + let d = PersistentDictionary(uniqueKeysWithValues: input.lazy.map { ($0, 2 * $0) }) + let indices = lookups.map { d.index(forKey: $0)! } + return { timer in + for i in indices { + blackHole(d[i]) + } + } + } + self.add( title: "PersistentDictionary subscript, successful lookups", input: ([Int], [Int]).self diff --git a/Benchmarks/Libraries/PersistentCollections.json b/Benchmarks/Libraries/PersistentCollections.json index ec1544442..9fe3eca0e 100644 --- a/Benchmarks/Libraries/PersistentCollections.json +++ b/Benchmarks/Libraries/PersistentCollections.json @@ -111,6 +111,15 @@ "OrderedDictionary subscript, remove existing", ] }, + { + "kind": "chart", + "title": "subscript(position:)", + "tasks": [ + "PersistentDictionary subscript(position:)", + "Dictionary subscript(position:)", + "OrderedDictionary subscript(position:)", + ] + }, { "kind": "chart", "title": "index(forKey:), successful index(forKey:)", diff --git a/Sources/PersistentCollections/PersistentDictionary+Collection.swift b/Sources/PersistentCollections/PersistentDictionary+Collection.swift index 658ca4f59..d2aa7dbb4 100644 --- a/Sources/PersistentCollections/PersistentDictionary+Collection.swift +++ b/Sources/PersistentCollections/PersistentDictionary+Collection.swift @@ -34,7 +34,6 @@ extension PersistentDictionary: Collection { /// /// Accesses the key-value pair at the specified position. /// - // TODO: add benchmark for this method across all dictionary types public subscript(position: Self.Index) -> Self.Element { return rootNode.get(position: position, 0, position.value) } From f930801d371c9a7aca65bd2860874c49af4a07de Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 20:40:28 +0200 Subject: [PATCH 174/176] [Capsule] Expand `index(forKey:)` tests --- .../PersistentCollections Smoke Tests.swift | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift index 14223a734..995a88ef2 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Smoke Tests.swift @@ -495,7 +495,7 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map1, map2) } - func testIndexForKey() { + func test_indexForKey_hashCollision() { let map: PersistentDictionary = [ CollidableInt(11, 1): "a", CollidableInt(12, 1): "a", @@ -507,6 +507,29 @@ final class CapsuleSmokeTests: CollectionTestCase { expectEqual(map.index(forKey: CollidableInt(32769)), PersistentDictionaryIndex(value: 2)) expectNil(map.index(forKey: CollidableInt(13, 1))) } + + func test_indexForKey_exhaustIndices() { + var map: PersistentDictionary = [:] + + let range = 0 ..< 10_000 + + for value in range { + map[CollidableInt(value)] = value + } + + var expectedPositions = Set(range) + + for expectedValue in range { + let position = map.index(forKey: CollidableInt(expectedValue))! + let actualValue = map[position].value + + expectEqual(expectedValue, actualValue) + + expectedPositions.remove(position.value) + } + + expectTrue(expectedPositions.isEmpty) + } } final class BitmapSmokeTests: CollectionTestCase { From fbb8aeda0848caa6070d085b26d5789553801dcc Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 20:44:49 +0200 Subject: [PATCH 175/176] [Capsule] Remove non-applicable tests --- .../PersistentCollections Tests.swift | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift index d288361db..04c7030a4 100644 --- a/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift +++ b/Tests/PersistentCollectionsTests/PersistentCollections Tests.swift @@ -19,20 +19,6 @@ class PersistentDictionaryTests: CollectionTestCase { expectEqual(d.count, 0) } -// func test_init_minimumCapacity() { -// let d = PersistentDictionary(minimumCapacity: 1000) -// expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) -// expectGreaterThanOrEqual(d.values.elements.capacity, 1000) -// expectEqual(d.keys.__unstable.reservedScale, 0) -// } - -// func test_init_minimumCapacity_persistent() { -// let d = PersistentDictionary(minimumCapacity: 1000, persistent: true) -// expectGreaterThanOrEqual(d.keys.__unstable.capacity, 1000) -// expectGreaterThanOrEqual(d.values.elements.capacity, 1000) -// expectNotEqual(d.keys.__unstable.reservedScale, 0) -// } - // func test_uniqueKeysWithValues_Dictionary() { // let items: Dictionary = [ // "zero": 0, @@ -148,18 +134,6 @@ class PersistentDictionaryTests: CollectionTestCase { // ] // let d = PersistentDictionary(uniqueKeysWithValues: items) // expectEqualElements(d, items) -// } - -// func test_uncheckedUniqueKeys_values() { -// let d = PersistentDictionary( -// uncheckedUniqueKeys: ["zero", "one", "two", "three"], -// values: [0, 1, 2, 3]) -// expectEqualElements(d, [ -// (key: "zero", value: 0), -// (key: "one", value: 1), -// (key: "two", value: 2), -// (key: "three", value: 3), -// ]) // } func test_ExpressibleByDictionaryLiteral() { @@ -1187,22 +1161,6 @@ class PersistentDictionaryTests: CollectionTestCase { // } // } -// func test_removeAll_keepCapacity() { -// withEvery("count", in: 0 ..< 30) { count in -// withEvery("isShared", in: [false, true]) { isShared in -// withLifetimeTracking { tracker in -// var (d, _, _) = tracker.persistentDictionary(keys: 0 ..< count) -// let origScale = d.keys.__unstable.scale -// withHiddenCopies(if: isShared, of: &d) { d in -// d.removeAll(keepingCapacity: true) -// expectEqual(d.keys.__unstable.scale, origScale) -// expectEqualElements(d, []) -// } -// } -// } -// } -// } - // func test_remove_at() { // withEvery("count", in: 0 ..< 30) { count in // withEvery("offset", in: 0 ..< count) { offset in From ccd11e55dd4991055b260bb39d7e20ceb09046e9 Mon Sep 17 00:00:00 2001 From: Michael Steindorfer Date: Tue, 6 Sep 2022 21:20:41 +0200 Subject: [PATCH 176/176] [Capsule] Clean-up annotations --- .../PersistentCollections/_BitmapIndexedDictionaryNode.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift index d6f7b10ae..1cb49be6a 100644 --- a/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift +++ b/Sources/PersistentCollections/_BitmapIndexedDictionaryNode.swift @@ -174,7 +174,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - @available(*, deprecated) convenience init(trieMap: Bitmap, firstNode: BitmapIndexedDictionaryNode) { self.init() @@ -186,7 +185,6 @@ final class BitmapIndexedDictionaryNode: DictionaryNode where Key: H assert(self.invariant) } - @available(*, deprecated) convenience init(dataMap: Bitmap, trieMap: Bitmap, firstKey: Key, firstValue: Value, firstNode: BitmapIndexedDictionaryNode) { self.init()