//===--- ProtocolConformance.cpp - Swift protocol conformance checking ----===// // // 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 https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Checking and caching of Swift protocol conformances. // //===----------------------------------------------------------------------===// #include "swift/Basic/LLVM.h" #include "swift/Basic/Lazy.h" #include "swift/Runtime/Concurrent.h" #include "swift/Runtime/Metadata.h" #include "swift/Runtime/Mutex.h" #include "ImageInspection.h" #include "Private.h" #if !defined(_WIN32) #include #endif using namespace swift; #if !defined(NDEBUG) && SWIFT_OBJC_INTEROP #include static const char *class_getName(const ClassMetadata* type) { return class_getName( reinterpret_cast(const_cast(type))); } template<> void ProtocolConformanceRecord::dump() const { auto symbolName = [&](const void *addr) -> const char * { Dl_info info; int ok = dladdr(addr, &info); if (!ok) return ""; return info.dli_sname; }; switch (auto kind = getTypeKind()) { case TypeMetadataRecordKind::Universal: printf("universal"); break; case TypeMetadataRecordKind::UniqueDirectType: case TypeMetadataRecordKind::NonuniqueDirectType: printf("%s direct type ", kind == TypeMetadataRecordKind::UniqueDirectType ? "unique" : "nonunique"); if (auto &ntd = getDirectType()->getNominalTypeDescriptor()) { printf("%s", ntd->Name.get()); } else { printf(""); } break; case TypeMetadataRecordKind::UniqueDirectClass: printf("unique direct class %s", class_getName(getDirectClass())); break; case TypeMetadataRecordKind::UniqueIndirectClass: printf("unique indirect class %s", class_getName(*getIndirectClass())); break; case TypeMetadataRecordKind::UniqueNominalTypeDescriptor: printf("unique nominal type descriptor %s", symbolName(getNominalTypeDescriptor())); break; } printf(" => "); switch (getConformanceKind()) { case ProtocolConformanceReferenceKind::WitnessTable: printf("witness table %s\n", symbolName(getStaticWitnessTable())); break; case ProtocolConformanceReferenceKind::WitnessTableAccessor: printf("witness table accessor %s\n", symbolName((const void *)(uintptr_t)getWitnessTableAccessor())); break; } } #endif /// Take the type reference inside a protocol conformance record and fetch the /// canonical metadata pointer for the type it refers to. /// Returns nil for universal or generic type references. template<> const Metadata *ProtocolConformanceRecord::getCanonicalTypeMetadata() const { switch (getTypeKind()) { case TypeMetadataRecordKind::UniqueDirectType: // Already unique. return getDirectType(); case TypeMetadataRecordKind::NonuniqueDirectType: // Ask the runtime for the unique metadata record we've canonized. return swift_getForeignTypeMetadata((ForeignTypeMetadata*)getDirectType()); case TypeMetadataRecordKind::UniqueIndirectClass: // The class may be ObjC, in which case we need to instantiate its Swift // metadata. The class additionally may be weak-linked, so we have to check // for null. if (auto *ClassMetadata = *getIndirectClass()) return swift_getObjCClassMetadata(ClassMetadata); return nullptr; case TypeMetadataRecordKind::UniqueDirectClass: // The class may be ObjC, in which case we need to instantiate its Swift // metadata. if (auto *ClassMetadata = getDirectClass()) return swift_getObjCClassMetadata(ClassMetadata); return nullptr; case TypeMetadataRecordKind::UniqueNominalTypeDescriptor: case TypeMetadataRecordKind::Universal: // The record does not apply to a single type. return nullptr; } } template<> const WitnessTable * ProtocolConformanceRecord::getWitnessTable(const Metadata *type) const { switch (getConformanceKind()) { case ProtocolConformanceReferenceKind::WitnessTable: return getStaticWitnessTable(); case ProtocolConformanceReferenceKind::WitnessTableAccessor: return getWitnessTableAccessor()(type); } } namespace { struct ConformanceSection { const ProtocolConformanceRecord *Begin, *End; const ProtocolConformanceRecord *begin() const { return Begin; } const ProtocolConformanceRecord *end() const { return End; } }; struct ConformanceCacheKey { /// Either a Metadata* or a NominalTypeDescriptor*. const void *Type; const ProtocolDescriptor *Proto; ConformanceCacheKey(const void *type, const ProtocolDescriptor *proto) : Type(type), Proto(proto) {} }; struct ConformanceCacheEntry { private: const void *Type; const ProtocolDescriptor *Proto; std::atomic Table; std::atomic FailureGeneration; public: ConformanceCacheEntry(ConformanceCacheKey key, const WitnessTable *table, uintptr_t failureGeneration) : Type(key.Type), Proto(key.Proto), Table(table), FailureGeneration(failureGeneration) { } int compareWithKey(const ConformanceCacheKey &key) const { if (key.Type != Type) { return (uintptr_t(key.Type) < uintptr_t(Type) ? -1 : 1); } else if (key.Proto != Proto) { return (uintptr_t(key.Proto) < uintptr_t(Proto) ? -1 : 1); } else { return 0; } } template static size_t getExtraAllocationSize(Args &&... ignored) { return 0; } bool isSuccessful() const { return Table.load(std::memory_order_relaxed) != nullptr; } void makeSuccessful(const WitnessTable *table) { Table.store(table, std::memory_order_release); } void updateFailureGeneration(uintptr_t failureGeneration) { assert(!isSuccessful()); FailureGeneration.store(failureGeneration, std::memory_order_relaxed); } /// Get the cached witness table, if successful. const WitnessTable *getWitnessTable() const { assert(isSuccessful()); return Table.load(std::memory_order_acquire); } /// Get the generation number under which this lookup failed. unsigned getFailureGeneration() const { assert(!isSuccessful()); return FailureGeneration.load(std::memory_order_relaxed); } }; } // Conformance Cache. struct ConformanceState { ConcurrentMap Cache; std::vector SectionsToScan; Mutex SectionsToScanLock; ConformanceState() { SectionsToScan.reserve(16); initializeProtocolConformanceLookup(); } void cacheSuccess(const void *type, const ProtocolDescriptor *proto, const WitnessTable *witness) { auto result = Cache.getOrInsert(ConformanceCacheKey(type, proto), witness, uintptr_t(0)); // If the entry was already present, we may need to update it. if (!result.second) { result.first->makeSuccessful(witness); } } void cacheFailure(const void *type, const ProtocolDescriptor *proto) { uintptr_t failureGeneration = SectionsToScan.size(); auto result = Cache.getOrInsert(ConformanceCacheKey(type, proto), (const WitnessTable *) nullptr, failureGeneration); // If the entry was already present, we may need to update it. if (!result.second) { result.first->updateFailureGeneration(failureGeneration); } } ConformanceCacheEntry *findCached(const void *type, const ProtocolDescriptor *proto) { return Cache.find(ConformanceCacheKey(type, proto)); } }; static Lazy Conformances; static void _registerProtocolConformances(ConformanceState &C, const ProtocolConformanceRecord *begin, const ProtocolConformanceRecord *end) { ScopedLock guard(C.SectionsToScanLock); C.SectionsToScan.push_back(ConformanceSection{begin, end}); } void swift::addImageProtocolConformanceBlockCallback(const void *conformances, uintptr_t conformancesSize) { assert(conformancesSize % sizeof(ProtocolConformanceRecord) == 0 && "weird-sized conformances section?!"); // If we have a section, enqueue the conformances for lookup. auto conformanceBytes = reinterpret_cast(conformances); auto recordsBegin = reinterpret_cast(conformances); auto recordsEnd = reinterpret_cast (conformanceBytes + conformancesSize); // Conformance cache should always be sufficiently initialized by this point. _registerProtocolConformances(Conformances.unsafeGetAlreadyInitialized(), recordsBegin, recordsEnd); } // This variable is used to signal when a cache was generated and // it is correct to avoid a new scan. static unsigned ConformanceCacheGeneration = 0; void swift::swift_registerProtocolConformances(const ProtocolConformanceRecord *begin, const ProtocolConformanceRecord *end){ auto &C = Conformances.get(); _registerProtocolConformances(C, begin, end); } /// Search the witness table in the ConformanceCache. \returns a pair of the /// WitnessTable pointer and a boolean value True if a definitive value is /// found. \returns false if the type or its superclasses were not found in /// the cache. static std::pair searchInConformanceCache(const Metadata *type, const ProtocolDescriptor *protocol, ConformanceCacheEntry *&foundEntry) { auto &C = Conformances.get(); auto origType = type; foundEntry = nullptr; recur_inside_cache_lock: // See if we have a cached conformance. Try the specific type first. { // Check if the type-protocol entry exists in the cache entry that we found. if (auto *Value = C.findCached(type, protocol)) { if (Value->isSuccessful()) return std::make_pair(Value->getWitnessTable(), true); // If we're still looking up for the original type, remember that // we found an exact match. if (type == origType) foundEntry = Value; // If we got a cached negative response, check the generation number. if (Value->getFailureGeneration() == C.SectionsToScan.size()) { // We found an entry with a negative value. return std::make_pair(nullptr, true); } } } { // For generic and resilient types, nondependent conformances // are keyed by the nominal type descriptor rather than the // metadata, so try that. auto *description = type->getNominalTypeDescriptor().get(); // Hash and lookup the type-protocol pair in the cache. if (auto *Value = C.findCached(description, protocol)) { if (Value->isSuccessful()) return std::make_pair(Value->getWitnessTable(), true); // We don't try to cache negative responses for generic // patterns. } } // If the type is a class, try its superclass. if (const ClassMetadata *classType = type->getClassObject()) { if (classHasSuperclass(classType)) { type = swift_getObjCClassMetadata(classType->SuperClass); goto recur_inside_cache_lock; } } // We did not find an entry. return std::make_pair(nullptr, false); } /// Checks if a given candidate is a type itself, one of its /// superclasses or a related generic type. /// /// This check is supposed to use the same logic that is used /// by searchInConformanceCache. /// /// \param candidate Pointer to a Metadata or a NominalTypeDescriptor. /// static bool isRelatedType(const Metadata *type, const void *candidate, bool candidateIsMetadata) { while (true) { if (type == candidate && candidateIsMetadata) return true; // If the type is resilient or generic, see if there's a witness table // keyed off the nominal type descriptor. auto *description = type->getNominalTypeDescriptor().get(); if (description == candidate && !candidateIsMetadata) return true; // If the type is a class, try its superclass. if (const ClassMetadata *classType = type->getClassObject()) { if (classHasSuperclass(classType)) { type = swift_getObjCClassMetadata(classType->SuperClass); continue; } } break; } return false; } const WitnessTable * swift::swift_conformsToProtocol(const Metadata *type, const ProtocolDescriptor *protocol) { auto &C = Conformances.get(); auto origType = type; unsigned numSections = 0; ConformanceCacheEntry *foundEntry; recur: // See if we have a cached conformance. The ConcurrentMap data structure // allows us to insert and search the map concurrently without locking. // We do lock the slow path because the SectionsToScan data structure is not // concurrent. auto FoundConformance = searchInConformanceCache(type, protocol, foundEntry); // The negative answer does not always mean that there is no conformance, // unless it is an exact match on the type. If it is not an exact match, // it may mean that all of the superclasses do not have this conformance, // but the actual type may still have this conformance. if (FoundConformance.second) { if (FoundConformance.first || foundEntry) return FoundConformance.first; } // If we didn't have an up-to-date cache entry, scan the conformance records. C.SectionsToScanLock.lock(); unsigned failedGeneration = ConformanceCacheGeneration; // If we have no new information to pull in (and nobody else pulled in // new information while we waited on the lock), we're done. if (C.SectionsToScan.size() == numSections) { if (failedGeneration != ConformanceCacheGeneration) { // Someone else pulled in new conformances while we were waiting. // Start over with our newly-populated cache. C.SectionsToScanLock.unlock(); type = origType; goto recur; } // Save the failure for this type-protocol pair in the cache. C.cacheFailure(type, protocol); C.SectionsToScanLock.unlock(); return nullptr; } // Update the last known number of sections to scan. numSections = C.SectionsToScan.size(); // Scan only sections that were not scanned yet. unsigned sectionIdx = foundEntry ? foundEntry->getFailureGeneration() : 0; unsigned endSectionIdx = C.SectionsToScan.size(); for (; sectionIdx < endSectionIdx; ++sectionIdx) { auto §ion = C.SectionsToScan[sectionIdx]; // Eagerly pull records for nondependent witnesses into our cache. for (const auto &record : section) { // If the record applies to a specific type, cache it. if (auto metadata = record.getCanonicalTypeMetadata()) { auto P = record.getProtocol(); // Look for an exact match. if (protocol != P) continue; if (!isRelatedType(type, metadata, /*isMetadata=*/true)) continue; // Store the type-protocol pair in the cache. auto witness = record.getWitnessTable(metadata); if (witness) { C.cacheSuccess(metadata, P, witness); } else { C.cacheFailure(metadata, P); } // TODO: "Nondependent witness table" probably deserves its own flag. // An accessor function might still be necessary even if the witness table // can be shared. } else if (record.getTypeKind() == TypeMetadataRecordKind::UniqueNominalTypeDescriptor) { auto R = record.getNominalTypeDescriptor(); auto P = record.getProtocol(); // Look for an exact match. if (protocol != P) continue; if (!isRelatedType(type, R, /*isMetadata=*/false)) continue; // Store the type-protocol pair in the cache. switch (record.getConformanceKind()) { case ProtocolConformanceReferenceKind::WitnessTable: // If the record provides a nondependent witness table for all // instances of a generic type, cache it for the generic pattern. C.cacheSuccess(R, P, record.getStaticWitnessTable()); break; case ProtocolConformanceReferenceKind::WitnessTableAccessor: // If the record provides a dependent witness table accessor, // cache the result for the instantiated type metadata. C.cacheSuccess(type, P, record.getWitnessTable(type)); break; } } } } ++ConformanceCacheGeneration; C.SectionsToScanLock.unlock(); // Start over with our newly-populated cache. type = origType; goto recur; } const Metadata * swift::_searchConformancesByMangledTypeName(const llvm::StringRef typeName) { auto &C = Conformances.get(); const Metadata *foundMetadata = nullptr; ScopedLock guard(C.SectionsToScanLock); unsigned sectionIdx = 0; unsigned endSectionIdx = C.SectionsToScan.size(); for (; sectionIdx < endSectionIdx; ++sectionIdx) { auto §ion = C.SectionsToScan[sectionIdx]; for (const auto &record : section) { if (auto metadata = record.getCanonicalTypeMetadata()) foundMetadata = _matchMetadataByMangledTypeName(typeName, metadata, nullptr); else if (auto ntd = record.getNominalTypeDescriptor()) foundMetadata = _matchMetadataByMangledTypeName(typeName, nullptr, ntd); if (foundMetadata != nullptr) break; } if (foundMetadata != nullptr) break; } return foundMetadata; }