From da5a01022797c4fbd7b1c02702eca41178e50873 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 16:31:20 +0000 Subject: [PATCH 01/16] [stdlib] Convert _Hashing, _HashingDetail to caseless enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty structs can be instantiated, which doesn’t make sense for these namespacing declarations. --- stdlib/public/core/Hashing.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index 02acd8e2b5551..f6fef168b2c05 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 From 46196aa7ee0c6067a4a850c7a82f123d0c3f48dd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 16:33:57 +0000 Subject: [PATCH 02/16] [stdlib] Fix tests relying on specific hash values or Set/Dictionary orderings We shouldn't need to update tests each time we modify the hash function. --- 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/Set.swift | 7 +- 5 files changed, 54 insertions(+), 86 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/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 f0bd8d7c1666a81339c0f9f68d0cbfbb14c633bf Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 14:21:32 +0000 Subject: [PATCH 03/16] [stdlib] Hashable: Implement interface for resilient hashing Introduce _UnsafeHasher, representing an opaque hash compression function with a faux-pure interface. Add the method _hash(into:) as a Hashable requirement, along with a default implementation that simply feeds hashValue to the hasher. Add _hash(into:) implementations for the default integer types, and replace hashValue. Note that Int.hashValue does not return self anymore. Add struct _LegacyHasher, emulating Swift 4.1 hashes in the new interface. --- stdlib/public/core/AnyHashable.swift | 12 ++ stdlib/public/core/Hashable.swift | 17 +++ stdlib/public/core/Hashing.swift | 127 ++++++++++++++++++++ stdlib/public/core/Integers.swift.gyb | 34 +++--- validation-test/stdlib/FixedPoint.swift.gyb | 22 ++-- 5 files changed, 185 insertions(+), 27 deletions(-) diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index ef3087b7c95eb..79568acdc2ac7 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: _UnsafeHasher) -> _UnsafeHasher 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: _UnsafeHasher) -> _UnsafeHasher { + return _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: _UnsafeHasher) -> _UnsafeHasher { + return _box._hash(_into: hasher) + } } extension AnyHashable : CustomStringConvertible { diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 98fd5ffb70390..b01b1f8fd0d9b 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 } + + /// Feed bits to be hashed into the hash function represented by `hasher`. + func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher +} + +extension Hashable { + @inline(__always) + 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 { + var value = value + return withUnsafePointer(to: &value) { _UnsafeHasher.hashValue(for: $0) } } // Called by the SwiftValue implementation. diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index f6fef168b2c05..82544dfe5544a 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -50,6 +50,7 @@ enum _Hashing { public // @testable enum _HashingDetail { + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) public // @testable static var fixedSeedOverride: UInt64 { @@ -64,6 +65,7 @@ enum _HashingDetail { } } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -74,6 +76,7 @@ enum _HashingDetail { return _HashingDetail.fixedSeedOverride == 0 ? seed : fixedSeedOverride } + // FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_versioned @_transparent @@ -98,6 +101,7 @@ enum _HashingDetail { // their inputs and just exhibit avalanche effect. // +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -112,6 +116,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 +124,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 +136,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 +144,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 +156,7 @@ func _mixUInt(_ value: UInt) -> UInt { #endif } +// FIXME(hasher): Remove @_inlineable // FIXME(sil-serialize-all) @_transparent public // @testable @@ -214,3 +223,121 @@ 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 + // not @_inlineable + @effects(readonly) // FIXME(hasher): 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() + } + } + + @_versioned + // not @_inlineable + @effects(readonly) + 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(_:)`. + // + // 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 + } + + @_versioned + // not @_inlineable + @effects(readonly) // See comment in appending(_: UInt) + internal func appending(bits value: UInt32) -> _UnsafeHasher { + _state.pointee.append(value) + return self + } + + @_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 Int(_truncatingBits: _state.pointee.finalize()._lowWord) + } +} + +// FIXME(hasher): This is purely for benchmarking; to be removed. +internal struct _LegacyHasher { + internal var _hash: Int + + @inline(__always) + internal init() { + _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) + } +} + +internal typealias _Hasher = _LegacyHasher diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 4f69a62527292..3f42031ecc0cd 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3610,21 +3610,27 @@ 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: _UnsafeHasher) -> _UnsafeHasher { + // 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: + return hasher.appending(bits: _lowWord) + % elif bits == 2 * word_bits: + if let word = ${"" if signed else "U"}Int(exactly: self) { + return hasher.appending(bits: word._lowWord) } + return hasher.appending(bits: UInt64(_value)) + % else: + fatalError("Unsupported integer width") + % end } } diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index 6cecc0c23282a..2ad63c3fb5ce5 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() +% 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)} From 3933df9e1434541a3425faf94ec0b5835f9ff527 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 14:08:48 +0000 Subject: [PATCH 04/16] [stdlib] Switch Dictionary and Set to use resilient hashing. This switches the primary hashing interface from hashValue to _hash(into:). --- .../public/core/HashedCollections.swift.gyb | 2 +- stdlib/public/core/Hashing.swift | 29 ------------------- validation-test/stdlib/Hashing.swift | 18 ------------ 3 files changed, 1 insertion(+), 48 deletions(-) diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 8045b6a43d565..8b4ea969d0269 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -3984,7 +3984,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 82544dfe5544a..1f81174efaee6 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -168,35 +168,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 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 b36fe56eecc648759cf05987871f3a83a2987e5a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 14:32:11 +0000 Subject: [PATCH 05/16] [stdlib] Switch to _hash(into:) in standard Hashable types. --- lib/SILGen/SILGenExpr.cpp | 4 +- stdlib/public/core/Bool.swift | 6 ++ stdlib/public/core/CTypes.swift | 7 +- stdlib/public/core/DoubleWidth.swift.gyb | 10 ++- stdlib/public/core/DropWhile.swift | 4 + stdlib/public/core/Flatten.swift | 8 +- .../public/core/FloatingPointTypes.swift.gyb | 30 +++---- .../public/core/HashedCollections.swift.gyb | 11 ++- stdlib/public/core/KeyPath.swift | 79 +++++++++++-------- stdlib/public/core/PrefixWhile.swift | 8 +- stdlib/public/core/Reverse.swift | 4 + stdlib/public/core/StringHashable.swift | 32 +++++--- stdlib/public/core/UnsafePointer.swift.gyb | 9 ++- 13 files changed, 137 insertions(+), 75 deletions(-) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 72d725b732090..0868bed72b415 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3583,8 +3583,8 @@ 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 _UnsafeHasher passed in as + // an extra parameter. { auto &index = indexes[0]; diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 6ab1c1780cb5b..449588124545d 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: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending((self ? 1 : 0) as UInt8) + } + @_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 c501978234f82..de34a38f43133 100644 --- a/stdlib/public/core/CTypes.swift +++ b/stdlib/public/core/CTypes.swift @@ -162,7 +162,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: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(Int(Builtin.ptrtoint_Word(_rawValue))) } } diff --git a/stdlib/public/core/DoubleWidth.swift.gyb b/stdlib/public/core/DoubleWidth.swift.gyb index b7474bbc85635..cb69767f00164 100644 --- a/stdlib/public/core/DoubleWidth.swift.gyb +++ b/stdlib/public/core/DoubleWidth.swift.gyb @@ -151,10 +151,12 @@ 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: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(low).appending(high) } } diff --git a/stdlib/public/core/DropWhile.swift b/stdlib/public/core/DropWhile.swift index 741808d3d53dd..6814bc8f76e36 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: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(base) + } } extension LazyDropWhileCollection: Collection { diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index ea8baded5aacd..659da19f917ee 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -233,7 +233,13 @@ 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 { + let h = hasher.appending(_outer) + guard let inner = _inner else { return h } + return h.appending(inner) } } diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 646e512850005..19fe8aefa1c64 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1508,26 +1508,26 @@ 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: _UnsafeHasher) -> _UnsafeHasher { + 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: + return hasher + .appending(v._representation.signAndExponent) + .appending(v.significandBitPattern) + %else: + return hasher.appending(v.bitPattern) + %end } + } extension ${Self} { diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 8b4ea969d0269..5377817911baa 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: _UnsafeHasher) -> _UnsafeHasher { + var hash = 0 for member in self { - result ^= _mixInt(member.hashValue) + hash ^= _hashValue(for: member) } - return result + return hasher.appending(hash) } } diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index 70dace66da613..5fbd423beb191 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -49,21 +49,27 @@ 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: _UnsafeHasher) -> _UnsafeHasher { + return withBuffer { + var hasher = hasher var buffer = $0 while true { let (component, type) = buffer.next() - hash ^= _mixInt(component.value.hashValue) + hasher = hasher.appending(component.value) if let type = type { - hash ^= _mixInt(unsafeBitCast(type, to: Int.self)) + hasher = hasher.appending(unsafeBitCast(type, to: Int.self)) } else { break } } + return hasher } - return hash } + @_inlineable // FIXME(sil-serialize-all) public static func ==(a: AnyKeyPath, b: AnyKeyPath) -> Bool { // Fast-path identical objects @@ -452,11 +458,15 @@ 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: _UnsafeHasher) -> _UnsafeHasher { + return hasher + .appending(value) + .appending(isStoredProperty) + .appending(isTableOffset) } } @@ -473,6 +483,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 @@ -584,46 +595,50 @@ 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( + 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 return hasher directly + if hash != 0 { + hasher = hasher.appending(hash) } } } 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 = 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 = hasher.appending(7).appending(id) + appendHashFromArgument(argument) } - return hash + return hasher } } diff --git a/stdlib/public/core/PrefixWhile.swift b/stdlib/public/core/PrefixWhile.swift index 92a627db5f96b..d801fb37cd6e2 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: _UnsafeHasher) -> _UnsafeHasher { switch _value { case .index(let value): - return value.hashValue + return hasher.appending(value) case .pastEnd: - return .max + return hasher.appending(Int.max) } } } 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 7607c9bba783a..3bb2ff8bafcc8 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 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,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 hasher.finalize() #endif } } diff --git a/stdlib/public/core/UnsafePointer.swift.gyb b/stdlib/public/core/UnsafePointer.swift.gyb index 05ae11e627913..e13e55049742a 100644 --- a/stdlib/public/core/UnsafePointer.swift.gyb +++ b/stdlib/public/core/UnsafePointer.swift.gyb @@ -902,10 +902,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: _UnsafeHasher) -> _UnsafeHasher { + return hasher.appending(Int(bitPattern: self)) } } - + extension ${Self}: Strideable { /// Returns a pointer to the next consecutive instance. /// From dc929b70799867bcde4832b24803e7468b6ef401 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 15:52:10 +0000 Subject: [PATCH 06/16] [stdlib] Switch to SipHash-1-3 for the standard hash function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beyond switching hashing algorithms, this also enables per-execution hash seeds, fulfilling a long-standing prophecy in Hashable’s documentation. To reduce the possibility of random test failures, StdlibUnittest’s TestSuite overrides the random hash seed on initialization. rdar://problem/24109692 rdar://problem/35052153 --- .../StdlibUnittest/StdlibUnittest.swift.gyb | 10 + stdlib/public/core/Hashing.swift | 2 +- stdlib/public/core/SipHash.swift.gyb | 282 ++++++------------ validation-test/stdlib/SipHash.swift | 125 ++++---- 4 files changed, 172 insertions(+), 247 deletions(-) diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb b/stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb index dacabd5a8e202..2e0284ff362ba 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 + _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, diff --git a/stdlib/public/core/Hashing.swift b/stdlib/public/core/Hashing.swift index 1f81174efaee6..bac559aa20b41 100644 --- a/stdlib/public/core/Hashing.swift +++ b/stdlib/public/core/Hashing.swift @@ -311,4 +311,4 @@ internal struct _LegacyHasher { } } -internal typealias _Hasher = _LegacyHasher +internal typealias _Hasher = _SipHash13 diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index 558cca3bef75b..bff4b08ed486c 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,115 @@ 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(__always) + public init() { + self.init(key: _Hashing.secretKey) } - // 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) - } + % for _ in range(0, c_rounds): + _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + % end 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 && 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() } - // 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/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() From 532ed7853c80d5ff37350d372df1d352164eb607 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 16:29:48 +0000 Subject: [PATCH 07/16] [Sema] Switch synthesized hashing to use the new resilient hasher interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Force-derive Hashable._hash(into:) whenever we need to derive hashValue. Do all the actual work there. _hash(into:) has a default implementation, so it wouldn’t normally be considered a requirement in need of synthesized conformance. However, for types that synthesize their hashability, we want to replace the default with a custom implementation. A better option would be to remove the default implementation and synthesize it (and/or hashValue) as needed: 1. If a witness hashValue cannot be resolved, synthesize one that simply returns _hashValue(for: self). (I.e., express hashValue in terms of _hash(into:).) 2. If a witness for _hash(into:) cannot be resolved: a) if there is an explicit witness for hashValue, synthesize _hash(into:) such that it simply feeds the hashValue to the hasher. b) otherwise if the parent context is not a struct or enum decl, then fail and produce a diagnostic. We can’t fully synthesize Hashable for classes or in extensions. c) if the components of the type aren’t all Hashable, then fail and produce a diagnostic about a missing hashValue implementation. c) otherwise synthesize _hash(into:) by feeding the type’s components into the hasher. However, the DerivedConformance is not yet a great fit for such complicated derivation rules, so we’d need to pretend Hashable is always derivable, and manually provide diagnostics in cases 2/b and 2/c. Providing high-quality, non-duplicated diagnostics is a challenge. Even worse, case 2/a would require us to support synthesizing _hash(into:) into extensions, classes, synthesized structs and imported types — and unfortunately, compiler support for doing this is currently limited. --- include/swift/AST/ASTContext.h | 12 +- include/swift/AST/KnownIdentifiers.def | 5 + include/swift/AST/KnownStdlibTypes.def | 11 +- lib/AST/ASTContext.cpp | 72 ++- .../DerivedConformanceEquatableHashable.cpp | 451 +++++++++++------- 5 files changed, 313 insertions(+), 238 deletions(-) diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index 0b417008822dd..856d4c48c4e97 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -400,8 +400,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" @@ -471,12 +471,8 @@ 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; /// 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 108b7fc39ae38..9863d6f2b2eaa 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) @@ -54,14 +55,18 @@ IDENTIFIER(encoder) IDENTIFIER(error) IDENTIFIER(forKeyedSubscript) IDENTIFIER(Foundation) +IDENTIFIER(for) IDENTIFIER(forKey) IDENTIFIER(from) IDENTIFIER(fromRaw) +IDENTIFIER_(hash) +IDENTIFIER(hasher) IDENTIFIER(hashValue) 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..33fc5bd5fe37b 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,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(UnsafeHasher, "_UnsafeHasher", NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(AnyKeyPath, NominalTypeDecl, 0) KNOWN_STDLIB_TYPE_DECL(PartialKeyPath, NominalTypeDecl, 1) @@ -79,3 +83,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 659a6ec35d8cc..65b1b39e8ba83 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" @@ -189,12 +189,9 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) /// func ==(Int, Int) -> Bool FuncDecl *EqualIntDecl = nullptr; - /// func _combineHashValues(Int, Int) -> Int - FuncDecl *CombineHashValuesDecl = nullptr; + /// func _hashValue(for: H) -> Int + FuncDecl *HashValueForDecl = nullptr; - /// func _mixInt(Int) -> Int - FuncDecl *MixIntDecl = nullptr; - /// func append(Element) -> void FuncDecl *ArrayAppendElementDecl = nullptr; @@ -616,11 +613,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" @@ -989,44 +986,29 @@ 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; +FuncDecl *ASTContext::getHashValueForDecl() const { + if (Impl.HashValueForDecl) + return Impl.HashValueForDecl; - 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; + 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 { diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 8cb788c045e60..d94ae864de7d1 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" @@ -744,66 +745,159 @@ 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 hasher The base expression to make the call on. +/// +/// \param hashable The parameter to the call. +static CallExpr* createHasherAppendingCall(ASTContext &C, + 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 FuncDecl * +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; + auto parentDC = cast(parentDecl); + + // 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(), parentDC); + auto *hasherParam = new (C) ParamDecl(VarDecl::Specifier::Owned, SourceLoc(), + SourceLoc(), C.Id_into, SourceLoc(), + C.Id_hasher, hasherType, parentDC); + 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), + parentDC); + hashDecl->setImplicit(); + hashDecl->setBodySynthesizer(bodySynthesizer); + + // Evaluate type of Self in (Self) -> (into: _UnsafeHasher) -> _UnsafeHasher + auto selfParam = computeSelfParam(hashDecl); + auto inputTypeElt = TupleTypeElt(hasherType, C.Id_into); + auto inputType = TupleType::get({inputTypeElt}, C); + auto innerType = FunctionType::get(inputType, hasherType, + FunctionType::ExtInfo()); + + Type interfaceType; + 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({selfParam}, innerType, + FunctionType::ExtInfo()); + } + hashDecl->setInterfaceType(interfaceType); + + // 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 + // 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; } +/// 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; @@ -815,9 +909,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(), @@ -830,17 +927,12 @@ 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, hasherExpr, ordinalExpr); } if (!hasNoAssociatedValues) { @@ -849,15 +941,15 @@ 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, 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)); } @@ -868,66 +960,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), + Expr* hasherExpr = new (C) DeclRefExpr(hasherParam, DeclNameLoc(), /*implicit*/ true); - statements.emplace_back(ASTNode(assignExpr)); - } 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); @@ -935,74 +999,45 @@ 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)); + // := .appending(self.) + hasherExpr = createHasherAppendingCall(C, hasherExpr, selfPropertyExpr); } - { - // 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); - } + auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); + SmallVector statements { ASTNode(returnStmt) }; + auto body = BraceStmt::create(C, SourceLoc(), statements, + SourceLoc(), /*implicit*/ true); + hashIntoDecl->setBody(body); +} - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); +/// 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; @@ -1045,7 +1080,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); @@ -1104,33 +1139,85 @@ bool DerivedConformance::canDeriveHashable(TypeChecker &tc, 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; + } + } + 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, Decl *parentDecl, 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); 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; } - // Build the necessary decl. - if (requirement->getBaseName() == "hashValue") { - if (theEnum) - return deriveHashable_hashValue(tc, parentDecl, theEnum, - &deriveBodyHashable_enum_hashValue); - else if (auto theStruct = dyn_cast(type)) - return deriveHashable_hashValue(tc, parentDecl, theStruct, - &deriveBodyHashable_struct_hashValue); - else - llvm_unreachable("todo"); + // var hashValue: Int + if (requirement->getBaseName() == C.Id_hashValue) { + 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 hashValueDecl; } + tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement); return nullptr; From 29f92f306fcdc395187e33fd38ab15a1a09c39a2 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 16:37:09 +0000 Subject: [PATCH 08/16] [test] Fix tests that look at Hashable requirements _hash(into:) needs to be included in expectations; tests looking at synthesized Hashable implementation bodies need to be updated for resilient hashing. --- test/IDE/complete_enum_elements.swift | 8 ++++++-- test/IDE/print_ast_tc_decls.swift | 4 ++++ test/IRGen/enum_derived.swift | 16 ++++++++++++---- test/SILGen/objc_bridging_any.swift | 1 + 4 files changed, 23 insertions(+), 6 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 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..e50a39434f41c 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_UnsafeHasherV9appending4bitsABSu_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 ccd9aee9648815cf42eebef848f0ee312051769c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 20:44:44 +0000 Subject: [PATCH 09/16] [stdlib] Fix type mismatches around _Hasher.finalize() --- stdlib/public/core/StringHashable.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/StringHashable.swift b/stdlib/public/core/StringHashable.swift index 3bb2ff8bafcc8..d7dc7fe85e5b1 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 Int(truncatingIfNeeded: 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 Int(truncatingIfNeeded: hasher.finalize()) #endif // _runtime(_ObjC) } } @@ -145,7 +145,7 @@ extension _UnmanagedOpaqueString { Unicode.hashUTF16( UnsafeBufferPointer(start: p, count: count), into: &hasher) - return hasher.finalize() + return Int(truncatingIfNeeded: hasher.finalize()) #endif } } From 6d1ca253b3d4b741186d29e2292274e7b84665eb Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 1 Mar 2018 20:50:58 +0000 Subject: [PATCH 10/16] [stdlib] SipHash: Fix warning about mixed-type arithmetic --- stdlib/public/core/SipHash.swift.gyb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index bff4b08ed486c..f55b40d581056 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -180,8 +180,8 @@ struct ${Self} { tailBytes: UInt64, tailByteCount: Int ) -> UInt64 { - _sanityCheck(tailByteCount >= 0 && tailByteCount < 8) - _sanityCheck(tailByteCount + (byteCount & 7) <= 7) + _sanityCheck(tailByteCount >= 0) + _sanityCheck(tailByteCount < 8 - (byteCount & 7)) _sanityCheck(tailBytes >> (tailByteCount << 3) == 0) let count = UInt64(_truncatingBits: tailByteCount._lowWord) let currentByteCount = byteCount & 7 From 3a0b8deafeeef32ee1e3171938fdb2d323360911 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 Mar 2018 03:17:30 +0000 Subject: [PATCH 11/16] [Sema] Incorporate the first round of review suggestions --- .../DerivedConformanceEquatableHashable.cpp | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index d94ae864de7d1..ee544e6e9e66d 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -735,7 +735,7 @@ ValueDecl *DerivedConformance::deriveEquatable(TypeChecker &tc, /// \p C The AST context. /// \p value The integer value. /// \return The integer literal expression. -static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { +static Expr *integerLiteralExpr(ASTContext &C, int64_t value) { llvm::SmallString<8> integerVal; APInt(32, value).toString(integerVal, 10, /*signed*/ false); auto integerStr = C.AllocateCopy(integerVal); @@ -754,21 +754,17 @@ static Expr* integerLiteralExpr(ASTContext &C, int64_t value) { /// \param hasher The base expression to make the call on. /// /// \param hashable The parameter to the call. -static CallExpr* createHasherAppendingCall(ASTContext &C, - Expr* hasher, - Expr* hashable) { +static CallExpr *createHasherAppendingCall(ASTContext &C, + Expr *hasher, + Expr *hashable) { // hasher.appending(_:) - SmallVector argNames{Identifier()}; - DeclName name(C, C.Id_appending, argNames); + DeclName name(C, C.Id_appending, {Identifier()}); 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)); + return CallExpr::createImplicit(C, appendingCall, {hashable}, {Identifier()}); } static FuncDecl * @@ -830,14 +826,20 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, } hashDecl->setInterfaceType(interfaceType); - // 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 (typeDecl->getFormalAccess() != AccessLevel::Private) { + hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); + } else { + // FIXME: We want to call copyFormalAccessAndVersionedAttrFrom here, but we + // can't copy the access level of a private type, because of the backhanded + // way we synthesize _hash(into:) in deriveHashable -- we need to make sure + // the resolver will find the new function, so it needs to be at least + // fileprivate. (The access level of synthesized members doesn't normally + // matter; they don't go through an access level check after being returned + // from the synthesizer.) + 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 @@ -896,7 +898,7 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { Type enumType = selfDecl->getType(); - // hasher + // Extract the decl for the hasher parameter. auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); unsigned index = 0; @@ -905,12 +907,15 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { auto hasNoAssociatedValues = enumDecl->hasOnlyCasesWithoutAssociatedValues(); // For each enum element, generate a case statement that binds the associated - // values so that their hash values can be obtained. + // values so that they can be fed to the hasher. for (auto elt : enumDecl->getAllElements()) { // case .(let a0, let a1, ...): SmallVector payloadVars; - // := hasher + // hasherExpr will hold the returned expression. It starts by the hasher + // parameter, but we will add one or more chained method calls to it below. + // + // := hasher Expr* hasherExpr = new (C) DeclRefExpr(ConcreteDeclRef(hasherParam), DeclNameLoc(), /*implicit*/ true); @@ -929,7 +934,7 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { // case does have associated values, then the ordinal is used as the first // term fed into the hasher. - // := .appending() + // := .appending() { auto ordinalExpr = integerLiteralExpr(C, index++); hasherExpr = createHasherAppendingCall(C, hasherExpr, ordinalExpr); @@ -941,15 +946,15 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { for (auto payloadVar : payloadVars) { auto payloadVarRef = new (C) DeclRefExpr(payloadVar, DeclNameLoc(), /*implicit*/ true); - // := .appending() + // := .appending() hasherExpr = createHasherAppendingCall(C, hasherExpr, payloadVarRef); } } auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); - SmallVector statements { ASTNode(returnStmt) }; auto hasBoundDecls = !payloadVars.empty(); - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); + auto body = BraceStmt::create(C, SourceLoc(), + {ASTNode(returnStmt)}, SourceLoc()); cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem, hasBoundDecls, SourceLoc(), body)); } @@ -960,8 +965,7 @@ deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) { auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef, SourceLoc(), cases, SourceLoc(), C); - SmallVector statements { ASTNode(switchStmt) }; - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc()); + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(switchStmt)}, SourceLoc()); hashIntoDecl->setBody(body); } @@ -981,17 +985,20 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { auto structDecl = parentDC->getAsStructOrStructExtensionContext(); auto selfDecl = hashIntoDecl->getImplicitSelfDecl(); - // hasher + // Extract the decl for the hasher parameter. auto hasherParam = hashIntoDecl->getParameterList(1)->get(0); + // hasherExpr will hold the returned expression. It starts by the hasher + // parameter, but we will add one or more chained method calls to it below. + // + // := hasher Expr* hasherExpr = new (C) DeclRefExpr(hasherParam, DeclNameLoc(), /*implicit*/ true); auto storedProperties = structDecl->getStoredProperties(/*skipInaccessible=*/true); - // For each stored property, generate a statement that feeds it into - // the hasher. + // Feed each stored property into the hasher. for (auto propertyDecl : storedProperties) { auto propertyRef = new (C) DeclRefExpr(propertyDecl, DeclNameLoc(), /*implicit*/ true); @@ -999,13 +1006,12 @@ deriveBodyHashable_struct_hashInto(AbstractFunctionDecl *hashIntoDecl) { /*implicit*/ true); auto selfPropertyExpr = new (C) DotSyntaxCallExpr(propertyRef, SourceLoc(), selfRef); - // := .appending(self.) + // := .appending(self.) hasherExpr = createHasherAppendingCall(C, hasherExpr, selfPropertyExpr); } auto returnStmt = new (C) ReturnStmt(SourceLoc(), hasherExpr); - SmallVector statements { ASTNode(returnStmt) }; - auto body = BraceStmt::create(C, SourceLoc(), statements, + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(returnStmt)}, SourceLoc(), /*implicit*/ true); hashIntoDecl->setBody(body); } @@ -1018,7 +1024,8 @@ deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { // return _hashValue(for: self) auto *hashFunc = C.getHashValueForDecl(); - auto hashExpr = new (C) DeclRefExpr(hashFunc, DeclNameLoc(), /*implicit*/ true); + auto hashExpr = new (C) DeclRefExpr(hashFunc, DeclNameLoc(), + /*implicit*/ true); auto selfDecl = hashValueDecl->getImplicitSelfDecl(); auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(), /*implicit*/ true); @@ -1026,8 +1033,7 @@ deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { { selfRef }, { C.Id_for }); auto returnStmt = new (C) ReturnStmt(SourceLoc(), callExpr); - SmallVector statements { returnStmt }; - auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(), + auto body = BraceStmt::create(C, SourceLoc(), {returnStmt}, SourceLoc(), /*implicit*/ true); hashValueDecl->setBody(body); } @@ -1201,10 +1207,10 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, else if (auto theStruct = dyn_cast(type)) hashIntoSynthesizer = &deriveBodyHashable_struct_hashInto; else - llvm_unreachable(""); + llvm_unreachable("Attempt to derive Hashable for a type other " + "than a struct or enum"); auto hashIntoDecl = deriveHashable_hashInto(tc, parentDecl, type, hashIntoSynthesizer); -#if !defined(NDEBUG) if (hashIntoDecl) { auto conformance = getHashableConformance(parentDecl); auto witnessDecl = conformance->getWitnessDecl(hashIntoReq, &tc); @@ -1213,7 +1219,6 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, diag::broken_hashable_requirement); } } -#endif } return hashValueDecl; } From ce7f29d941a2dfed470045e63d6c92954e564573 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 Mar 2018 03:18:14 +0000 Subject: [PATCH 12/16] [stdlib] SipHash: Un-unroll SipRound loop in _compress(_:). --- stdlib/public/core/SipHash.swift.gyb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/SipHash.swift.gyb b/stdlib/public/core/SipHash.swift.gyb index f55b40d581056..ff97ef3658f63 100644 --- a/stdlib/public/core/SipHash.swift.gyb +++ b/stdlib/public/core/SipHash.swift.gyb @@ -117,9 +117,9 @@ struct ${Self} { @_versioned internal mutating func _compress(_ m: UInt64) { v3 ^= m - % for _ in range(0, c_rounds): - _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) - % end + for _ in 0..<${c_rounds} { + _SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3) + } v0 ^= m } From 6b5f9a20c34d5f2fa9d9b35d6a7cec4b0181ce66 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 Mar 2018 03:58:11 +0000 Subject: [PATCH 13/16] [Sema] Pick a nit --- lib/Sema/DerivedConformanceEquatableHashable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index ee544e6e9e66d..3e73cf15c36fc 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -836,7 +836,7 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, // fileprivate. (The access level of synthesized members doesn't normally // matter; they don't go through an access level check after being returned // from the synthesizer.) - hashDecl->setAccess(std::max(typeDecl->getFormalAccess(), AccessLevel::FilePrivate)); + hashDecl->setAccess(AccessLevel::FilePrivate); if (typeDecl->getAttrs().hasAttribute()) { hashDecl->getAttrs().add(new (C) VersionedAttr(/*implicit=*/true)); } From 73af8ffbd6895c000384bfe05dc40339550986db Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 Mar 2018 18:45:54 +0000 Subject: [PATCH 14/16] Replace default implementation for _hash(into:) with automatic synthesis This removes the glaring cheat of force-synthesizing _hash(into:) whenever we derive hashValue. _hash(into:) is now automatically derived whenever it's not explicitly implemented, including in classes and extensions of (possibly imported or synthesized) structs, enums or classes. This is not entirely free of magic, though. To determine which implementation to generate, the body synthesizer takes a peek into the parent decl context -- if it finds an implicit hashValue there, the generated body implements hashing from scratch, feeding components into the hasher. Otherwise, we assume hashValue has been resolved to an explicit implementation, and we generate the body for the (previously) default implementation of _hash(into:). This change introduces some diagnostic regressions, and also requires a followup change in the CoreFoundation overlay. --- .../DerivedConformanceEquatableHashable.cpp | 186 ++++++++++-------- lib/Sema/DerivedConformances.cpp | 24 ++- stdlib/public/core/Hashable.swift | 10 +- 3 files changed, 123 insertions(+), 97 deletions(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 3e73cf15c36fc..8ec5c642d2edd 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -825,22 +825,7 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl, FunctionType::ExtInfo()); } hashDecl->setInterfaceType(interfaceType); - - if (typeDecl->getFormalAccess() != AccessLevel::Private) { - hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); - } else { - // FIXME: We want to call copyFormalAccessAndVersionedAttrFrom here, but we - // can't copy the access level of a private type, because of the backhanded - // way we synthesize _hash(into:) in deriveHashable -- we need to make sure - // the resolver will find the new function, so it needs to be at least - // fileprivate. (The access level of synthesized members doesn't normally - // matter; they don't go through an access level check after being returned - // from the synthesizer.) - hashDecl->setAccess(AccessLevel::FilePrivate); - if (typeDecl->getAttrs().hasAttribute()) { - hashDecl->getAttrs().add(new (C) VersionedAttr(/*implicit=*/true)); - } - } + hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl); // 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 @@ -860,9 +845,55 @@ 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; +} + +/// Derive the body for the _hash(into:) method when hashValue has a non-derived +/// implementation. +static void +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); + auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(returnStmt)}, + SourceLoc(), /*implicit*/ true); + hashIntoDecl->setBody(body); +} + /// 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 { @@ -972,6 +1003,10 @@ 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 @@ -1016,6 +1051,15 @@ 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 (parentHasDerivedHashValueImplementation(hashIntoDecl)) { + llvm_unreachable("Derived hashValue into a class."); + } + deriveBodyHashable_compat_hashInto(hashIntoDecl); +} + /// Derive the body for the 'hashValue' getter. static void deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { @@ -1141,33 +1185,14 @@ 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); -} - -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; - } - } - 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; + 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, @@ -1175,52 +1200,47 @@ 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); - 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 hashValueDecl = deriveHashable_hashValue(tc, parentDecl, type); + 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); + } - // 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 + // 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, an enum, or a class. + 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("Attempt to derive Hashable for a type other " - "than a struct or enum"); - auto hashIntoDecl = deriveHashable_hashInto(tc, parentDecl, type, - hashIntoSynthesizer); - if (hashIntoDecl) { - auto conformance = getHashableConformance(parentDecl); - auto witnessDecl = conformance->getWitnessDecl(hashIntoReq, &tc); - if (witnessDecl != hashIntoDecl) { - tc.diagnose(requirement->getLoc(), - diag::broken_hashable_requirement); - } - } - } - return hashValueDecl; + "than a struct, enum or class"); } tc.diagnose(requirement->getLoc(), 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/core/Hashable.swift b/stdlib/public/core/Hashable.swift index b01b1f8fd0d9b..cdd0cb065968a 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -110,16 +110,12 @@ 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 { - @inline(__always) - 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 bb64446d41d7233bc08b392e1677edbf31b2f695 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 2 Mar 2018 18:56:10 +0000 Subject: [PATCH 15/16] [CoreFoundation] _CFObject: Provide default implementation for _hash(into:) Without this change, the Hashable synthesizer attempts to add _hash(into:) methods directly on CF types, which (somewhat unsurprisingly) fails with the following assert: Assertion failed: (!decl->isForeign() && "Use getForeignMetadataLayout()"), function getClassMetadataLayout, file /Users/lorentey/Swift/swift/lib/IRGen/MetadataLayout.cpp, line 86. 0 swift 0x0000000108e28b08 llvm::sys::PrintStackTrace(llvm::raw_ostream&) + 40 1 swift 0x0000000108e29216 SignalHandler(int) + 694 2 libsystem_platform.dylib 0x00007fff587aef5a _sigtramp + 26 3 libsystem_platform.dylib 0x0000000000003628 _sigtramp + 2810529512 4 libsystem_c.dylib 0x00007fff585571ae abort + 127 5 libsystem_c.dylib 0x00007fff5851f1ac basename_r + 0 6 swift 0x0000000105a2a460 swift::irgen::IRGenModule::getClassMetadataLayout(swift::ClassDecl*) + 96 7 swift 0x0000000105970dc8 swift::irgen::emitVirtualMethodValue(swift::irgen::IRGenFunction&, llvm::Value*, swift::SILDeclRef, swift::CanTypeWrapper) + 184 8 swift 0x000000010597121d swift::irgen::emitVirtualMethodValue(swift::irgen::IRGenFunction&, llvm::Value*, swift::SILType, swift::SILDeclRef, swift::CanTypeWrapper, bool) + 685 9 swift 0x0000000105a01482 swift::SILInstructionVisitor<(anonymous namespace)::IRGenSILFunction, void>::visit(swift::SILInstruction*) + 36562 10 swift 0x00000001059f4e4f (anonymous namespace)::IRGenSILFunction::emitSILFunction() + 6863 11 swift 0x00000001059f2e1b swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 1371 12 swift 0x00000001058fccab swift::irgen::IRGenerator::emitLazyDefinitions() + 1051 13 swift 0x00000001059cf676 performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, std::__1::unique_ptr >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, swift::SourceFile*, llvm::GlobalVariable**, unsigned int) + 1366 14 swift 0x00000001059cfc5e swift::performIRGeneration(swift::IRGenOptions&, swift::SourceFile&, std::__1::unique_ptr >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, unsigned int, llvm::GlobalVariable**) + 94 15 swift 0x000000010586d2a9 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 15337 16 swift 0x0000000105868715 swift::performFrontend(llvm::ArrayRef, char const*, void*, swift::FrontendObserver*) + 2869 17 swift 0x000000010581f300 main + 2832 18 libdyld.dylib 0x00007fff584ab015 start + 1 --- 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 01c41b64a7c935fded78f12a2c15d526be762483 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 5 Mar 2018 14:31:15 +0000 Subject: [PATCH 16/16] [Sema] Always allow hashValue to be synthesized when _hash(into:) is present This makes it possible to implement Hashable by defining either hashValue or _hash(into:). --- .../DerivedConformanceEquatableHashable.cpp | 136 +++++++++--------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index 8ec5c642d2edd..d7027ece5a310 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -845,23 +845,8 @@ 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; -} - -/// Derive the body for the _hash(into:) method when hashValue has a non-derived -/// implementation. +/// Derive the body for the _hash(into:) method when hashValue has a +/// user-supplied implementation. static void deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) { // func _hash(into hasher: _UnsafeHasher) -> _UnsafeHasher { @@ -890,10 +875,6 @@ deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) { /// 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 { @@ -1003,10 +984,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 @@ -1051,15 +1028,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 (parentHasDerivedHashValueImplementation(hashIntoDecl)) { - llvm_unreachable("Derived hashValue into a class."); - } - deriveBodyHashable_compat_hashInto(hashIntoDecl); -} - /// Derive the body for the 'hashValue' getter. static void deriveBodyHashable_hashValue(AbstractFunctionDecl *hashValueDecl) { @@ -1182,6 +1150,31 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl, return hashValueDecl; } +static ValueDecl * +getHashValueRequirement(ASTContext &C) { + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + for (auto member: hashableProto->getMembers()) { + if (auto fd = dyn_cast(member)) { + if (fd->getBaseName() == C.Id_hashValue) + return fd; + } + } + 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; +} + bool DerivedConformance::canDeriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { @@ -1200,47 +1193,58 @@ ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc, NominalTypeDecl *type, ValueDecl *requirement) { ASTContext &C = parentDecl->getASTContext(); - auto theEnum = dyn_cast(type); // 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; - } + // We always allow hashValue to be synthesized; invalid cases are diagnosed + // during _hash(into:) synthesis. return deriveHashable_hashValue(tc, parentDecl, type); } // 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, an enum, or a class. - 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 + // Start by resolving hashValue conformance. + auto hashValueReq = getHashValueRequirement(C); + auto conformance = getHashableConformance(parentDecl); + auto hashValueDecl = conformance->getWitnessDecl(hashValueReq, &tc); + + if (hashValueDecl->isImplicit()) { + // Neither hashValue nor _hash(into:) is explicitly defined; we need to do + // a full Hashable derivation. + + // Refuse to synthesize Hashable if type isn't a struct or enum, or if it + // has non-Hashable stored properties/associated values. + auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable); + if (!canDeriveConformance(tc, type, hashableProto)) { + tc.diagnose(parentDecl->getLoc(), diag::type_does_not_conform, + type->getDeclaredType(), hashableProto->getDeclaredType()); + return nullptr; + } + // Hashable can't be fully 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); + if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) && + type != parentDecl) { + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + hashableProto->getDeclaredType()); + return nullptr; + } + 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 llvm_unreachable("Attempt to derive Hashable for a type other " - "than a struct, enum or class"); + "than a struct or enum"); + } else { + // We can always derive _hash(into:) if hashValue has an explicit + // implementation. + return deriveHashable_hashInto(tc, parentDecl, type, + &deriveBodyHashable_compat_hashInto); + } } tc.diagnose(requirement->getLoc(),