Skip to content

Commit 95463a8

Browse files
authored
Merge pull request #4157 from apple/swift3-stdlib-AnyHashable-fixes
[swift-3.0-branch] stdlib and runtime: a bunch of fixes for AnyHashable and id-as-Any
2 parents 58e710e + 37f54bc commit 95463a8

19 files changed

+871
-170
lines changed

stdlib/private/StdlibUnittest/MinimalTypes.swift.gyb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ public func < (
103103
return MinimalComparableValue.lessImpl.value(lhs.value, rhs.value)
104104
}
105105

106-
% for kind in [ 'Value', 'Class' ]:
106+
% for (kind, decl_keyword) in [ ('Value', 'struct'), ('Class', 'class') ]:
107107
% Self = 'MinimalHashable%s' % kind
108108

109109
/// A type that conforms only to `Equatable` and `Hashable`.
110110
///
111111
/// This type can be used to check that generic functions don't rely on any
112112
/// other conformances.
113-
public struct ${Self} : Equatable, Hashable {
113+
public ${decl_keyword} ${Self} : Equatable, Hashable {
114114
public static var timesEqualEqualWasCalled: Int = 0
115115
public static var timesHashValueWasCalled: Int = 0
116116

@@ -148,7 +148,7 @@ public func == (
148148

149149
% end
150150

151-
% for kind in [ 'Value', 'Class' ]:
151+
% for (kind, decl_keyword) in [ ('Value', 'struct'), ('Class', 'class') ]:
152152
% Self = 'GenericMinimalHashable%s' % kind
153153

154154
public var ${Self}_timesEqualEqualWasCalled: Int = 0
@@ -163,7 +163,7 @@ public var ${Self}_hashValueImpl = ResettableValue<(Any) -> Int>(
163163
///
164164
/// This type can be used to check that generic functions don't rely on any
165165
/// other conformances.
166-
public struct ${Self}<Wrapped> : Equatable, Hashable {
166+
public ${decl_keyword} ${Self}<Wrapped> : Equatable, Hashable {
167167
public var value: Wrapped
168168
public var identity: Int
169169

stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,7 @@ public enum TestRunPredicate : CustomStringConvertible {
19401940
public func checkEquatable<Instances : Collection>(
19411941
_ instances: Instances,
19421942
oracle: (Instances.Index, Instances.Index) -> Bool,
1943+
allowBrokenTransitivity: Bool = false,
19431944
${TRACE}
19441945
) where
19451946
Instances.Iterator.Element : Equatable,
@@ -1950,12 +1951,14 @@ public func checkEquatable<Instances : Collection>(
19501951
let indices = Array(instances.indices)
19511952
_checkEquatableImpl(
19521953
Array(instances),
1953-
oracle: { oracle(indices[$0], indices[$1]) })
1954+
oracle: { oracle(indices[$0], indices[$1]) },
1955+
allowBrokenTransitivity: allowBrokenTransitivity)
19541956
}
19551957

19561958
internal func _checkEquatableImpl<Instance : Equatable>(
19571959
_ instances: [Instance],
19581960
oracle: (Int, Int) -> Bool,
1961+
allowBrokenTransitivity: Bool = false,
19591962
${TRACE}
19601963
) {
19611964
// For each index (which corresponds to an instance being tested) track the
@@ -1988,22 +1991,24 @@ internal func _checkEquatableImpl<Instance : Equatable>(
19881991
"lhs (at index \(i)): \(x)\nrhs (at index \(j)): \(y)",
19891992
stackTrace: ${stackTrace})
19901993

1991-
// Check transitivity of the predicate represented by the oracle.
1992-
// If we are adding the instance `j` into an equivalence set, check that
1993-
// it is equal to every other instance in the set.
1994-
if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted {
1995-
if transitivityScoreboard[i].value.count == 1 {
1996-
transitivityScoreboard[i].value.insert(i)
1997-
}
1998-
for k in transitivityScoreboard[i].value {
1999-
expectTrue(
2000-
oracle(j, k),
2001-
"bad oracle: broken transitivity at indices \(i), \(j), \(k)")
2002-
// No need to check equality between actual values, we will check
2003-
// them with the checks above.
1994+
if !allowBrokenTransitivity {
1995+
// Check transitivity of the predicate represented by the oracle.
1996+
// If we are adding the instance `j` into an equivalence set, check that
1997+
// it is equal to every other instance in the set.
1998+
if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted {
1999+
if transitivityScoreboard[i].value.count == 1 {
2000+
transitivityScoreboard[i].value.insert(i)
2001+
}
2002+
for k in transitivityScoreboard[i].value {
2003+
expectTrue(
2004+
oracle(j, k),
2005+
"bad oracle: broken transitivity at indices \(i), \(j), \(k)")
2006+
// No need to check equality between actual values, we will check
2007+
// them with the checks above.
2008+
}
2009+
precondition(transitivityScoreboard[j].value.isEmpty)
2010+
transitivityScoreboard[j] = transitivityScoreboard[i]
20042011
}
2005-
precondition(transitivityScoreboard[j].value.isEmpty)
2006-
transitivityScoreboard[j] = transitivityScoreboard[i]
20072012
}
20082013
}
20092014
}
@@ -2024,15 +2029,20 @@ public func checkEquatable<T : Equatable>(
20242029
public func checkHashable<Instances : Collection>(
20252030
_ instances: Instances,
20262031
equalityOracle: (Instances.Index, Instances.Index) -> Bool,
2032+
allowBrokenTransitivity: Bool = false,
20272033
${TRACE}
20282034
) where
20292035
Instances.Iterator.Element : Hashable,
20302036
// FIXME(compiler limitation): these constraints should be applied to
20312037
// associated types of Collection.
20322038
Instances.Indices.Iterator.Element == Instances.Index {
2033-
2034-
checkEquatable(instances, oracle: equalityOracle, ${trace})
2035-
2039+
2040+
checkEquatable(
2041+
instances,
2042+
oracle: equalityOracle,
2043+
allowBrokenTransitivity: allowBrokenTransitivity,
2044+
${trace})
2045+
20362046
for i in instances.indices {
20372047
let x = instances[i]
20382048
for j in instances.indices {

stdlib/public/core/AnyHashable.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,8 @@ extension Hashable {
195195
/// Returns a default (non-custom) representation of `self`
196196
/// as `AnyHashable`.
197197
///
198-
/// Completely ignores the
199-
/// `_HasCustomAnyHashableRepresentation` conformance, if
200-
/// any.
198+
/// Completely ignores the `_HasCustomAnyHashableRepresentation`
199+
/// conformance, if it exstis.
201200
@_silgen_name("_swift_stdlib_makeAnyHashableUsingDefaultRepresentation")
202201
public // COMPILER_INTRINSIC (actually, called from the runtime)
203202
func _stdlib_makeAnyHashableUsingDefaultRepresentation<H : Hashable>(
@@ -223,14 +222,16 @@ func _convertToAnyHashable<H : Hashable>(_ value: H) -> AnyHashable {
223222
public // COMPILER_INTRINSIC (actually, called from the runtime)
224223
func _convertToAnyHashableIndirect<H : Hashable>(
225224
_ value: H,
226-
_ target: UnsafeMutablePointer<AnyHashable>) {
225+
_ target: UnsafeMutablePointer<AnyHashable>
226+
) {
227227
target.initialize(to: AnyHashable(value))
228228
}
229229

230230
@_silgen_name("_swift_anyHashableDownCastConditionalIndirect")
231231
public // COMPILER_INTRINSIC (actually, called from the runtime)
232232
func _anyHashableDownCastConditionalIndirect<T>(
233233
_ value: UnsafePointer<AnyHashable>,
234-
_ target: UnsafeMutablePointer<T>) -> Bool {
234+
_ target: UnsafeMutablePointer<T>
235+
) -> Bool {
235236
return value.pointee._downCastConditional(into: target)
236-
}
237+
}

stdlib/public/core/Hashable.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,22 @@ public protocol Hashable : _Hashable, Equatable {
9898
var hashValue: Int { get }
9999
}
100100

101+
public enum _RuntimeHelpers {}
102+
103+
extension _RuntimeHelpers {
104+
@_silgen_name("swift_stdlib_Hashable_isEqual_indirect")
105+
public static func Hashable_isEqual_indirect<T : Hashable>(
106+
_ lhs: UnsafePointer<T>,
107+
_ rhs: UnsafePointer<T>
108+
) -> Bool {
109+
return lhs.pointee == rhs.pointee
110+
}
111+
112+
@_silgen_name("swift_stdlib_Hashable_hashValue_indirect")
113+
public static func Hashable_hashValue_indirect<T : Hashable>(
114+
_ value: UnsafePointer<T>
115+
) -> Int {
116+
return value.pointee.hashValue
117+
}
118+
}
119+

stdlib/public/stubs/AnyHashableSupport.cpp renamed to stdlib/public/runtime/AnyHashableSupport.cpp

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
#include "swift/Runtime/Concurrent.h"
1616
#include "swift/Runtime/Debug.h"
1717
#include "swift/Runtime/Metadata.h"
18-
#include "../runtime/Private.h"
18+
#include "Private.h"
19+
#include "SwiftValue.h"
20+
#include "SwiftHashableSupport.h"
1921

2022
using namespace swift;
21-
22-
/// The name demangles to "protocol descriptor for Swift.Hashable".
23-
extern "C" const ProtocolDescriptor _TMps8Hashable;
23+
using namespace swift::hashable_support;
2424

2525
namespace {
2626
struct HashableConformanceKey {
@@ -36,6 +36,9 @@ struct HashableConformanceEntry {
3636

3737
/// The highest (closest to the root) type in the superclass chain
3838
/// that conforms to `Hashable`.
39+
///
40+
/// Always non-NULL. We don't cache negative responses so that we
41+
/// don't have to deal with cache invalidation.
3942
const Metadata *baseTypeThatConformsToHashable;
4043

4144
HashableConformanceEntry(HashableConformanceKey key,
@@ -59,16 +62,26 @@ struct HashableConformanceEntry {
5962
};
6063
} // end unnamed namesapce
6164

65+
// FIXME(performance): consider merging this cache into the regular
66+
// protocol conformance cache.
6267
static Lazy<ConcurrentMap<HashableConformanceEntry>> HashableConformances;
6368

64-
/// Find the base type that introduces the `Hashable` conformance.
65-
///
66-
/// - Precondition: `type` conforms to `Hashable` (not checked).
67-
static const Metadata *findHashableBaseType(const Metadata *type) {
69+
template<bool KnownToConformToHashable>
70+
LLVM_ATTRIBUTE_ALWAYS_INLINE
71+
static const Metadata *findHashableBaseTypeImpl(const Metadata *type) {
72+
// Check the cache first.
6873
if (HashableConformanceEntry *entry =
6974
HashableConformances->find(HashableConformanceKey{type})) {
7075
return entry->baseTypeThatConformsToHashable;
7176
}
77+
if (!KnownToConformToHashable &&
78+
!swift_conformsToProtocol(type, &_TMps8Hashable)) {
79+
// Don't cache the negative response because we don't invalidate
80+
// this cache when a new conformance is loaded dynamically.
81+
return nullptr;
82+
}
83+
// By this point, `type` is known to conform to `Hashable`.
84+
7285
const Metadata *baseTypeThatConformsToHashable = type;
7386
while (true) {
7487
const Metadata *superclass =
@@ -84,6 +97,26 @@ static const Metadata *findHashableBaseType(const Metadata *type) {
8497
return baseTypeThatConformsToHashable;
8598
}
8699

100+
/// Find the base type that introduces the `Hashable` conformance.
101+
/// Because the provided type is known to conform to `Hashable`, this
102+
/// function always returns non-null.
103+
///
104+
/// - Precondition: `type` conforms to `Hashable` (not checked).
105+
const Metadata *swift::hashable_support::findHashableBaseTypeOfHashableType(
106+
const Metadata *type) {
107+
auto result =
108+
findHashableBaseTypeImpl</*KnownToConformToHashable=*/ true>(type);
109+
assert(result && "Known-hashable types should have a `Hashable` conformance.");
110+
return result;
111+
}
112+
113+
/// Find the base type that introduces the `Hashable` conformance.
114+
/// If `type` does not conform to `Hashable`, `nullptr` is returned.
115+
const Metadata *swift::hashable_support::findHashableBaseType(
116+
const Metadata *type) {
117+
return findHashableBaseTypeImpl</*KnownToConformToHashable=*/ false>(type);
118+
}
119+
87120
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
88121
extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
89122
const OpaqueValue *value,
@@ -94,7 +127,7 @@ extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
94127

95128
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
96129
extern "C" void _swift_stdlib_makeAnyHashableUpcastingToHashableBaseType(
97-
const OpaqueValue *value,
130+
OpaqueValue *value,
98131
const void *anyHashableResultPointer,
99132
const Metadata *type,
100133
const WitnessTable *hashableWT
@@ -103,8 +136,35 @@ extern "C" void _swift_stdlib_makeAnyHashableUpcastingToHashableBaseType(
103136
case MetadataKind::Class:
104137
case MetadataKind::ObjCClassWrapper:
105138
case MetadataKind::ForeignClass: {
139+
#if SWIFT_OBJC_INTEROP
140+
id srcObject;
141+
memcpy(&srcObject, value, sizeof(id));
142+
// Do we have a SwiftValue?
143+
if (SwiftValue *srcSwiftValue = getAsSwiftValue(srcObject)) {
144+
// If so, extract the boxed value and try to cast it.
145+
const Metadata *unboxedType;
146+
const OpaqueValue *unboxedValue;
147+
std::tie(unboxedType, unboxedValue) =
148+
getValueFromSwiftValue(srcSwiftValue);
149+
150+
if (auto unboxedHashableWT =
151+
swift_conformsToProtocol(type, &_TMps8Hashable)) {
152+
ValueBuffer unboxedCopyBuf;
153+
auto unboxedValueCopy = unboxedType->vw_initializeBufferWithCopy(
154+
&unboxedCopyBuf, const_cast<OpaqueValue *>(unboxedValue));
155+
_swift_stdlib_makeAnyHashableUpcastingToHashableBaseType(
156+
unboxedValueCopy, anyHashableResultPointer, unboxedType,
157+
unboxedHashableWT);
158+
unboxedType->vw_deallocateBuffer(&unboxedCopyBuf);
159+
type->vw_destroy(value);
160+
return;
161+
}
162+
}
163+
#endif
164+
106165
_swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
107-
value, anyHashableResultPointer, findHashableBaseType(type),
166+
value, anyHashableResultPointer,
167+
findHashableBaseTypeOfHashableType(type),
108168
hashableWT);
109169
return;
110170
}

stdlib/public/runtime/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ else()
3333
endif()
3434

3535
set(swift_runtime_sources
36+
AnyHashableSupport.cpp
3637
Casting.cpp
3738
CygwinPort.cpp
3839
Demangle.cpp

stdlib/public/runtime/Casting.cpp

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "ErrorObject.h"
3030
#include "ExistentialMetadataImpl.h"
3131
#include "Private.h"
32+
#include "SwiftHashableSupport.h"
3233
#include "../SwiftShims/RuntimeShims.h"
3334
#include "stddef.h"
3435
#if SWIFT_OBJC_INTEROP
@@ -40,6 +41,7 @@
4041
#include <type_traits>
4142

4243
using namespace swift;
44+
using namespace swift::hashable_support;
4345
using namespace metadataimpl;
4446

4547
#if SWIFT_OBJC_INTEROP
@@ -905,28 +907,15 @@ static bool isAnyHashableType(const Metadata *type) {
905907
return false;
906908
}
907909

908-
SWIFT_CC(swift)
909-
extern "C"
910-
void _swift_convertToAnyHashableIndirect(OpaqueValue *source,
911-
OpaqueValue *destination,
912-
const Metadata *sourceType,
913-
const WitnessTable *sourceConformance);
914-
915-
SWIFT_CC(swift)
916-
extern "C"
917-
bool _swift_anyHashableDownCastConditionalIndirect(OpaqueValue *source,
918-
OpaqueValue *destination,
919-
const Metadata *targetType);
920-
921910
/// Perform a dynamic cast from a nominal type to AnyHashable.
922911
static bool _dynamicCastToAnyHashable(OpaqueValue *destination,
923912
OpaqueValue *source,
924913
const Metadata *sourceType,
925914
const Metadata *targetType,
926915
DynamicCastFlags flags) {
927916
// Look for a conformance to Hashable.
928-
auto hashableConformance =
929-
swift_conformsToProtocol(sourceType, &_TMps8Hashable);
917+
auto hashableConformance = reinterpret_cast<const HashableWitnessTable *>(
918+
swift_conformsToProtocol(sourceType, &_TMps8Hashable));
930919

931920
// If we don't find one, the cast fails.
932921
if (!hashableConformance) {
@@ -1642,11 +1631,6 @@ extern "C" const ProtocolDescriptor _TMps5Error;
16421631
static const WitnessTable *findErrorWitness(const Metadata *srcType) {
16431632
return swift_conformsToProtocol(srcType, &_TMps5Error);
16441633
}
1645-
1646-
static const Metadata *getNSErrorMetadata() {
1647-
return SWIFT_LAZY_CONSTANT(
1648-
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
1649-
}
16501634
#endif
16511635

16521636
/// Perform a dynamic cast from an existential type to some kind of

0 commit comments

Comments
 (0)