Skip to content

Commit a274a0c

Browse files
committed
[stdlib] Implement top-level hashing for stdlib types
1 parent 3a162d2 commit a274a0c

8 files changed

+154
-20
lines changed

stdlib/public/core/Arrays.swift.gyb

+11
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,17 @@ extension ${Self}: Hashable where Element: Hashable {
22702270
hasher.combine(element)
22712271
}
22722272
}
2273+
2274+
@inlinable // FIXME(sil-serialize-all)
2275+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
2276+
var hasher = Hasher(_seed: seed)
2277+
// Note that we don't need to use delimiters/discriminators for top-level
2278+
// hashing.
2279+
for element in self {
2280+
hasher.combine(element)
2281+
}
2282+
return hasher._finalize()
2283+
}
22732284
}
22742285

22752286
extension ${Self} {

stdlib/public/core/Bool.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,21 @@ extension Bool : CustomStringConvertible {
144144
public // COMPILER_INTRINSIC
145145
func _getBool(_ v: Builtin.Int1) -> Bool { return Bool(v) }
146146

147-
extension Bool : Equatable, Hashable {
148-
@inlinable // FIXME(sil-serialize-all)
149-
public func hash(into hasher: inout Hasher) {
150-
hasher.combine((self ? 1 : 0) as UInt8)
151-
}
152-
147+
extension Bool: Equatable {
153148
@inlinable // FIXME(sil-serialize-all)
154149
@_transparent
155150
public static func == (lhs: Bool, rhs: Bool) -> Bool {
156151
return Bool(Builtin.cmp_eq_Int1(lhs._value, rhs._value))
157152
}
158153
}
159154

155+
extension Bool: Hashable {
156+
@inlinable // FIXME(sil-serialize-all)
157+
public func hash(into hasher: inout Hasher) {
158+
hasher.combine((self ? 1 : 0) as UInt8)
159+
}
160+
}
161+
160162
extension Bool : LosslessStringConvertible {
161163
/// Creates a new Boolean value from the given string.
162164
///

stdlib/public/core/Dictionary.swift

+16-6
Original file line numberDiff line numberDiff line change
@@ -1453,13 +1453,28 @@ extension Dictionary: Hashable where Value: Hashable {
14531453
public func hash(into hasher: inout Hasher) {
14541454
var commutativeHash = 0
14551455
for (k, v) in self {
1456-
var elementHasher = Hasher()
1456+
// Note that we use a copy of the outer hasher here. This makes collisions
1457+
// dependent on its state, eliminating static collision patterns.
1458+
var elementHasher = hasher
14571459
elementHasher.combine(k)
14581460
elementHasher.combine(v)
14591461
commutativeHash ^= elementHasher._finalize()
14601462
}
14611463
hasher.combine(commutativeHash)
14621464
}
1465+
1466+
@inlinable // FIXME(sil-serialize-all)
1467+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
1468+
var commutativeHash = 0
1469+
let hasher = Hasher(_seed: seed)
1470+
for (k, v) in self {
1471+
var elementHasher = hasher
1472+
elementHasher.combine(k)
1473+
elementHasher.combine(v)
1474+
commutativeHash ^= elementHasher._finalize()
1475+
}
1476+
return commutativeHash
1477+
}
14631478
}
14641479

14651480
extension Dictionary: CustomStringConvertible, CustomDebugStringConvertible {
@@ -4325,11 +4340,6 @@ extension Dictionary.Index {
43254340
}
43264341
}
43274342

4328-
@inlinable // FIXME(sil-serialize-all)
4329-
public var hashValue: Int {
4330-
return _hashValue(for: self)
4331-
}
4332-
43334343
@inlinable // FIXME(sil-serialize-all)
43344344
public func hash(into hasher: inout Hasher) {
43354345
#if _runtime(_ObjC)

stdlib/public/core/FloatingPointTypes.swift.gyb

+16
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,22 @@ extension ${Self} : Hashable {
15371537
%end
15381538
}
15391539

1540+
@inlinable // FIXME(sil-serialize-all)
1541+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
1542+
// To satisfy the axiom that equality implies hash equality, we need to
1543+
// finesse the hash value of -0.0 to match +0.0.
1544+
let v = isZero ? 0 : self
1545+
%if bits == 80:
1546+
var hasher = Hasher(_seed: seed)
1547+
hasher.combine(v._representation.signAndExponent)
1548+
hasher.combine(v.significandBitPattern)
1549+
return hasher._finalize()
1550+
%elif bits == 64:
1551+
return Hasher._hash(seed: seed, v.bitPattern)
1552+
%elif bits == 32:
1553+
return Hasher._hash(seed: seed, bytes: UInt64(v.bitPattern), count: 4)
1554+
%end
1555+
}
15401556
}
15411557

15421558
extension ${Self} {

stdlib/public/core/Integers.swift.gyb

+16
Original file line numberDiff line numberDiff line change
@@ -3713,6 +3713,22 @@ extension ${Self} : Hashable {
37133713
fatalError("Unsupported integer width")
37143714
% end
37153715
}
3716+
3717+
@inlinable // FIXME(sil-serialize-all)
3718+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
3719+
// FIXME(hasher): Note that the AnyHashable concern applies here too,
3720+
// because hashValue uses top-level hashing.
3721+
% if bits <= word_bits:
3722+
return Hasher._hash(seed: seed, _lowWord)
3723+
% elif bits == 2 * word_bits:
3724+
if let word = ${"" if signed else "U"}Int(exactly: self) {
3725+
return Hasher._hash(seed: seed, word._lowWord)
3726+
}
3727+
return Hasher._hash(seed: seed, UInt64(_value))
3728+
% else:
3729+
fatalError("Unsupported integer width")
3730+
% end
3731+
}
37163732
}
37173733

37183734

stdlib/public/core/Set.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,16 @@ extension Set: Hashable {
487487
@inlinable // FIXME(sil-serialize-all)
488488
public func hash(into hasher: inout Hasher) {
489489
// FIXME(ABI)#177: <rdar://problem/18915294> Cache Set<T> hashValue
490+
hasher.combine(_rawHashValue(seed: hasher._generateSeed()))
491+
}
492+
493+
@inlinable // FIXME(sil-serialize-all)
494+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
490495
var hash = 0
491496
for member in self {
492-
hash ^= _hashValue(for: member)
497+
hash ^= member._rawHashValue(seed: seed)
493498
}
494-
hasher.combine(hash)
499+
return hash
495500
}
496501
}
497502

