From ea1f2735fcebe78b228d418447130809faaf6543 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 18 Oct 2017 17:48:43 +0100 Subject: [PATCH 01/10] [stdlib] Add alternative, streaming interface for hashing - Add the method _hash(into:) as a Hashable requirement. It takes a closure into which the function can feed integer values representing bits to be hashed. - Implement _hash(into:) in terms of hashValue in an extension, for compatibility. - Add struct _QuickHasher, for emulating Swift 4 hashes in the new interface. - Add _defaultHashValue(for:), implementing hashValue in terms of _hash(into:) and _QuickHasher. --- stdlib/public/core/Hashable.swift | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 98fd5ffb70390..8fb9948364cd0 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -108,6 +108,23 @@ public protocol Hashable : Equatable { /// Hash values are not guaranteed to be equal across different executions of /// your program. Do not save hash values to use during a future execution. var hashValue: Int { get } + + func _hash(into hasher: (Int) -> Void) +} + +@_versioned +@_inlineable +@inline(__always) +internal func _defaultHashValue(for value: T) -> Int { + var hasher = _DefaultHasher(_inlineable: ()) + value._hash(into: { hasher.append($0) }) + return hasher._finalize_alwaysInline() +} + +extension Hashable { + public func _hash(into hasher: (Int) -> Void) { + hasher(self.hashValue) + } } // Called by the SwiftValue implementation. @@ -127,3 +144,50 @@ internal func Hashable_hashValue_indirect( return value.pointee.hashValue } +// FIXME: This is purely for benchmarking; to be removed. +@_fixed_layout +public struct _QuickHasher { + @_versioned + internal var _hash: Int + + @inline(never) + public init() { + _hash = 0 + } + + @_inlineable + @_versioned + internal init(_inlineable: Void) { + _hash = 0 + } + + @inline(never) + public mutating func append(_ value: Int) { + _append_alwaysInline(value) + } + + @_inlineable + @_versioned + @inline(__always) + internal mutating func _append_alwaysInline(_ value: Int) { + if _hash == 0 { + _hash = value + return + } + _hash = _combineHashValues(_hash, value) + } + + @inline(never) + public mutating func finalize() -> Int { + return _finalize_alwaysInline() + } + + @_inlineable // FIXME(sil-serialize-all) + @_versioned + @inline(__always) + internal mutating func _finalize_alwaysInline() -> Int { + return _mixInt(_hash) + } +} + +public typealias _DefaultHasher = _QuickHasher From 6a0e200e3170c6181f316b575877326752c7c4a0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 18 Oct 2017 17:48:43 +0100 Subject: [PATCH 02/10] [stdlib] Simplify SipHash13 and SipHash24 implementations We don't need to stream bytes, just words. --- stdlib/public/core/SipHash.swift.gyb | 215 +++++------------------- stdlib/public/core/StringHashable.swift | 12 +- 2 files changed, 52 insertions(+), 175 deletions(-) diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 558cca3bef75b..b16062aa1a636 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -29,52 +29,6 @@ internal enum _SipHashDetail { return (x &<< UInt64(amount)) | (x &>> UInt64(64 - amount)) } - @_inlineable // FIXME(sil-serialize-all) - @_versioned - @inline(__always) - internal static func _loadUnalignedUInt64LE( - from p: UnsafeRawPointer - ) -> UInt64 { - // FIXME(integers): split into multiple expressions to speed up the - // typechecking - var result = - UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) - result |= - (UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< (24 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< (32 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< (40 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< (48 as UInt64)) - result |= - (UInt64(p.load(fromByteOffset: 7, as: UInt8.self)) &<< (56 as UInt64)) - return result - } - - @_inlineable // FIXME(sil-serialize-all) - @_versioned - @inline(__always) - internal static func _loadPartialUnalignedUInt64LE( - from p: UnsafeRawPointer, - byteCount: Int - ) -> UInt64 { - _sanityCheck((0..<8).contains(byteCount)) - var result: UInt64 = 0 - if byteCount >= 1 { result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) } - if byteCount >= 2 { result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt64) } - if byteCount >= 3 { result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt64) } - if byteCount >= 4 { result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) &<< (24 as UInt64) } - if byteCount >= 5 { result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) &<< (32 as UInt64) } - if byteCount >= 6 { result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) &<< (40 as UInt64) } - if byteCount >= 7 { result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) &<< (48 as UInt64) } - return result - } - @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) @@ -102,7 +56,7 @@ internal enum _SipHashDetail { } % for (c_rounds, d_rounds) in [(2, 4), (1, 3)]: -% Self = '_SipHash{}{}Context'.format(c_rounds, d_rounds) +% Self = '_SipHash{}{}'.format(c_rounds, d_rounds) @_fixed_layout // FIXME(sil-serialize-all) public // @testable @@ -123,15 +77,7 @@ struct ${Self} { @_versioned internal var hashedByteCount: UInt64 = 0 - @_versioned - internal var dataTail: UInt64 = 0 - - @_versioned - internal var dataTailByteCount: Int = 0 - - @_versioned - internal var finalizedHash: UInt64? - +// @inline(never) @_inlineable // FIXME(sil-serialize-all) public init(key: (UInt64, UInt64)) { v3 ^= key.1 @@ -140,72 +86,38 @@ struct ${Self} { v0 ^= key.0 } - // FIXME(ABI)#62 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func append(_ data: UnsafeRawPointer, byteCount: Int) { - _append_alwaysInline(data, byteCount: byteCount) + @inline(never) + public init() { + self.init(key: _Hashing.secretKey) } - // FIXME(ABI)#63 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) + @_inlineable @_versioned - @inline(__always) - internal mutating func _append_alwaysInline( - _ data: UnsafeRawPointer, - byteCount: Int - ) { - precondition(finalizedHash == nil) - _sanityCheck((0..<8).contains(dataTailByteCount)) - - let dataEnd = data + byteCount - - var data = data - var byteCount = byteCount - if dataTailByteCount != 0 { - let restByteCount = min( - MemoryLayout.size - dataTailByteCount, - byteCount) - let rest = _SipHashDetail._loadPartialUnalignedUInt64LE( - from: data, - byteCount: restByteCount) - dataTail |= rest &<< UInt64(dataTailByteCount * 8) - dataTailByteCount += restByteCount - data += restByteCount - byteCount -= restByteCount - } + internal init(_inlineable: Void) { + self.init(key: _Hashing.secretKey) + } - if dataTailByteCount == MemoryLayout.size { - _appendDirectly(dataTail) - dataTail = 0 - dataTailByteCount = 0 - } else if dataTailByteCount != 0 { - _sanityCheck(data == dataEnd) - return - } + @inline(never) + public mutating func append(_ m: Int) { + append_alwaysInline(m) + } - let endOfWords = - data + byteCount - (byteCount % MemoryLayout.size) - while data != endOfWords { - _appendDirectly(_SipHashDetail._loadUnalignedUInt64LE(from: data)) - data += 8 - // No need to update `byteCount`, it is not used beyond this point. - } + @inline(never) + public mutating func append(_ m: UInt64) { + append_alwaysInline(m) + } - if data != dataEnd { - dataTailByteCount = dataEnd - data - dataTail = _SipHashDetail._loadPartialUnalignedUInt64LE( - from: data, - byteCount: dataTailByteCount) - } + @_inlineable + @_versioned + @inline(__always) + internal mutating func append_alwaysInline(_ m: Int) { + append_alwaysInline(UInt64(truncatingIfNeeded: UInt(bitPattern: m))) } - /// This function mixes in the given word directly into the state, - /// ignoring `dataTail`. - @_inlineable // FIXME(sil-serialize-all) + @_inlineable @_versioned @inline(__always) - internal mutating func _appendDirectly(_ m: UInt64) { + internal mutating func append_alwaysInline(_ m: UInt64) { v3 ^= m for _ in 0..<${c_rounds} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) @@ -214,33 +126,38 @@ struct ${Self} { hashedByteCount += 8 } -% for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: - @_inlineable // FIXME(sil-serialize-all) + @inline(never) public // @testable - mutating func append(_ data: ${data_type}) { - var data = data - _append_alwaysInline(&data, byteCount: MemoryLayout.size(ofValue: data)) + mutating func finalize() -> Int { + return _finalize_alwaysInline() } -% end - @_inlineable // FIXME(sil-serialize-all) + @inline(never) + @_inlineable public // @testable - mutating func finalizeAndReturnHash() -> UInt64 { - return _finalizeAndReturnHash_alwaysInline() + mutating func finalize(tail: UInt64, tailByteCount: Int) -> UInt64 { + return _finalize_alwaysInline(tail: tail, tailByteCount: tailByteCount) } @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) - internal mutating func _finalizeAndReturnHash_alwaysInline() -> UInt64 { - if let finalizedHash = finalizedHash { - return finalizedHash - } + internal mutating func _finalize_alwaysInline() -> Int { + return Int( + truncatingIfNeeded: _finalize_alwaysInline(tail: 0, tailByteCount: 0)) + } - _sanityCheck((0..<8).contains(dataTailByteCount)) + @_inlineable // FIXME(sil-serialize-all) + @_versioned + @inline(__always) + internal mutating func _finalize_alwaysInline( + tail: UInt64, + tailByteCount: Int + ) -> UInt64 { + _sanityCheck((0..<8).contains(tailByteCount)) - hashedByteCount += UInt64(dataTailByteCount) - let b: UInt64 = (hashedByteCount << 56) | dataTail + hashedByteCount += UInt64(tailByteCount) + let b: UInt64 = (hashedByteCount << 56) | tail v3 ^= b for _ in 0..<${c_rounds} { @@ -254,47 +171,7 @@ struct ${Self} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) } - finalizedHash = v0 ^ v1 ^ v2 ^ v3 - return finalizedHash! - } - - @_inlineable // FIXME(sil-serialize-all) - @_versioned // FIXME(sil-serialize-all) - internal mutating func _finalizeAndReturnIntHash() -> Int { - let hash: UInt64 = finalizeAndReturnHash() -#if arch(i386) || arch(arm) - return Int(truncatingIfNeeded: hash) -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) - return Int(Int64(bitPattern: hash)) -#endif - } - - // FIXME(ABI)#64 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - public // @testable - static func hash( - data: UnsafeRawPointer, - dataByteCount: Int, - key: (UInt64, UInt64) - ) -> UInt64 { - return ${Self}._hash_alwaysInline( - data: data, - dataByteCount: dataByteCount, - key: key) - } - - // FIXME(ABI)#65 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - @inline(__always) - public // @testable - static func _hash_alwaysInline( - data: UnsafeRawPointer, - dataByteCount: Int, - key: (UInt64, UInt64) - ) -> UInt64 { - var context = ${Self}(key: key) - context._append_alwaysInline(data, byteCount: dataByteCount) - return context._finalizeAndReturnHash_alwaysInline() + return v0 ^ v1 ^ v2 ^ v3 } } % end diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 7607c9bba783a..0c8e7b7cd5133 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -40,17 +40,17 @@ extension Unicode { _ string: UnsafeBufferPointer ) -> Int { let collationTable = _swift_stdlib_unicode_getASCIICollationTable() - var hasher = _SipHash13Context(key: _Hashing.secretKey) + var hasher = _DefaultHasher() for c in string { _precondition(c <= 127) let element = collationTable[Int(c)] // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher.append(element) + hasher.append(Int(truncatingIfNeeded: element)) } } - return hasher._finalizeAndReturnIntHash() + return hasher.finalize() } // FIXME: cannot be marked @_versioned. See @@ -64,7 +64,7 @@ extension Unicode { UInt32(string.count)) defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) } - var hasher = _SipHash13Context(key: _Hashing.secretKey) + var hasher = _DefaultHasher() while true { var hitEnd = false let element = @@ -75,10 +75,10 @@ extension Unicode { // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher.append(element) + hasher.append(Int(truncatingIfNeeded: element)) } } - return hasher._finalizeAndReturnIntHash() + return hasher.finalize() } } From 1d29dd9145d54f2d08a6343b18c5c3e6bbb32ddf Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 18 Oct 2017 17:48:43 +0100 Subject: [PATCH 03/10] [stdlib] Implement _hash(into:) in standard Hashable types. --- stdlib/public/core/AnyHashable.swift | 12 ++++++++ stdlib/public/core/Bool.swift | 6 ++++ stdlib/public/core/CTypes.swift | 5 ++++ stdlib/public/core/DoubleWidth.swift.gyb | 11 ++++--- .../public/core/FloatingPointTypes.swift.gyb | 29 +++++++++---------- stdlib/public/core/Integers.swift.gyb | 24 +++++++-------- stdlib/public/core/KeyPath.swift | 26 +++++++++++------ stdlib/public/core/UnsafePointer.swift.gyb | 7 ++++- 8 files changed, 77 insertions(+), 43 deletions(-) diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index ef3087b7c95eb..afe875e7a58a5 100644 --- a/stdlib/public/core/AnyHashable.swift +++ b/stdlib/public/core/AnyHashable.swift @@ -48,6 +48,7 @@ internal protocol _AnyHashableBox { /// no comparison is possible. Otherwise, contains the result of `==`. func _isEqual(to: _AnyHashableBox) -> Bool? var _hashValue: Int { get } + func _hash(_into hasher: (Int) -> Void) var _base: Any { get } func _downCastConditional(into result: UnsafeMutablePointer) -> Bool @@ -93,6 +94,12 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { return _baseHashable.hashValue } + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + func _hash(_into hasher: (Int) -> Void) { + _baseHashable._hash(into: hasher) + } + @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var _base: Any { @@ -295,6 +302,11 @@ extension AnyHashable : Hashable { public var hashValue: Int { return _box._hashValue } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + _box._hash(_into: hasher) + } } extension AnyHashable : CustomStringConvertible { diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 6ab1c1780cb5b..f409c29e8083c 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -159,6 +159,12 @@ extension Bool : Equatable, Hashable { return self ? 1 : 0 } + @_inlineable // FIXME(sil-serialize-all) + @_transparent + public func _hash(into hasher: (Int) -> Void) { + hasher(self ? 1 : 0) + } + @_inlineable // FIXME(sil-serialize-all) @_transparent public static func == (lhs: Bool, rhs: Bool) -> Bool { diff --git a/stdlib/public/core/CTypes.swift b/stdlib/public/core/CTypes.swift index bf73e273a3601..a5db605015553 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -166,6 +166,11 @@ extension OpaquePointer: Hashable { public var hashValue: Int { return Int(Builtin.ptrtoint_Word(_rawValue)) } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + hasher(Int(bitPattern: self)) + } } extension OpaquePointer : CustomDebugStringConvertible { diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index b7474bbc85635..d67ddb8c929ae 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -151,10 +151,13 @@ extension DoubleWidth : Comparable { extension DoubleWidth : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - var result = 0 - result = _combineHashValues(result, _storage.high.hashValue) - result = _combineHashValues(result, _storage.low.hashValue) - return result + return _defaultHashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + low._hash(into: hasher) + high._hash(into: hasher) } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 36bd1c67cb094..d8c16a119811a 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1440,26 +1440,25 @@ extension ${Self} : Hashable { /// your program. Do not save hash values to use during a future execution. @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { + return _defaultHashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + var v = self if isZero { // To satisfy the axiom that equality implies hash equality, we need to // finesse the hash value of -0.0 to match +0.0. - return 0 - } else { - %if bits <= word_bits: - return Int(bitPattern: UInt(bitPattern)) - %elif bits == 64: # Double -> 32-bit Int - return Int(truncatingIfNeeded: bitPattern &>> 32) ^ - Int(truncatingIfNeeded: bitPattern) - %elif word_bits == 32: # Float80 -> 32-bit Int - return Int(truncatingIfNeeded: significandBitPattern &>> 32) ^ - Int(truncatingIfNeeded: significandBitPattern) ^ - Int(_representation.signAndExponent) - %else: # Float80 -> 64-bit Int - return Int(bitPattern: UInt(significandBitPattern)) ^ - Int(_representation.signAndExponent) - %end + v = 0 } + %if bits == 80: + v._representation.signAndExponent._hash(into: hasher) + v.significandBitPattern._hash(into: hasher) + %else: + v.bitPattern._hash(into: hasher) + %end } + } extension ${Self} { diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 04a61d805bdc3..981dd349579d4 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3603,22 +3603,18 @@ extension ${Self} : Hashable { public var hashValue: Int { @inline(__always) get { -% if bits <= word_bits and signed: - // Sign extend the value. - return Int(self) -% elif bits <= word_bits and not signed: - // Sign extend the value. - return Int(${OtherSelf}(bitPattern: self)) -% elif bits == word_bits * 2: - // We have twice as many bits as we need to return. - return - Int(truncatingIfNeeded: self) ^ - Int(truncatingIfNeeded: self &>> 32) -% else: - _Unimplemented() -% end + return _defaultHashValue(for: self) } } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + % if bits <= word_bits: + hasher(Int(bitPattern: _lowWord)) + % else: + words.forEach { hasher(Int(bitPattern: $0)) } + % end + } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 70dace66da613..d172281c465f5 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -49,21 +49,25 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { @_inlineable // FIXME(sil-serialize-all) final public var hashValue: Int { - var hash = 0 + return _defaultHashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { withBuffer { var buffer = $0 while true { let (component, type) = buffer.next() - hash ^= _mixInt(component.value.hashValue) + component.value._hash(into: hasher) if let type = type { - hash ^= _mixInt(unsafeBitCast(type, to: Int.self)) + hasher(unsafeBitCast(type, to: Int.self)) } else { break } } } - return hash } + @_inlineable // FIXME(sil-serialize-all) public static func ==(a: AnyKeyPath, b: AnyKeyPath) -> Bool { // Fast-path identical objects @@ -452,12 +456,16 @@ internal struct ComputedPropertyID: Hashable { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var hashValue: Int { - var hash = 0 - hash ^= _mixInt(value) - hash ^= _mixInt(isStoredProperty ? 13 : 17) - hash ^= _mixInt(isTableOffset ? 19 : 23) - return hash + return _defaultHashValue(for: self) } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + hasher(value) + isStoredProperty._hash(into: hasher) + isTableOffset._hash(into: hasher) + } + } @_versioned // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index e6ff2a114e67a..cfc05954fb9ec 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -904,8 +904,13 @@ extension ${Self}: Hashable { public var hashValue: Int { return Int(bitPattern: self) } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + hasher(Int(bitPattern: self)) + } } - + extension ${Self}: Strideable { /// Returns a pointer to the next consecutive instance. /// From f3c8ee9ae86480e91e4b76db47c3e3b513ba1651 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 18 Oct 2017 17:48:43 +0100 Subject: [PATCH 04/10] [stdlib] Update Dictionary and Set to use hash(into:) rather than hashValue --- .../public/core/HashedCollections.swift.gyb | 13 ++++++--- stdlib/public/core/Hashing.swift | 29 ------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 8045b6a43d565..b3221391b18a6 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -792,11 +792,16 @@ extension Set : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { // FIXME(ABI)#177: Cache Set hashValue - var result: Int = _mixInt(0) + return _defaultHashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: (Int) -> Void) { + var hash = 0 for member in self { - result ^= _mixInt(member.hashValue) + hash ^= _defaultHashValue(for: member) } - return result + hasher(hash) } } @@ -3984,7 +3989,7 @@ extension _Native${Self}Buffer @_versioned @inline(__always) // For performance reasons. internal func _bucket(_ k: Key) -> Int { - return _squeezeHashValue(k.hashValue, bucketCount) + return _defaultHashValue(for: k) & _bucketMask } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index 02acd8e2b5551..50669623305c6 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -159,35 +159,6 @@ func _mixInt(_ value: Int) -> Int { #endif } -/// Given a hash value, returns an integer value in the range of -/// 0..<`upperBound` that corresponds to a hash value. -/// -/// The `upperBound` must be positive and a power of 2. -/// -/// This function is superior to computing the remainder of `hashValue` by -/// the range length. Some types have bad hash functions; sometimes simple -/// patterns in data sets create patterns in hash values and applying the -/// remainder operation just throws away even more information and invites -/// even more hash collisions. This effect is especially bad because the -/// range is a power of two, which means to throws away high bits of the hash -/// (which would not be a problem if the hash was known to be good). This -/// function mixes the bits in the hash value to compensate for such cases. -/// -/// Of course, this function is a compressing function, and applying it to a -/// hash value does not change anything fundamentally: collisions are still -/// possible, and it does not prevent malicious users from constructing data -/// sets that will exhibit pathological collisions. -@_inlineable // FIXME(sil-serialize-all) -public // @testable -func _squeezeHashValue(_ hashValue: Int, _ upperBound: Int) -> Int { - _sanityCheck(_isPowerOf2(upperBound)) - let mixedHashValue = _mixInt(hashValue) - - // As `upperBound` is a power of two we can do a bitwise-and to calculate - // mixedHashValue % upperBound. - return mixedHashValue & (upperBound &- 1) -} - /// Returns a new value that combines the two given hash values. /// /// Combining is performed using [a hash function][ref] described by T.C. Hoad From c3be2081a4b38c63e2d7649fe85a30815af68c03 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 19 Oct 2017 13:27:19 +0100 Subject: [PATCH 05/10] [WIP][SQUASH ME] Preparations for synthesizing Hashable with hashers --- include/swift/AST/ASTContext.h | 4 ++-- include/swift/AST/KnownIdentifiers.def | 2 ++ include/swift/AST/KnownStdlibTypes.def | 12 +++++++++--- lib/AST/ASTContext.cpp | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index ea9125eba6813..cb23062180e0e 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -405,8 +405,8 @@ class ASTContext { ProtocolDecl *getErrorDecl() const; CanType getExceptionType() const; -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ - /** Retrieve the declaration of Swift.NAME. */ \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ + /** Retrieve the declaration of Swift.IDSTR. */ \ DECL_CLASS *get##NAME##Decl() const; #include "swift/AST/KnownStdlibTypes.def" diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 0b7f4d4a36dd9..a554e1a07ce80 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -57,10 +57,12 @@ IDENTIFIER(forKey) IDENTIFIER(from) IDENTIFIER(fromRaw) IDENTIFIER(hashValue) +IDENTIFIER_(hash) IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) IDENTIFIER(initialValue) +IDENTIFIER(into) IDENTIFIER(intValue) IDENTIFIER(Key) IDENTIFIER(KeyedDecodingContainer) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index d450b13479537..45a870695defa 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -15,16 +15,19 @@ // //===----------------------------------------------------------------------===// -#ifndef KNOWN_STDLIB_TYPE_DECL -/// KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) +#ifndef KNOWN_STDLIB_TYPE_DECL_WITH_NAME +/// KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) /// /// The macro is expanded for each known standard library type. NAME is /// bound to the unqualified name of the type. DECL_CLASS is bound to the /// Decl subclass it is expected to be an instance of. NUM_GENERIC_PARAMS is /// bound to the number of generic parameters the type is expected to have. -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) #endif +#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ + KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, #NAME, DECL_CLASS, NUM_GENERIC_PARAMS) + KNOWN_STDLIB_TYPE_DECL(Bool, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(Int, NominalTypeDecl, 0) @@ -50,6 +53,8 @@ KNOWN_STDLIB_TYPE_DECL(Sequence, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(Dictionary, NominalTypeDecl, 2) KNOWN_STDLIB_TYPE_DECL(AnyHashable, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(MutableCollection, ProtocolDecl, 1) +KNOWN_STDLIB_TYPE_DECL_WITH_NAME(DefaultHasher, "_DefaultHasher", NominalTypeDecl, 0) +KNOWN_STDLIB_TYPE_DECL_WITH_NAME(Hasher, "_Hasher", ProtocolDecl, 1) KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1) @@ -79,3 +84,4 @@ KNOWN_STDLIB_TYPE_DECL(KeyedDecodingContainer, NominalTypeDecl, 1) KNOWN_STDLIB_TYPE_DECL(RangeReplaceableCollection, ProtocolDecl, 1) #undef KNOWN_STDLIB_TYPE_DECL +#undef KNOWN_STDLIB_TYPE_DECL_WITH_NAME diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 82b79b6f48ab6..e2be5e22d83ad 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -135,7 +135,7 @@ struct ASTContext::Implementation { /// The AnyObject type. CanType AnyObjectType; -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ /** The declaration of Swift.NAME. */ \ DECL_CLASS *NAME##Decl = nullptr; #include "swift/AST/KnownStdlibTypes.def" @@ -622,11 +622,11 @@ FuncDecl *ASTContext::getPlusFunctionOnString() const { return Impl.PlusFunctionOnString; } -#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \ +#define KNOWN_STDLIB_TYPE_DECL_WITH_NAME(NAME, IDSTR, DECL_CLASS, NUM_GENERIC_PARAMS) \ DECL_CLASS *ASTContext::get##NAME##Decl() const { \ if (!Impl.NAME##Decl) \ Impl.NAME##Decl = dyn_cast_or_null( \ - findStdlibType(*this, #NAME, NUM_GENERIC_PARAMS)); \ + findStdlibType(*this, IDSTR, NUM_GENERIC_PARAMS)); \ return Impl.NAME##Decl; \ } #include "swift/AST/KnownStdlibTypes.def" From c433efd8347449f86433d82cda21b81c4d570073 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 9 Feb 2018 16:25:54 +0000 Subject: [PATCH 06/10] [experiment] func _hash(into hasher: _Hasher) -> _Hasher This is a version for hasher that uses a nonmutating append, so that we can mark it @effects(readonly), reducing retain/releases around its calls. --- benchmark/single-source/DictTest4.swift | 14 ++++++++ stdlib/public/core/AnyHashable.swift | 10 +++--- stdlib/public/core/Bool.swift | 4 +-- stdlib/public/core/CTypes.swift | 4 +-- stdlib/public/core/DoubleWidth.swift.gyb | 5 ++- .../public/core/FloatingPointTypes.swift.gyb | 9 ++--- stdlib/public/core/Hashable.swift | 33 +++++++++++++++---- .../public/core/HashedCollections.swift.gyb | 4 +-- stdlib/public/core/Integers.swift.gyb | 8 +++-- stdlib/public/core/KeyPath.swift | 19 ++++++----- stdlib/public/core/StringHashable.swift | 4 +-- stdlib/public/core/UnsafePointer.swift.gyb | 4 +-- 12 files changed, 78 insertions(+), 40 deletions(-) diff --git a/benchmark/single-source/DictTest4.swift b/benchmark/single-source/DictTest4.swift index d2849fae76e47..ec0f260449731 100644 --- a/benchmark/single-source/DictTest4.swift +++ b/benchmark/single-source/DictTest4.swift @@ -42,6 +42,20 @@ struct LargeKey: Hashable { self.p = value & 8 == 0 self.q = value & 16 == 0 } +#if true + func _hash(into hasher: _Hasher) -> _Hasher { + return hasher + .appending(i) + .appending(j) + .appending(k) + .appending(l) + .appending(m) + .appending(n) + .appending(o) + .appending(p) + .appending(q) + } +#endif } @inline(never) diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index afe875e7a58a5..9f3a48ad6e331 100644 --- a/stdlib/public/core/AnyHashable.swift +++ b/stdlib/public/core/AnyHashable.swift @@ -48,7 +48,7 @@ internal protocol _AnyHashableBox { /// no comparison is possible. Otherwise, contains the result of `==`. func _isEqual(to: _AnyHashableBox) -> Bool? var _hashValue: Int { get } - func _hash(_into hasher: (Int) -> Void) + func _hash(_into hasher: _Hasher) -> _Hasher var _base: Any { get } func _downCastConditional(into result: UnsafeMutablePointer) -> Bool @@ -96,8 +96,8 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) - func _hash(_into hasher: (Int) -> Void) { - _baseHashable._hash(into: hasher) + func _hash(_into hasher: _Hasher) -> _Hasher { + return _baseHashable._hash(into: hasher) } @_inlineable // FIXME(sil-serialize-all) @@ -304,8 +304,8 @@ extension AnyHashable : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - _box._hash(_into: hasher) + public func _hash(into hasher: _Hasher) -> _Hasher { + return _box._hash(_into: hasher) } } diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index f409c29e8083c..ced68e6c417ef 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -161,8 +161,8 @@ extension Bool : Equatable, Hashable { @_inlineable // FIXME(sil-serialize-all) @_transparent - public func _hash(into hasher: (Int) -> Void) { - hasher(self ? 1 : 0) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher.appending(self ? 1 : 0) } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/CTypes.swift b/stdlib/public/core/CTypes.swift index a5db605015553..432e4e7df1317 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -168,8 +168,8 @@ extension OpaquePointer: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - hasher(Int(bitPattern: self)) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher.appending(Int(bitPattern: self)) } } diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index d67ddb8c929ae..4bd00a6918865 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -155,9 +155,8 @@ extension DoubleWidth : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - low._hash(into: hasher) - high._hash(into: hasher) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher.appending(low).appending(high) } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index d8c16a119811a..642d85e34ef8d 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1444,7 +1444,7 @@ extension ${Self} : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { + public func _hash(into hasher: _Hasher) -> _Hasher { var v = self if isZero { // To satisfy the axiom that equality implies hash equality, we need to @@ -1452,10 +1452,11 @@ extension ${Self} : Hashable { v = 0 } %if bits == 80: - v._representation.signAndExponent._hash(into: hasher) - v.significandBitPattern._hash(into: hasher) + return hasher + .appending(v._representation.signAndExponent) + .appending(v.significandBitPattern) %else: - v.bitPattern._hash(into: hasher) + return hasher.appending(v.bitPattern) %end } diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 8fb9948364cd0..d4fbfbc717cb1 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -109,21 +109,19 @@ public protocol Hashable : Equatable { /// your program. Do not save hash values to use during a future execution. var hashValue: Int { get } - func _hash(into hasher: (Int) -> Void) + func _hash(into hasher: _Hasher) -> _Hasher } @_versioned @_inlineable @inline(__always) internal func _defaultHashValue(for value: T) -> Int { - var hasher = _DefaultHasher(_inlineable: ()) - value._hash(into: { hasher.append($0) }) - return hasher._finalize_alwaysInline() + return _Hasher(_inlineable: ()).appending(value).finalized() } extension Hashable { - public func _hash(into hasher: (Int) -> Void) { - hasher(self.hashValue) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher.appending(self.hashValue) } } @@ -161,6 +159,21 @@ public struct _QuickHasher { _hash = 0 } + @inline(never) + @effects(readonly) + public func appending(_ value: Int) -> _QuickHasher { + var hasher = self + hasher._append_alwaysInline(value) + return hasher + } + + //@inline(__always) + @_inlineable + @_transparent + public func appending(_ value: H) -> _QuickHasher { + return value._hash(into: self) + } + @inline(never) public mutating func append(_ value: Int) { _append_alwaysInline(value) @@ -177,6 +190,12 @@ public struct _QuickHasher { _hash = _combineHashValues(_hash, value) } + @_inlineable // FIXME(sil-serialize-all) + public func finalized() -> Int { + var hasher = self + return hasher._finalize_alwaysInline() + } + @inline(never) public mutating func finalize() -> Int { return _finalize_alwaysInline() @@ -190,4 +209,4 @@ public struct _QuickHasher { } } -public typealias _DefaultHasher = _QuickHasher +public typealias _Hasher = _QuickHasher diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index b3221391b18a6..0060ebe456751 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -796,12 +796,12 @@ extension Set : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { + public func _hash(into hasher: _Hasher) -> _Hasher { var hash = 0 for member in self { hash ^= _defaultHashValue(for: member) } - hasher(hash) + return hasher.appending(hash) } } diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 981dd349579d4..8aa3bb0bf88ab 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3608,11 +3608,13 @@ extension ${Self} : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { + public func _hash(into hasher: _Hasher) -> _Hasher { % if bits <= word_bits: - hasher(Int(bitPattern: _lowWord)) + return hasher.appending(Int(bitPattern: _lowWord)) % else: - words.forEach { hasher(Int(bitPattern: $0)) } + var hasher = hasher + words.forEach { hasher = hasher.appending(Int(bitPattern: $0)) } + return hasher % end } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index d172281c465f5..8c106246227bf 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -53,18 +53,20 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - withBuffer { + public func _hash(into hasher: _Hasher) -> _Hasher { + return withBuffer { + var hasher = hasher var buffer = $0 while true { let (component, type) = buffer.next() - component.value._hash(into: hasher) + hasher = hasher.appending(component.value) if let type = type { - hasher(unsafeBitCast(type, to: Int.self)) + hasher = hasher.appending(unsafeBitCast(type, to: Int.self)) } else { break } } + return hasher } } @@ -460,10 +462,11 @@ internal struct ComputedPropertyID: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - hasher(value) - isStoredProperty._hash(into: hasher) - isTableOffset._hash(into: hasher) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher + .appending(value) + .appending(isStoredProperty) + .appending(isTableOffset) } } diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 0c8e7b7cd5133..5bd0989987972 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -40,7 +40,7 @@ extension Unicode { _ string: UnsafeBufferPointer ) -> Int { let collationTable = _swift_stdlib_unicode_getASCIICollationTable() - var hasher = _DefaultHasher() + var hasher = _Hasher() for c in string { _precondition(c <= 127) let element = collationTable[Int(c)] @@ -64,7 +64,7 @@ extension Unicode { UInt32(string.count)) defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) } - var hasher = _DefaultHasher() + var hasher = _Hasher() while true { var hitEnd = false let element = diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index cfc05954fb9ec..01637ff13a18b 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -906,8 +906,8 @@ extension ${Self}: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: (Int) -> Void) { - hasher(Int(bitPattern: self)) + public func _hash(into hasher: _Hasher) -> _Hasher { + return hasher.appending(Int(bitPattern: self)) } } From da02fe3e0129f7e9617ad83adab33938528fbcb3 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 9 Feb 2018 16:53:23 +0000 Subject: [PATCH 07/10] Add missing @_inlineable on default _hash(into:) implementation This restores speed of String hashing, among other types. --- stdlib/public/core/Hashable.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index d4fbfbc717cb1..cdf3e25b356a6 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -120,6 +120,8 @@ internal func _defaultHashValue(for value: T) -> Int { } extension Hashable { + @_inlineable + @inline(__always) public func _hash(into hasher: _Hasher) -> _Hasher { return hasher.appending(self.hashValue) } From da8063f126897258b562314c57c20476fa88dc9a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 12 Feb 2018 17:12:13 +0000 Subject: [PATCH 08/10] [stdlib] Switch to a faux-pure hasher interface --- stdlib/public/core/AnyHashable.swift | 6 +- stdlib/public/core/Bool.swift | 2 +- stdlib/public/core/CTypes.swift | 2 +- stdlib/public/core/DoubleWidth.swift.gyb | 2 +- .../public/core/FloatingPointTypes.swift.gyb | 2 +- stdlib/public/core/Hashable.swift | 75 +++++++++++++------ .../public/core/HashedCollections.swift.gyb | 2 +- stdlib/public/core/Integers.swift.gyb | 2 +- stdlib/public/core/KeyPath.swift | 4 +- stdlib/public/core/UnsafePointer.swift.gyb | 2 +- 10 files changed, 63 insertions(+), 36 deletions(-) diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index 9f3a48ad6e331..79568acdc2ac7 100644 --- a/stdlib/public/core/AnyHashable.swift +++ b/stdlib/public/core/AnyHashable.swift @@ -48,7 +48,7 @@ internal protocol _AnyHashableBox { /// no comparison is possible. Otherwise, contains the result of `==`. func _isEqual(to: _AnyHashableBox) -> Bool? var _hashValue: Int { get } - func _hash(_into hasher: _Hasher) -> _Hasher + func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher var _base: Any { get } func _downCastConditional(into result: UnsafeMutablePointer) -> Bool @@ -96,7 +96,7 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) - func _hash(_into hasher: _Hasher) -> _Hasher { + func _hash(_into hasher: _UnsafeHasher) -> _UnsafeHasher { return _baseHashable._hash(into: hasher) } @@ -304,7 +304,7 @@ extension AnyHashable : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return _box._hash(_into: hasher) } } diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index ced68e6c417ef..80ca877f8980f 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -161,7 +161,7 @@ extension Bool : Equatable, Hashable { @_inlineable // FIXME(sil-serialize-all) @_transparent - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher.appending(self ? 1 : 0) } diff --git a/stdlib/public/core/CTypes.swift b/stdlib/public/core/CTypes.swift index 432e4e7df1317..fcda8bf1f62de 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -168,7 +168,7 @@ extension OpaquePointer: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher.appending(Int(bitPattern: self)) } } diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index 4bd00a6918865..1ef5dddc85e30 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -155,7 +155,7 @@ extension DoubleWidth : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher.appending(low).appending(high) } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 642d85e34ef8d..2b96e7fad67ee 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1444,7 +1444,7 @@ extension ${Self} : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { var v = self if isZero { // To satisfy the axiom that equality implies hash equality, we need to diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index cdf3e25b356a6..a9d7fb953ff9c 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -109,20 +109,23 @@ public protocol Hashable : Equatable { /// your program. Do not save hash values to use during a future execution. var hashValue: Int { get } - func _hash(into hasher: _Hasher) -> _Hasher + func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher } @_versioned @_inlineable @inline(__always) internal func _defaultHashValue(for value: T) -> Int { - return _Hasher(_inlineable: ()).appending(value).finalized() + var hasher = _Hasher(_inlineable: ()) + return withUnsafeMutablePointer(to: &hasher) { p in + return _UnsafeHasher(p).appending(value).finalized() + } } extension Hashable { @_inlineable @inline(__always) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher.appending(self.hashValue) } } @@ -144,6 +147,51 @@ internal func Hashable_hashValue_indirect( return value.pointee.hashValue } +/// An unsafe wrapper around a stateful hash function, presenting a faux purely +/// functional interface to eliminate ARC overhead. +/// +/// This is not a true value type; calling `appending` or `finalized` actually +/// mutates `self`'s state. +@_fixed_layout +public struct _UnsafeHasher { + @_versioned + internal let _state: UnsafeMutablePointer<_Hasher> + + @_inlineable + @_versioned + internal init(_ state: UnsafeMutablePointer<_Hasher>) { + self._state = state + } + + @effects(readonly) + @inline(never) + public func appending(_ value: Int) -> _UnsafeHasher { + // The effects attribute is a lie; however, it enables the compiler to + // eliminate unnecessary retain/releases protecting Hashable state around + // calls to `_Hasher.append(_:)`. + // + // We don't have a way to describe the side-effects of an opaque function -- + // if it doesn't have an @effects attribute, the compiler has no choice but + // to assume it may mutate the hashable we're visiting. We know that won't + // be the case (the stdlib owns the hash function), but the only way to tell + // this to the compiler is to pretend the state update is pure. + _state.pointee._append_alwaysInline(value) + return self + } + + @_inlineable + @_transparent + public func appending(_ value: H) -> _UnsafeHasher { + return value._hash(into: self) + } + + @_inlineable + @_versioned + internal func finalized() -> Int { + return _state.pointee.finalize() + } +} + // FIXME: This is purely for benchmarking; to be removed. @_fixed_layout public struct _QuickHasher { @@ -161,21 +209,6 @@ public struct _QuickHasher { _hash = 0 } - @inline(never) - @effects(readonly) - public func appending(_ value: Int) -> _QuickHasher { - var hasher = self - hasher._append_alwaysInline(value) - return hasher - } - - //@inline(__always) - @_inlineable - @_transparent - public func appending(_ value: H) -> _QuickHasher { - return value._hash(into: self) - } - @inline(never) public mutating func append(_ value: Int) { _append_alwaysInline(value) @@ -192,12 +225,6 @@ public struct _QuickHasher { _hash = _combineHashValues(_hash, value) } - @_inlineable // FIXME(sil-serialize-all) - public func finalized() -> Int { - var hasher = self - return hasher._finalize_alwaysInline() - } - @inline(never) public mutating func finalize() -> Int { return _finalize_alwaysInline() diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 0060ebe456751..5181c52cf8228 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -796,7 +796,7 @@ extension Set : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { var hash = 0 for member in self { hash ^= _defaultHashValue(for: member) diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 8aa3bb0bf88ab..45a121fced88f 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3608,7 +3608,7 @@ extension ${Self} : Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { % if bits <= word_bits: return hasher.appending(Int(bitPattern: _lowWord)) % else: diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 8c106246227bf..93f1790f1ae61 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -53,7 +53,7 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return withBuffer { var hasher = hasher var buffer = $0 @@ -462,7 +462,7 @@ internal struct ComputedPropertyID: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher .appending(value) .appending(isStoredProperty) diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index 01637ff13a18b..3824dc742c4ae 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -906,7 +906,7 @@ extension ${Self}: Hashable { } @_inlineable // FIXME(sil-serialize-all) - public func _hash(into hasher: _Hasher) -> _Hasher { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher.appending(Int(bitPattern: self)) } } From 52ee8183a760fea1e2522b2c3fa0728f2a928ee7 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 9 Feb 2018 19:24:24 +0000 Subject: [PATCH 09/10] Switch to SipHash-13 # Conflicts: # stdlib/public/core/Hashable.swift --- stdlib/public/core/Hashable.swift | 2 +- stdlib/public/core/SipHash.swift.gyb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index a9d7fb953ff9c..5b91e8e43257d 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -238,4 +238,4 @@ public struct _QuickHasher { } } -public typealias _Hasher = _QuickHasher +public typealias _Hasher = _SipHash13 diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index b16062aa1a636..217ba7edf7717 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -99,25 +99,25 @@ struct ${Self} { @inline(never) public mutating func append(_ m: Int) { - append_alwaysInline(m) + _append_alwaysInline(m) } @inline(never) public mutating func append(_ m: UInt64) { - append_alwaysInline(m) + _append_alwaysInline(m) } @_inlineable @_versioned @inline(__always) - internal mutating func append_alwaysInline(_ m: Int) { - append_alwaysInline(UInt64(truncatingIfNeeded: UInt(bitPattern: m))) + internal mutating func _append_alwaysInline(_ m: Int) { + _append_alwaysInline(UInt64(truncatingIfNeeded: UInt(bitPattern: m))) } @_inlineable @_versioned @inline(__always) - internal mutating func append_alwaysInline(_ m: UInt64) { + internal mutating func _append_alwaysInline(_ m: UInt64) { v3 ^= m for _ in 0..<${c_rounds} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) From 8dbf8dd1ab189326511842b253cf2433c7dce2d3 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 13 Feb 2018 11:24:49 +0000 Subject: [PATCH 10/10] [benchmark] Update DictTest4 benchmark for new _hash(into:) variant --- benchmark/single-source/DictTest4.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/single-source/DictTest4.swift b/benchmark/single-source/DictTest4.swift index ec0f260449731..bc7f38548d572 100644 --- a/benchmark/single-source/DictTest4.swift +++ b/benchmark/single-source/DictTest4.swift @@ -42,8 +42,8 @@ struct LargeKey: Hashable { self.p = value & 8 == 0 self.q = value & 16 == 0 } -#if true - func _hash(into hasher: _Hasher) -> _Hasher { +#if true // FIXME remove once synthesized hashing generates _hash(into:) + func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { return hasher .appending(i) .appending(j)