runtime: make _SwiftNativeNSError use the Hashable conformance, if available

If the Swift error wrapped in a _SwiftNativeNSError box conforms to
Hashable, the box now uses the Swift's conformance to Hashable.

Part of rdar://problem/27574348.
This commit is contained in:
Dmitri Gribenko
2016-08-01 20:28:14 -07:00
parent 08c772fdac
commit b162f60070
12 changed files with 400 additions and 42 deletions

View File

@@ -98,3 +98,22 @@ public protocol Hashable : _Hashable, Equatable {
var hashValue: Int { get }
}
public enum _RuntimeHelpers {}
extension _RuntimeHelpers {
@_silgen_name("swift_stdlib_Hashable_isEqual_indirect")
public static func Hashable_isEqual_indirect<T : Hashable>(
_ lhs: UnsafePointer<T>,
_ rhs: UnsafePointer<T>
) -> Bool {
return lhs.pointee == rhs.pointee
}
@_silgen_name("swift_stdlib_Hashable_hashValue_indirect")
public static func Hashable_hashValue_indirect<T : Hashable>(
_ value: UnsafePointer<T>
) -> Int {
return value.pointee.hashValue
}
}

View File

@@ -15,12 +15,11 @@
#include "swift/Runtime/Concurrent.h"
#include "swift/Runtime/Debug.h"
#include "swift/Runtime/Metadata.h"
#include "../runtime/Private.h"
#include "Private.h"
#include "SwiftHashableSupport.h"
using namespace swift;
/// The name demangles to "protocol descriptor for Swift.Hashable".
extern "C" const ProtocolDescriptor _TMps8Hashable;
using namespace swift::hashable_support;
namespace {
struct HashableConformanceKey {
@@ -36,6 +35,9 @@ struct HashableConformanceEntry {
/// The highest (closest to the root) type in the superclass chain
/// that conforms to `Hashable`.
///
/// Always non-NULL. We don't cache negative responses so that we
/// don't have to deal with cache invalidation.
const Metadata *baseTypeThatConformsToHashable;
HashableConformanceEntry(HashableConformanceKey key,
@@ -59,16 +61,26 @@ struct HashableConformanceEntry {
};
} // end unnamed namesapce
// FIXME(performance): consider merging this cache into the regular
// protocol conformance cache.
static Lazy<ConcurrentMap<HashableConformanceEntry>> HashableConformances;
/// Find the base type that introduces the `Hashable` conformance.
///
/// - Precondition: `type` conforms to `Hashable` (not checked).
static const Metadata *findHashableBaseType(const Metadata *type) {
template<bool KnownToConformToHashable>
LLVM_ATTRIBUTE_ALWAYS_INLINE
static const Metadata *findHashableBaseTypeImpl(const Metadata *type) {
// Check the cache first.
if (HashableConformanceEntry *entry =
HashableConformances->find(HashableConformanceKey{type})) {
return entry->baseTypeThatConformsToHashable;
}
if (!KnownToConformToHashable &&
!swift_conformsToProtocol(type, &_TMps8Hashable)) {
// Don't cache the negative response because we don't invalidate
// this cache when a new conformance is loaded dynamically.
return nullptr;
}
// By this point, `type` is known to conform to `Hashable`.
const Metadata *baseTypeThatConformsToHashable = type;
while (true) {
const Metadata *superclass =
@@ -84,6 +96,26 @@ static const Metadata *findHashableBaseType(const Metadata *type) {
return baseTypeThatConformsToHashable;
}
/// Find the base type that introduces the `Hashable` conformance.
/// Because the provided type is known to conform to `Hashable`, this
/// function always returns non-null.
///
/// - Precondition: `type` conforms to `Hashable` (not checked).
const Metadata *swift::hashable_support::findHashableBaseTypeOfHashableType(
const Metadata *type) {
auto result =
findHashableBaseTypeImpl</*KnownToConformToHashable=*/ true>(type);
assert(result && "Known-hashable types should have a `Hashable` conformance.");
return result;
}
/// Find the base type that introduces the `Hashable` conformance.
/// If `type` does not conform to `Hashable`, `nullptr` is returned.
const Metadata *swift::hashable_support::findHashableBaseType(
const Metadata *type) {
return findHashableBaseTypeImpl</*KnownToConformToHashable=*/ false>(type);
}
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
const OpaqueValue *value,
@@ -104,7 +136,8 @@ extern "C" void _swift_stdlib_makeAnyHashableUpcastingToHashableBaseType(
case MetadataKind::ObjCClassWrapper:
case MetadataKind::ForeignClass: {
_swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
value, anyHashableResultPointer, findHashableBaseType(type),
value, anyHashableResultPointer,
findHashableBaseTypeOfHashableType(type),
hashableWT);
return;
}

View File

@@ -34,6 +34,7 @@ else()
endif()
set(swift_runtime_sources
AnyHashableSupport.cpp
Casting.cpp
CygwinPort.cpp
Demangle.cpp

View File

@@ -1642,11 +1642,6 @@ extern "C" const ProtocolDescriptor _TMps5Error;
static const WitnessTable *findErrorWitness(const Metadata *srcType) {
return swift_conformsToProtocol(srcType, &_TMps5Error);
}
static const Metadata *getNSErrorMetadata() {
return SWIFT_LAZY_CONSTANT(
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
}
#endif
/// Perform a dynamic cast from an existential type to some kind of

View File

@@ -26,6 +26,7 @@
#include "swift/Runtime/Metadata.h"
#include "swift/Runtime/HeapObject.h"
#include "SwiftHashableSupport.h"
#include <atomic>
#if SWIFT_OBJC_INTEROP
# include <CoreFoundation/CoreFoundation.h>
@@ -70,12 +71,25 @@ struct SwiftError : SwiftErrorHeader {
// Core Foundation's refcounting scheme.
/// The type of Swift error value contained in the box.
/// This is only available for native Swift errors.
/// This member is only available for native Swift errors.
const Metadata *type;
/// The Error witness table.
/// This is only available for native Swift errors.
/// The witness table for `Error` conformance.
/// This member is only available for native Swift errors.
const WitnessTable *errorConformance;
/// The base type that introduces the `Hashable` conformance.
/// This member is only available for native Swift errors.
/// This member is lazily-initialized.
/// Instead of using it directly, call `getHashableBaseType()`.
mutable std::atomic<const Metadata *> hashableBaseType;
/// The witness table for `Hashable` conformance.
/// This member is only available for native Swift errors.
/// This member is lazily-initialized.
/// Instead of using it directly, call `getHashableConformance()`.
mutable std::atomic<const hashable_support::HashableWitnessTable *> hashableConformance;
/// Get a pointer to the value contained inside the indirectly-referenced
/// box reference.
static const OpaqueValue *getIndirectValue(const SwiftError * const *ptr) {
@@ -129,6 +143,14 @@ struct SwiftError : SwiftErrorHeader {
const WitnessTable *getErrorConformance() const { return errorConformance; }
#endif
/// Get the base type that conforms to `Hashable`.
/// Returns NULL if the type does not conform.
const Metadata *getHashableBaseType() const;
/// Get the `Hashable` protocol witness table for the contained type.
/// Returns NULL if the type does not conform.
const hashable_support::HashableWitnessTable *getHashableConformance() const;
// Don't copy or move, please.
SwiftError(const SwiftError &) = delete;
SwiftError(SwiftError &&) = delete;
@@ -202,6 +224,9 @@ bool tryDynamicCastNSErrorToValue(OpaqueValue *dest,
/// Get the NSError Objective-C class.
Class getNSErrorClass();
/// Get the NSError metadata.
const Metadata *getNSErrorMetadata();
#endif
} // namespace swift

View File

@@ -34,6 +34,7 @@
#include <Foundation/Foundation.h>
using namespace swift;
using namespace swift::hashable_support;
/// A subclass of NSError used to represent bridged native Swift errors.
/// This type cannot be subclassed, and should not ever be instantiated
@@ -97,12 +98,59 @@ using namespace swift;
return getNSErrorClass();
}
// Note: We support comparing cases of `@objc` enums defined in Swift to
// pure `NSError`s. They should compare equal as long as the domain and
// code match. Equal values should have equal hash values. Thus, we can't
// use the Swift hash value computation that comes from the `Hashable`
// conformance if one exists, and we must use the `NSError` hashing
// algorithm.
//
// So we are not overriding the `hash` method, even though we are
// overriding `isEqual:`.
- (BOOL)isEqual:(id)other {
auto self_ = (const SwiftError *)self;
auto other_ = (const SwiftError *)other;
assert(!self_->isPureNSError());
if (self == other) {
return YES;
}
if (!other) {
return NO;
}
if (other_->isPureNSError()) {
return [super isEqual:other];
}
auto hashableBaseType = self_->getHashableBaseType();
if (!hashableBaseType || other_->getHashableBaseType() != hashableBaseType) {
return [super isEqual:other];
}
auto hashableConformance = self_->getHashableConformance();
if (!hashableConformance) {
return [super isEqual:other];
}
return swift_stdlib_Hashable_isEqual_indirect(
self_->getValue(), other_->getValue(), hashableBaseType,
hashableConformance);
}
@end
Class swift::getNSErrorClass() {
return SWIFT_LAZY_CONSTANT([NSError class]);
}
const Metadata *swift::getNSErrorMetadata() {
return SWIFT_LAZY_CONSTANT(
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
}
static Class getSwiftNativeNSErrorClass() {
return SWIFT_LAZY_CONSTANT([_SwiftNativeNSError class]);
}
@@ -141,6 +189,8 @@ _swift_allocError_(const Metadata *type,
// Initialize the Swift type metadata.
instance->type = type;
instance->errorConformance = errorConformance;
instance->hashableBaseType = nullptr;
instance->hashableConformance = nullptr;
auto valueBytePtr = reinterpret_cast<char*>(instance) + valueOffset;
auto valuePtr = reinterpret_cast<OpaqueValue*>(valueBytePtr);
@@ -195,12 +245,22 @@ static const WitnessTable *getNSErrorConformanceToError() {
auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT,
"_TWPCSo7CFErrors5Error10Foundation"));
assert(TheWitnessTable &&
"Foundation overlay not loaded, or CFError: Error conformance "
"Foundation overlay not loaded, or 'CFError : Error' conformance "
"not available");
return reinterpret_cast<const WitnessTable *>(TheWitnessTable);
}
static const HashableWitnessTable *getNSErrorConformanceToHashable() {
auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT,
"__TWPCSo8NSObjects8Hashable10ObjectiveC"));
assert(TheWitnessTable &&
"ObjectiveC overlay not loaded, or 'NSObject : Hashable' conformance "
"not available");
return reinterpret_cast<const HashableWitnessTable *>(TheWitnessTable);
}
bool SwiftError::isPureNSError() const {
auto TheSwiftNativeNSError = getSwiftNativeNSErrorClass();
// We can do an exact type check; _SwiftNativeNSError shouldn't be subclassed
@@ -223,6 +283,47 @@ const WitnessTable *SwiftError::getErrorConformance() const {
return errorConformance;
}
const Metadata *SwiftError::getHashableBaseType() const {
if (isPureNSError()) {
return getNSErrorMetadata();
}
if (auto type = hashableBaseType.load(std::memory_order_acquire)) {
if (reinterpret_cast<uintptr_t>(type) == 1) {
return nullptr;
}
return type;
}
const Metadata *expectedType = nullptr;
const Metadata *hashableBaseType = findHashableBaseType(type);
this->hashableBaseType.compare_exchange_strong(
expectedType, hashableBaseType ? hashableBaseType
: reinterpret_cast<const Metadata *>(1),
std::memory_order_acq_rel);
return type;
}
const HashableWitnessTable *SwiftError::getHashableConformance() const {
if (isPureNSError()) {
return getNSErrorConformanceToHashable();
}
if (auto wt = hashableConformance.load(std::memory_order_acquire)) {
if (reinterpret_cast<uintptr_t>(wt) == 1) {
return nullptr;
}
return wt;
}
const HashableWitnessTable *expectedWT = nullptr;
const HashableWitnessTable *wt =
reinterpret_cast<const HashableWitnessTable *>(
swift_conformsToProtocol(type, &_TMps8Hashable));
hashableConformance.compare_exchange_strong(
expectedWT, wt ? wt : reinterpret_cast<const HashableWitnessTable *>(1),
std::memory_order_acq_rel);
return wt;
}
/// Extract a pointer to the value, the type metadata, and the Error
/// protocol witness from an error object.
///

View File

@@ -17,13 +17,15 @@
//
//===----------------------------------------------------------------------===//
#include "swift/Runtime/Config.h"
#if !SWIFT_OBJC_INTEROP
#include <stdio.h>
#include "swift/Runtime/Debug.h"
#include "ErrorObject.h"
#include "Private.h"
#if !SWIFT_OBJC_INTEROP
using namespace swift;
/// Determine the size and alignment of an Error box containing the given

View File

@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_RUNTIME_SWIFT_HASHABLE_SUPPORT_H
#define SWIFT_RUNTIME_SWIFT_HASHABLE_SUPPORT_H
#include "swift/Runtime/Metadata.h"
#include <stdint.h>
namespace swift {
namespace hashable_support {
/// The name demangles to "protocol descriptor for Swift.Hashable".
extern "C" const ProtocolDescriptor _TMps8Hashable;
struct HashableWitnessTable;
/// Calls `Equatable.==` through a `Hashable` (not Equatable!) witness
/// table.
extern "C" bool swift_stdlib_Hashable_isEqual_indirect(
const void *lhsValue, const void *rhsValue, const Metadata *type,
const HashableWitnessTable *wt);
/// Calls `Hashable.hashValue.get` through a `Hashable` witness table.
extern "C" intptr_t swift_stdlib_Hashable_hashValue_indirect(
const void *value, const Metadata *type, const HashableWitnessTable *wt);
/// Find the base type that introduces the `Hashable` conformance.
/// Because the provided type is known to conform to `Hashable`, this
/// function always returns non-null.
///
/// - Precondition: `type` conforms to `Hashable` (not checked).
const Metadata *findHashableBaseTypeOfHashableType(
const Metadata *type);
/// Find the base type that introduces the `Hashable` conformance.
/// If `type` does not conform to `Hashable`, `nullptr` is returned.
const Metadata *findHashableBaseType(const Metadata *type);
} // namespace hashable_support
} // namespace swift
#endif

View File

@@ -17,7 +17,6 @@ else()
endif()
add_swift_library(swiftStdlibStubs OBJECT_LIBRARY TARGET_LIBRARY
AnyHashableSupport.cpp
Assert.cpp
CommandLine.cpp
GlobalObjects.cpp

View File

@@ -27,6 +27,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND
Mutex.cpp
Enum.cpp
Refcounting.cpp
Stdlib.cpp
${PLATFORM_SOURCES}
# The runtime tests link to internal runtime symbols, which aren't exported

View File

@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/Runtime/Metadata.h"
using namespace swift;
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE
extern "C" void _swift_stdlib_makeAnyHashableUsingDefaultRepresentation(
const OpaqueValue *value,
const void *anyHashableResultPointer,
const Metadata *T,
const WitnessTable *hashableWT
) {
abort();
}

View File

@@ -115,6 +115,46 @@ AnyHashableTests.test("AnyHashable(${wrapped}).base") {
% end
% end
AnyHashableTests.test("AnyHashable(mixed minimal hashables)/Hashable") {
var xs: [AnyHashable] = []
% for wrapped in ['MinimalHashableValue', 'MinimalHashableClass']:
xs += (0...5).flatMap {
[ ${wrapped}($0, identity: 0),
${wrapped}($0, identity: 1) ].map(AnyHashable.init)
}
% end
% for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']:
${wrapped}_equalImpl.value = {
(lhs, rhs) in
if let lhs = lhs as? OpaqueValue<Int>,
let rhs = rhs as? OpaqueValue<Int> {
return lhs.value == rhs.value
}
return (lhs as! LifetimeTracked) == (rhs as! LifetimeTracked)
}
${wrapped}_hashValueImpl.value = {
payload in
if let x = payload as? OpaqueValue<Int> {
return x.value
}
return (payload as! LifetimeTracked).value
}
% end
% for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']:
% for payload in [ 'OpaqueValue<Int>', 'LifetimeTracked' ]:
xs += (0...5).flatMap {
[ ${wrapped}(${payload}($0), identity: 0),
${wrapped}(${payload}($0), identity: 1) ].map(AnyHashable.init)
}
% end
% end
checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 })
}
% for (kw, name) in [
% ('class', 'Class'),
% ('struct', 'PODStruct'),
@@ -606,7 +646,14 @@ enum MinimalHashableRCSwiftError : Error, Hashable {
case caseC(LifetimeTracked)
var hashValue: Int {
return 0
switch self {
case .caseA:
return 10
case .caseB:
return 20
case .caseC:
return 30
}
}
static func == (
@@ -672,15 +719,35 @@ AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftEr
.caseB, .caseB,
.caseC, .caseC,
]
let nsErrors: [NSError] = swiftErrors.map { $0 as NSError }
let nsErrors: [NSError] = swiftErrors.flatMap {
swiftError -> [NSError] in
let bridgedNSError = swiftError as NSError
return [
bridgedNSError,
NSError(domain: bridgedNSError.domain, code: bridgedNSError.code)
]
}
expectEqual(
.objCClassWrapper,
SwiftRuntime.metadataKind(of: nsErrors.first!))
SwiftRuntime.metadataKind(of: nsErrors[0]))
expectEqual("_SwiftNativeNSError", String(describing: type(of: nsErrors[0])))
checkHashable(nsErrors, equalityOracle: { $0 / 2 == $1 / 2 })
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
// Swift errors compare equal to the `NSError`s that have the same domain
// and code.
return lhs / 4 == rhs / 4
}
checkHashable(nsErrors, equalityOracle: equalityOracle)
checkHashable(
nsErrors.map(AnyHashable.init),
equalityOracle: { $0 / 2 == $1 / 2 })
equalityOracle: equalityOracle)
// FIXME(id-as-any): run `checkHashable` on an array of mixed
// `AnyHashable(MinimalHashablePODSwiftError)` and
// `AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError))`. For
// this to succeed, we need to eagerly bridge Swift errors into the Swift
// representation when wrapped in `AnyHashable`.
}
AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError)).base") {
@@ -697,23 +764,59 @@ AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftErr
.caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(1)),
.caseC(LifetimeTracked(2)), .caseC(LifetimeTracked(2)),
]
let nsErrors: [NSError] = swiftErrors.map { $0 as NSError }
let nsErrors: [NSError] = swiftErrors.flatMap {
swiftError -> [NSError] in
let bridgedNSError = swiftError as NSError
return [
bridgedNSError,
NSError(domain: bridgedNSError.domain, code: bridgedNSError.code)
]
}
expectEqual(
.objCClassWrapper,
SwiftRuntime.metadataKind(of: nsErrors.first!))
SwiftRuntime.metadataKind(of: nsErrors[0]))
expectEqual("_SwiftNativeNSError", String(describing: type(of: nsErrors[0])))
expectFailure {
// FIXME(id-as-any): make NSError bridging consistent with Swift's notion
// of hashing and equality.
checkHashable(nsErrors, equalityOracle: { $0 / 2 == $1 / 2 })
expectEqual(
.objCClassWrapper,
SwiftRuntime.metadataKind(of: nsErrors[1]))
expectEqual("NSError", String(describing: type(of: nsErrors[1])))
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
// Equality of bridged Swift errors takes the payload into account, so for
// a fixed X, Y, all `.caseX(LifetimeTracked(Y))` compare equal.
// They also compare equal to the `NSError`s that have the same domain
// and code.
if lhs / 4 == rhs / 4 {
return true
}
expectFailure {
// FIXME(id-as-any): make NSError bridging consistent with Swift's notion
// of hashing and equality.
// `NSError`s that have the same domain and code as a bridged Swift
// error compare equal to all Swift errors with the same domain and code
// regardless of the payload.
if (lhs % 2 == 1 || rhs % 2 == 1) && (lhs / 8 == rhs / 8) {
return true
}
return false
}
// FIXME: transitivity is broken because pure `NSError`s can compare equal to
// Swift errors with payloads just based on the domain and code, and Swift
// errors with payloads don't compare equal when payloads differ.
checkHashable(
nsErrors,
equalityOracle: equalityOracle,
allowBrokenTransitivity: true)
checkHashable(
nsErrors.map(AnyHashable.init),
equalityOracle: { $0 / 2 == $1 / 2 })
}
equalityOracle: equalityOracle,
allowBrokenTransitivity: true)
// FIXME(id-as-any): run `checkHashable` on an array of mixed
// `AnyHashable(MinimalHashableRCSwiftError)` and
// `AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftError))`. For
// this to succeed, we need to eagerly bridge Swift errors into the Swift
// representation when wrapped in `AnyHashable`.
}
AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashableRCSwiftError)).base") {