From ea1f2735fcebe78b228d418447130809faaf6543 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 18 Oct 2017 17:48:43 +0100 Subject: [PATCH 01/37] [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/37] [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/37] [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/37] [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/37] [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/37] [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/37] 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/37] [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 4ae88581fbf5d2829aa5ce8df24c07b38187cfd4 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 13 Feb 2018 11:24:49 +0000 Subject: [PATCH 09/37] [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) From 44b5da89a692e98847b9337e7ef4f789b3e6e814 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 14 Feb 2018 16:44:20 +0000 Subject: [PATCH 10/37] _UnsafeHasher.finalized(): Add @effects(readonly) --- stdlib/public/core/Hashable.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index a9d7fb953ff9c..321b2a018f2f8 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -185,10 +185,11 @@ public struct _UnsafeHasher { return value._hash(into: self) } - @_inlineable + @effects(readonly) // See comment on appending(_:) above + @inline(never) @_versioned internal func finalized() -> Int { - return _state.pointee.finalize() + return _state.pointee._finalize_alwaysInline() } } From f0aca431e634b09087f1490df7d268a422a7d821 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 12:47:52 +0000 Subject: [PATCH 11/37] _defaultHashValue(for:) => _hashValue(for:) --- stdlib/public/core/DoubleWidth.swift.gyb | 2 +- stdlib/public/core/FloatingPointTypes.swift.gyb | 2 +- stdlib/public/core/Hashable.swift | 2 +- stdlib/public/core/HashedCollections.swift.gyb | 6 +++--- stdlib/public/core/Integers.swift.gyb | 2 +- stdlib/public/core/KeyPath.swift | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index 1ef5dddc85e30..cb69767f00164 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -151,7 +151,7 @@ extension DoubleWidth : Comparable { extension DoubleWidth : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - return _defaultHashValue(for: self) + return _hashValue(for: self) } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 2b96e7fad67ee..808b52e2c412b 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1440,7 +1440,7 @@ 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) + return _hashValue(for: self) } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 321b2a018f2f8..d4e8662ac9b8c 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -115,7 +115,7 @@ public protocol Hashable : Equatable { @_versioned @_inlineable @inline(__always) -internal func _defaultHashValue(for value: T) -> Int { +internal func _hashValue(for value: H) -> Int { var hasher = _Hasher(_inlineable: ()) return withUnsafeMutablePointer(to: &hasher) { p in return _UnsafeHasher(p).appending(value).finalized() diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 5181c52cf8228..5377817911baa 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -792,14 +792,14 @@ extension Set : Hashable { @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { // FIXME(ABI)#177: Cache Set hashValue - return _defaultHashValue(for: self) + return _hashValue(for: self) } @_inlineable // FIXME(sil-serialize-all) public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { var hash = 0 for member in self { - hash ^= _defaultHashValue(for: member) + hash ^= _hashValue(for: member) } return hasher.appending(hash) } @@ -3989,7 +3989,7 @@ extension _Native${Self}Buffer @_versioned @inline(__always) // For performance reasons. internal func _bucket(_ k: Key) -> Int { - return _defaultHashValue(for: k) & _bucketMask + return _hashValue(for: k) & _bucketMask } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 45a121fced88f..b022e7b90c42a 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3603,7 +3603,7 @@ extension ${Self} : Hashable { public var hashValue: Int { @inline(__always) get { - return _defaultHashValue(for: self) + return _hashValue(for: self) } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 93f1790f1ae61..893466bf20452 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -49,7 +49,7 @@ public class AnyKeyPath: Hashable, _AppendKeyPath { @_inlineable // FIXME(sil-serialize-all) final public var hashValue: Int { - return _defaultHashValue(for: self) + return _hashValue(for: self) } @_inlineable // FIXME(sil-serialize-all) @@ -458,7 +458,7 @@ internal struct ComputedPropertyID: Hashable { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var hashValue: Int { - return _defaultHashValue(for: self) + return _hashValue(for: self) } @_inlineable // FIXME(sil-serialize-all) From 543ee0c04e8c1a672879b50ee956c9842efd0990 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 12:49:40 +0000 Subject: [PATCH 12/37] Hide _Hasher entirely behind resilient function boundaries --- stdlib/public/core/Hashable.swift | 96 ++++++++++++++++--------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index d4e8662ac9b8c..a68d2b539af43 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -112,21 +112,38 @@ public protocol Hashable : Equatable { func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher } +extension Hashable { + @_inlineable + @inline(__always) + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(self.hashValue) + } +} + @_versioned -@_inlineable @inline(__always) internal func _hashValue(for value: H) -> Int { - var hasher = _Hasher(_inlineable: ()) + var value = value + return withUnsafePointer(to: &value) { _hashValue(for: $0) } +} + +@_versioned +@inline(never) +internal func _hashValue(for pointer: UnsafePointer) -> Int { + var hasher = _Hasher() return withUnsafeMutablePointer(to: &hasher) { p in - return _UnsafeHasher(p).appending(value).finalized() + return pointer.pointee._hash(into: _UnsafeHasher(p))._finalized() } } -extension Hashable { - @_inlineable - @inline(__always) - public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher.appending(self.hashValue) +@_versioned +@inline(never) +internal func _hashValue( + with hash: (_UnsafeHasher) -> _UnsafeHasher +) -> Int { + var hasher = _Hasher() + return withUnsafeMutablePointer(to: &hasher) { p in + return hash(_UnsafeHasher(p))._finalized() } } @@ -155,12 +172,17 @@ internal func Hashable_hashValue_indirect( @_fixed_layout public struct _UnsafeHasher { @_versioned - internal let _state: UnsafeMutablePointer<_Hasher> + internal let _rawState: UnsafeMutableRawPointer - @_inlineable + internal var _state: UnsafeMutablePointer<_Hasher> { + @inline(__always) + get { return _rawState.assumingMemoryBound(to: _Hasher.self) } + } + + @inline(__always) @_versioned internal init(_ state: UnsafeMutablePointer<_Hasher>) { - self._state = state + self._rawState = UnsafeMutableRawPointer(state) } @effects(readonly) @@ -175,50 +197,39 @@ public struct _UnsafeHasher { // 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) + _state.pointee._append(value) return self } @_inlineable - @_transparent + @inline(__always) public func appending(_ value: H) -> _UnsafeHasher { return value._hash(into: self) } - @effects(readonly) // See comment on appending(_:) above - @inline(never) - @_versioned - internal func finalized() -> Int { - return _state.pointee._finalize_alwaysInline() + @inline(__always) + internal func _appending(_ value: Int) -> _UnsafeHasher { + _state.pointee._append(value) + return self + } + + @inline(__always) + internal func _finalized() -> Int { + return _state.pointee._finalize() } } // FIXME: This is purely for benchmarking; to be removed. -@_fixed_layout -public struct _QuickHasher { - @_versioned +internal struct _QuickHasher { internal var _hash: Int - @inline(never) - public init() { - _hash = 0 - } - - @_inlineable - @_versioned - internal init(_inlineable: Void) { + @inline(__always) + internal init() { _hash = 0 } - @inline(never) - public mutating func append(_ value: Int) { - _append_alwaysInline(value) - } - - @_inlineable - @_versioned @inline(__always) - internal mutating func _append_alwaysInline(_ value: Int) { + internal mutating func _append(_ value: Int) { if _hash == 0 { _hash = value return @@ -226,17 +237,10 @@ public struct _QuickHasher { _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 { + internal mutating func _finalize() -> Int { return _mixInt(_hash) } } -public typealias _Hasher = _QuickHasher +internal typealias _Hasher = _QuickHasher From fd9d7e10a57dbcb9bbd451b6d95ede55a5ca1ee6 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 15 Feb 2018 19:25:58 +0000 Subject: [PATCH 13/37] Add _hash(into:) implementations for types conditionally conforming to Hashable --- stdlib/public/core/DropWhile.swift.gyb | 4 ++++ stdlib/public/core/Flatten.swift | 6 +++++ stdlib/public/core/PrefixWhile.swift.gyb | 9 +++++++ stdlib/public/core/Reverse.swift | 4 ++++ stdlib/public/core/StringHashable.swift | 30 ++++++++++++++---------- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/stdlib/public/core/DropWhile.swift.gyb b/stdlib/public/core/DropWhile.swift.gyb index c15e285fcc09b..4ada979ebfe39 100644 --- a/stdlib/public/core/DropWhile.swift.gyb +++ b/stdlib/public/core/DropWhile.swift.gyb @@ -144,6 +144,10 @@ extension LazyDropWhileIndex : Hashable where Base.Index : Hashable { public var hashValue: Int { return base.hashValue } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(base) + } } % for Traversal in ['Forward', 'Bidirectional']: diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index a3c806c65365a..aac5eb1f2bd43 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -235,6 +235,12 @@ extension FlattenCollection.Index : Hashable public var hashValue: Int { return _mixInt(_inner?.hashValue ?? 0) ^ _outer.hashValue } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + let h = hasher.appending(_outer) + guard let inner = _inner else { return h } + return h.appending(inner) + } } extension FlattenCollection : Sequence { diff --git a/stdlib/public/core/PrefixWhile.swift.gyb b/stdlib/public/core/PrefixWhile.swift.gyb index 91bc14a61cc12..bda5a26d9c8d0 100644 --- a/stdlib/public/core/PrefixWhile.swift.gyb +++ b/stdlib/public/core/PrefixWhile.swift.gyb @@ -171,6 +171,15 @@ extension LazyPrefixWhileIndex : Hashable where Base.Index : Hashable { return .max } } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + switch _value { + case .index(let value): + return hasher.appending(value) + case .pastEnd: + return hasher.appending(.max) + } + } } % for Traversal in ['Forward', 'Bidirectional']: diff --git a/stdlib/public/core/Reverse.swift b/stdlib/public/core/Reverse.swift index ad08943de8241..97d16ed79be75 100644 --- a/stdlib/public/core/Reverse.swift +++ b/stdlib/public/core/Reverse.swift @@ -197,6 +197,10 @@ extension ReversedCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { return base.hashValue } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(base) + } } extension ReversedCollection: BidirectionalCollection { diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 5bd0989987972..6b1d7858b2918 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -37,34 +37,33 @@ extension Unicode { // @_inlineable // FIXME(sil-serialize-all) // @_versioned // FIXME(sil-serialize-all) internal static func hashASCII( - _ string: UnsafeBufferPointer - ) -> Int { + _ string: UnsafeBufferPointer, + into hasher: inout _Hasher + ) { let collationTable = _swift_stdlib_unicode_getASCIICollationTable() - var hasher = _Hasher() 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(Int(truncatingIfNeeded: element)) + hasher._append(Int(truncatingIfNeeded: element)) } } - return hasher.finalize() } // FIXME: cannot be marked @_versioned. See // @_inlineable // FIXME(sil-serialize-all) // @_versioned // FIXME(sil-serialize-all) internal static func hashUTF16( - _ string: UnsafeBufferPointer - ) -> Int { + _ string: UnsafeBufferPointer, + into hasher: inout _Hasher + ) { let collationIterator = _swift_stdlib_unicodeCollationIterator_create( string.baseAddress!, UInt32(string.count)) defer { _swift_stdlib_unicodeCollationIterator_delete(collationIterator) } - var hasher = _Hasher() while true { var hitEnd = false let element = @@ -75,10 +74,9 @@ extension Unicode { // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher.append(Int(truncatingIfNeeded: element)) + hasher._append(Int(truncatingIfNeeded: element)) } } - return hasher.finalize() } } @@ -100,7 +98,9 @@ extension _UnmanagedString where CodeUnit == UInt8 { // Swift.String.hashValue and NSString.hash being the same. return stringHashOffset ^ hash #else - return Unicode.hashASCII(self.buffer) + var hasher = _Hasher() + Unicode.hashASCII(self.buffer, into: &hasher) + return hasher._finalize() #endif // _runtime(_ObjC) } } @@ -118,7 +118,9 @@ extension _UnmanagedString where CodeUnit == UTF16.CodeUnit { // Swift.String.hashValue and NSString.hash being the same. return stringHashOffset ^ hash #else - return Unicode.hashUTF16(self.buffer) + var hasher = _Hasher() + Unicode.hashUTF16(self.buffer, into: &hasher) + return hasher._finalize() #endif // _runtime(_ObjC) } } @@ -139,7 +141,9 @@ extension _UnmanagedOpaqueString { defer { p.deallocate(capacity: count) } let buffer = UnsafeMutableBufferPointer(start: p, count: count) _copy(into: buffer) - return Unicode.hashUTF16(UnsafeBufferPointer(buffer)) + var hasher = _Hasher() + Unicode.hashASCII(buffer, into: &hasher) + return hasher._finalize() #endif } } From 2180c4b3fd942178f2c2db98a18c39dede429f6a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 21:17:43 +0000 Subject: [PATCH 14/37] Make _hashValue(for:) public. This is necessary for synthesized Hashable conformances. --- stdlib/public/core/Hashable.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index a68d2b539af43..3df815c76ea81 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -120,9 +120,8 @@ extension Hashable { } } -@_versioned @inline(__always) -internal func _hashValue(for value: H) -> Int { +public func _hashValue(for value: H) -> Int { var value = value return withUnsafePointer(to: &value) { _hashValue(for: $0) } } From 0d3204de2c35914c736cb64bd101c9bd842f0894 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 21:18:02 +0000 Subject: [PATCH 15/37] Remove _hashValue(with:) --- stdlib/public/core/Hashable.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 3df815c76ea81..97ae93e6be54b 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -135,17 +135,6 @@ internal func _hashValue(for pointer: UnsafePointer) -> Int { } } -@_versioned -@inline(never) -internal func _hashValue( - with hash: (_UnsafeHasher) -> _UnsafeHasher -) -> Int { - var hasher = _Hasher() - return withUnsafeMutablePointer(to: &hasher) { p in - return hash(_UnsafeHasher(p))._finalized() - } -} - // Called by the SwiftValue implementation. @_silgen_name("_swift_stdlib_Hashable_isEqual_indirect") internal func Hashable_isEqual_indirect( From 278594291b31cea7703685ce08afb873d3104aa1 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 21:18:31 +0000 Subject: [PATCH 16/37] Add @effects(readonly) to non-inlineable version of _hashValue(for:). --- stdlib/public/core/Hashable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 97ae93e6be54b..f47bd6db20d87 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -128,6 +128,7 @@ public func _hashValue(for value: H) -> Int { @_versioned @inline(never) +@effects(readonly) // FIXME: Unjustified internal func _hashValue(for pointer: UnsafePointer) -> Int { var hasher = _Hasher() return withUnsafeMutablePointer(to: &hasher) { p in From 157d2f1fa76fa8f95d20f3de8f62d1f88fdebae3 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 22:17:17 +0000 Subject: [PATCH 17/37] First draft of synthesized Hashable conformance with _hash(into:) This is rather tricky, as _hash(into:) has a default implementation and as such it wouldn't normally be synthesized. We piggyback on hashValue and unconditionally generate _hash(into:) as well. --- include/swift/AST/ASTContext.h | 3 + include/swift/AST/KnownIdentifiers.def | 5 +- include/swift/AST/KnownStdlibTypes.def | 3 +- lib/AST/ASTContext.cpp | 31 ++ .../DerivedConformanceEquatableHashable.cpp | 408 +++++++++--------- 5 files changed, 247 insertions(+), 203 deletions(-) diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index cb23062180e0e..a9792ee076b7a 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -489,6 +489,9 @@ class ASTContext { /// Retrieve the declaration of Swift._mixInt(Int) -> Int. FuncDecl *getMixIntDecl() const; + /// Retrieve the declaration of Swift._hashValue(for: H) -> Int. + FuncDecl *getHashValueForDecl() const; + /// Retrieve the declaration of Array.append(element:) FuncDecl *getArrayAppendElementDecl() const; diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index a554e1a07ce80..b5d78a0878721 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -26,6 +26,7 @@ IDENTIFIER(alloc) IDENTIFIER(allocWithZone) IDENTIFIER(allZeros) IDENTIFIER(Any) +IDENTIFIER(appending) IDENTIFIER(ArrayLiteralElement) IDENTIFIER(atIndexedSubscript) IDENTIFIER_(bridgeToObjectiveC) @@ -53,11 +54,13 @@ IDENTIFIER(encoder) IDENTIFIER(error) IDENTIFIER(forKeyedSubscript) IDENTIFIER(Foundation) +IDENTIFIER(for) IDENTIFIER(forKey) IDENTIFIER(from) IDENTIFIER(fromRaw) -IDENTIFIER(hashValue) IDENTIFIER_(hash) +IDENTIFIER(hasher) +IDENTIFIER(hashValue) IDENTIFIER(init) IDENTIFIER(initialize) IDENTIFIER(initStorage) diff --git a/include/swift/AST/KnownStdlibTypes.def b/include/swift/AST/KnownStdlibTypes.def index 45a870695defa..33fc5bd5fe37b 100644 --- a/include/swift/AST/KnownStdlibTypes.def +++ b/include/swift/AST/KnownStdlibTypes.def @@ -53,8 +53,7 @@ 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_WITH_NAME(UnsafeHasher, "_UnsafeHasher", NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index e2be5e22d83ad..79257ae25ad0f 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -201,6 +201,9 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) /// func _mixInt(Int) -> Int FuncDecl *MixIntDecl = nullptr; + /// func _hashValue(for: H) -> Int + FuncDecl *HashValueForDecl = nullptr; + /// func append(Element) -> void FuncDecl *ArrayAppendElementDecl = nullptr; @@ -1072,6 +1075,34 @@ FuncDecl *ASTContext::getMixIntDecl() const { return decl; } +FuncDecl *ASTContext::getHashValueForDecl() const { + if (Impl.HashValueForDecl) + return Impl.HashValueForDecl; + + auto resolver = getLazyResolver(); + auto hasherType = getUnsafeHasherDecl()->getDeclaredType(); + + SmallVector results; + lookupInSwiftModule("_hashValue", results); + for (auto result : results) { + auto *fd = dyn_cast(result); + if (!fd) + continue; + auto paramLists = fd->getParameterLists(); + if (paramLists.size() != 1 || paramLists[0]->size() != 1) + continue; + auto paramDecl = paramLists[0]->get(0); + if (paramDecl->getArgumentName() != Id_for) + continue; + auto genericParams = fd->getGenericParams(); + if (!genericParams || genericParams->size() != 1) + continue; + Impl.HashValueForDecl = fd; + return fd; + } + return nullptr; +} + FuncDecl *ASTContext::getArrayAppendElementDecl() const { if (Impl.ArrayAppendElementDecl) return Impl.ArrayAppendElementDecl; diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 0ea6c331fe1a8..41a3eb3c8326b 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -744,89 +744,154 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { return integerExpr; } -/// Returns a new assignment expression that combines the hash value of an -/// expression into a variable. -/// \p C The AST context. -/// \p resultVar The variable into which the hash value will be combined. -/// \p exprToHash The expression whose hash value should be combined. -/// \return The expression that combines the hash value into the variable. -static Expr* combineHashValuesAssignmentExpr(ASTContext &C, - VarDecl* resultVar, - Expr *exprToHash) { - // .hashValue - auto hashValueExpr = new (C) UnresolvedDotExpr(exprToHash, SourceLoc(), - C.Id_hashValue, DeclNameLoc(), - /*implicit*/ true); +/// Returns a new \c CallExpr representing +/// +/// hasher.appending(hashable) +/// +/// \param C The AST context to create the expression in. +/// +/// \param DC The \c DeclContext to create any decls in. +/// +/// \param hasher The base expression to make the call on. +/// +/// \param hashable The parameter to the call. +static CallExpr* createHasherAppendingCall(ASTContext &C, DeclContext *DC, + Expr* hasher, + Expr* hashable) { + // hasher.appending(_:) + SmallVector argNames{Identifier()}; + DeclName name(C, C.Id_appending, argNames); + auto *appendingCall = new (C) UnresolvedDotExpr(hasher, SourceLoc(), + name, DeclNameLoc(), + /*implicit*/ true); + + // hasher.appending(hashable) + Expr *args[1] = {hashable}; + Identifier argLabels[1] = {Identifier()}; + return CallExpr::createImplicit(C, appendingCall, C.AllocateCopy(args), + C.AllocateCopy(argLabels)); +} - // _combineHashValues(result, .hashValue) - auto combineFunc = C.getCombineHashValuesDecl(); - auto combineFuncExpr = new (C) DeclRefExpr(combineFunc, DeclNameLoc(), - /*implicit*/ true); - auto rhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto combineResultExpr = CallExpr::createImplicit( - C, combineFuncExpr, { rhsResultExpr, hashValueExpr }, {}); +static ValueDecl * +deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, + NominalTypeDecl *typeDecl, + void (*bodySynthesizer)(AbstractFunctionDecl *)) { + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher - // result = _combineHashValues(result, .hashValue) - auto lhsResultExpr = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(lhsResultExpr, SourceLoc(), - combineResultExpr, /*implicit*/ true); - return assignExpr; -} + ASTContext &C = tc.Context; + + // Expected type: (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher + // Constructed as: + // func type(input: Self, + // output: func type(input: _UnsafeHasher, + // output: _UnsafeHasher)) + // Created from the inside out: + + Type hasherType = C.getUnsafeHasherDecl()->getDeclaredType(); + + + // Params: self (implicit), hasher + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDecl); + auto *hasherParam = new (C) ParamDecl(VarDecl::Specifier::Owned, SourceLoc(), + SourceLoc(), C.Id_into, SourceLoc(), + C.Id_hasher, hasherType, typeDecl); + hasherParam->setInterfaceType(hasherType); + + ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl), + ParameterList::createWithoutLoc(hasherParam)}; + + // Func name: _hash(into: _UnsafeHasher) + DeclName name(C, C.Id_hash, params[1]); + auto *hashDecl = FuncDecl::create(C, + SourceLoc(), StaticSpellingKind::None, + SourceLoc(), name, SourceLoc(), + /*Throws=*/false, SourceLoc(), + nullptr, params, + TypeLoc::withoutLoc(hasherType), + typeDecl); + hashDecl->setImplicit(); + hashDecl->setBodySynthesizer(bodySynthesizer); + + // Evaluate type of Self in (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher + Type selfType = typeDecl->getDeclaredInterfaceType(); + auto inputTypeElt = TupleTypeElt(hasherType, C.Id_into); + auto inputType = TupleType::get(ArrayRef(inputTypeElt), C); + auto innerType = FunctionType::get(inputType, hasherType, + FunctionType::ExtInfo()); -/// Returns a new assignment expression that invokes _mixInt on a variable and -/// assigns the result back to the same variable. -/// \p C The AST context. -/// \p resultVar The integer variable to be mixed. -/// \return The expression that mixes the integer value. -static Expr* mixIntAssignmentExpr(ASTContext &C, VarDecl* resultVar) { - auto mixinFunc = C.getMixIntDecl(); - auto mixinFuncExpr = new (C) DeclRefExpr(mixinFunc, DeclNameLoc(), - /*implicit*/ true); - auto rhsResultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - // _mixInt(result) - auto mixedResultExpr = CallExpr::createImplicit(C, mixinFuncExpr, - { rhsResultRef }, {}); - - // result = _mixInt(result) - auto lhsResultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(lhsResultRef, SourceLoc(), - mixedResultExpr, /*implicit*/ true); - return assignExpr; + Type interfaceType; + if (auto sig = typeDecl->getGenericSignatureOfContext()) { + hashDecl->setGenericEnvironment(typeDecl->getGenericEnvironmentOfContext()); + interfaceType = GenericFunctionType::get(sig, selfType, innerType, + FunctionType::ExtInfo()); + } else { + // (Self) -> innerType == (_UnsafeHasher) -> _UnsafeHasher + interfaceType = FunctionType::get(selfType, innerType, + FunctionType::ExtInfo()); + } + hashDecl->setInterfaceType(interfaceType); + +// hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); + auto access = typeDecl->getFormalAccess(); + hashDecl->setAccess( + access != AccessLevel::Private ? access : AccessLevel::FilePrivate); + + // Inherit the @_versioned attribute. + if (typeDecl->getAttrs().hasAttribute()) { + auto *clonedAttr = new (C) VersionedAttr(/*implicit=*/true); + hashDecl->getAttrs().add(clonedAttr); + } + + // If the type was not imported, the derived conformance is either from the + // type itself or an extension, in which case we will emit the declaration + // normally. + if (typeDecl->hasClangNode()) + tc.Context.addExternalDecl(hashDecl); + + typeDecl->addMember(hashDecl); + return hashDecl; } +/// Derive the body for the '_hash(into:)' method for an enum. static void -deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { - auto parentDC = hashValueDecl->getDeclContext(); +deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // enum SomeEnum { + // case A, B, C + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // switch self { + // case A: + // return hasher.appending(0) + // case B: + // return hasher.appending(1) + // case C: + // return hasher.appending(2) + // } + // } + // } + // + // enum SomeEnumWithAssociatedValues { + // case A, B(Int), C(String, Int) + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // switch self { + // case A: + // return hasher.appending(0) + // case B(let a0): + // return hasher.appending(1).appending(a0) + // case C(let a0, let a1): + // return hasher.appending(2).appending(a0).appending(a1) + // } + // } + // } + auto parentDC = hashIntoDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); auto enumDecl = parentDC->getAsEnumOrEnumExtensionContext(); - SmallVector statements; - auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); Type enumType = selfDecl->getType(); - Type intType = C.getIntDecl()->getDeclaredType(); - auto resultVar = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Specifier::Var, - /*IsCaptureList*/ false, SourceLoc(), - C.getIdentifier("result"), intType, - hashValueDecl); - resultVar->setInterfaceType(intType); - resultVar->setImplicit(); - - // var result - Pattern *resultPat = new (C) NamedPattern(resultVar, /*implicit*/ true); - resultPat->setType(intType); - resultPat = new (C) TypedPattern(resultPat, TypeLoc::withoutLoc(intType)); - resultPat->setType(intType); - auto resultBind = PatternBindingDecl::create(C, SourceLoc(), - StaticSpellingKind::None, - SourceLoc(), - resultPat, nullptr, - hashValueDecl); + // hasher + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); unsigned index = 0; SmallVector cases; @@ -838,9 +903,12 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { for (auto elt : enumDecl->getAllElements()) { // case .(let a0, let a1, ...): SmallVector payloadVars; - SmallVector combineExprs; - auto payloadPattern = enumElementPayloadSubpattern(elt, 'a', hashValueDecl, + // := hasher + Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), + DeclNameLoc(), /*implicit*/ true); + + auto payloadPattern = enumElementPayloadSubpattern(elt, 'a', hashIntoDecl, payloadVars); auto pat = new (C) EnumElementPattern(TypeLoc::withoutLoc(enumType), SourceLoc(), SourceLoc(), @@ -853,17 +921,13 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { // If the enum has no associated values, we use the ordinal alone as the // hash value, because that is sufficient for a good distribution. If any // case does have associated values, then the ordinal is used as the first - // term combined into _combineHashValues, and the final result after - // combining the payload is passed to _mixInt to improve the distribution. + // term fed into the hasher. - // result = + // := .appending() { auto ordinalExpr = integerLiteralExpr(C, index++); - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(resultRef, SourceLoc(), - ordinalExpr, /*implicit*/ true); - combineExprs.emplace_back(ASTNode(assignExpr)); + hasherExpr = createHasherAppendingCall(C, parentDC, + hasherExpr, ordinalExpr); } if (!hasNoAssociatedValues) { @@ -872,15 +936,16 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { for (auto payloadVar : payloadVars) { auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); - // result = _combineHashValues(result, .hashValue) - auto combineExpr = combineHashValuesAssignmentExpr(C, resultVar, - payloadVarRef); - combineExprs.emplace_back(ASTNode(combineExpr)); + // := .appending() + hasherExpr = createHasherAppendingCall(C, parentDC, + hasherExpr, payloadVarRef); } } + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + SmallVector statements { ASTNode(returnStmt) }; auto hasBoundDecls = !payloadVars.empty(); - auto body = BraceStmt::create(C, SourceLoc(), combineExprs, SourceLoc()); + auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, hasBoundDecls, SourceLoc(), body)); } @@ -891,66 +956,38 @@ deriveBodyHashable_enum_hashValue(AbstractFunctionDecl *hashValueDecl) { auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef, SourceLoc(), cases, SourceLoc(), C); - statements.push_back(resultBind); - statements.push_back(switchStmt); - - // generate: return result - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true, - AccessSemantics::Ordinary, intType); - auto returnStmt = new (C) ReturnStmt(SourceLoc(), resultRef); - statements.push_back(returnStmt); - + SmallVector statements { ASTNode(switchStmt) }; auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); - hashValueDecl->setBody(body); + hashIntoDecl->setBody(body); } -/// Derive the body for the 'hashValue' getter for a struct. +/// Derive the body for the '_hash(into:)' method for a struct. static void -deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) { - auto parentDC = hashValueDecl->getDeclContext(); +deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // struct SomeStruct { + // var x: Int + // var y: String + // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // return hasher.appending(x).appending(y) + // } + // } + auto parentDC = hashIntoDecl->getDeclContext(); ASTContext &C = parentDC->getASTContext(); auto structDecl = parentDC->getAsStructOrStructExtensionContext(); - SmallVector statements; - auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); - Type intType = C.getIntDecl()->getDeclaredType(); + // hasher + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); - auto resultVar = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Specifier::Var, - /*IsCaptureList*/ false, SourceLoc(), - C.getIdentifier("result"), intType, - hashValueDecl); - resultVar->setInterfaceType(intType); - resultVar->setImplicit(); - - // var result: Int - Pattern *resultPat = new (C) NamedPattern(resultVar, /*implicit*/ true); - resultPat->setType(intType); - resultPat = new (C) TypedPattern(resultPat, TypeLoc::withoutLoc(intType)); - resultPat->setType(intType); - auto resultBind = PatternBindingDecl::create(C, SourceLoc(), - StaticSpellingKind::None, - SourceLoc(), - resultPat, nullptr, - hashValueDecl); - statements.push_back(resultBind); - - // result = 0 - { - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true); - auto assignExpr = new (C) AssignExpr(resultRef, SourceLoc(), - integerLiteralExpr(C, 0), - /*implicit*/ true); - statements.emplace_back(ASTNode(assignExpr)); - } + Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), + DeclNameLoc(), /*implicit*/ true); auto storedProperties = structDecl->getStoredProperties(/*skipInaccessible=*/true); - // For each stored property, generate a statement that combines its hash value - // into the result. + // For each stored property, generate a statement that feeds it into + // the hasher. for (auto propertyDecl : storedProperties) { auto propertyRef = new (C) DeclRefExpr(propertyDecl, DeclNameLoc(), /*implicit*/ true); @@ -958,74 +995,44 @@ deriveBodyHashable_struct_hashValue(AbstractFunctionDecl *hashValueDecl) { /*implicit*/ true); auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(), selfRef); - // result = _combineHashValues(result, .hashValue) - auto combineExpr = combineHashValuesAssignmentExpr(C, resultVar, - selfPropertyExpr); - statements.emplace_back(ASTNode(combineExpr)); - } - - { - // return result - auto resultRef = new (C) DeclRefExpr(resultVar, DeclNameLoc(), - /*implicit*/ true, - AccessSemantics::Ordinary, intType); - auto returnStmt = new (C) ReturnStmt(SourceLoc(), resultRef); - statements.push_back(returnStmt); + // := .appending(self.) + hasherExpr = createHasherAppendingCall(C, parentDC, + hasherExpr, selfPropertyExpr); } + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + SmallVector statements { ASTNode(returnStmt) }; auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); + hashIntoDecl->setBody(body); +} + +/// Derive the body for the 'hashValue' getter. +static void +deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { + auto parentDC = hashValueDecl->getDeclContext(); + ASTContext &C = parentDC->getASTContext(); + + // return _hashValue(for: self) + auto *hashFunc = C.getHashValueForDecl(); + auto hashExpr = new (C) DeclRefExpr(hashFunc, DeclNameLoc(), /*implicit*/ true); + auto selfDecl = hashValueDecl->getImplicitSelfDecl(); + auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), + /*implicit*/ true); + auto callExpr = CallExpr::createImplicit(C, hashExpr, { selfRef }, { C.Id_for }); + auto returnStmt = new (C) ReturnStmt(SourceLoc(), callExpr); + + SmallVector statements { returnStmt }; + auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + /*implicit*/ true); hashValueDecl->setBody(body); } -/// Derive a 'hashValue' implementation for an enum. +/// Derive a 'hashValue' implementation. static ValueDecl * deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, - NominalTypeDecl *typeDecl, - void (*bodySynthesizer)(AbstractFunctionDecl *)) { - // enum SomeEnum { - // case A, B, C - // @derived var hashValue: Int { - // var result: Int - // switch self { - // case A: - // result = 0 - // case B: - // result = 1 - // case C: - // result = 2 - // } - // return result - // } - // } - // - // enum SomeEnumWithAssociatedValues { - // case A, B(Int), C(String, Int) - // @derived var hashValue: Int { - // var result: Int - // switch self { - // case A: - // result = 0 - // case B(let a0): - // result = 1 - // result = _combineHashValues(result, a0.hashValue) - // case C(let a0, let a1): - // result = 2 - // result = _combineHashValues(result, a0.hashValue) - // result = _combineHashValues(result, a1.hashValue) - // } - // return result - // } - // } - // - // struct SomeStruct { - // var x: Int - // var y: String - // @derived var hashValue: Int { - // var result = 0 - // result = _combineHashValues(result, x.hashValue) - // result = _combineHashValues(result, y.hashValue) - // return result - // } + NominalTypeDecl *typeDecl) { + // @derived var hashValue: Int { + // return _hashValue(for: self) // } ASTContext &C = tc.Context; @@ -1068,7 +1075,7 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, /*GenericParams=*/nullptr, params, TypeLoc::withoutLoc(intType), parentDC); getterDecl->setImplicit(); - getterDecl->setBodySynthesizer(bodySynthesizer); + getterDecl->setBodySynthesizer(&deriveBodyHashable_hashValue); // Compute the type of hashValue(). Type methodType = FunctionType::get(TupleType::getEmpty(tc.Context), intType); @@ -1145,14 +1152,15 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, // Build the necessary decl. if (requirement->getBaseName() == "hashValue") { + // Also derive _hash(into:) -- it has a default implementation, so we + // wouldn't otherwise consider it as a candidate for synthesizing. if (theEnum) - return deriveHashable_hashValue(tc, parentDecl, theEnum, - &deriveBodyHashable_enum_hashValue); + deriveHashable_hashInto(tc, parentDecl, theEnum, + &deriveBodyHashable_enum_hashInto); else if (auto theStruct = dyn_cast(type)) - return deriveHashable_hashValue(tc, parentDecl, theStruct, - &deriveBodyHashable_struct_hashValue); - else - llvm_unreachable("todo"); + deriveHashable_hashInto(tc, parentDecl, theStruct, + &deriveBodyHashable_struct_hashInto); + return deriveHashable_hashValue(tc, parentDecl, type); } tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement); From 7721e3b56711d5f8372d4df0eb7d5e1a45ec8d31 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 19 Feb 2018 22:03:46 +0000 Subject: [PATCH 18/37] Synthesize _hash(into:) instead of providing a default implementation. This *almost* works, except the compiler doesn't currently support deriving _hash(into:) method implementations for imported enums that are implicitly Hashable. --- lib/AST/ASTContext.cpp | 3 - .../DerivedConformanceEquatableHashable.cpp | 192 ++++++++++++------ lib/Sema/DerivedConformances.cpp | 24 ++- stdlib/public/SDK/ObjectiveC/ObjectiveC.swift | 4 + stdlib/public/core/Hashable.swift | 15 +- stdlib/public/core/Integers.swift.gyb | 6 +- stdlib/public/core/PrefixWhile.swift.gyb | 2 +- 7 files changed, 167 insertions(+), 79 deletions(-) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 79257ae25ad0f..fc95bd63d3b39 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1079,9 +1079,6 @@ FuncDecl *ASTContext::getHashValueForDecl() const { if (Impl.HashValueForDecl) return Impl.HashValueForDecl; - auto resolver = getLazyResolver(); - auto hasherType = getUnsafeHasherDecl()->getDeclaredType(); - SmallVector results; lookupInSwiftModule("_hashValue", results); for (auto result : results) { diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 41a3eb3c8326b..9a6cd117cd3bc 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -23,6 +23,7 @@ #include "swift/AST/Module.h" #include "swift/AST/Pattern.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/ProtocolConformance.h" #include "swift/AST/Types.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/SmallString.h" @@ -750,12 +751,10 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { /// /// \param C The AST context to create the expression in. /// -/// \param DC The \c DeclContext to create any decls in. -/// /// \param hasher The base expression to make the call on. /// /// \param hashable The parameter to the call. -static CallExpr* createHasherAppendingCall(ASTContext &C, DeclContext *DC, +static CallExpr* createHasherAppendingCall(ASTContext &C, Expr* hasher, Expr* hashable) { // hasher.appending(_:) @@ -772,14 +771,15 @@ static CallExpr* createHasherAppendingCall(ASTContext &C, DeclContext *DC, C.AllocateCopy(argLabels)); } -static ValueDecl * +static FuncDecl * deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, NominalTypeDecl *typeDecl, void (*bodySynthesizer)(AbstractFunctionDecl *)) { // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher ASTContext &C = tc.Context; - + auto parentDC = cast(parentDecl); + // Expected type: (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher // Constructed as: // func type(input: Self, @@ -789,12 +789,11 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, Type hasherType = C.getUnsafeHasherDecl()->getDeclaredType(); - // Params: self (implicit), hasher - auto *selfDecl = ParamDecl::createSelf(SourceLoc(), typeDecl); + auto *selfDecl = ParamDecl::createSelf(SourceLoc(), parentDC); auto *hasherParam = new (C) ParamDecl(VarDecl::Specifier::Owned, SourceLoc(), SourceLoc(), C.Id_into, SourceLoc(), - C.Id_hasher, hasherType, typeDecl); + C.Id_hasher, hasherType, parentDC); hasherParam->setInterfaceType(hasherType); ParameterList *params[] = {ParameterList::createWithoutLoc(selfDecl), @@ -808,39 +807,29 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, /*Throws=*/false, SourceLoc(), nullptr, params, TypeLoc::withoutLoc(hasherType), - typeDecl); + parentDC); hashDecl->setImplicit(); hashDecl->setBodySynthesizer(bodySynthesizer); // Evaluate type of Self in (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher - Type selfType = typeDecl->getDeclaredInterfaceType(); + auto selfParam = computeSelfParam(hashDecl); auto inputTypeElt = TupleTypeElt(hasherType, C.Id_into); - auto inputType = TupleType::get(ArrayRef(inputTypeElt), C); + auto inputType = TupleType::get({inputTypeElt}, C); auto innerType = FunctionType::get(inputType, hasherType, FunctionType::ExtInfo()); Type interfaceType; - if (auto sig = typeDecl->getGenericSignatureOfContext()) { - hashDecl->setGenericEnvironment(typeDecl->getGenericEnvironmentOfContext()); - interfaceType = GenericFunctionType::get(sig, selfType, innerType, + if (auto sig = parentDC->getGenericSignatureOfContext()) { + hashDecl->setGenericEnvironment(parentDC->getGenericEnvironmentOfContext()); + interfaceType = GenericFunctionType::get(sig, {selfParam}, innerType, FunctionType::ExtInfo()); } else { // (Self) -> innerType == (_UnsafeHasher) -> _UnsafeHasher - interfaceType = FunctionType::get(selfType, innerType, + interfaceType = FunctionType::get({selfParam}, innerType, FunctionType::ExtInfo()); } hashDecl->setInterfaceType(interfaceType); - -// hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); - auto access = typeDecl->getFormalAccess(); - hashDecl->setAccess( - access != AccessLevel::Private ? access : AccessLevel::FilePrivate); - - // Inherit the @_versioned attribute. - if (typeDecl->getAttrs().hasAttribute()) { - auto *clonedAttr = new (C) VersionedAttr(/*implicit=*/true); - hashDecl->getAttrs().add(clonedAttr); - } + hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); // If the type was not imported, the derived conformance is either from the // type itself or an extension, in which case we will emit the declaration @@ -848,13 +837,63 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, if (typeDecl->hasClangNode()) tc.Context.addExternalDecl(hashDecl); - typeDecl->addMember(hashDecl); + cast(parentDecl)->addMember(hashDecl); return hashDecl; } +static bool +parentHasDerivedHashValueImplementation(AbstractFunctionDecl *hashIntoDecl) { + ASTContext &C = hashIntoDecl->getASTContext(); + auto parentDC = hashIntoDecl->getDeclContext(); + auto decl = parentDC->getAsDeclOrDeclExtensionContext(); + for (auto member: cast(decl)->getMembers()) { + if (auto varDecl = dyn_cast(member)) { + if (varDecl->getBaseName() == C.Id_hashValue) { + return varDecl->isImplicit(); + } + } + } + return false; +} + +/// Determine if hashValue has an explicit implementation, and if so, derive +/// the body for the _hash(into:) method to use it, and return true. +/// Returns false if hashValue has a derived implementation. +static bool +deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) { + // func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + // return hasher.appending(self.hashValue) + // } + auto parentDC = hashIntoDecl->getDeclContext(); + ASTContext &C = parentDC->getASTContext(); + + auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); + auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), + /*implicit*/ true); + auto hashValueExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(), + C.Id_hashValue, DeclNameLoc(), + /*implicit*/ true); + auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); + auto hasherRef = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), + DeclNameLoc(), /*implicit*/ true); + auto hasherExpr = createHasherAppendingCall(C, hasherRef, hashValueExpr); + + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + SmallVector statements { ASTNode(returnStmt) }; + auto body = BraceStmt::create(C, SourceLoc(), statements, + SourceLoc(), /*implicit*/ true); + hashIntoDecl->setBody(body); + return true; +} + /// Derive the body for the '_hash(into:)' method for an enum. static void deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { + if (!parentHasDerivedHashValueImplementation(hashIntoDecl)) { + deriveBodyHashable_compat_hashInto(hashIntoDecl); + return; + } + // enum SomeEnum { // case A, B, C // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { @@ -926,8 +965,7 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { // := .appending() { auto ordinalExpr = integerLiteralExpr(C, index++); - hasherExpr = createHasherAppendingCall(C, parentDC, - hasherExpr, ordinalExpr); + hasherExpr = createHasherAppendingCall(C, hasherExpr, ordinalExpr); } if (!hasNoAssociatedValues) { @@ -937,8 +975,7 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); // := .appending() - hasherExpr = createHasherAppendingCall(C, parentDC, - hasherExpr, payloadVarRef); + hasherExpr = createHasherAppendingCall(C, hasherExpr, payloadVarRef); } } @@ -964,6 +1001,11 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { /// Derive the body for the '_hash(into:)' method for a struct. static void deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { + if (!parentHasDerivedHashValueImplementation(hashIntoDecl)) { + deriveBodyHashable_compat_hashInto(hashIntoDecl); + return; + } + // struct SomeStruct { // var x: Int // var y: String @@ -980,8 +1022,8 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { // hasher auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); - Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), - DeclNameLoc(), /*implicit*/ true); + Expr* hasherExpr = new (C) DeclRefExpr(hasherParam, DeclNameLoc(), + /*implicit*/ true); auto storedProperties = structDecl->getStoredProperties(/*skipInaccessible=*/true); @@ -996,16 +1038,24 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(), selfRef); // := .appending(self.) - hasherExpr = createHasherAppendingCall(C, parentDC, - hasherExpr, selfPropertyExpr); + hasherExpr = createHasherAppendingCall(C, hasherExpr, selfPropertyExpr); } auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); SmallVector statements { ASTNode(returnStmt) }; - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); + auto body = BraceStmt::create(C, SourceLoc(), statements, + SourceLoc(), /*implicit*/ true); hashIntoDecl->setBody(body); } +/// Derive the body for the '_hash(into:)' method for a class. +static void +deriveBodyHashable_class_hashInto(AbstractFunctionDecl *hashIntoDecl) { + if (deriveBodyHashable_compat_hashInto(hashIntoDecl)) + return; + llvm_unreachable("Synthesized hashValue for class"); +} + /// Derive the body for the 'hashValue' getter. static void deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { @@ -1018,7 +1068,8 @@ deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { auto selfDecl = hashValueDecl->getImplicitSelfDecl(); auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), /*implicit*/ true); - auto callExpr = CallExpr::createImplicit(C, hashExpr, { selfRef }, { C.Id_for }); + auto callExpr = CallExpr::createImplicit(C, hashExpr, + { selfRef }, { C.Id_for }); auto returnStmt = new (C) ReturnStmt(SourceLoc(), callExpr); SmallVector statements { returnStmt }; @@ -1130,37 +1181,64 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, bool DerivedConformance::canDeriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { - auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable); - return canDeriveConformance(tc, type, hashableProto); + if (!isa(type) && !isa(type) && !isa(type)) { + return false; + } + // FIXME: This is not actually correct. We cannot promise to always + // provide a witness here in all cases. Unfortunately, figuring out + // whether this is actually possible requires a parent decl context. + // When the answer is no, DerivedConformance::deriveHashable will output + // its own diagnostics. + return true; } ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, Decl *parentDecl, NominalTypeDecl *type, ValueDecl *requirement) { - // Conformance can't be synthesized in an extension; we allow it as a special - // case for enums with no associated values to preserve source compatibility. + ASTContext &C = parentDecl->getASTContext(); + auto theEnum = dyn_cast(type); - if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && - type != parentDecl) { - auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable); - auto hashableType = hashableProto->getDeclaredType(); - tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, - hashableType); - return nullptr; + + // Hashable.hashValue + if (requirement->getBaseName() == C.Id_hashValue) { + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + // Refuse to synthesize hashValue if type isn't a struct or enum, or if it + // has non-Hashable stored properties/associated values. + if (!canDeriveConformance(tc, type, hashableProto)) { + tc.diagnose(parentDecl->getLoc(), diag::type_does_not_conform, + type->getDeclaredType(), hashableProto->getDeclaredType()); + return nullptr; + } + // hashValue can't be synthesized in an extension; we allow it as a special + // case for enums with no associated values to preserve source + // compatibility. + if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && + type != parentDecl) { + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + hashableProto->getDeclaredType()); + return nullptr; + } + + return deriveHashable_hashValue(tc, parentDecl, type); } - // Build the necessary decl. - if (requirement->getBaseName() == "hashValue") { - // Also derive _hash(into:) -- it has a default implementation, so we - // wouldn't otherwise consider it as a candidate for synthesizing. + // Hashable._hash(into:) + if (requirement->getBaseName() == C.Id_hash) { + // We always allow _hash(into:) to be synthesized. Its body changes + // depending on whether hashValue was implemented explicitly, and whether + // the type is a struct or an enum. if (theEnum) - deriveHashable_hashInto(tc, parentDecl, theEnum, - &deriveBodyHashable_enum_hashInto); + return deriveHashable_hashInto(tc, parentDecl, theEnum, + &deriveBodyHashable_enum_hashInto); else if (auto theStruct = dyn_cast(type)) - deriveHashable_hashInto(tc, parentDecl, theStruct, - &deriveBodyHashable_struct_hashInto); - return deriveHashable_hashValue(tc, parentDecl, type); + return deriveHashable_hashInto(tc, parentDecl, theStruct, + &deriveBodyHashable_struct_hashInto); + else if (auto theClass = dyn_cast(type)) + return deriveHashable_hashInto(tc, parentDecl, theClass, + &deriveBodyHashable_class_hashInto); + else + llvm_unreachable("todo"); } tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement); diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 610db73e59c8d..22108b6c8e17f 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -31,6 +31,13 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, if (!knownProtocol) return false; + if (*knownProtocol == KnownProtocolKind::Hashable) { + // We can always complete a partial Hashable implementation, and we can + // synthesize a full Hashable implementation for structs and enums with + // Hashable components. + return canDeriveHashable(tc, nominal, protocol); + } + if (auto *enumDecl = dyn_cast(nominal)) { switch (*knownProtocol) { // The presence of a raw type is an explicit declaration that @@ -38,12 +45,10 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, case KnownProtocolKind::RawRepresentable: return enumDecl->hasRawType(); - // Enums without associated values can implicitly derive Equatable and - // Hashable conformance. + // Enums without associated values can implicitly derive Equatable + // conformance. case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, enumDecl, protocol); - case KnownProtocolKind::Hashable: - return canDeriveHashable(tc, enumDecl, protocol); // @objc enums can explicitly derive their _BridgedNSError conformance. case KnownProtocolKind::BridgedNSError: @@ -87,13 +92,11 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, return true; } - // Structs can explicitly derive Equatable and Hashable conformance. + // Structs can explicitly derive Equatable conformance. if (auto structDecl = dyn_cast(nominal)) { switch (*knownProtocol) { case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, structDecl, protocol); - case KnownProtocolKind::Hashable: - return canDeriveHashable(tc, structDecl, protocol); default: return false; } @@ -162,6 +165,13 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, return getRequirement(KnownProtocolKind::Encodable); } + // Hashable._hash(into: _UnsafeHasher) -> _UnsafeHasher + if (name.isCompoundName() && name.getBaseName() == ctx.Id_hash) { + auto argumentNames = name.getArgumentNames(); + if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_into) + return getRequirement(KnownProtocolKind::Hashable); + } + return nullptr; } diff --git a/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift b/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift index dbf43f11c8882..f51cf2c8a0dac 100644 --- a/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift +++ b/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift @@ -210,6 +210,10 @@ extension NSObject : Equatable, Hashable { open var hashValue: Int { return hash } + + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(self.hashValue) + } } public func == (lhs: NSObject, rhs: NSObject) -> Bool { diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index f47bd6db20d87..caa838f74af68 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -109,17 +109,14 @@ public protocol Hashable : Equatable { /// your program. Do not save hash values to use during a future execution. var hashValue: Int { get } + /// Feed bits to be hashed into the hash function represented by `hasher`. + /// + /// If this requirement is not explicitly implemented, the compiler + /// automatically synthesizes an implementation for it. func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher } -extension Hashable { - @_inlineable - @inline(__always) - public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher.appending(self.hashValue) - } -} - +// Used in synthesized `hashValue` implementations. @inline(__always) public func _hashValue(for value: H) -> Int { var value = value @@ -176,7 +173,7 @@ public struct _UnsafeHasher { @effects(readonly) @inline(never) - public func appending(_ value: Int) -> _UnsafeHasher { + public func appending(bitPattern 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(_:)`. diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index b022e7b90c42a..761c6acfbf83d 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3610,10 +3610,12 @@ extension ${Self} : Hashable { @_inlineable // FIXME(sil-serialize-all) public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { % if bits <= word_bits: - return hasher.appending(Int(bitPattern: _lowWord)) + return hasher.appending(bitPattern: Int(bitPattern: _lowWord)) % else: var hasher = hasher - words.forEach { hasher = hasher.appending(Int(bitPattern: $0)) } + for word in words { + hasher = hasher.appending(bitPattern: Int(bitPattern: word)) + } return hasher % end } diff --git a/stdlib/public/core/PrefixWhile.swift.gyb b/stdlib/public/core/PrefixWhile.swift.gyb index bda5a26d9c8d0..92b92d0e8adfe 100644 --- a/stdlib/public/core/PrefixWhile.swift.gyb +++ b/stdlib/public/core/PrefixWhile.swift.gyb @@ -177,7 +177,7 @@ extension LazyPrefixWhileIndex : Hashable where Base.Index : Hashable { case .index(let value): return hasher.appending(value) case .pastEnd: - return hasher.appending(.max) + return hasher.appending(bitPattern: .max) } } } From 8391e70cdea76ca4792d5e72001fef7916c069f0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 22 Feb 2018 22:14:53 +0000 Subject: [PATCH 19/37] Cover all special cases of implicit Hashable conformance. - Assume derived _hash(into:) typechecks for imported types - Mark _hash(into:) as an external decl when the type itself was imported or synthesized --- .../DerivedConformanceEquatableHashable.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 9a6cd117cd3bc..d860427b647b9 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -831,12 +831,20 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, hashDecl->setInterfaceType(interfaceType); hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); - // If the type was not imported, the derived conformance is either from the - // type itself or an extension, in which case we will emit the declaration - // normally. - if (typeDecl->hasClangNode()) - tc.Context.addExternalDecl(hashDecl); - + // If we aren't synthesizing into an imported/derived type, the derived conformance is + // either from the type itself or an extension, in which case we will emit the + // declaration normally. + // + // We're checking for a source file here rather than the presence of a Clang + // node because otherwise we wouldn't catch synthesized Hashable structs, like + // SE-0112's imported Error types. + if (!isa(parentDC->getModuleScopeContext())) { + C.addExternalDecl(hashDecl); + // Assume the new function is already typechecked; TypeChecker::validateDecl + // would otherwise reject it. + hashDecl->setValidationStarted(); + } + cast(parentDecl)->addMember(hashDecl); return hashDecl; } From 7b853e3b95706934aed95341693dd69687a2fd52 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 16 Feb 2018 22:25:01 +0000 Subject: [PATCH 20/37] [benchmark] DictTest4: Remove obsolete _hash(into:) definition --- benchmark/single-source/DictTest4.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/benchmark/single-source/DictTest4.swift b/benchmark/single-source/DictTest4.swift index bc7f38548d572..d2849fae76e47 100644 --- a/benchmark/single-source/DictTest4.swift +++ b/benchmark/single-source/DictTest4.swift @@ -42,20 +42,6 @@ struct LargeKey: Hashable { self.p = value & 8 == 0 self.q = value & 16 == 0 } -#if true // FIXME remove once synthesized hashing generates _hash(into:) - func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher - .appending(i) - .appending(j) - .appending(k) - .appending(l) - .appending(m) - .appending(n) - .appending(o) - .appending(p) - .appending(q) - } -#endif } @inline(never) From d0228df23e851cd7310a5ae648b603b1c7baef2c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 23 Feb 2018 19:06:35 +0000 Subject: [PATCH 21/37] Review SipHash implementation and switch to SipHash13 --- stdlib/public/core/Hashable.swift | 12 +-- stdlib/public/core/SipHash.swift.gyb | 129 ++++++++++++------------ stdlib/public/core/StringHashable.swift | 4 +- 3 files changed, 71 insertions(+), 74 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index caa838f74af68..1492cb8374943 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -183,7 +183,7 @@ public struct _UnsafeHasher { // 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(value) + _state.pointee.append(value) return self } @@ -195,13 +195,13 @@ public struct _UnsafeHasher { @inline(__always) internal func _appending(_ value: Int) -> _UnsafeHasher { - _state.pointee._append(value) + _state.pointee.append(value) return self } @inline(__always) internal func _finalized() -> Int { - return _state.pointee._finalize() + return _state.pointee.finalize() } } @@ -215,7 +215,7 @@ internal struct _QuickHasher { } @inline(__always) - internal mutating func _append(_ value: Int) { + internal mutating func append(_ value: Int) { if _hash == 0 { _hash = value return @@ -224,9 +224,9 @@ internal struct _QuickHasher { } @inline(__always) - internal mutating func _finalize() -> Int { + internal mutating func finalize() -> Int { return _mixInt(_hash) } } -internal typealias _Hasher = _QuickHasher +internal typealias _Hasher = _SipHash13 diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index b16062aa1a636..d56552be3b5a9 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -19,17 +19,20 @@ /// * Daniel J. Bernstein //===----------------------------------------------------------------------===// +%{ +# Number of bits in the Builtin.Word type +word_bits = int(CMAKE_SIZEOF_VOID_P) * 8 +}% + @_fixed_layout // FIXME(sil-serialize-all) @_versioned internal enum _SipHashDetail { - @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) - internal static func _rotate(_ x: UInt64, leftBy amount: Int) -> UInt64 { - return (x &<< UInt64(amount)) | (x &>> UInt64(64 - amount)) + internal static func _rotate(_ x: UInt64, leftBy amount: UInt64) -> UInt64 { + return (x &<< amount) | (x &>> (64 - amount)) } - @_inlineable // FIXME(sil-serialize-all) @_versioned @inline(__always) internal static func _sipRound( @@ -74,11 +77,14 @@ struct ${Self} { @_versioned internal var v3: UInt64 = 0x7465646279746573 + /// This value holds the byte count and the pending bytes that haven't been + /// compressed yet, in the format that the finalization step needs. (The least + /// significant 56 bits hold the trailing bytes, while the most significant 8 + /// bits hold the count of bytes appended so far, mod 256.) @_versioned - internal var hashedByteCount: UInt64 = 0 + internal var tailAndByteCount: UInt64 = 0 -// @inline(never) - @_inlineable // FIXME(sil-serialize-all) + @inline(__always) public init(key: (UInt64, UInt64)) { v3 ^= key.1 v2 ^= key.0 @@ -86,84 +92,75 @@ struct ${Self} { v0 ^= key.0 } - @inline(never) + @inline(__always) public init() { self.init(key: _Hashing.secretKey) } - @_inlineable - @_versioned - internal init(_inlineable: Void) { - self.init(key: _Hashing.secretKey) - } - - @inline(never) - public mutating func append(_ m: Int) { - append_alwaysInline(m) + internal var byteCount: UInt64 { + @inline(__always) get { + return tailAndByteCount &>> 56 + } } - @inline(never) - public mutating func append(_ m: UInt64) { - append_alwaysInline(m) + internal var tail: UInt64 { + @inline(__always) get { + return tailAndByteCount & ~(0xFF &<< 56) + } } - @_inlineable - @_versioned @inline(__always) - 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 _compress(_ m: UInt64) { v3 ^= m - for _ in 0..<${c_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) - } + % for _ in range(0, c_rounds): + _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + % end v0 ^= m - hashedByteCount += 8 - } - - @inline(never) - public // @testable - mutating func finalize() -> Int { - return _finalize_alwaysInline() } - @inline(never) - @_inlineable - public // @testable - mutating func finalize(tail: UInt64, tailByteCount: Int) -> UInt64 { - return _finalize_alwaysInline(tail: tail, tailByteCount: tailByteCount) + @inline(__always) + public mutating func append(_ value: Int) { + let m = UInt64(_truncatingBits: value._lowWord) + % if word_bits == 64: + append(m) + % elif word_bits == 32: + if byteCount & 4 == 0 { + _sanityCheck(byteCount & 7 == 0 && tail == 0) + tailAndByteCount = (tailAndByteCount | m) &+ (4 &<< 56) + } else { + _sanityCheck(byteCount & 3 == 0) + _compress((m &<< 32) | tail) + tailAndByteCount = (byteCount &+ 4) &<< 56 + } + % else: + fatalError("Unsupported word width") + % end } - @_inlineable // FIXME(sil-serialize-all) - @_versioned @inline(__always) - internal mutating func _finalize_alwaysInline() -> Int { - return Int( - truncatingIfNeeded: _finalize_alwaysInline(tail: 0, tailByteCount: 0)) + public mutating func append(_ m: UInt64) { + % if word_bits == 64: + _compress(m) + tailAndByteCount = tailAndByteCount &+ (8 &<< 56) + % elif word_bits == 32: + if byteCount & 4 == 0 { + _sanityCheck(byteCount & 7 == 0 && tail == 0) + _compress(m) + tailAndByteCount = tailAndByteCount &+ (8 &<< 56) + } else { + _sanityCheck(byteCount & 3 == 0) + _compress((m &<< 32) | tail) + tailAndByteCount = ((byteCount &+ 8) &<< 56) | (m &>> 32) + } + % else: + fatalError("Unsupported word width") + % end } - @_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(tailByteCount) - let b: UInt64 = (hashedByteCount << 56) | tail - - v3 ^= b - for _ in 0..<${c_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) - } - v0 ^= b + public mutating func finalize() -> Int { + _compress(tailAndByteCount) v2 ^= 0xff @@ -171,7 +168,7 @@ struct ${Self} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) } - return v0 ^ v1 ^ v2 ^ v3 + return Int(_truncatingBits: (v0 ^ v1 ^ v2 ^ v3)._lowWord) } } % end diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 6b1d7858b2918..6124209658285 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -47,7 +47,7 @@ extension Unicode { // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher._append(Int(truncatingIfNeeded: element)) + hasher.append(Int(truncatingIfNeeded: element)) } } } @@ -74,7 +74,7 @@ extension Unicode { // Ignore zero valued collation elements. They don't participate in the // ordering relation. if element != 0 { - hasher._append(Int(truncatingIfNeeded: element)) + hasher.append(Int(truncatingIfNeeded: element)) } } } From caa2cc71c96e3e828c466299908417ce7b12781d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 14:48:29 +0000 Subject: [PATCH 22/37] FlattenCollection.Index.hashValue: Delegate to _hash(into:). --- stdlib/public/core/Flatten.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index 7718ab52b6e2f..659da19f917ee 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -233,7 +233,7 @@ extension FlattenCollection.Index : Comparable { extension FlattenCollection.Index : Hashable where Base.Index : Hashable, Base.Element.Index : Hashable { public var hashValue: Int { - return _combineHashValues(_inner?.hashValue ?? 0, _outer.hashValue) + return _hashValue(for: self) } public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { From 801004b683ee3f0b1692e3cd2ffb5020cb5892a2 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 14:50:39 +0000 Subject: [PATCH 23/37] [stdlib] Move _UnsafeHasher and its posse to Hashing.swift. --- stdlib/public/core/Hashable.swift | 95 +------------------------------ stdlib/public/core/Hashing.swift | 95 ++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 1492cb8374943..7c214000cbcd4 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -116,21 +116,11 @@ public protocol Hashable : Equatable { func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher } -// Used in synthesized `hashValue` implementations. +// Called by synthesized `hashValue` implementations. @inline(__always) public func _hashValue(for value: H) -> Int { var value = value - return withUnsafePointer(to: &value) { _hashValue(for: $0) } -} - -@_versioned -@inline(never) -@effects(readonly) // FIXME: Unjustified -internal func _hashValue(for pointer: UnsafePointer) -> Int { - var hasher = _Hasher() - return withUnsafeMutablePointer(to: &hasher) { p in - return pointer.pointee._hash(into: _UnsafeHasher(p))._finalized() - } + return withUnsafePointer(to: &value) { _UnsafeHasher.hashValue(for: $0) } } // Called by the SwiftValue implementation. @@ -149,84 +139,3 @@ internal func Hashable_hashValue_indirect( ) -> Int { 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 _rawState: UnsafeMutableRawPointer - - internal var _state: UnsafeMutablePointer<_Hasher> { - @inline(__always) - get { return _rawState.assumingMemoryBound(to: _Hasher.self) } - } - - @inline(__always) - @_versioned - internal init(_ state: UnsafeMutablePointer<_Hasher>) { - self._rawState = UnsafeMutableRawPointer(state) - } - - @effects(readonly) - @inline(never) - public func appending(bitPattern 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(value) - return self - } - - @_inlineable - @inline(__always) - public func appending(_ value: H) -> _UnsafeHasher { - return value._hash(into: self) - } - - @inline(__always) - internal func _appending(_ value: Int) -> _UnsafeHasher { - _state.pointee.append(value) - return self - } - - @inline(__always) - internal func _finalized() -> Int { - return _state.pointee.finalize() - } -} - -// FIXME: This is purely for benchmarking; to be removed. -internal struct _QuickHasher { - internal var _hash: Int - - @inline(__always) - internal init() { - _hash = 0 - } - - @inline(__always) - internal mutating func append(_ value: Int) { - if _hash == 0 { - _hash = value - return - } - _hash = _combineHashValues(_hash, value) - } - - @inline(__always) - internal mutating func finalize() -> Int { - return _mixInt(_hash) - } -} - -internal typealias _Hasher = _SipHash13 diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index 50669623305c6..b805e6f93e55b 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -25,7 +25,7 @@ import SwiftShims @_fixed_layout // FIXME(sil-serialize-all) public // @testable -struct _Hashing { +enum _Hashing { // FIXME(ABI)#41 : make this an actual public API. @_inlineable // FIXME(sil-serialize-all) public // SPI @@ -48,7 +48,7 @@ struct _Hashing { @_fixed_layout // FIXME(sil-serialize-all) public // @testable -struct _HashingDetail { +enum _HashingDetail { @_inlineable // FIXME(sil-serialize-all) public // @testable @@ -185,3 +185,94 @@ func _combineHashValues(_ firstValue: Int, _ secondValue: Int) -> Int { x ^= UInt(bitPattern: secondValue) &+ magic &+ (x &<< 6) &+ (x &>> 2) return Int(bitPattern: x) } + +/// 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 _rawState: UnsafeMutableRawPointer + + internal var _state: UnsafeMutablePointer<_Hasher> { + @inline(__always) + get { return _rawState.assumingMemoryBound(to: _Hasher.self) } + } + + @inline(__always) + @_versioned + internal init(_ state: UnsafeMutablePointer<_Hasher>) { + self._rawState = UnsafeMutableRawPointer(state) + } + + @_versioned + @inline(never) + @effects(readonly) // FIXME: Unjustified + static func hashValue(for pointer: UnsafePointer) -> Int { + var hasher = _Hasher() + return withUnsafeMutablePointer(to: &hasher) { p in + return pointer.pointee._hash(into: _UnsafeHasher(p))._finalized() + } + } + + @effects(readonly) + @inline(never) + public func appending(bitPattern 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(value) + return self + } + + @_inlineable + @inline(__always) + public func appending(_ value: H) -> _UnsafeHasher { + return value._hash(into: self) + } + + @inline(__always) + internal func _appending(_ value: Int) -> _UnsafeHasher { + _state.pointee.append(value) + return self + } + + @inline(__always) + internal func _finalized() -> Int { + return _state.pointee.finalize() + } +} + +// FIXME: This is purely for benchmarking; to be removed. +internal struct _QuickHasher { + internal var _hash: Int + + @inline(__always) + internal init() { + _hash = 0 + } + + @inline(__always) + internal mutating func append(_ value: Int) { + if _hash == 0 { + _hash = value + return + } + _hash = _combineHashValues(_hash, value) + } + + @inline(__always) + internal mutating func finalize() -> Int { + return _mixInt(_hash) + } +} + +internal typealias _Hasher = _SipHash13 From c5a63c9fa359d9ecb83e9a17346e12a6a2a0b3df Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 17:01:11 +0000 Subject: [PATCH 24/37] [stdlib] Update tests that assumed specific hash values or Dictionary/Set ordering --- test/Interpreter/SDK/Foundation_bridge.swift | 26 +++---- test/Interpreter/repl.swift | 2 +- test/stdlib/ReflectionHashing.swift | 77 ++++---------------- validation-test/stdlib/Dictionary.swift | 28 +++++-- validation-test/stdlib/FixedPoint.swift.gyb | 32 +++++--- validation-test/stdlib/Set.swift | 7 +- 6 files changed, 74 insertions(+), 98 deletions(-) diff --git a/test/Interpreter/SDK/Foundation_bridge.swift b/test/Interpreter/SDK/Foundation_bridge.swift index 6812ed5480254..609ca6c6ce55e 100644 --- a/test/Interpreter/SDK/Foundation_bridge.swift +++ b/test/Interpreter/SDK/Foundation_bridge.swift @@ -106,9 +106,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = World; -// CHECK-NEXT: 1 = Hello; -// CHECK-NEXT: } +// CHECK-DAG: 1 = Hello; +// CHECK-DAG: 2 = World; +// CHECK: } do { var dict: Dictionary = [1: "Hello", 2: "World"] let obj = _bridgeAnythingToObjectiveC(dict) @@ -116,9 +116,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = World; -// CHECK-NEXT: 1 = Hello; -// CHECK-NEXT: } +// CHECK-DAG: 1 = Hello; +// CHECK-DAG: 2 = World; +// CHECK: } do { var dict2 = [1: "Hello", 2: "World"] let obj = _bridgeAnythingToObjectiveC(dict2) @@ -126,9 +126,9 @@ do { } // CHECK: dictionary bridges to { -// CHECK-NEXT: 2 = "(\"World\", 2)"; -// CHECK-NEXT: 1 = "(\"Hello\", 1)"; -// CHECK-NEXT: } +// CHECK-DAG: 1 = "(\"Hello\", 1)"; +// CHECK-DAG: 2 = "(\"World\", 2)"; +// CHECK: } do { var dict3 = [1: ("Hello", 1), 2: ("World", 2)] let obj = _bridgeAnythingToObjectiveC(dict3) @@ -141,11 +141,11 @@ var dict4 = propListStr.propertyListFromStringsFileFormat()! var hello: NSString = "Hello" var world: NSString = "World" -// Print out the keys. We only check one of these because the order is -// nondeterministic. -// CHECK: Hello +// Print out the keys. +// CHECK-DAG: Bridged key: Hello +// CHECK-DAG: Bridged key: World for key in dict4.keys { - print(key.description) + print("Bridged key: \(key.description)") } // CHECK: Hello: 1 diff --git a/test/Interpreter/repl.swift b/test/Interpreter/repl.swift index 77654f0132b6a..e68e704568d2c 100644 --- a/test/Interpreter/repl.swift +++ b/test/Interpreter/repl.swift @@ -41,7 +41,7 @@ String(4.0) // CHECK: String = "4.0" 123 . hashValue -// CHECK: Int = 123{{$}} +// CHECK: Int = {{[0-9]+$}} // Check that we handle unmatched parentheses in REPL. 1+1) diff --git a/test/stdlib/ReflectionHashing.swift b/test/stdlib/ReflectionHashing.swift index abde47737c387..d25770900856a 100644 --- a/test/stdlib/ReflectionHashing.swift +++ b/test/stdlib/ReflectionHashing.swift @@ -2,14 +2,6 @@ // RUN: %target-run %t.out // REQUIRES: executable_test -// This test expects consistent hash code generation. However String instance -// generate different values on Linux depending on which version of libicu is -// installed. Therefore we run this test only on non-linux OSes, which is -// best described as having objc_interop. - -// REQUIRES: objc_interop - -// // This file contains reflection tests that depend on hash values. // Don't add other tests here. // @@ -36,46 +28,16 @@ Reflection.test("Dictionary") { var output = "" dump(dict, to: &output) -#if arch(i386) || arch(arm) - var expected = "" - expected += "▿ 5 key/value pairs\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Four\"\n" - expected += " - value: 4\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"One\"\n" - expected += " - value: 1\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Two\"\n" - expected += " - value: 2\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Five\"\n" - expected += " - value: 5\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Three\"\n" - expected += " - value: 3\n" -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + // The order of elements in output depends on the hash values of dict's items, + // which isn't deterministic. However, iterating over dict will get us the + // same elements in the same order. var expected = "" expected += "▿ 5 key/value pairs\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Three\"\n" - expected += " - value: 3\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Two\"\n" - expected += " - value: 2\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Four\"\n" - expected += " - value: 4\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"One\"\n" - expected += " - value: 1\n" - expected += " ▿ (2 elements)\n" - expected += " - key: \"Five\"\n" - expected += " - value: 5\n" -#else - fatalError("unimplemented") -#endif - + for (key, value) in dict { + expected += " ▿ (2 elements)\n" + expected += " - key: \"\(key)\"\n" + expected += " - value: \(value)\n" + } expectEqual(expected, output) } @@ -85,25 +47,14 @@ Reflection.test("Set") { var output = "" dump(s, to: &output) -#if arch(i386) || arch(arm) - var expected = "" - expected += "▿ 5 members\n" - expected += " - 3\n" - expected += " - 1\n" - expected += " - 5\n" - expected += " - 2\n" - expected += " - 4\n" -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + // The order of elements in output depends on the hash values of dict's items, + // which isn't deterministic. However, iterating over dict will get us the + // same elements in the same order. var expected = "" expected += "▿ 5 members\n" - expected += " - 5\n" - expected += " - 2\n" - expected += " - 3\n" - expected += " - 1\n" - expected += " - 4\n" -#else - fatalError("unimplemented") -#endif + for i in s { + expected += " - \(i)\n" + } expectEqual(expected, output) } diff --git a/validation-test/stdlib/Dictionary.swift b/validation-test/stdlib/Dictionary.swift index b2117d6597376..2eb86884076c5 100644 --- a/validation-test/stdlib/Dictionary.swift +++ b/validation-test/stdlib/Dictionary.swift @@ -4492,7 +4492,8 @@ DictionaryTestSuite.test("dropsBridgedCache") { } DictionaryTestSuite.test("getObjects:andKeys:") { - let d = ([1: "one", 2: "two"] as Dictionary) as NSDictionary + let native = [1: "one", 2: "two"] as Dictionary + let d = native as NSDictionary var keys = UnsafeMutableBufferPointer( start: UnsafeMutablePointer.allocate(capacity: 2), count: 2) var values = UnsafeMutableBufferPointer( @@ -4501,17 +4502,27 @@ DictionaryTestSuite.test("getObjects:andKeys:") { var vp = AutoreleasingUnsafeMutablePointer(values.baseAddress!) var null: AutoreleasingUnsafeMutablePointer? + let expectedKeys: [NSNumber] + let expectedValues: [NSString] + if native.first?.key == 1 { + expectedKeys = [1, 2] + expectedValues = ["one", "two"] + } else { + expectedKeys = [2, 1] + expectedValues = ["two", "one"] + } + d.available_getObjects(null, andKeys: null) // don't segfault d.available_getObjects(null, andKeys: kp) - expectEqual([2, 1] as [NSNumber], Array(keys)) + expectEqual(expectedKeys, Array(keys)) d.available_getObjects(vp, andKeys: null) - expectEqual(["two", "one"] as [NSString], Array(values)) + expectEqual(expectedValues, Array(values)) d.available_getObjects(vp, andKeys: kp) - expectEqual([2, 1] as [NSNumber], Array(keys)) - expectEqual(["two", "one"] as [NSString], Array(values)) + expectEqual(expectedKeys, Array(keys)) + expectEqual(expectedValues, Array(values)) } #endif @@ -4530,11 +4541,14 @@ DictionaryTestSuite.test("popFirst") { 2020: 2020, 3030: 3030, ] - let expected = Array(d.map{($0.0, $0.1)}) + let expected = [(1010, 1010), (2020, 2020), (3030, 3030)] while let element = d.popFirst() { popped.append(element) } - expectEqualSequence(expected, Array(popped)) { + // Note that removing an element may reorder remaining items, so we + // can't compare ordering here. + popped.sort(by: { $0.0 < $1.0 }) + expectEqualSequence(expected, popped) { (lhs: (Int, Int), rhs: (Int, Int)) -> Bool in lhs.0 == rhs.0 && lhs.1 == rhs.1 } diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index 6cecc0c23282a..be69abeb5206d 100644 --- a/validation-test/stdlib/FixedPoint.swift.gyb +++ b/validation-test/stdlib/FixedPoint.swift.gyb @@ -51,15 +51,6 @@ def prepare_bit_pattern(bit_pattern, dst_bits, dst_signed): if dst <= ((1 << (dst_bits - 1)) - 1): return dst return dst - mask - 1 - -def get_fixed_point_hash(bit_pattern, src_bits, word_bits): - if src_bits <= word_bits: - bit_pattern = prepare_bit_pattern(bit_pattern, src_bits, True) - return prepare_bit_pattern(bit_pattern, word_bits, True) - if src_bits == word_bits * 2: - return prepare_bit_pattern( - bit_pattern ^ (bit_pattern >> 32), word_bits, True) - }% //===----------------------------------------------------------------------===// @@ -233,6 +224,14 @@ _UnimplementedError() // 'Int.hashValue' property //===----------------------------------------------------------------------===// +func expectedHash(_ bits: [Int]) -> Int { + var hasher = _SipHash13() + for i in bits { + hasher.append(i) + } + return hasher.finalize() +} + %{ hash_value_test_template = gyb.parse_template("hash_value", @@ -250,7 +249,18 @@ FixedPoint.test("${Self}.hashValue") { % input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed) let input = get${Self}(${input}) let output = getInt(input.hashValue) - let expected = getInt(${get_fixed_point_hash(input, self_ty.bits, word_bits)}) + + + var hasher = _SipHash13() +% if self_ty.bits <= word_bits: + hasher.append(${prepare_bit_pattern(input, word_bits, True)}) +% elif self_ty.bits == word_bits * 2: + hasher.append(${prepare_bit_pattern(bit_pattern, word_bits, True)}) + hasher.append(${prepare_bit_pattern(bit_pattern >> 32, word_bits, True)}) +% else: + fatalError("Unsupported integer width") +% end + let expected = getInt(hasher.finalize()) expectEqual(expected, output) } @@ -269,7 +279,6 @@ FixedPoint.test("${Self}.hashValue") { ${gyb.execute_template( hash_value_test_template, prepare_bit_pattern=prepare_bit_pattern, - get_fixed_point_hash=get_fixed_point_hash, test_bit_patterns=test_bit_patterns, word_bits=32)} @@ -278,7 +287,6 @@ FixedPoint.test("${Self}.hashValue") { ${gyb.execute_template( hash_value_test_template, prepare_bit_pattern=prepare_bit_pattern, - get_fixed_point_hash=get_fixed_point_hash, test_bit_patterns=test_bit_patterns, word_bits=64)} diff --git a/validation-test/stdlib/Set.swift b/validation-test/stdlib/Set.swift index ff15a83abb8a5..f8a943541112a 100644 --- a/validation-test/stdlib/Set.swift +++ b/validation-test/stdlib/Set.swift @@ -3365,11 +3365,14 @@ SetTestSuite.test("popFirst") { do { var popped = [Int]() var s = Set([1010, 2020, 3030]) - let expected = Array(s) + let expected = [1010, 2020, 3030] while let element = s.popFirst() { popped.append(element) } - expectEqualSequence(expected, Array(popped)) + // Note that removing an element may reorder remaining items, so we + // can't compare ordering here. + popped.sort() + expectEqualSequence(expected, popped) expectTrue(s.isEmpty) } } From b76ff8cac3dbcc2c7f514b3c3d4c0015f2334a61 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 17:02:27 +0000 Subject: [PATCH 25/37] Update tests looking at Hashable members --- test/IDE/print_ast_tc_decls.swift | 4 ++++ test/IRGen/enum_derived.swift | 16 ++++++++++++---- test/SILGen/objc_bridging_any.swift | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/test/IDE/print_ast_tc_decls.swift b/test/IDE/print_ast_tc_decls.swift index 11b19f17bd11b..c844726d45ceb 100644 --- a/test/IDE/print_ast_tc_decls.swift +++ b/test/IDE/print_ast_tc_decls.swift @@ -587,6 +587,7 @@ struct d0200_EscapedIdentifiers { // PASS_COMMON-NEXT: {{^}} case `case`{{$}} // PASS_COMMON-NEXT: {{^}} {{.*}}static func __derived_enum_equals(_ a: d0200_EscapedIdentifiers.`enum`, _ b: d0200_EscapedIdentifiers.`enum`) -> Bool // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} }{{$}} class `class` {} @@ -1015,6 +1016,7 @@ enum d2000_EnumDecl1 { // PASS_COMMON-NEXT: {{^}} case ED1_Second{{$}} // PASS_COMMON-NEXT: {{^}} {{.*}}static func __derived_enum_equals(_ a: d2000_EnumDecl1, _ b: d2000_EnumDecl1) -> Bool // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} enum d2100_EnumDecl2 { @@ -1072,6 +1074,7 @@ enum d2300_EnumDeclWithValues1 : Int { // PASS_COMMON-NEXT: {{^}} case EDV2_Second{{$}} // PASS_COMMON-NEXT: {{^}} typealias RawValue = Int // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} init?(rawValue: Int){{$}} // PASS_COMMON-NEXT: {{^}} var rawValue: Int { get }{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} @@ -1085,6 +1088,7 @@ enum d2400_EnumDeclWithValues2 : Double { // PASS_COMMON-NEXT: {{^}} case EDV3_Second{{$}} // PASS_COMMON-NEXT: {{^}} typealias RawValue = Double // PASS_COMMON-NEXT: {{^}} var hashValue: Int { get }{{$}} +// PASS_COMMON-NEXT: {{^}} func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher{{$}} // PASS_COMMON-NEXT: {{^}} init?(rawValue: Double){{$}} // PASS_COMMON-NEXT: {{^}} var rawValue: Double { get }{{$}} // PASS_COMMON-NEXT: {{^}}}{{$}} diff --git a/test/IRGen/enum_derived.swift b/test/IRGen/enum_derived.swift index c52ddbd54ac6d..7645edc10d20f 100644 --- a/test/IRGen/enum_derived.swift +++ b/test/IRGen/enum_derived.swift @@ -5,8 +5,8 @@ import def_enum -// Check if the hashValue and == for an enum (without payload) are generated and -// check if that functions are compiled in an optimal way. +// Check if hashValue, _hash(into:) and == for an enum (without payload) are +// generated and check that functions are compiled in an optimal way. enum E { case E0 @@ -22,11 +22,19 @@ enum E { // CHECK: %2 = icmp eq i8 %0, %1 // CHECK: ret i1 %2 -// Check if the hashValue getter can be compiled to a simple zext instruction. +// Check for the presence of the hashValue getter. // CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$S12enum_derived1EO9hashValueSivg"(i8) // CHECK-TESTABLE-LABEL:define{{( protected)?}} swiftcc i{{.*}} @"$S12enum_derived1EO9hashValueSivg"(i8) -// CHECK: [[R:%.*]] = zext i8 %0 to i{{.*}} +// CHECK: ret i{{.*}} + +// Check if the _hash(into:) method can be compiled to a simple zext instruction +// followed by a call to _UnsafeHasher(appending:). + +// CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" +// CHECK-TESTABLE-LABEL:define{{( protected)?}} swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" +// CHECK: [[V:%.*]] = zext i8 %1 to i{{.*}} +// CHECK: [[R:%.*]] = tail call swiftcc i{{.*}} @"$Ss13_UnsafeHasherV9appending10bitPatternABSi_tF"(i{{.*}} [[V]], // CHECK: ret i{{.*}} [[R]] // Derived conformances from extensions diff --git a/test/SILGen/objc_bridging_any.swift b/test/SILGen/objc_bridging_any.swift index aa23de2da4002..142fd76a5f6ab 100644 --- a/test/SILGen/objc_bridging_any.swift +++ b/test/SILGen/objc_bridging_any.swift @@ -757,4 +757,5 @@ func bridgeOptionalFunctionToAnyObject(fn: (() -> ())?) -> AnyObject { // CHECK-LABEL: sil_witness_table shared [serialized] GenericOption: Hashable module objc_generics { // CHECK-NEXT: base_protocol Equatable: GenericOption: Equatable module objc_generics // CHECK-NEXT: method #Hashable.hashValue!getter.1: {{.*}} : @$SSo13GenericOptionas8HashableSCsACP9hashValueSivgTW +// CHECK-NEXT: method #Hashable._hash!1: {{.*}} : @$SSo13GenericOptionas8HashableSCsACP5_hash4intos13_UnsafeHasherVAH_tFTW // CHECK-NEXT: } From 729e587117df611ef3ec20cb28598caeee01b592 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 17:28:34 +0000 Subject: [PATCH 26/37] [Foundation] _CFObject: Add _hash(into:) implementation. --- stdlib/public/SDK/CoreFoundation/CoreFoundation.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift index 29b779905ef77..9d0eac7313779 100644 --- a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift +++ b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift @@ -17,6 +17,9 @@ extension _CFObject { public var hashValue: Int { return Int(bitPattern: CFHash(self)) } + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(self.hashValue) + } public static func ==(left: Self, right: Self) -> Bool { return CFEqual(left, right) } From c92a60567ae497e86bc7aef6800396658ac431ad Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 17:28:52 +0000 Subject: [PATCH 27/37] Remove test for _squeezeHashValue. --- validation-test/stdlib/Hashing.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/validation-test/stdlib/Hashing.swift b/validation-test/stdlib/Hashing.swift index 0018c423601c9..b8f0fa1882e83 100644 --- a/validation-test/stdlib/Hashing.swift +++ b/validation-test/stdlib/Hashing.swift @@ -70,24 +70,6 @@ HashingTestSuite.test("_mixInt/GoldenValues") { #endif } -HashingTestSuite.test("_squeezeHashValue/Int") { - // Check that the function can return values that cover the whole range. - func checkRange(_ r: Int) { - var results = Set() - for _ in 0..<(14 * r) { - let v = _squeezeHashValue(randInt(), r) - expectTrue(v < r) - results.insert(v) - } - expectEqual(r, results.count) - } - checkRange(1) - checkRange(2) - checkRange(4) - checkRange(8) - checkRange(16) -} - HashingTestSuite.test("String/hashValue/topBitsSet") { #if _runtime(_ObjC) #if arch(x86_64) || arch(arm64) From 3da030ec786b6ff5aef94dd165469d52ebe4978b Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 20:30:57 +0000 Subject: [PATCH 28/37] Revert to having a default implementation of _hash(into:) Always synthesizing _hash(into:) almost worked, but it still had trouble with some imported classes and it had diagnostics regressions that seemed impossible to resolve without a major rework of how DerivedConformance works. This approach works more reliably although it's a lot uglier. --- .../DerivedConformanceEquatableHashable.cpp | 188 ++++++++---------- lib/Sema/DerivedConformances.cpp | 24 +-- .../SDK/CoreFoundation/CoreFoundation.swift | 3 - stdlib/public/SDK/ObjectiveC/ObjectiveC.swift | 4 - stdlib/public/core/Hashable.swift | 9 +- 5 files changed, 92 insertions(+), 136 deletions(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index d860427b647b9..d94ae864de7d1 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -829,7 +829,16 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, FunctionType::ExtInfo()); } hashDecl->setInterfaceType(interfaceType); - hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); + + // FIXME: We need to adjust the access level because of the backhanded way we + // synthesize _hash(into:) in deriveHashable. The access level wouldn't + // otherwise matter. + + // hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); + hashDecl->setAccess(std::max(typeDecl->getFormalAccess(), AccessLevel::FilePrivate)); + if (typeDecl->getAttrs().hasAttribute()) { + hashDecl->getAttrs().add(new (C) VersionedAttr(/*implicit=*/true)); + } // If we aren't synthesizing into an imported/derived type, the derived conformance is // either from the type itself or an extension, in which case we will emit the @@ -849,59 +858,9 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, return hashDecl; } -static bool -parentHasDerivedHashValueImplementation(AbstractFunctionDecl *hashIntoDecl) { - ASTContext &C = hashIntoDecl->getASTContext(); - auto parentDC = hashIntoDecl->getDeclContext(); - auto decl = parentDC->getAsDeclOrDeclExtensionContext(); - for (auto member: cast(decl)->getMembers()) { - if (auto varDecl = dyn_cast(member)) { - if (varDecl->getBaseName() == C.Id_hashValue) { - return varDecl->isImplicit(); - } - } - } - return false; -} - -/// Determine if hashValue has an explicit implementation, and if so, derive -/// the body for the _hash(into:) method to use it, and return true. -/// Returns false if hashValue has a derived implementation. -static bool -deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) { - // func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - // return hasher.appending(self.hashValue) - // } - auto parentDC = hashIntoDecl->getDeclContext(); - ASTContext &C = parentDC->getASTContext(); - - auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); - auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), - /*implicit*/ true); - auto hashValueExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(), - C.Id_hashValue, DeclNameLoc(), - /*implicit*/ true); - auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); - auto hasherRef = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), - DeclNameLoc(), /*implicit*/ true); - auto hasherExpr = createHasherAppendingCall(C, hasherRef, hashValueExpr); - - auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); - SmallVector statements { ASTNode(returnStmt) }; - auto body = BraceStmt::create(C, SourceLoc(), statements, - SourceLoc(), /*implicit*/ true); - hashIntoDecl->setBody(body); - return true; -} - /// Derive the body for the '_hash(into:)' method for an enum. static void deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { - if (!parentHasDerivedHashValueImplementation(hashIntoDecl)) { - deriveBodyHashable_compat_hashInto(hashIntoDecl); - return; - } - // enum SomeEnum { // case A, B, C // @derived func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { @@ -1009,11 +968,6 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { /// Derive the body for the '_hash(into:)' method for a struct. static void deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { - if (!parentHasDerivedHashValueImplementation(hashIntoDecl)) { - deriveBodyHashable_compat_hashInto(hashIntoDecl); - return; - } - // struct SomeStruct { // var x: Int // var y: String @@ -1056,14 +1010,6 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { hashIntoDecl->setBody(body); } -/// Derive the body for the '_hash(into:)' method for a class. -static void -deriveBodyHashable_class_hashInto(AbstractFunctionDecl *hashIntoDecl) { - if (deriveBodyHashable_compat_hashInto(hashIntoDecl)) - return; - llvm_unreachable("Synthesized hashValue for class"); -} - /// Derive the body for the 'hashValue' getter. static void deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { @@ -1189,15 +1135,33 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, bool DerivedConformance::canDeriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { - if (!isa(type) && !isa(type) && !isa(type)) { - return false; + auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable); + return canDeriveConformance(tc, type, hashableProto); +} + +static ValueDecl * +getHashIntoRequirement(ASTContext &C) { + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + for (auto member: hashableProto->getMembers()) { + if (auto fd = dyn_cast(member)) { + if (fd->getBaseName() == C.Id_hash) + return fd; + } } - // FIXME: This is not actually correct. We cannot promise to always - // provide a witness here in all cases. Unfortunately, figuring out - // whether this is actually possible requires a parent decl context. - // When the answer is no, DerivedConformance::deriveHashable will output - // its own diagnostics. - return true; + return nullptr; +} + +static ProtocolConformance * +getHashableConformance(Decl *parentDecl) { + ASTContext &C = parentDecl->getASTContext(); + auto DC = cast(parentDecl); + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + for (auto conformance: DC->getLocalConformances()) { + if (conformance->getProtocol() == hashableProto) { + return conformance; + } + } + return nullptr; } ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, @@ -1205,49 +1169,55 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { ASTContext &C = parentDecl->getASTContext(); + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + // Conformance can't be synthesized in an extension; we allow it as a special + // case for enums with no associated values to preserve source compatibility. auto theEnum = dyn_cast(type); - - // Hashable.hashValue + if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && + type != parentDecl) { + auto hashableType = hashableProto->getDeclaredType(); + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + hashableType); + return nullptr; + } + + // var hashValue: Int if (requirement->getBaseName() == C.Id_hashValue) { - auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); - // Refuse to synthesize hashValue if type isn't a struct or enum, or if it - // has non-Hashable stored properties/associated values. - if (!canDeriveConformance(tc, type, hashableProto)) { - tc.diagnose(parentDecl->getLoc(), diag::type_does_not_conform, - type->getDeclaredType(), hashableProto->getDeclaredType()); - return nullptr; - } - // hashValue can't be synthesized in an extension; we allow it as a special - // case for enums with no associated values to preserve source - // compatibility. - if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && - type != parentDecl) { - tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, - hashableProto->getDeclaredType()); - return nullptr; + auto hashValueDecl = deriveHashable_hashValue(tc, parentDecl, type); + + // Also derive _hash(into:) -- it has a default implementation, so we + // wouldn't otherwise consider it as a candidate for synthesizing. + // + // FIXME: This assumes that _hash(into:) hasn't already been resolved. It + // would be nicer to remove the default implementation and independently + // synthesize either/both of the two Hashable requirements as needed; + // however, synthesizing methods (as opposed to properties) into imported + // types is not yet fully supported. + if (auto hashIntoReq = getHashIntoRequirement(C)) { + AbstractFunctionDecl::BodySynthesizer hashIntoSynthesizer; + if (theEnum) + hashIntoSynthesizer = &deriveBodyHashable_enum_hashInto; + else if (auto theStruct = dyn_cast(type)) + hashIntoSynthesizer = &deriveBodyHashable_struct_hashInto; + else + llvm_unreachable(""); + auto hashIntoDecl = deriveHashable_hashInto(tc, parentDecl, type, + hashIntoSynthesizer); +#if !defined(NDEBUG) + if (hashIntoDecl) { + auto conformance = getHashableConformance(parentDecl); + auto witnessDecl = conformance->getWitnessDecl(hashIntoReq, &tc); + if (witnessDecl != hashIntoDecl) { + tc.diagnose(requirement->getLoc(), + diag::broken_hashable_requirement); + } + } +#endif } - - return deriveHashable_hashValue(tc, parentDecl, type); + return hashValueDecl; } - // Hashable._hash(into:) - if (requirement->getBaseName() == C.Id_hash) { - // We always allow _hash(into:) to be synthesized. Its body changes - // depending on whether hashValue was implemented explicitly, and whether - // the type is a struct or an enum. - if (theEnum) - return deriveHashable_hashInto(tc, parentDecl, theEnum, - &deriveBodyHashable_enum_hashInto); - else if (auto theStruct = dyn_cast(type)) - return deriveHashable_hashInto(tc, parentDecl, theStruct, - &deriveBodyHashable_struct_hashInto); - else if (auto theClass = dyn_cast(type)) - return deriveHashable_hashInto(tc, parentDecl, theClass, - &deriveBodyHashable_class_hashInto); - else - llvm_unreachable("todo"); - } tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement); return nullptr; diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 22108b6c8e17f..610db73e59c8d 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -31,13 +31,6 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, if (!knownProtocol) return false; - if (*knownProtocol == KnownProtocolKind::Hashable) { - // We can always complete a partial Hashable implementation, and we can - // synthesize a full Hashable implementation for structs and enums with - // Hashable components. - return canDeriveHashable(tc, nominal, protocol); - } - if (auto *enumDecl = dyn_cast(nominal)) { switch (*knownProtocol) { // The presence of a raw type is an explicit declaration that @@ -45,10 +38,12 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, case KnownProtocolKind::RawRepresentable: return enumDecl->hasRawType(); - // Enums without associated values can implicitly derive Equatable - // conformance. + // Enums without associated values can implicitly derive Equatable and + // Hashable conformance. case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, enumDecl, protocol); + case KnownProtocolKind::Hashable: + return canDeriveHashable(tc, enumDecl, protocol); // @objc enums can explicitly derive their _BridgedNSError conformance. case KnownProtocolKind::BridgedNSError: @@ -92,11 +87,13 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, return true; } - // Structs can explicitly derive Equatable conformance. + // Structs can explicitly derive Equatable and Hashable conformance. if (auto structDecl = dyn_cast(nominal)) { switch (*knownProtocol) { case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, structDecl, protocol); + case KnownProtocolKind::Hashable: + return canDeriveHashable(tc, structDecl, protocol); default: return false; } @@ -165,13 +162,6 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, return getRequirement(KnownProtocolKind::Encodable); } - // Hashable._hash(into: _UnsafeHasher) -> _UnsafeHasher - if (name.isCompoundName() && name.getBaseName() == ctx.Id_hash) { - auto argumentNames = name.getArgumentNames(); - if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_into) - return getRequirement(KnownProtocolKind::Hashable); - } - return nullptr; } diff --git a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift index 9d0eac7313779..29b779905ef77 100644 --- a/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift +++ b/stdlib/public/SDK/CoreFoundation/CoreFoundation.swift @@ -17,9 +17,6 @@ extension _CFObject { public var hashValue: Int { return Int(bitPattern: CFHash(self)) } - public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher.appending(self.hashValue) - } public static func ==(left: Self, right: Self) -> Bool { return CFEqual(left, right) } diff --git a/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift b/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift index f51cf2c8a0dac..dbf43f11c8882 100644 --- a/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift +++ b/stdlib/public/SDK/ObjectiveC/ObjectiveC.swift @@ -210,10 +210,6 @@ extension NSObject : Equatable, Hashable { open var hashValue: Int { return hash } - - public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher.appending(self.hashValue) - } } public func == (lhs: NSObject, rhs: NSObject) -> Bool { diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 7c214000cbcd4..959802184b510 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -110,12 +110,15 @@ public protocol Hashable : Equatable { var hashValue: Int { get } /// Feed bits to be hashed into the hash function represented by `hasher`. - /// - /// If this requirement is not explicitly implemented, the compiler - /// automatically synthesizes an implementation for it. func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher } +extension Hashable { + public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(self.hashValue) + } +} + // Called by synthesized `hashValue` implementations. @inline(__always) public func _hashValue(for value: H) -> Int { From b013a5213142cc18f82066981c8d1af0c8f6f26b Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 20:31:08 +0000 Subject: [PATCH 29/37] Update tests looking at code completions in Hashable types. --- test/IDE/complete_enum_elements.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/IDE/complete_enum_elements.swift b/test/IDE/complete_enum_elements.swift index 21b573010f855..486cefb176f96 100644 --- a/test/IDE/complete_enum_elements.swift +++ b/test/IDE/complete_enum_elements.swift @@ -86,11 +86,13 @@ enum FooEnum { // FOO_ENUM_NO_DOT: Begin completions // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_NO_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: ._hash({#self: FooEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(FooEnum) // FOO_ENUM_NO_DOT-NEXT: End completions // FOO_ENUM_DOT: Begin completions // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: _hash({#self: FooEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(FooEnum) // FOO_ENUM_DOT-NEXT: End completions // FOO_ENUM_DOT_ELEMENTS: Begin completions, 2 items @@ -231,17 +233,19 @@ enum QuxEnum : Int { // QUX_ENUM_TYPE_CONTEXT-DAG: Decl[EnumElement]/ExprSpecific: .Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_TYPE_CONTEXT: End completions -// QUX_ENUM_NO_DOT: Begin completions, 4 items +// QUX_ENUM_NO_DOT: Begin completions, 5 items // QUX_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Qux1[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: Decl[TypeAlias]/CurrNominal: .RawValue[#Int#]{{; name=.+$}} +// QUX_ENUM_NO_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: ._hash({#self: QuxEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(QuxEnum) // QUX_ENUM_NO_DOT-NEXT: Decl[Constructor]/CurrNominal: ({#rawValue: Int#})[#QuxEnum?#]{{; name=.+$}} // QUX_ENUM_NO_DOT-NEXT: End completions -// QUX_ENUM_DOT: Begin completions, 4 items +// QUX_ENUM_DOT: Begin completions, 5 items // QUX_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Qux1[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Qux2[#QuxEnum#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: Decl[TypeAlias]/CurrNominal: RawValue[#Int#]{{; name=.+$}} +// QUX_ENUM_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: _hash({#self: QuxEnum#})[#(into: _UnsafeHasher) -> _UnsafeHasher#]; name=_hash(QuxEnum) // QUX_ENUM_DOT-NEXT: Decl[Constructor]/CurrNominal: init({#rawValue: Int#})[#QuxEnum?#]{{; name=.+$}} // QUX_ENUM_DOT-NEXT: End completions From 9dbb3c945e891a523ca07bd0dec0a8a2237bef08 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 20:34:39 +0000 Subject: [PATCH 30/37] StdlibUnittest: zero out the hash key in TestSuite.init. This eliminates the chance of random test failures due to changing hash values, freak hash collision situations and unstable Dictionary ordering. On the other hand, it allows tests to rely on specific hash values, which is unwise. It also requires that a TestSuite be initialized before the first element is inserted into any Set or Dictionary. --- stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb index dacabd5a8e202..e1783b837e284 100644 --- a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb +++ b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb @@ -1089,6 +1089,15 @@ struct PersistentState { static var runNoTestsWasCalled: Bool = false static var ranSomething: Bool = false static var complaintInstalled = false + static var hashingKeyOverridden = false + + static func overrideHashingKey() { + if !hashingKeyOverridden { + // FIXME: This has to run before creating the first Set/Dictionary + _Hashing.secretKey = (0, 0) + hashingKeyOverridden = true + } + } static func complainIfNothingRuns() { if !complaintInstalled { @@ -1200,6 +1209,7 @@ func stopTrackingObjects(_: UnsafePointer) -> Int public final class TestSuite { public init(_ name: String) { + PersistentState.overrideHashingKey() self.name = name _precondition( _testNameToIndex[name] == nil, From 9f08836477724d822fdade22825d9a2d0384aee4 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 20:39:44 +0000 Subject: [PATCH 31/37] [DELETE] Temporarily disable SipHash tests. --- validation-test/stdlib/SipHash.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/validation-test/stdlib/SipHash.swift b/validation-test/stdlib/SipHash.swift index cbf5edb070db4..34fa85ef6ec0a 100644 --- a/validation-test/stdlib/SipHash.swift +++ b/validation-test/stdlib/SipHash.swift @@ -1,5 +1,8 @@ // RUN: %target-run-simple-swiftgyb // REQUIRES: executable_test +// +// Temporarily disabled; needs updating to streamlined SipHash interface. +// XFAIL: * import StdlibUnittest From 908318c2403fc222355d64074517db3dab5b1a76 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 27 Feb 2018 21:19:17 +0000 Subject: [PATCH 32/37] [stdlib] SipHash: Fix 32-bit platform support Also, support appending 32-bit values on 64-bit platforms, too. --- stdlib/public/core/SipHash.swift.gyb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index d56552be3b5a9..914297d74dcab 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -97,12 +97,14 @@ struct ${Self} { self.init(key: _Hashing.secretKey) } + @_versioned internal var byteCount: UInt64 { @inline(__always) get { return tailAndByteCount &>> 56 } } + @_versioned internal var tail: UInt64 { @inline(__always) get { return tailAndByteCount & ~(0xFF &<< 56) @@ -121,10 +123,18 @@ struct ${Self} { @inline(__always) public mutating func append(_ value: Int) { - let m = UInt64(_truncatingBits: value._lowWord) % if word_bits == 64: - append(m) + append(UInt64(_truncatingBits: value._lowWord)) % elif word_bits == 32: + append(UInt32(_truncatingBits: value._lowWord)) + % else: + fatalError("Unsupported word width") + % end + } + + @inline(__always) + public mutating func append(_ value: UInt32) { + let m = UInt64(_truncatingBits: value._lowWord) if byteCount & 4 == 0 { _sanityCheck(byteCount & 7 == 0 && tail == 0) tailAndByteCount = (tailAndByteCount | m) &+ (4 &<< 56) @@ -133,17 +143,10 @@ struct ${Self} { _compress((m &<< 32) | tail) tailAndByteCount = (byteCount &+ 4) &<< 56 } - % else: - fatalError("Unsupported word width") - % end } @inline(__always) public mutating func append(_ m: UInt64) { - % if word_bits == 64: - _compress(m) - tailAndByteCount = tailAndByteCount &+ (8 &<< 56) - % elif word_bits == 32: if byteCount & 4 == 0 { _sanityCheck(byteCount & 7 == 0 && tail == 0) _compress(m) @@ -153,9 +156,6 @@ struct ${Self} { _compress((m &<< 32) | tail) tailAndByteCount = ((byteCount &+ 8) &<< 56) | (m &>> 32) } - % else: - fatalError("Unsupported word width") - % end } @inline(__always) From fd590e4a08e861ade9b71d8af738079c909ad3cd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 28 Feb 2018 16:06:54 +0000 Subject: [PATCH 33/37] [AST] Remove _combineHashValues and _mixInt from ASTContext --- include/swift/AST/ASTContext.h | 7 ------ lib/AST/ASTContext.cpp | 46 ---------------------------------- 2 files changed, 53 deletions(-) diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index 561b63ce3357b..856d4c48c4e97 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -471,13 +471,6 @@ class ASTContext { /// Retrieve the declaration of Swift.==(Int, Int) -> Bool. FuncDecl *getEqualIntDecl() const; - /// Retrieve the declaration of - /// Swift._combineHashValues(Int, Int) -> Int. - FuncDecl *getCombineHashValuesDecl() const; - - /// Retrieve the declaration of Swift._mixInt(Int) -> Int. - FuncDecl *getMixIntDecl() const; - /// Retrieve the declaration of Swift._hashValue(for: H) -> Int. FuncDecl *getHashValueForDecl() const; diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index ac6fdd095acb0..2f55051161edf 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -189,12 +189,6 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) /// func ==(Int, Int) -> Bool FuncDecl *EqualIntDecl = nullptr; - /// func _combineHashValues(Int, Int) -> Int - FuncDecl *CombineHashValuesDecl = nullptr; - - /// func _mixInt(Int) -> Int - FuncDecl *MixIntDecl = nullptr; - /// func _hashValue(for: H) -> Int FuncDecl *HashValueForDecl = nullptr; @@ -992,46 +986,6 @@ FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const { return decl; } -FuncDecl *ASTContext::getCombineHashValuesDecl() const { - if (Impl.CombineHashValuesDecl) - return Impl.CombineHashValuesDecl; - - auto resolver = getLazyResolver(); - auto intType = getIntDecl()->getDeclaredType(); - - auto callback = [&](Type inputType, Type resultType) { - // Look for the signature (Int, Int) -> Int - auto tupleType = dyn_cast(inputType.getPointer()); - assert(tupleType); - return tupleType->getNumElements() == 2 && - tupleType->getElementType(0)->isEqual(intType) && - tupleType->getElementType(1)->isEqual(intType) && - resultType->isEqual(intType); - }; - - auto decl = lookupLibraryIntrinsicFunc( - *this, "_combineHashValues", resolver, callback); - Impl.CombineHashValuesDecl = decl; - return decl; -} - -FuncDecl *ASTContext::getMixIntDecl() const { - if (Impl.MixIntDecl) - return Impl.MixIntDecl; - - auto resolver = getLazyResolver(); - auto intType = getIntDecl()->getDeclaredType(); - - auto callback = [&](Type inputType, Type resultType) { - // Look for the signature (Int) -> Int - return inputType->isEqual(intType) && resultType->isEqual(intType); - }; - - auto decl = lookupLibraryIntrinsicFunc(*this, "_mixInt", resolver, callback); - Impl.MixIntDecl = decl; - return decl; -} - FuncDecl *ASTContext::getHashValueForDecl() const { if (Impl.HashValueForDecl) return Impl.HashValueForDecl; From 24856cf52c1a33d92420719baa5ccbc7ba2bed49 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 28 Feb 2018 17:52:40 +0000 Subject: [PATCH 34/37] [SQUASH] Fix typo --- stdlib/public/core/StringHashable.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 6124209658285..3bb2ff8bafcc8 100644 --- a/stdlib/public/core/StringHashable.swift +++ b/stdlib/public/core/StringHashable.swift @@ -100,7 +100,7 @@ extension _UnmanagedString where CodeUnit == UInt8 { #else var hasher = _Hasher() Unicode.hashASCII(self.buffer, into: &hasher) - return hasher._finalize() + return hasher.finalize() #endif // _runtime(_ObjC) } } @@ -120,7 +120,7 @@ extension _UnmanagedString where CodeUnit == UTF16.CodeUnit { #else var hasher = _Hasher() Unicode.hashUTF16(self.buffer, into: &hasher) - return hasher._finalize() + return hasher.finalize() #endif // _runtime(_ObjC) } } @@ -142,8 +142,10 @@ extension _UnmanagedOpaqueString { let buffer = UnsafeMutableBufferPointer(start: p, count: count) _copy(into: buffer) var hasher = _Hasher() - Unicode.hashASCII(buffer, into: &hasher) - return hasher._finalize() + Unicode.hashUTF16( + UnsafeBufferPointer(start: p, count: count), + into: &hasher) + return hasher.finalize() #endif } } From e14aba0d40096ea5ca7525af95a372b201b06162 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 28 Feb 2018 17:12:17 +0000 Subject: [PATCH 35/37] [stdlib] KeyPathComponent: Switch to new-style hashing --- lib/SILGen/SILGenExpr.cpp | 4 +- stdlib/public/core/KeyPath.swift | 54 ++++++++++++------------- test/stdlib/KeyPathImplementation.swift | 13 ++++-- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 9cf10dd07a7a9..9f303cd9510a3 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3584,8 +3584,8 @@ getOrCreateKeyPathEqualsAndHash(SILGenFunction &SGF, SILValue hashCode; - // TODO: Combine hashes of the indexes. There isn't a great hash combining - // interface in the standard library to do this yet. + // TODO: Combine hashes of the indexes using an _UnsafeHasher passed in as + // an extra parameter. { auto &index = indexes[0]; diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 893466bf20452..c9d4c6e85e913 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -484,6 +484,7 @@ internal struct ComputedArgumentWitnesses { (_ xInstanceArguments: UnsafeRawPointer, _ yInstanceArguments: UnsafeRawPointer, _ size: Int) -> Bool + // FIXME(hashing) Pass in, append to and return _UnsafeHasher instead internal typealias Hash = @convention(thin) (_ instanceArguments: UnsafeRawPointer, _ size: Int) -> Int @@ -595,46 +596,45 @@ internal enum KeyPathComponent: Hashable { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) internal var hashValue: Int { - var hash: Int = 0 - func mixHashFromArgument(_ argument: KeyPathComponent.ArgumentRef?) { + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + internal func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { + var hasher = hasher + func appendHashFromArgument( + _ argument: KeyPathComponent.ArgumentRef? + ) { if let argument = argument { - let addedHash = argument.witnesses.pointee.hash( - argument.data.baseAddress.unsafelyUnwrapped, - argument.data.count) - // Returning 0 indicates that the arguments should not impact the - // hash value of the overall key path. - if addedHash != 0 { - hash ^= _mixInt(addedHash) - } + hasher = hasher.appending( + argument.witnesses.pointee.hash( + argument.data.baseAddress.unsafelyUnwrapped, + argument.data.count)) } } switch self { case .struct(offset: let a): - hash ^= _mixInt(0) - hash ^= _mixInt(a) + hasher = hasher.appending(0).appending(a) case .class(offset: let b): - hash ^= _mixInt(1) - hash ^= _mixInt(b) + hasher = hasher.appending(1).appending(b) case .optionalChain: - hash ^= _mixInt(2) + hasher = hasher.appending(2) case .optionalForce: - hash ^= _mixInt(3) + hasher = hasher.appending(3) case .optionalWrap: - hash ^= _mixInt(4) + hasher = hasher.appending(4) case .get(id: let id, get: _, argument: let argument): - hash ^= _mixInt(5) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher = hasher.appending(5).appending(id) + appendHashFromArgument(argument) case .mutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(6) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher.appending(6).appending(id) + appendHashFromArgument(argument) case .nonmutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(7) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher.appending(7).appending(id) + appendHashFromArgument(argument) } - return hash + return hasher } } diff --git a/test/stdlib/KeyPathImplementation.swift b/test/stdlib/KeyPathImplementation.swift index 9ab1af0eb4488..98d93ded7b582 100644 --- a/test/stdlib/KeyPathImplementation.swift +++ b/test/stdlib/KeyPathImplementation.swift @@ -118,7 +118,8 @@ struct ComputedArgumentWitnesses { _ size: Int) -> Bool typealias Hash = @convention(thin) (_ instanceArguments: UnsafeRawPointer, - _ size: Int) -> Int + _ size: Int, + _ hasher: _UnsafeHasher) -> _UnsafeHasher let destroy: Destroy? let copy: Copy @@ -965,10 +966,14 @@ func testEquals(_ a: UnsafeRawPointer, } var numberOfHashOperations = 0 -func testHash(_ a: UnsafeRawPointer, _ count: Int) -> Int { +func testHash( + _ a: UnsafeRawPointer, + _ count: Int, + _ hasher: _UnsafeHasher +) -> _UnsafeHasher { numberOfHashOperations += 1 - // Don't use this hash function at home - return count + // Don't use this hash at home + return hasher.appending(count) } var testWitnesses = ComputedArgumentWitnesses( From 2170afc02640a82e13739d00ce8c3d78d0ae6fb2 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 28 Feb 2018 21:55:04 +0000 Subject: [PATCH 36/37] [stdlib] Hash integers <= 32 bits as UInt32 --- stdlib/public/core/Bool.swift | 2 +- stdlib/public/core/Hashing.swift | 63 +++++++++++++++------ stdlib/public/core/Integers.swift.gyb | 15 ++--- stdlib/public/core/PrefixWhile.swift.gyb | 2 +- stdlib/public/core/SipHash.swift.gyb | 25 ++++++-- test/IRGen/enum_derived.swift | 2 +- validation-test/stdlib/FixedPoint.swift.gyb | 17 +++--- 7 files changed, 84 insertions(+), 42 deletions(-) diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 80ca877f8980f..449588124545d 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -162,7 +162,7 @@ extension Bool : Equatable, Hashable { @_inlineable // FIXME(sil-serialize-all) @_transparent public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - return hasher.appending(self ? 1 : 0) + return hasher.appending((self ? 1 : 0) as UInt8) } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index b805e6f93e55b..7d555189a4970 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -208,7 +208,7 @@ public struct _UnsafeHasher { } @_versioned - @inline(never) + // not @_inlineable @effects(readonly) // FIXME: Unjustified static func hashValue(for pointer: UnsafePointer) -> Int { var hasher = _Hasher() @@ -217,9 +217,10 @@ public struct _UnsafeHasher { } } + @_versioned + // not @_inlineable @effects(readonly) - @inline(never) - public func appending(bitPattern value: Int) -> _UnsafeHasher { + internal func appending(bits value: UInt) -> _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(_:)`. @@ -233,26 +234,36 @@ public struct _UnsafeHasher { return self } - @_inlineable - @inline(__always) - public func appending(_ value: H) -> _UnsafeHasher { - return value._hash(into: self) + @_versioned + // not @_inlineable + @effects(readonly) // See comment in appending(_: UInt) + internal func appending(bits value: UInt32) -> _UnsafeHasher { + _state.pointee.append(value) + return self } - @inline(__always) - internal func _appending(_ value: Int) -> _UnsafeHasher { + @_versioned + // not @_inlineable + @effects(readonly) // See comment in appending(_: UInt) + internal func appending(bits value: UInt64) -> _UnsafeHasher { _state.pointee.append(value) return self } + @_inlineable + @inline(__always) + public func appending(_ value: H) -> _UnsafeHasher { + return value._hash(into: self) + } + @inline(__always) internal func _finalized() -> Int { - return _state.pointee.finalize() + return Int(_truncatingBits: _state.pointee.finalize()._lowWord) } } -// FIXME: This is purely for benchmarking; to be removed. -internal struct _QuickHasher { +// FIXME(hashing): This is purely for benchmarking; to be removed. +internal struct _LegacyHasher { internal var _hash: Int @inline(__always) @@ -262,16 +273,32 @@ internal struct _QuickHasher { @inline(__always) internal mutating func append(_ value: Int) { - if _hash == 0 { - _hash = value - return + _hash = (_hash == 0 ? value : _combineHashValues(_hash, value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt) { + append(Int(bitPattern: value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt32) { + append(Int(truncatingIfNeeded: value)) + } + + @inline(__always) + internal mutating func append(_ value: UInt64) { + if UInt64.bitWidth > Int.bitWidth { + append(Int(truncatingIfNeeded: value ^ (value &>> 32))) + } else { + append(Int(truncatingIfNeeded: value)) } - _hash = _combineHashValues(_hash, value) } @inline(__always) - internal mutating func finalize() -> Int { - return _mixInt(_hash) + internal mutating func finalize() -> UInt64 { + return UInt64( + _truncatingBits: UInt(bitPattern: _mixInt(_hash))._lowWord) } } diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index d6ce2c66c751e..05fca78251549 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3616,14 +3616,15 @@ extension ${Self} : Hashable { @_inlineable // FIXME(sil-serialize-all) public func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { - % if bits <= word_bits: - return hasher.appending(bitPattern: Int(bitPattern: _lowWord)) + % if bits == word_bits: + return hasher.appending(bits: _lowWord) + % elif bits <= 32: + return hasher.appending( + bits: UInt32(Builtin.${z}extOrBitCast_Int${bits}_Int32(_value))) + % elif bits == 2 * word_bits: + return hasher.appending(bits: UInt64(_value)) % else: - var hasher = hasher - for word in words { - hasher = hasher.appending(bitPattern: Int(bitPattern: word)) - } - return hasher + fatalError("Unsupported integer width") % end } } diff --git a/stdlib/public/core/PrefixWhile.swift.gyb b/stdlib/public/core/PrefixWhile.swift.gyb index 92b92d0e8adfe..b3c865fe2e255 100644 --- a/stdlib/public/core/PrefixWhile.swift.gyb +++ b/stdlib/public/core/PrefixWhile.swift.gyb @@ -177,7 +177,7 @@ extension LazyPrefixWhileIndex : Hashable where Base.Index : Hashable { case .index(let value): return hasher.appending(value) case .pastEnd: - return hasher.appending(bitPattern: .max) + return hasher.appending(Int.max) } } } diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 914297d74dcab..104e1f4494aae 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -99,14 +99,16 @@ struct ${Self} { @_versioned internal var byteCount: UInt64 { - @inline(__always) get { + @inline(__always) + get { return tailAndByteCount &>> 56 } } @_versioned internal var tail: UInt64 { - @inline(__always) get { + @inline(__always) + get { return tailAndByteCount & ~(0xFF &<< 56) } } @@ -123,6 +125,11 @@ struct ${Self} { @inline(__always) public mutating func append(_ value: Int) { + append(UInt(bitPattern: value)) + } + + @inline(__always) + public mutating func append(_ value: UInt) { % if word_bits == 64: append(UInt64(_truncatingBits: value._lowWord)) % elif word_bits == 32: @@ -132,6 +139,11 @@ struct ${Self} { % end } + @inline(__always) + public mutating func append(_ value: Int32) { + append(UInt32(bitPattern: value)) + } + @inline(__always) public mutating func append(_ value: UInt32) { let m = UInt64(_truncatingBits: value._lowWord) @@ -145,6 +157,11 @@ struct ${Self} { } } + @inline(__always) + public mutating func append(_ value: Int64) { + append(UInt64(bitPattern: value)) + } + @inline(__always) public mutating func append(_ m: UInt64) { if byteCount & 4 == 0 { @@ -159,7 +176,7 @@ struct ${Self} { } @inline(__always) - public mutating func finalize() -> Int { + public mutating func finalize() -> UInt64 { _compress(tailAndByteCount) v2 ^= 0xff @@ -168,7 +185,7 @@ struct ${Self} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) } - return Int(_truncatingBits: (v0 ^ v1 ^ v2 ^ v3)._lowWord) + return (v0 ^ v1 ^ v2 ^ v3) } } % end diff --git a/test/IRGen/enum_derived.swift b/test/IRGen/enum_derived.swift index 7645edc10d20f..e50a39434f41c 100644 --- a/test/IRGen/enum_derived.swift +++ b/test/IRGen/enum_derived.swift @@ -34,7 +34,7 @@ enum E { // CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" // CHECK-TESTABLE-LABEL:define{{( protected)?}} swiftcc i{{.*}} @"$S12enum_derived1EO5_hash4intos13_UnsafeHasherVAG_tF" // CHECK: [[V:%.*]] = zext i8 %1 to i{{.*}} -// CHECK: [[R:%.*]] = tail call swiftcc i{{.*}} @"$Ss13_UnsafeHasherV9appending10bitPatternABSi_tF"(i{{.*}} [[V]], +// CHECK: [[R:%.*]] = tail call swiftcc i{{.*}} @"$Ss13_UnsafeHasherV9appending4bitsABSu_tF"(i{{.*}} [[V]], // CHECK: ret i{{.*}} [[R]] // Derived conformances from extensions diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index be69abeb5206d..1a864a74097d1 100644 --- a/validation-test/stdlib/FixedPoint.swift.gyb +++ b/validation-test/stdlib/FixedPoint.swift.gyb @@ -229,7 +229,7 @@ func expectedHash(_ bits: [Int]) -> Int { for i in bits { hasher.append(i) } - return hasher.finalize() + return Int(truncatingIfNeeded: hasher.finalize()) } %{ @@ -250,18 +250,15 @@ FixedPoint.test("${Self}.hashValue") { let input = get${Self}(${input}) let output = getInt(input.hashValue) - var hasher = _SipHash13() -% if self_ty.bits <= word_bits: - hasher.append(${prepare_bit_pattern(input, word_bits, True)}) -% elif self_ty.bits == word_bits * 2: - hasher.append(${prepare_bit_pattern(bit_pattern, word_bits, True)}) - hasher.append(${prepare_bit_pattern(bit_pattern >> 32, word_bits, True)}) +% if self_ty.bits <= 32: + hasher.append(${prepare_bit_pattern(input, 32, False)} as UInt32) % else: - fatalError("Unsupported integer width") + hasher.append(input) % end - let expected = getInt(hasher.finalize()) - expectEqual(expected, output) + let expected = getInt( + Int(bitPattern: UInt(truncatingIfNeeded: hasher.finalize()))) + expectEqual(expected, output, "input: \(input)") } % end From 35c9aa4a009af8856f217fa521642b7fc008d58c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 28 Feb 2018 21:59:09 +0000 Subject: [PATCH 37/37] [stdlib] SipHash: Revive ability to hash byte sequences of arbitrary length; restore some tests. Appending is still limited to 32- or 64-bit values. However, a new version of finalize accepts a partial word to append at the end of the sequence. This is only used for testing. --- stdlib/public/core/SipHash.swift.gyb | 15 ++++ validation-test/stdlib/SipHash.swift | 128 ++++++++++++++------------- 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 104e1f4494aae..bff4b08ed486c 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -175,6 +175,21 @@ struct ${Self} { } } + @inline(__always) + public mutating func finalize( + tailBytes: UInt64, + tailByteCount: Int + ) -> UInt64 { + _sanityCheck(tailByteCount >= 0 && tailByteCount < 8) + _sanityCheck(tailByteCount + (byteCount & 7) <= 7) + _sanityCheck(tailBytes >> (tailByteCount << 3) == 0) + let count = UInt64(_truncatingBits: tailByteCount._lowWord) + let currentByteCount = byteCount & 7 + tailAndByteCount |= (tailBytes &<< (currentByteCount &<< 3)) + tailAndByteCount = tailAndByteCount &+ (count &<< 56) + return finalize() + } + @inline(__always) public mutating func finalize() -> UInt64 { _compress(tailAndByteCount) diff --git a/validation-test/stdlib/SipHash.swift b/validation-test/stdlib/SipHash.swift index 34fa85ef6ec0a..7bda072d069cf 100644 --- a/validation-test/stdlib/SipHash.swift +++ b/validation-test/stdlib/SipHash.swift @@ -1,8 +1,5 @@ // RUN: %target-run-simple-swiftgyb // REQUIRES: executable_test -// -// Temporarily disabled; needs updating to streamlined SipHash interface. -// XFAIL: * import StdlibUnittest @@ -246,12 +243,61 @@ func loadUnalignedUIntLE( #endif } +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 +} + +func loadPartialUnalignedUInt32LE( + from p: UnsafeRawPointer, + byteCount: Int +) -> UInt32 { + _sanityCheck((0..<4).contains(byteCount)) + var result: UInt32 = 0 + if byteCount >= 1 { result |= UInt32(p.load(fromByteOffset: 0, as: UInt8.self)) } + if byteCount >= 2 { result |= UInt32(p.load(fromByteOffset: 1, as: UInt8.self)) &<< (8 as UInt32) } + if byteCount >= 3 { result |= UInt32(p.load(fromByteOffset: 2, as: UInt8.self)) &<< (16 as UInt32) } + return result +} + +func loadPartialUnalignedUIntLE( + from p: UnsafeRawPointer, + byteCount: Int +) -> UInt { +#if arch(i386) || arch(arm) + return UInt(loadPartialUnalignedUInt32LE(from: p, byteCount: byteCount)) +#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) + return UInt(loadPartialUnalignedUInt64LE(from: p, byteCount: byteCount)) +#endif +} + % for data_type in ['Int', 'Int64', 'Int32']: func loadUnaligned${data_type}LE( from p: UnsafeRawPointer ) -> ${data_type} { return ${data_type}(bitPattern: loadUnalignedU${data_type}LE(from: p)) } + +func loadPartialUnaligned${data_type}LE( + from p: UnsafeRawPointer, + byteCount: Int +) -> ${data_type} { + return ${data_type}( + bitPattern: loadPartialUnalignedU${data_type}LE( + from: p, + byteCount: byteCount)) +} % end % for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: @@ -260,85 +306,45 @@ func loadUnaligned${data_type}( ) -> ${data_type} { return ${data_type}(littleEndian: loadUnaligned${data_type}LE(from: p)) } +func loadPartialUnaligned${data_type}( + from p: UnsafeRawPointer, + byteCount: Int +) -> ${data_type} { + return ${data_type}(littleEndian: + loadPartialUnaligned${data_type}LE(from: p, byteCount: byteCount)) +} % end % for (Self, tests) in [ -% ('_SipHash13Context', 'sipHash13Tests'), -% ('_SipHash24Context', 'sipHash24Tests') +% ('_SipHash13', 'sipHash13Tests'), +% ('_SipHash24', 'sipHash24Tests') % ]: -SipHashTests.test("${Self}/Oneshot").forEach(in: ${tests}) { - test in - - expectEqual( - test.output, - ${Self}.hash( - data: test.input, - dataByteCount: test.input.count, - key: test.key)) -} - -SipHashTests.test("${Self}.append(UnsafeRawPointer)") - .forEach(in: cartesianProduct(${tests}, incrementalPatterns)) { - test_ in - let (test, pattern) = test_ - - var context = ${Self}(key: test.key) - var startIndex = 0 - var chunkSizeIndex = 0 - while startIndex != test.input.endIndex { - let chunkSize = min( - pattern[chunkSizeIndex], - test.input.endIndex - startIndex) - context.append( - Array(test.input[startIndex..<(startIndex+chunkSize)]), - byteCount: chunkSize) - startIndex += chunkSize - chunkSizeIndex += 1 - chunkSizeIndex %= pattern.count - } - expectEqual( - test.output, - context.finalizeAndReturnHash()) - - // Check that we can query the hash value more than once. - expectEqual( - test.output, - context.finalizeAndReturnHash()) -} - % for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: SipHashTests.test("${Self}.append(${data_type})").forEach(in: ${tests}) { test in - var context = ${Self}(key: test.key) + var hasher = ${Self}(key: test.key) let chunkSize = MemoryLayout<${data_type}>.size var startIndex = 0 let endIndex = test.input.count - (test.input.count % chunkSize) while startIndex != endIndex { - context.append( + hasher.append( loadUnaligned${data_type}( from: Array( test.input[startIndex..<(startIndex+chunkSize)]))) startIndex += chunkSize } - context.append( - Array(test.input.suffix(from: endIndex)), - byteCount: test.input.count - endIndex) - - expectEqual( - test.output, - context.finalizeAndReturnHash()) + let tailCount = test.input.count - endIndex + let hash = hasher.finalize( + tailBytes: loadPartialUnalignedUInt64( + from: Array(test.input.suffix(from: endIndex)), + byteCount: tailCount), + tailByteCount: tailCount) + expectEqual(test.output, hash) } % end - -SipHashTests.test("${Self}/AppendAfterFinalizing") { - var context = ${Self}(key: (0, 0)) - _ = context.finalizeAndReturnHash() - expectCrashLater() - context.append([], byteCount: 0) -} % end runAllTests()