diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 5128020a107ce..76dcb0310e22d 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3593,8 +3593,7 @@ getOrCreateKeyPathEqualsAndHash(SILGenModule &SGM, 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 inout _Hasher { auto &index = indexes[0]; diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb index dacabd5a8e202..2052aa19e8a0b 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(hasher): This has to run before creating the first Set/Dictionary + _Hasher._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, diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index ef3087b7c95eb..3e8b2439b1edc 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: inout _Hasher) 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: inout _Hasher) { + _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: inout _Hasher) { + _box._hash(_into: &hasher) + } } extension AnyHashable : CustomStringConvertible { diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 6ab1c1780cb5b..c4b22c6d32f72 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -154,9 +154,13 @@ extension Bool : Equatable, Hashable { /// invocations of the same program. Do not persist the hash value across /// program runs. @_inlineable // FIXME(sil-serialize-all) - @_transparent public var hashValue: Int { - return self ? 1 : 0 + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + hasher.append((self ? 1 : 0) as UInt8) } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/CTypes.swift b/stdlib/public/core/CTypes.swift index e35d1d7b0d7ac..628903bea5008 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -182,7 +182,12 @@ extension OpaquePointer: Hashable { /// program runs. @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - return Int(Builtin.ptrtoint_Word(_rawValue)) + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + hasher.append(Int(Builtin.ptrtoint_Word(_rawValue))) } } diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index b7474bbc85635..fe6f93c38ebee 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 _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + hasher.append(low) + hasher.append(high) } } diff --git a/stdlib/public/core/DropWhile.swift b/stdlib/public/core/DropWhile.swift index 741808d3d53dd..08e8ed87658e0 100644 --- a/stdlib/public/core/DropWhile.swift +++ b/stdlib/public/core/DropWhile.swift @@ -190,6 +190,10 @@ extension LazyDropWhileCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { return base.hashValue } + + public func _hash(into hasher: inout _Hasher) { + hasher.append(base) + } } extension LazyDropWhileCollection: Collection { diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index ea8baded5aacd..f94f0aff65e92 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -233,7 +233,14 @@ 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: inout _Hasher) { + hasher.append(_outer) + if let inner = _inner { + hasher.append(inner) + } } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 39cdc31f9d679..ff9a700259989 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1524,26 +1524,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 _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + 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: + hasher.append(v._representation.signAndExponent) + hasher.append(v.significandBitPattern) + %else: + hasher.append(v.bitPattern) + %end } + } extension ${Self} { diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 98fd5ffb70390..d92cad5b19937 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -108,6 +108,24 @@ 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 } + + /// Feed bits to be hashed into the hash function represented by `hasher`. + func _hash(into hasher: inout _Hasher) +} + +extension Hashable { + @inline(__always) + public func _hash(into hasher: inout _Hasher) { + hasher.append(self.hashValue) + } +} + +// Called by synthesized `hashValue` implementations. +@inline(__always) +public func _hashValue(for value: H) -> Int { + var hasher = _Hasher() + hasher.append(value) + return hasher.finalize() } // Called by the SwiftValue implementation. @@ -126,4 +144,3 @@ internal func Hashable_hashValue_indirect( ) -> Int { return value.pointee.hashValue } - diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index b91923f21d312..e755ee2a1cd48 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 _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + var hash = 0 for member in self { - result ^= _mixInt(member.hashValue) + hash ^= _hashValue(for: member) } - return result + hasher.append(hash) } } @@ -3985,7 +3990,7 @@ extension _Native${Self}Buffer @_versioned @inline(__always) // For performance reasons. internal func _bucket(_ k: Key) -> Int { - return _squeezeHashValue(k.hashValue, bucketCount) + return _hashValue(for: k) & _bucketMask } @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index f6fef168b2c05..97ff40b469276 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -23,33 +23,11 @@ import SwiftShims -@_fixed_layout // FIXME(sil-serialize-all) -public // @testable -enum _Hashing { - // FIXME(ABI)#41 : make this an actual public API. - @_inlineable // FIXME(sil-serialize-all) - public // SPI - static var secretKey: (UInt64, UInt64) { - get { - // The variable itself is defined in C++ code so that it is initialized - // during static construction. Almost every Swift program uses hash - // tables, so initializing the secret key during the startup seems to be - // the right trade-off. - return ( - _swift_stdlib_Hashing_secretKey.key0, - _swift_stdlib_Hashing_secretKey.key1) - } - set { - (_swift_stdlib_Hashing_secretKey.key0, - _swift_stdlib_Hashing_secretKey.key1) = newValue - } - } -} - @_fixed_layout // FIXME(sil-serialize-all) public // @testable enum _HashingDetail { + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) public // @testable static var fixedSeedOverride: UInt64 { @@ -64,6 +42,7 @@ enum _HashingDetail { } } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -74,6 +53,7 @@ enum _HashingDetail { return _HashingDetail.fixedSeedOverride == 0 ? seed : fixedSeedOverride } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -98,6 +78,7 @@ enum _HashingDetail { // their inputs and just exhibit avalanche effect. // +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -112,6 +93,7 @@ func _mixUInt32(_ value: UInt32) -> UInt32 { return UInt32((extendedResult >> 3) & 0xffff_ffff) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -119,6 +101,7 @@ func _mixInt32(_ value: Int32) -> Int32 { return Int32(bitPattern: _mixUInt32(UInt32(bitPattern: value))) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -130,6 +113,7 @@ func _mixUInt64(_ value: UInt64) -> UInt64 { return _HashingDetail.hash16Bytes(seed &+ (low << 3), high) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -137,6 +121,7 @@ func _mixInt64(_ value: Int64) -> Int64 { return Int64(bitPattern: _mixUInt64(UInt64(bitPattern: value))) } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -148,6 +133,7 @@ func _mixUInt(_ value: UInt) -> UInt { #endif } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -159,35 +145,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 @@ -214,3 +171,130 @@ func _combineHashValues(_ firstValue: Int, _ secondValue: Int) -> Int { x ^= UInt(bitPattern: secondValue) &+ magic &+ (x &<< 6) &+ (x &>> 2) return Int(bitPattern: x) } + +// FIXME(hasher): This hasher emulates Swift 4.1 hashValues. It is purely for +// benchmarking; to be removed. +internal struct _LegacyHasher { + internal var _hash: Int + + @inline(__always) + internal init(key: (UInt64, UInt64) = (0, 0)) { // key is ignored + _hash = 0 + } + + @inline(__always) + internal mutating func append(_ value: Int) { + _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)) + } + } + + @inline(__always) + internal mutating func finalize() -> UInt64 { + return UInt64( + _truncatingBits: UInt(bitPattern: _mixInt(_hash))._lowWord) + } +} + + +// NOT @_fixed_layout +public struct _Hasher { + internal typealias Core = _SipHash13 + + // NOT @_versioned + internal var _core: Core + + // NOT @_inlineable + @effects(releasenone) + public init() { + self._core = Core(key: _Hasher._secretKey) + } + + // NOT @_inlineable + @effects(releasenone) + public init(key: (UInt64, UInt64)) { + self._core = Core(key: key) + } + + // FIXME(ABI)#41 : make this an actual public API. + @_inlineable // FIXME(sil-serialize-all) + public // SPI + static var _secretKey: (UInt64, UInt64) { + get { + // The variable itself is defined in C++ code so that it is initialized + // during static construction. Almost every Swift program uses hash + // tables, so initializing the secret key during the startup seems to be + // the right trade-off. + return ( + _swift_stdlib_Hashing_secretKey.key0, + _swift_stdlib_Hashing_secretKey.key1) + } + set { + // FIXME(hasher) Replace setter with some override mechanism inside + // the runtime + (_swift_stdlib_Hashing_secretKey.key0, + _swift_stdlib_Hashing_secretKey.key1) = newValue + } + } + + @_inlineable + @inline(__always) + public mutating func append(_ value: H) { + value._hash(into: &self) + } + + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: UInt) { + _core.append(bits) + } + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: UInt32) { + _core.append(bits) + } + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: UInt64) { + _core.append(bits) + } + + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: Int) { + _core.append(UInt(bitPattern: bits)) + } + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: Int32) { + _core.append(UInt32(bitPattern: bits)) + } + // NOT @_inlineable + @effects(releasenone) + public mutating func append(bits: Int64) { + _core.append(UInt64(bitPattern: bits)) + } + + // NOT @_inlineable + @effects(releasenone) + public mutating func finalize() -> Int { + return Int(truncatingIfNeeded: _core.finalize()) + } +} diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 76bc9cad88409..a2a02b8c939bc 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3610,21 +3610,28 @@ 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 _hashValue(for: self) + } + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + // FIXME(hasher): To correctly bridge `Set`s/`Dictionary`s containing + // `AnyHashable`-boxed integers, all integer values are currently required + // to hash exactly the same way as the corresponding (U)Int64 value. To fix + // this, we should introduce a custom AnyHashable box for integer values + // that sign-extends values to 64 bits. + % if bits <= word_bits: + hasher.append(bits: _lowWord) + % elif bits == 2 * word_bits: + if let word = ${"" if signed else "U"}Int(exactly: self) { + hasher.append(bits: word._lowWord) + } else { + hasher.append(bits: UInt64(_value)) } + % else: + fatalError("Unsupported integer width") + % end } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 70dace66da613..28e9cb7e498f5 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 - withBuffer { + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + return withBuffer { var buffer = $0 while true { let (component, type) = buffer.next() - hash ^= _mixInt(component.value.hashValue) + hasher.append(component.value) if let type = type { - hash ^= _mixInt(unsafeBitCast(type, to: Int.self)) + hasher.append(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,11 +456,14 @@ 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 _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + hasher.append(value) + hasher.append(isStoredProperty) + hasher.append(isTableOffset) } } @@ -473,6 +480,7 @@ internal struct ComputedArgumentWitnesses { (_ xInstanceArguments: UnsafeRawPointer, _ yInstanceArguments: UnsafeRawPointer, _ size: Int) -> Bool + // FIXME(hasher) Append to an inout _Hasher instead internal typealias Hash = @convention(thin) (_ instanceArguments: UnsafeRawPointer, _ size: Int) -> Int @@ -584,46 +592,54 @@ 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: inout _Hasher) { + var hasher = hasher + func appendHashFromArgument( + _ argument: KeyPathComponent.ArgumentRef? + ) { if let argument = argument { - let addedHash = argument.witnesses.pointee.hash( + let hash = 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) + // FIXME(hasher): hash witness should just mutate hasher directly + if hash != 0 { + hasher.append(hash) } } } switch self { case .struct(offset: let a): - hash ^= _mixInt(0) - hash ^= _mixInt(a) + hasher.append(0) + hasher.append(a) case .class(offset: let b): - hash ^= _mixInt(1) - hash ^= _mixInt(b) + hasher.append(1) + hasher.append(b) case .optionalChain: - hash ^= _mixInt(2) + hasher.append(2) case .optionalForce: - hash ^= _mixInt(3) + hasher.append(3) case .optionalWrap: - hash ^= _mixInt(4) + hasher.append(4) case .get(id: let id, get: _, argument: let argument): - hash ^= _mixInt(5) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher.append(5) + hasher.append(id) + appendHashFromArgument(argument) case .mutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(6) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher.append(6) + hasher.append(id) + appendHashFromArgument(argument) case .nonmutatingGetSet(id: let id, get: _, set: _, argument: let argument): - hash ^= _mixInt(7) - hash ^= _mixInt(id.hashValue) - mixHashFromArgument(argument) + hasher.append(7) + hasher.append(id) + appendHashFromArgument(argument) } - return hash } } diff --git a/stdlib/public/core/PrefixWhile.swift b/stdlib/public/core/PrefixWhile.swift index 92a627db5f96b..b5e7b1e1b927e 100644 --- a/stdlib/public/core/PrefixWhile.swift +++ b/stdlib/public/core/PrefixWhile.swift @@ -208,11 +208,15 @@ extension LazyPrefixWhileCollection.Index: Comparable { extension LazyPrefixWhileCollection.Index: Hashable where Base.Index: Hashable { public var hashValue: Int { + return _hashValue(for: self) + } + + public func _hash(into hasher: inout _Hasher) { switch _value { case .index(let value): - return value.hashValue + hasher.append(value) case .pastEnd: - return .max + hasher.append(Int.max) } } } diff --git a/stdlib/public/core/Reverse.swift b/stdlib/public/core/Reverse.swift index ad08943de8241..01585dd3dba90 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: inout _Hasher) { + hasher.append(base) + } } extension ReversedCollection: BidirectionalCollection { diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 558cca3bef75b..54f4d0d6956aa 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -19,63 +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)) - } - - @_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 + 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( @@ -102,7 +59,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 @@ -120,19 +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 - - @_versioned - internal var dataTail: UInt64 = 0 - - @_versioned - internal var dataTailByteCount: Int = 0 + internal var tailAndByteCount: UInt64 = 0 - @_versioned - internal var finalizedHash: UInt64? - - @_inlineable // FIXME(sil-serialize-all) + @inline(__always) public init(key: (UInt64, UInt64)) { v3 ^= key.1 v2 ^= key.0 @@ -140,161 +92,110 @@ 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) - } - - // FIXME(ABI)#63 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) @_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 - } - - if dataTailByteCount == MemoryLayout.size { - _appendDirectly(dataTail) - dataTail = 0 - dataTailByteCount = 0 - } else if dataTailByteCount != 0 { - _sanityCheck(data == dataEnd) - return - } - - 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. + internal var byteCount: UInt64 { + @inline(__always) + get { + return tailAndByteCount &>> 56 } + } - if data != dataEnd { - dataTailByteCount = dataEnd - data - dataTail = _SipHashDetail._loadPartialUnalignedUInt64LE( - from: data, - byteCount: dataTailByteCount) + @_versioned + internal var tail: UInt64 { + @inline(__always) + get { + return tailAndByteCount & ~(0xFF &<< 56) } } - /// This function mixes in the given word directly into the state, - /// ignoring `dataTail`. - @_inlineable // FIXME(sil-serialize-all) - @_versioned @inline(__always) - internal mutating func _appendDirectly(_ m: UInt64) { + @_versioned + internal mutating func _compress(_ m: UInt64) { v3 ^= m for _ in 0..<${c_rounds} { _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) } v0 ^= m - hashedByteCount += 8 } -% for data_type in ['UInt', 'Int', 'UInt64', 'Int64', 'UInt32', 'Int32']: - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func append(_ data: ${data_type}) { - var data = data - _append_alwaysInline(&data, byteCount: MemoryLayout.size(ofValue: data)) + @inline(__always) + public mutating func append(_ value: Int) { + append(UInt(bitPattern: value)) } -% end - @_inlineable // FIXME(sil-serialize-all) - public // @testable - mutating func finalizeAndReturnHash() -> UInt64 { - return _finalizeAndReturnHash_alwaysInline() + @inline(__always) + public mutating func append(_ value: UInt) { + % if word_bits == 64: + append(UInt64(_truncatingBits: value._lowWord)) + % elif word_bits == 32: + append(UInt32(_truncatingBits: value._lowWord)) + % else: + fatalError("Unsupported word width") + % end } - @_inlineable // FIXME(sil-serialize-all) - @_versioned @inline(__always) - internal mutating func _finalizeAndReturnHash_alwaysInline() -> UInt64 { - if let finalizedHash = finalizedHash { - return finalizedHash - } - - _sanityCheck((0..<8).contains(dataTailByteCount)) - - hashedByteCount += UInt64(dataTailByteCount) - let b: UInt64 = (hashedByteCount << 56) | dataTail - - v3 ^= b - for _ in 0..<${c_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) - } - v0 ^= b - - v2 ^= 0xff + public mutating func append(_ value: Int32) { + append(UInt32(bitPattern: value)) + } - for _ in 0..<${d_rounds} { - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + @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) + } else { + _sanityCheck(byteCount & 3 == 0) + _compress((m &<< 32) | tail) + tailAndByteCount = (byteCount &+ 4) &<< 56 } + } - finalizedHash = v0 ^ v1 ^ v2 ^ v3 - return finalizedHash! + @inline(__always) + public mutating func append(_ value: Int64) { + append(UInt64(bitPattern: value)) } - @_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 + @inline(__always) + public mutating func append(_ m: UInt64) { + 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) + } } - // FIXME(ABI)#64 (UnsafeRawBufferPointer): Use UnsafeRawBufferPointer. - @_inlineable // FIXME(sil-serialize-all) - public // @testable - static func hash( - data: UnsafeRawPointer, - dataByteCount: Int, - key: (UInt64, UInt64) + @inline(__always) + public mutating func finalize( + tailBytes: UInt64, + tailByteCount: Int ) -> UInt64 { - return ${Self}._hash_alwaysInline( - data: data, - dataByteCount: dataByteCount, - key: key) + _sanityCheck(tailByteCount >= 0) + _sanityCheck(tailByteCount < 8 - (byteCount & 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() } - // 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() + public mutating func finalize() -> UInt64 { + _compress(tailAndByteCount) + + v2 ^= 0xff + + for _ in 0..<${d_rounds} { + _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + } + + return (v0 ^ v1 ^ v2 ^ v3) } } % end diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 7607c9bba783a..c87ff59fa63fe 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 = _SipHash13Context(key: _Hashing.secretKey) 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() } // 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 = _SipHash13Context(key: _Hashing.secretKey) 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(element) + hasher.append(Int(truncatingIfNeeded: element)) } } - return hasher._finalizeAndReturnIntHash() } } @@ -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 Int(truncatingIfNeeded: 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 Int(truncatingIfNeeded: hasher.finalize()) #endif // _runtime(_ObjC) } } @@ -139,7 +141,11 @@ 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.hashUTF16( + UnsafeBufferPointer(start: p, count: count), + into: &hasher) + return Int(truncatingIfNeeded: hasher.finalize()) #endif } } @@ -195,6 +201,11 @@ extension String : Hashable { let gutsBits = _guts.rawBits return _StringGuts._computeHashValue(_unsafeBitPattern: gutsBits) } + + @_inlineable + public func _hash(into hasher: inout _Hasher) { + hasher.append(self.hashValue) + } } extension StringProtocol { @@ -202,4 +213,9 @@ extension StringProtocol { public var hashValue : Int { return _wholeString._guts._computeHashValue(_encodedOffsetRange) } + + @_inlineable + public func _hash(into hasher: inout _Hasher) { + hasher.append(self.hashValue) + } } diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index 288b484b1a5e8..4e314bd69566a 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -904,10 +904,15 @@ extension ${Self}: Hashable { /// program runs. @_inlineable public var hashValue: Int { - return Int(bitPattern: self) + return _hashValue(for: self) + } + + @_inlineable // FIXME(sil-serialize-all) + public func _hash(into hasher: inout _Hasher) { + hasher.append(Int(bitPattern: self)) } } - + extension ${Self}: Strideable { /// Returns a pointer to the next consecutive instance. /// diff --git a/test/SILGen/objc_bridging_any.swift b/test/SILGen/objc_bridging_any.swift index aa23de2da4002..6a5b6f900e43a 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_hash4intoys7_HasherVz_tFTW // CHECK-NEXT: } diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index 6cecc0c23282a..f405b05a11f08 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) - }% //===----------------------------------------------------------------------===// @@ -250,8 +241,15 @@ 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)}) - expectEqual(expected, output) + + var hasher = _SipHash13(key: _Hasher._secretKey) +% if prepare_bit_pattern(input, word_bits, self_ty.is_signed) == input: + hasher.append(${input} as ${"" if self_ty.is_signed else "U"}Int) +% else: + hasher.append(input) +% end + let expected = getInt(Int(truncatingIfNeeded: hasher.finalize())) + expectEqual(expected, output, "input: \(input)") } % end @@ -269,7 +267,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 +275,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/Hashing.swift b/validation-test/stdlib/Hashing.swift index 0018c423601c9..a88498da81407 100644 --- a/validation-test/stdlib/Hashing.swift +++ b/validation-test/stdlib/Hashing.swift @@ -8,109 +8,65 @@ import StdlibUnittest var HashingTestSuite = TestSuite("Hashing") -HashingTestSuite.test("_mixUInt32/GoldenValues") { - expectEqual(0x11b882c9, _mixUInt32(0x0)) - expectEqual(0x60d0aafb, _mixUInt32(0x1)) - expectEqual(0x636847b5, _mixUInt32(0xffff)) - expectEqual(0x203f5350, _mixUInt32(0xffff_ffff)) - - expectEqual(0xb8747ef6, _mixUInt32(0xa62301f9)) - expectEqual(0xef4eeeb2, _mixUInt32(0xfe1b46c6)) - expectEqual(0xd44c9cf1, _mixUInt32(0xe4daf7ca)) - expectEqual(0xfc1eb1de, _mixUInt32(0x33ff6f5c)) - expectEqual(0x5605f0c0, _mixUInt32(0x13c2a2b8)) - expectEqual(0xd9c48026, _mixUInt32(0xf3ad1745)) - expectEqual(0x471ab8d0, _mixUInt32(0x656eff5a)) - expectEqual(0xfe265934, _mixUInt32(0xfd2268c9)) +func checkHash( + for value: UInt64, + withKey key: (UInt64, UInt64), + expected: UInt64, + file: String = #file, line: UInt = #line +) { + var hasher = _Hasher(key: key) + hasher.append(bits: value) + let hash = hasher.finalize() + expectEqual( + hash, Int(truncatingIfNeeded: expected), + file: file, line: line) } -HashingTestSuite.test("_mixInt32/GoldenValues") { - expectEqual(Int32(bitPattern: 0x11b882c9 as UInt32), _mixInt32(0x0)) +HashingTestSuite.test("_Hasher/CustomKeys") { + // This assumes _Hasher implements SipHash-1-3. + checkHash(for: 0, withKey: (0, 0), expected: 0xbd60acb658c79e45) + checkHash(for: 0, withKey: (0, 1), expected: 0x1ce32b0b44e61175) + checkHash(for: 0, withKey: (1, 0), expected: 0x9c44b7c8df2ca74b) + checkHash(for: 0, withKey: (1, 1), expected: 0x9653ca0a3b455506) + checkHash(for: 0, withKey: (.max, .max), expected: 0x3ab336a4895e4d36) + + checkHash(for: 1, withKey: (0, 0), expected: 0x1e9f734161d62dd9) + checkHash(for: 1, withKey: (0, 1), expected: 0xb6fcf32d09f76cba) + checkHash(for: 1, withKey: (1, 0), expected: 0xacb556b13007504a) + checkHash(for: 1, withKey: (1, 1), expected: 0x7defec680db51d24) + checkHash(for: 1, withKey: (.max, .max), expected: 0x212798441870ef6b) + + checkHash(for: .max, withKey: (0, 0), expected: 0x2f205be2fec8e38d) + checkHash(for: .max, withKey: (0, 1), expected: 0x3ff7fa33381ecf7b) + checkHash(for: .max, withKey: (1, 0), expected: 0x404afd8eb2c4b22a) + checkHash(for: .max, withKey: (1, 1), expected: 0x855642d657c1bd46) + checkHash(for: .max, withKey: (.max, .max), expected: 0x5b16b7a8181980c2) } -HashingTestSuite.test("_mixUInt64/GoldenValues") { - expectEqual(0xb2b2_4f68_8dc4_164d, _mixUInt64(0x0)) - expectEqual(0x792e_33eb_0685_57de, _mixUInt64(0x1)) - expectEqual(0x9ec4_3423_1b42_3dab, _mixUInt64(0xffff)) - expectEqual(0x4cec_e9c9_01fa_9a84, _mixUInt64(0xffff_ffff)) - expectEqual(0xcba5_b650_bed5_b87c, _mixUInt64(0xffff_ffff_ffff)) - expectEqual(0xe583_5646_3fb8_ac99, _mixUInt64(0xffff_ffff_ffff_ffff)) - - expectEqual(0xf5d0079f828d43a5, _mixUInt64(0x94ce7d9319f8d233)) - expectEqual(0x61900a6be9db9c3f, _mixUInt64(0x2728821e8c5b1f7)) - expectEqual(0xf2fd34b1b7d4b46e, _mixUInt64(0xe7f67ec98c64f482)) - expectEqual(0x216199ed628c821, _mixUInt64(0xd7c277b5438873ac)) - expectEqual(0xb1b486ff5f2e0e53, _mixUInt64(0x8399f1d563c42f82)) - expectEqual(0x61acc92bd91c030, _mixUInt64(0x488cefd48a2c4bfd)) - expectEqual(0xa7a52d6e4a8e3ddf, _mixUInt64(0x270a15116c351f95)) - expectEqual(0x98ceedc363c4e56a, _mixUInt64(0xe5fb9b5f6c426a84)) -} +HashingTestSuite.test("_Hasher/DefaultKey") { + let value: UInt64 = 0x0102030405060708 -HashingTestSuite.test("_mixUInt64/GoldenValues") { - expectEqual(Int64(bitPattern: 0xb2b2_4f68_8dc4_164d as UInt64), _mixInt64(0x0)) -} + let defaultHash = _hashValue(for: value) -HashingTestSuite.test("_mixUInt/GoldenValues") { -#if arch(i386) || arch(arm) - expectEqual(0x11b8_82c9, _mixUInt(0x0)) -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) - expectEqual(0xb2b2_4f68_8dc4_164d, _mixUInt(0x0)) -#else - fatalError("unimplemented") -#endif -} + var defaultHasher = _Hasher() + defaultHasher.append(bits: value) + expectEqual(defaultHasher.finalize(), defaultHash) -HashingTestSuite.test("_mixInt/GoldenValues") { -#if arch(i386) || arch(arm) - expectEqual(Int(bitPattern: 0x11b8_82c9 as UInt), _mixInt(0x0)) -#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) - expectEqual(Int(bitPattern: 0xb2b2_4f68_8dc4_164d as UInt), _mixInt(0x0)) -#else - fatalError("unimplemented") -#endif + var customHasher = _Hasher(key: _Hasher._secretKey) + customHasher.append(bits: value) + expectEqual(customHasher.finalize(), defaultHash) } -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("_Hasher/keyOverride") { + let value: UInt64 = 0x0102030405060708 + let expected = Int(truncatingIfNeeded: 0x661dac5d71c78013 as UInt64) -HashingTestSuite.test("String/hashValue/topBitsSet") { -#if _runtime(_ObjC) -#if arch(x86_64) || arch(arm64) - // Make sure that we don't accidentally throw away bits by storing the result - // of NSString.hash into an int in the runtime. - - // This is the bit pattern that we xor to NSString's hash value. - let hashOffset = UInt(bitPattern: 0x429b_1266_0000_0000 as Int) - let hash = "efghijkl".hashValue - // When we are not equal to the top bit of the xor'ed hashOffset pattern - // there where some bits set. - let topHashBits = UInt(bitPattern: hash) & 0xffff_ffff_0000_0000 - expectTrue(hash > 0) - expectTrue(topHashBits != hashOffset) -#endif -#endif -} + let originalKey = _Hasher._secretKey + _Hasher._secretKey = (1, 2) + let hash = _hashValue(for: value) + _Hasher._secretKey = originalKey -HashingTestSuite.test("overridePerExecutionHashSeed/overflow") { - // Test that we don't use checked arithmetic on the seed. - _HashingDetail.fixedSeedOverride = UInt64.max - expectEqual(0x4344_dc3a_239c_3e81, _mixUInt64(0xffff_ffff_ffff_ffff)) - _HashingDetail.fixedSeedOverride = 0 + expectEqual(hash, expected) } runAllTests() diff --git a/validation-test/stdlib/HashingAvalanche.swift b/validation-test/stdlib/HashingAvalanche.swift index 51b89dab453d0..09b3365179155 100644 --- a/validation-test/stdlib/HashingAvalanche.swift +++ b/validation-test/stdlib/HashingAvalanche.swift @@ -47,12 +47,12 @@ func avalancheTest( // White-box testing: assume that the other N-bit to N-bit mixing functions // just dispatch to these. (Avalanche test is relatively expensive.) -HashingTestSuite.test("_mixUInt64/avalanche") { - avalancheTest(64, _mixUInt64, 0.02) +HashingTestSuite.test("_Hasher.append(UInt64)/avalanche") { + avalancheTest(64, { UInt64(truncatingIfNeeded: _hashValue(for: $0)) }, 0.02) } -HashingTestSuite.test("_mixUInt32/avalanche") { - avalancheTest(32, { UInt64(_mixUInt32(UInt32($0 & 0xffff_ffff))) }, 0.02) +HashingTestSuite.test("_Hasher.append(UInt32)/avalanche") { + avalancheTest(32, { UInt64(truncatingIfNeeded: _hashValue(for: $0)) }, 0.02) } runAllTests() diff --git a/validation-test/stdlib/SipHash.swift b/validation-test/stdlib/SipHash.swift index cbf5edb070db4..7bda072d069cf 100644 --- a/validation-test/stdlib/SipHash.swift +++ b/validation-test/stdlib/SipHash.swift @@ -243,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']: @@ -257,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()