@@ -3647,11 +3652,6 @@ extension Set.Index {
36473652
}
36483653
}
36493654

3650-
@inlinable // FIXME(sil-serialize-all)
3651-
public var hashValue: Int {
3652-
return _hashValue(for: self)
3653-
}
3654-
36553655
@inlinable // FIXME(sil-serialize-all)
36563656
public func hash(into hasher: inout Hasher) {
36573657
#if _runtime(_ObjC)

stdlib/public/core/StringHashable.swift

+74
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,36 @@ extension _UnmanagedString where CodeUnit == UInt8 {
4949
self.hashASCII(into: &hasher._core)
5050
hasher._core.combine(0xFF as UInt8) // terminator
5151
}
52+
53+
internal func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
54+
return Hasher._hash(seed: seed, bytes: rawBuffer)
55+
}
5256
}
5357

5458
extension _UnmanagedString where CodeUnit == UInt16 {
5559
internal func hash(into hasher: inout Hasher) {
5660
self.hashUTF16(into: &hasher._core)
5761
hasher._core.combine(0xFF as UInt8) // terminator
5862
}
63+
64+
internal func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
65+
var core = Hasher.Core(seed: seed)
66+
self.hashUTF16(into: &core)
67+
return Int(truncatingIfNeeded: core.finalize())
68+
}
5969
}
6070

