//===--- SwiftValue.mm - Boxed Swift value class --------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This implements the Objective-C class that is used to carry Swift values // that have been bridged to Objective-C objects without special handling. // The class is opaque to user code, but is `NSObject`- and `NSCopying`- // conforming and is understood by the Swift runtime for dynamic casting // back to the contained type. // //===----------------------------------------------------------------------===// #include "swift/Runtime/Config.h" #if SWIFT_OBJC_INTEROP #include "SwiftObject.h" #include "SwiftValue.h" #include "swift/Basic/Lazy.h" #include "swift/Runtime/Bincompat.h" #include "swift/Runtime/Casting.h" #include "swift/Runtime/HeapObject.h" #include "swift/Runtime/Metadata.h" #include "swift/Runtime/ObjCBridge.h" #include "swift/Runtime/Debug.h" #include "swift/Threading/Mutex.h" #include "Private.h" #include "SwiftEquatableSupport.h" #include "SwiftHashableSupport.h" #include #include #include #include using namespace swift; using namespace swift::hashable_support; // TODO: Making this a SwiftObject subclass would let us use Swift refcounting, // but we would need to be able to emit __SwiftValue's Objective-C class object // with the Swift destructor pointer prefixed before it. // // The layout of `__SwiftValue` is: // - object header, // - `SwiftValueHeader` instance, // - the payload, tail-allocated (the Swift value contained in this box). // // NOTE: older runtimes called this _SwiftValue. The two must // coexist, so it was renamed. The old name must not be used in the new // runtime. @interface __SwiftValue : NSObject - (id)copyWithZone:(NSZone *)zone; @end /// The fixed-size ivars of `__SwiftValue`. The actual boxed value is /// tail-allocated. struct SwiftValueHeader { /// The type of the value contained in the `__SwiftValue` box. const Metadata *type; /// The base type that introduces the `Hashable` or `Equatable` conformance. /// This member is lazily-initialized. /// Instead of using it directly, call `getHashableBaseType()` or `getEquatableBaseType()` /// Value here is encoded: /// * Least-significant bit set: This is an Equatable base type /// * Least-significant bit not set: This is a Hashable base type mutable std::atomic cachedBaseType; /// The witness table for `Hashable` conformance. /// This member is lazily-initialized. /// Instead of using it directly, call `getHashableConformance()`. /// Value here is encoded: /// * Least-significant bit set: This is an Equatable conformance /// * Least-significant bit not set: This is a Hashable conformance mutable std::atomic cachedConformance; /// Get the base type that conforms to `Hashable`. /// Returns NULL if the type does not conform. const Metadata *getHashableBaseType() const; /// Get the base type that conforms to `Equatable`. /// Returns NULL if the type does not conform. const Metadata *getEquatableBaseType() 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; /// Get the `Equatable` protocol witness table for the contained type. /// Returns NULL if the type does not conform. const equatable_support::EquatableWitnessTable *getEquatableConformance() const; /// Populate the `cachedConformance` with the Hashable conformance /// (if there is one), else the Equatable conformance. /// Returns the encoded conformance: least-significant /// bit is set if this is an Equatable conformance, /// else it is a Hashable conformance. 0 (or 1) indicates /// neither was found. uintptr_t cacheHashableEquatableConformance() const; SwiftValueHeader() : cachedBaseType(0), cachedConformance(0) {} }; // Set cachedConformance to the Hashable conformance if // there is one, else the Equatable conformance. // Also set cachedBaseType to the parent type that // introduced the Hashable/Equatable conformance. // The cached conformance and type are encoded: // * If the LSbit is not set, it's the Hashable conformance // * If the value is exactly 1, neither conformance is present // * If the LSbit is 1, strip it and you'll have the Equatable conformance // (Null indicates the cache has not been initialized yet; // that will never be true on exit of this function.) // Return: encoded cachedConformance value uintptr_t SwiftValueHeader::cacheHashableEquatableConformance() const { // Relevant conformance and baseType uintptr_t conformance; uintptr_t baseType; // First, see if it's Hashable const HashableWitnessTable *hashable = reinterpret_cast( swift_conformsToProtocolCommon(type, &HashableProtocolDescriptor)); if (hashable != nullptr) { conformance = reinterpret_cast(hashable); baseType = reinterpret_cast(findHashableBaseType(type)); } else { // If not Hashable, maybe Equatable? auto equatable = swift_conformsToProtocolCommon(type, &equatable_support::EquatableProtocolDescriptor); // Encode the equatable conformance conformance = reinterpret_cast(equatable) | 1; if (equatable != nullptr) { // Find equatable base type #if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES const auto *description = lookThroughOptionalConditionalWitnessTable( reinterpret_cast(equatable)) ->getDescription(); #else const auto *description = equatable->getDescription(); #endif const Metadata *baseTypeThatConformsToEquatable = findConformingSuperclass(type, description); // Encode the equatable base type baseType = reinterpret_cast(baseTypeThatConformsToEquatable) | 1; } else { baseType = 1; // Neither equatable nor hashable } } // Set the conformance/baseType caches atomically uintptr_t expectedConformance = 0; cachedConformance.compare_exchange_strong( expectedConformance, conformance, std::memory_order_acq_rel); uintptr_t expectedType = 0; cachedBaseType.compare_exchange_strong( expectedType, baseType, std::memory_order_acq_rel); return conformance; } const Metadata *SwiftValueHeader::getHashableBaseType() const { auto type = cachedBaseType.load(std::memory_order_acquire); if (type == 0) { cacheHashableEquatableConformance(); type = cachedBaseType.load(std::memory_order_acquire); } if ((type & 1) == 0) { // A Hashable conformance was found return reinterpret_cast(type); } else { // Equatable conformance (or no conformance) found return nullptr; } } const Metadata *SwiftValueHeader::getEquatableBaseType() const { auto type = cachedBaseType.load(std::memory_order_acquire); if (type == 0) { cacheHashableEquatableConformance(); type = cachedBaseType.load(std::memory_order_acquire); } if ((type & 1) == 0) { // A Hashable conformance was found return nullptr; } else { // An Equatable conformance (or neither) was found return reinterpret_cast(type & ~1ULL); } } const hashable_support::HashableWitnessTable * SwiftValueHeader::getHashableConformance() const { uintptr_t wt = cachedConformance.load(std::memory_order_acquire); if (wt == 0) { wt = cacheHashableEquatableConformance(); } if ((wt & 1) == 0) { // Hashable conformance found return reinterpret_cast(wt); } else { // Equatable conformance (or no conformance) found return nullptr; } } const equatable_support::EquatableWitnessTable * SwiftValueHeader::getEquatableConformance() const { uintptr_t wt = cachedConformance.load(std::memory_order_acquire); if (wt == 0) { wt = cacheHashableEquatableConformance(); } if ((wt & 1) == 0) { // Hashable conformance found return nullptr; } else { // Equatable conformance (or no conformance) found return reinterpret_cast(wt & ~1ULL); } } static constexpr const size_t SwiftValueHeaderOffset = sizeof(Class); // isa pointer static constexpr const size_t SwiftValueMinAlignMask = alignof(Class) - 1; /* TODO: If we're able to become a SwiftObject subclass in the future, * change to this: static constexpr const size_t SwiftValueHeaderOffset = sizeof(HeapObject); static constexpr const size_t SwiftValueMinAlignMask = alignof(HeapObject) - 1; */ static Class _getSwiftValueClass() { auto theClass = [__SwiftValue class]; // Fixed instance size of __SwiftValue should be same as object header. assert(class_getInstanceSize(theClass) == SwiftValueHeaderOffset && "unexpected size of __SwiftValue?!"); return theClass; } static Class getSwiftValueClass() { return SWIFT_LAZY_CONSTANT(_getSwiftValueClass()); } static constexpr size_t getSwiftValuePayloadOffset(size_t alignMask) { return (SwiftValueHeaderOffset + sizeof(SwiftValueHeader) + alignMask) & ~alignMask; } static SwiftValueHeader *getSwiftValueHeader(__SwiftValue *v) { auto instanceBytes = reinterpret_cast(v); return reinterpret_cast(instanceBytes + SwiftValueHeaderOffset); } static OpaqueValue *getSwiftValuePayload(__SwiftValue *v, size_t alignMask) { auto instanceBytes = reinterpret_cast(v); return reinterpret_cast(instanceBytes + getSwiftValuePayloadOffset(alignMask)); } static size_t getSwiftValuePayloadAlignMask(const Metadata *type) { return type->getValueWitnesses()->getAlignmentMask() | SwiftValueMinAlignMask; } const Metadata *swift::getSwiftValueTypeMetadata(__SwiftValue *v) { return getSwiftValueHeader(v)->type; } std::pair swift::getValueFromSwiftValue(__SwiftValue *v) { auto instanceType = getSwiftValueTypeMetadata(v); size_t alignMask = getSwiftValuePayloadAlignMask(instanceType); return {instanceType, getSwiftValuePayload(v, alignMask)}; } __SwiftValue *swift::bridgeAnythingToSwiftValueObject(OpaqueValue *src, const Metadata *srcType, bool consume) { size_t alignMask = getSwiftValuePayloadAlignMask(srcType); size_t totalSize = getSwiftValuePayloadOffset(alignMask) + srcType->getValueWitnesses()->size; void *instanceMemory = swift_slowAlloc(totalSize, alignMask); __SwiftValue *instance = objc_constructInstance(getSwiftValueClass(), instanceMemory); /* TODO: If we're able to become a SwiftObject subclass in the future, * change to this: auto instance = swift_allocObject(getSwiftValueClass(), totalSize, alignMask); */ auto header = getSwiftValueHeader(instance); ::new (header) SwiftValueHeader(); header->type = srcType; auto payload = getSwiftValuePayload(instance, alignMask); if (consume) srcType->vw_initializeWithTake(payload, src); else srcType->vw_initializeWithCopy(payload, src); return instance; } __SwiftValue *swift::getAsSwiftValue(id object) { // __SwiftValue should have no subclasses or proxies. We can do an exact // class check. if (object_getClass(object) == getSwiftValueClass()) return object; return nil; } bool swift::findSwiftValueConformances(const ExistentialTypeMetadata *existentialType, const WitnessTable **tablesBuffer) { // __SwiftValue never satisfies a superclass constraint. if (existentialType->getSuperclassConstraint() != nullptr) return false; Class cls = nullptr; // Note that currently we never modify tablesBuffer because // __SwiftValue doesn't conform to any protocols that need witness tables. for (auto protocol : existentialType->getProtocols()) { // __SwiftValue only conforms to ObjC protocols. We specifically // don't want to say that __SwiftValue conforms to the Swift protocols // that NSObject conforms to because that would create a situation // where arguably an arbitrary type would conform to those protocols // by way of coercion through __SwiftValue. Eventually we want to // change __SwiftValue to not be an NSObject subclass at all. if (!protocol.isObjC()) return false; if (!cls) cls = _getSwiftValueClass(); // Check whether the class conforms to the protocol. if (![cls conformsToProtocol: protocol.getObjCProtocol()]) return false; } return true; } @implementation __SwiftValue + (instancetype)allocWithZone:(NSZone *)zone { swift::crash("__SwiftValue cannot be instantiated"); } - (id)copyWithZone:(NSZone *)zone { // Instances are immutable, so we can just retain. return objc_retain(self); /* TODO: If we're able to become a SwiftObject subclass in the future, * change to this: swift_retain((HeapObject*)self); return self; */ } // Since we allocate using Swift's allocator to properly handle alignment, // we need to deallocate ourselves instead of delegating to // -[NSObject dealloc]. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - (void)dealloc { // TODO: If we're able to become a SwiftObject subclass in the future, // this should move to the heap metadata destructor function. // Destroy the contained value. auto instanceType = getSwiftValueTypeMetadata(self); size_t alignMask = getSwiftValuePayloadAlignMask(instanceType); getSwiftValueHeader(self)->~SwiftValueHeader(); instanceType->vw_destroy(getSwiftValuePayload(self, alignMask)); // Deallocate ourselves. objc_destructInstance(self); auto totalSize = getSwiftValuePayloadOffset(alignMask) + instanceType->getValueWitnesses()->size; swift_slowDealloc(self, totalSize, alignMask); } #pragma clang diagnostic pop - (BOOL)isEqual:(id)other { if (self == other) { return YES; } if (!other) { return NO; } // `other` must also be a _SwiftValue box if (![other isKindOfClass:getSwiftValueClass()]) { return NO; } auto selfHeader = getSwiftValueHeader(self); auto otherHeader = getSwiftValueHeader(other); if (auto hashableConformance = selfHeader->getHashableConformance()) { if (auto selfHashableBaseType = selfHeader->getHashableBaseType()) { auto otherHashableBaseType = otherHeader->getHashableBaseType(); if (selfHashableBaseType == otherHashableBaseType) { return _swift_stdlib_Hashable_isEqual_indirect( getSwiftValuePayload(self, getSwiftValuePayloadAlignMask(selfHeader->type)), getSwiftValuePayload(other, getSwiftValuePayloadAlignMask(otherHeader->type)), selfHashableBaseType, hashableConformance); } } } // if (runtime::bincompat::useLegacySwiftObjCHashing()) { // // Legacy behavior only proxies isEqual: for Hashable, not Equatable // return NO; // } if (auto equatableConformance = selfHeader->getEquatableConformance()) { if (auto selfEquatableBaseType = selfHeader->getEquatableBaseType()) { auto otherEquatableBaseType = otherHeader->getEquatableBaseType(); if (selfEquatableBaseType == otherEquatableBaseType) { return _swift_stdlib_Equatable_isEqual_indirect( getSwiftValuePayload(self, getSwiftValuePayloadAlignMask(selfHeader->type)), getSwiftValuePayload(other, getSwiftValuePayloadAlignMask(otherHeader->type)), selfEquatableBaseType, equatableConformance); } } } // Not Equatable, not Hashable, and not the same box return NO; } - (NSUInteger)hash { // If Swift type is Hashable, get the hash value from there auto selfHeader = getSwiftValueHeader(self); auto hashableConformance = selfHeader->getHashableConformance(); if (hashableConformance) { return _swift_stdlib_Hashable_hashValue_indirect( getSwiftValuePayload(self, getSwiftValuePayloadAlignMask(selfHeader->type)), selfHeader->type, hashableConformance); } // if (runtime::bincompat::useLegacySwiftObjCHashing()) { // // Legacy behavior doesn't honor Equatable conformance, only Hashable // return (NSUInteger)self; // } // If Swift type is Equatable but not Hashable, // we have to return something here that is compatible // with the `isEqual:` above. auto equatableConformance = selfHeader->getEquatableConformance(); if (equatableConformance) { // Warn once per type about this auto metadata = getSwiftValueTypeMetadata(self); static Lazy> warned; static LazyMutex warnedLock; LazyMutex::ScopedLock guard(warnedLock); auto result = warned.get().insert(metadata); auto inserted = std::get<1>(result); if (inserted) { TypeNamePair typeName = swift_getTypeName(metadata, true); warning(0, "Obj-C `-hash` invoked on a Swift value of type `%s` that is Equatable but not Hashable; " "this can lead to severe performance problems.\n", typeName.data); } // Constant value (yuck!) is the only choice here return (NSUInteger)1; } // If the Swift type is neither Equatable nor Hashable, // then we can hash the identity, which should be pretty // good in practice. return (NSUInteger)self; } static id getValueDescription(__SwiftValue *self) { const Metadata *type; const OpaqueValue *value; std::tie(type, value) = getValueFromSwiftValue(self); // Copy the value, since it will be consumed by getSummary. ValueBuffer copyBuf; auto copy = type->allocateBufferIn(©Buf); type->vw_initializeWithCopy(copy, const_cast(value)); id string = getDescription(copy, type); type->deallocateBufferIn(©Buf); return string; } - (id /* NSString */)description { return getValueDescription(self); } - (id /* NSString */)debugDescription { return getValueDescription(self); } // Private methods for debugging purposes. - (const Metadata *)_swiftTypeMetadata { return getSwiftValueTypeMetadata(self); } - (id /* NSString */)_swiftTypeName { TypeNamePair typeName = swift_getTypeName(getSwiftValueTypeMetadata(self), true); id str = swift_stdlib_NSStringFromUTF8(typeName.data, typeName.length); return [str autorelease]; } - (const OpaqueValue *)_swiftValue { return getValueFromSwiftValue(self).second; } @end #endif // TODO: We could pick specialized __SwiftValue subclasses for trivial types // or for types with known size and alignment characteristics. Probably // not enough of a real perf bottleneck to be worth it...