Skip to content

Commit ddbfe2b

Browse files
authored
Merge pull request #4887 from eeckstein/dict-set
stdlib: some small improvements for Dictionary and Set
2 parents 870b785 + 9d631eb commit ddbfe2b

File tree

4 files changed

+47
-86
lines changed

4 files changed

+47
-86
lines changed

stdlib/public/core/HashedCollections.swift.gyb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2523,7 +2523,7 @@ final internal class _Native${Self}StorageImpl<${TypeParameters}> {
25232523

25242524
@_versioned
25252525
internal var _capacity: Int {
2526-
return _body.capacity
2526+
return _assumeNonNegative(_body.capacity)
25272527
}
25282528

25292529
@_versioned
@@ -2532,7 +2532,7 @@ final internal class _Native${Self}StorageImpl<${TypeParameters}> {
25322532
_body.count = newValue
25332533
}
25342534
get {
2535-
return _body.count
2535+
return _assumeNonNegative(_body.count)
25362536
}
25372537
}
25382538

@@ -2703,7 +2703,7 @@ struct _Native${Self}Storage<${TypeParametersDecl}> :
27032703
@_versioned
27042704
@inline(__always)
27052705
internal func key(at i: Int) -> Key {
2706-
_precondition(i >= 0 && i < capacity)
2706+
_sanityCheck(i >= 0 && i < capacity)
27072707
_sanityCheck(isInitializedEntry(at: i))
27082708

27092709
let res = (keys + i).pointee
@@ -2713,7 +2713,7 @@ struct _Native${Self}Storage<${TypeParametersDecl}> :
27132713

27142714
@_versioned
27152715
internal func isInitializedEntry(at i: Int) -> Bool {
2716-
_precondition(i >= 0 && i < capacity)
2716+
_sanityCheck(i >= 0 && i < capacity)
27172717
return initializedEntries[i]
27182718
}
27192719

@@ -2747,7 +2747,7 @@ struct _Native${Self}Storage<${TypeParametersDecl}> :
27472747
}
27482748

27492749
internal func setKey(_ key: Key, at i: Int) {
2750-
_precondition(i >= 0 && i < capacity)
2750+
_sanityCheck(i >= 0 && i < capacity)
27512751
_sanityCheck(isInitializedEntry(at: i))
27522752

27532753
(keys + i).pointee = key
@@ -2804,8 +2804,9 @@ struct _Native${Self}Storage<${TypeParametersDecl}> :
28042804
}
28052805

28062806
@_versioned
2807+
@inline(__always) // For performance reasons.
28072808
internal func _bucket(_ k: Key) -> Int {
2808-
return _squeezeHashValue(k.hashValue, 0..<capacity)
2809+
return _squeezeHashValue(k.hashValue, capacity)
28092810
}
28102811

28112812
@_versioned
@@ -2933,6 +2934,7 @@ struct _Native${Self}Storage<${TypeParametersDecl}> :
29332934
}
29342935

