Skip to content

Commit f0e04f7

Browse files
lorenteyCatfish-Man
authored andcommitted
[stdlib] Process elements from back to front
1 parent 039891c commit f0e04f7

File tree

3 files changed

+43
-62
lines changed

3 files changed

+43
-62
lines changed

stdlib/public/core/DictionaryBuilder.swift

+42-61
Original file line numberDiff line numberDiff line change
@@ -95,95 +95,76 @@ extension _NativeDictionary {
9595
) -> Void
9696
) {
9797
self.init(capacity: capacity)
98-
var count = 0
98+
var initializedCount = 0
9999
initializer(
100100
UnsafeMutableBufferPointer(start: _keys, count: capacity),
101101
UnsafeMutableBufferPointer(start: _values, count: capacity),
102-
&count)
102+
&initializedCount)
103103
_precondition(count >= 0 && count <= capacity)
104-
_storage._count = count
104+
_storage._count = initializedCount
105105

106106
// Hash initialized elements and move each of them into their correct
107107
// buckets.
108108
//
109-
// - The storage is separated into three regions: "before bucket", "bucket
110-
// up to count", and "count and after".
109+
// - We have some number of unprocessed elements at the start of the
110+
// key/value buffers -- buckets up to and including `bucket`. Everything
111+
// this region is either unprocessed or in use. There are no uninitialized
112+
// entries in it.
111113
//
112-
// - Everything in the middle region, "bucket up to count" is either
113-
// unprocessed or in use. There are no uninitialized entries in it.
114-
//
115-
// - Everything in "before bucket" and "count and after" is either
116-
// uninitialized or in use. These regions work like regular dictionary
117-
// storage.
114+
// - Everything after `bucket` is either uninitialized or in use. This
115+
// region works exactly like regular dictionary storage.
118116
//
119117
// - "in use" is tracked by the bitmap in `hashTable`, the same way it would
120118
// be for a working Dictionary.
121119
//
122120
// Each iteration of the loop below processes an unprocessed element, and/or
123-
// reduces the size of the "bucket up to count" region, while ensuring the
124-
// above invariants.
125-
var bucket = _HashTable.Bucket(offset: 0)
126-
while bucket.offset < count {
121+
// reduces the size of the unprocessed region, while ensuring the above
122+
// invariants.
123+
var bucket = _HashTable.Bucket(offset: initializedCount - 1)
124+
while bucket.offset >= 0 {
127125
if hashTable._isOccupied(bucket) {
128126
// We've moved an element here in a previous iteration.
129-
bucket.offset += 1
127+
bucket.offset -= 1
130128
continue
131129
}
132130
// Find the target bucket for this entry and mark it as in use.
133-
let target = _preloadEntry(
134-
in: bucket,
135-
allowingDuplicates: allowingDuplicates)
131+
let target: Bucket
132+
if _isDebugAssertConfiguration() || allowingDuplicates {
133+
let (b, found) = find(_keys[bucket.offset])
134+
if found {
135+
_internalInvariant(b != bucket)
136+
_precondition(allowingDuplicates, "Duplicate keys found")
137+
// Discard existing entry, then move the current entry in place of it.
138+
uncheckedDestroy(at: b)
139+
_storage._count -= 1
140+
moveEntry(from: bucket, to: b)
141+
bucket.offset -= 1
142+
continue
143+
}
144+
hashTable.insert(b)
145+
target = b
146+
} else {
147+
let hashValue = self.hashValue(for: _keys[bucket.offset])
148+
target = hashTable.insertNew(hashValue: hashValue)
149+
}
136150

137-
if target < bucket || target.offset >= count {
138-
// The target is in either the "before bucket" or the "count and after"
139-
// region. We can simply move the entry, leaving behind an
140-
// uninitialized bucket.
151+
if target > bucket {
152+
// The target is outside the unprocessed region. We can simply move the
153+
// entry, leaving behind an uninitialized bucket.
141154
moveEntry(from: bucket, to: target)
142-
// Restore invariants by moving the region boundary such that the bucket
143-
// becomes part of the "before bucket" region.
144-
bucket.offset += 1
155+
// Restore invariants by lowering the region boundary.
156+
bucket.offset -= 1
145157
} else if target == bucket {
146158
// Already in place.
147-
bucket.offset += 1
159+
bucket.offset -= 1
148160
} else {
149-
// The target bucket is also in the middle region. Swap the current
161+
// The target bucket is also in the unprocessed region. Swap the current
150162
// item into place, then try again with the swapped-in value, so that we
151163
// don't lose it.
152164
swapEntry(target, with: bucket)
153165
}
154166
}
155-
// When the middle region disappears, we're left with a valid Dictionary.
156-
}
157-
158-
/// Find and return the correct bucket for the entry stored in `bucket`, while
159-
/// also preparing to place it there. This is for use in the bulk loading
160-
/// initializer, `init(_unsafeUninitializedCapacity:, allowingDuplicates:,
161-
/// initializingWith:)`.
162-
///
163-
/// When this function returns, it is guaranteed that
164-
/// - the returned bucket is marked occupied in the hash table's bitmap,
165-
/// - any duplicate that was already in the returned bucket is destroyed, and
166-
/// - the original item is still in its original bucket.
167-
@inlinable
168-
@inline(__always)
169-
internal func _preloadEntry(
170-
in bucket: Bucket,
171-
allowingDuplicates: Bool
172-
) -> Bucket {
173-
if _isDebugAssertConfiguration() || allowingDuplicates {
174-
let (b, found) = find(_keys[bucket.offset])
175-
if found {
176-
_precondition(allowingDuplicates, "Duplicate keys found")
177-
// Discard existing key & value in preparation of overwriting it.
178-
uncheckedDestroy(at: b)
179-
_storage._count -= 1
180-
} else {
181-
hashTable.insert(b)
182-
}
183-
return b
184-
}
185-
186-
let hashValue = self.hashValue(for: _keys[bucket.offset])
187-
return hashTable.insertNew(hashValue: hashValue)
167+
// When there are no more unprocessed entries, we're left with a valid
168+
// Dictionary.
188169
}
189170
}

stdlib/public/core/HashTable.swift

-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ extension _HashTable {
127127
@inlinable
128128
@inline(__always)
129129
internal init(offset: Int) {
130-
_internalInvariant(offset >= 0)
131130
self.offset = offset
132131
}
133132

validation-test/stdlib/Dictionary.swift

+1
Original file line numberDiff line numberDiff line change
@@ -5739,6 +5739,7 @@ DictionaryTestSuite.test("BulkLoadingInitializer.Nonunique") {
57395739
},
57405740
uniquingKeysWith: { a, b in a })
57415741

5742+
expectEqual(d1.count, d2.count)
57425743
for i in 0 ..< c / 2 {
57435744
expectEqual(TestEquatableValueTy(i), d1[TestKeyTy(i)])
57445745
}

0 commit comments

Comments
 (0)