6171
extension _UnmanagedOpaqueString {
6272
internal func hash(into hasher: inout Hasher) {
6373
self.hashUTF16(into: &hasher._core)
6474
hasher._core.combine(0xFF as UInt8) // terminator
6575
}
76+
77+
internal func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
78+
var core = Hasher.Core(seed: seed)
79+
self.hashUTF16(into: &core)
80+
return Int(truncatingIfNeeded: core.finalize())
81+
}
6682
}
6783

6884
extension _SmallUTF8String {
@@ -75,6 +91,17 @@ extension _SmallUTF8String {
7591
return
7692
}
7793
self.withUnmanagedUTF16 { $0.hash(into: &hasher) }
94+
#endif // 64-bit
95+
}
96+
97+
internal func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
98+
#if arch(i386) || arch(arm)
99+
unsupportedOn32bit()
100+
#else
101+
if isASCII {
102+
return self.withUnmanagedASCII { $0._rawHashValue(seed: seed) }
103+
}
104+
return self.withUnmanagedUTF16 { $0._rawHashValue(seed: seed) }
78105
#endif // 64-bit
79106
}
80107
}
@@ -119,18 +146,65 @@ extension _StringGuts {
119146
}
120147
_unmanagedUTF16View[range].hash(into: &hasher)
121148
}
149+
150+
@effects(releasenone) // FIXME: Is this valid in the opaque case?
151+
@usableFromInline
152+
internal func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
153+
if _isSmall {
154+
return _smallUTF8String._rawHashValue(seed: seed)
155+
}
156+
157+
defer { _fixLifetime(self) }
158+
if _slowPath(_isOpaque) {
159+
return _asOpaque()._rawHashValue(seed: seed)
160+
}
161+
if isASCII {
162+
return _unmanagedASCIIView._rawHashValue(seed: seed)
163+
}
164+
return _unmanagedUTF16View._rawHashValue(seed: seed)
165+
}
166+
167+
@effects(releasenone) // FIXME: Is this valid in the opaque case?
168+
@usableFromInline
169+
internal func _rawHashValue(
170+
_ range: Range<Int>,
171+
seed: (UInt64, UInt64)
172+
) -> Int {
173+
if _isSmall {
174+
return _smallUTF8String[range]._rawHashValue(seed: seed)
175+
}
176+
177+
defer { _fixLifetime(self) }
178+
if _slowPath(_isOpaque) {
179+
return _asOpaque()[range]._rawHashValue(seed: seed)
180+
}
181+
if isASCII {
182+
return _unmanagedASCIIView[range]._rawHashValue(seed: seed)
183+
}
184+
return _unmanagedUTF16View[range]._rawHashValue(seed: seed)
185+
}
122186
}
123187

124188
extension String : Hashable {
125189
@inlinable
126190
public func hash(into hasher: inout Hasher) {
127191
_guts.hash(into: &hasher)
128192
}
193+
194+
@inlinable
195+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
196+
return _guts._rawHashValue(seed: seed)
197+
}
129198
}
130199

131200
extension StringProtocol {
132201
@inlinable
133202
public func hash(into hasher: inout Hasher) {
134203
_wholeString._guts.hash(_encodedOffsetRange, into: &hasher)
135204
}
205+
206+
@inlinable
207+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
208+
return _wholeString._guts._rawHashValue(_encodedOffsetRange, seed: seed)
209+
}
136210
}

stdlib/public/core/UnsafePointer.swift.gyb

+6-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,12 @@ extension ${Self}: Comparable {
899899
extension ${Self}: Hashable {
900900
@inlinable // FIXME(sil-serialize-all)
901901
public func hash(into hasher: inout Hasher) {
902-
hasher.combine(Int(bitPattern: self))
902+
hasher.combine(UInt(bitPattern: self))
903+
}
904+
905+
@inlinable // FIXME(sil-serialize-all)
906+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
907+
return Hasher._hash(seed: seed, UInt(bitPattern: self))
903908
}
904909
}
905910

0 commit comments

Comments
 (0)