29352936
internal func assertingGet(_ i: Index) -> SequenceElement {
2937+
_precondition(i.offset >= 0 && i.offset < capacity)
29362938
_precondition(
29372939
isInitializedEntry(at: i.offset),
29382940
"attempting to access ${Self} elements using an invalid Index")

stdlib/public/core/Hashing.swift

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -147,51 +147,31 @@ func _mixInt(_ value: Int) -> Int {
147147
#endif
148148
}
149149

150-
/// Given a hash value, returns an integer value within the given range that
151-
/// corresponds to a hash value.
150+
/// Given a hash value, returns an integer value in the range of
151+
/// 0..<`upperBound` that corresponds to a hash value.
152+
///
153+
/// The `upperBound` must be positive and a power of 2.
152154
///
153155
/// This function is superior to computing the remainder of `hashValue` by
154156
/// the range length. Some types have bad hash functions; sometimes simple
155157
/// patterns in data sets create patterns in hash values and applying the
156158
/// remainder operation just throws away even more information and invites
157-
/// even more hash collisions. This effect is especially bad if the length
158-
/// of the required range is a power of two -- applying the remainder
159-
/// operation just throws away high bits of the hash (which would not be
160-
/// a problem if the hash was known to be good). This function mixes the
161-
/// bits in the hash value to compensate for such cases.
159+
/// even more hash collisions. This effect is especially bad because the
160+
/// range is a power of two, which means to throws away high bits of the hash
161+
/// (which would not be a problem if the hash was known to be good). This
162+
/// function mixes the bits in the hash value to compensate for such cases.
162163
///
163164
/// Of course, this function is a compressing function, and applying it to a
164165
/// hash value does not change anything fundamentally: collisions are still
165166
/// possible, and it does not prevent malicious users from constructing data
166167
/// sets that will exhibit pathological collisions.
167168
public // @testable
168-
func _squeezeHashValue(_ hashValue: Int, _ resultRange: Range<Int>) -> Int {
169-
// Length of a Range<Int> does not fit into an Int, but fits into an UInt.
170-
// An efficient way to compute the length is to rely on two's complement
171-
// arithmetic.
172-
let resultCardinality =
173-
UInt(bitPattern: resultRange.upperBound &- resultRange.lowerBound)
174-
175-
// Calculate the result as `UInt` to handle the case when
176-
// `resultCardinality >= Int.max`.
177-
let unsignedResult =
178-
_squeezeHashValue(hashValue, UInt(0)..<resultCardinality)
179-
180-
// We perform the unchecked arithmetic on `UInt` (instead of doing
181-
// straightforward computations on `Int`) in order to handle the following
182-
// tricky case: `startIndex` is negative, and `resultCardinality >= Int.max`.
183-
// We cannot convert the latter to `Int`.
184-
return
185-
Int(bitPattern:
186-
UInt(bitPattern: resultRange.lowerBound) &+ unsignedResult)
187-
}
169+
func _squeezeHashValue(_ hashValue: Int, _ upperBound: Int) -> Int {
170+
_sanityCheck(_isPowerOf2(upperBound))
171+
let mixedHashValue = _mixInt(hashValue)
188172

189-
public // @testable
190-
func _squeezeHashValue(_ hashValue: Int, _ resultRange: Range<UInt>) -> UInt {
191-
let mixedHashValue = UInt(bitPattern: _mixInt(hashValue))
192-
let resultCardinality: UInt = resultRange.upperBound - resultRange.lowerBound
193-
if _isPowerOf2(resultCardinality) {
194-
return mixedHashValue & (resultCardinality - 1)
195-
}
196-
return resultRange.lowerBound + (mixedHashValue % resultCardinality)
173+
// As `upperBound` is a power of two we can do a bitwise-and to calculate
174+
// mixedHashValue % upperBound.
175+
return mixedHashValue & (upperBound &- 1)
197176
}
177+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %target-swift-frontend -O -emit-ir -parse-as-library -primary-file %s | %FileCheck %s
2+
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
3+
4+
5+
// A dictionary lookup should not contain any trap. Nothing can go wrong
6+
// from the user's perspective.
7+
8+
// CHECK-NOT: llvm.trap
9+
10+
public func testit(_ s: [Int : Int]) -> Int? {
11+
return s[27]
12+
}
13+

validation-test/stdlib/Hashing.swift

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -72,54 +72,20 @@ HashingTestSuite.test("_mixInt/GoldenValues") {
7272

7373
HashingTestSuite.test("_squeezeHashValue/Int") {
7474
// Check that the function can return values that cover the whole range.
75-
func checkRange(_ r: Range<Int>) {
76-
var results = [Int : Void]()
77-
for _ in 0..<(14 * (r.upperBound - r.lowerBound)) {
75+
func checkRange(_ r: Int) {
76+
var results = Set<Int>()
77+
for _ in 0..<(14 * r) {
7878
let v = _squeezeHashValue(randInt(), r)
79-
expectTrue(r ~= v)
80-
if results[v] == nil {
81-
results[v] = Void()
82-
}
79+
expectTrue(v < r)
80+
results.insert(v)
8381
}
84-
expectEqual(r.upperBound - r.lowerBound, results.count)
82+
expectEqual(r, results.count)
8583
}
86-
checkRange(Int.min..<(Int.min+10))
87-
checkRange(0..<4)
88-
checkRange(0..<8)
89-
checkRange(-5..<5)
90-
checkRange((Int.max-10)..<(Int.max-1))
91-
92-
// Check that we can handle ranges that span more than `Int.max`.
93-
#if arch(i386) || arch(arm)
94-
expectEqual(-0x6e477d37, _squeezeHashValue(0, Int.min..<(Int.max - 1)))
95-
expectEqual(0x38a3ea26, _squeezeHashValue(2, Int.min..<(Int.max - 1)))
96-
#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x)
97-
expectEqual(0x32b24f688dc4164d, _squeezeHashValue(0, Int.min..<(Int.max - 1)))
98-
expectEqual(-0x6d1cc14f97aa822, _squeezeHashValue(1, Int.min..<(Int.max - 1)))
99-
#else
100-
fatalError("unimplemented")
101-
#endif
102-
}
103-
104-
HashingTestSuite.test("_squeezeHashValue/UInt") {
105-
// Check that the function can return values that cover the whole range.
106-
func checkRange(_ r: Range<UInt>) {
107-
var results = [UInt : Void]()
108-
let cardinality = r.upperBound - r.lowerBound
109-
for _ in 0..<(10*cardinality) {
110-
let v = _squeezeHashValue(randInt(), r)
111-
expectTrue(r ~= v)
112-
if results[v] == nil {
113-
results[v] = Void()
114-
}
115-
}
116-
expectEqual(Int(cardinality), results.count)
117-
}
118-
checkRange(0..<4)
119-
checkRange(0..<8)
120-
checkRange(0..<10)
121-
checkRange(10..<20)
122-
checkRange((UInt.max-10)..<(UInt.max-1))
84+
checkRange(1)
85+
checkRange(2)
86+
checkRange(4)
87+
checkRange(8)
88+
checkRange(16)
12389
}
12490

12591
HashingTestSuite.test("String/hashValue/topBitsSet") {

0 commit comments

Comments
 (0)