mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Revert "Protocol conformance cache for generic types"
This reverts commit 7989dbe24e merged in https://github.com/swiftlang/swift/pull/82818
This commit is contained in:
@@ -593,51 +593,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Common implementation for `getOrInsert` and `GetOrInsertManyScope`
|
||||
template <class KeyTy, typename Call>
|
||||
void getOrInsertExternallyLocked(KeyTy key, const Call &call) {
|
||||
auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
|
||||
auto indicesCapacityLog2 = indices.getCapacityLog2();
|
||||
auto elementCount = ElementCount.load(std::memory_order_relaxed);
|
||||
auto *elements = Elements.load(std::memory_order_relaxed);
|
||||
auto *elementsPtr = elements ? elements->data() : nullptr;
|
||||
|
||||
|
||||
auto found = this->find(key, indices, elementCount, elementsPtr);
|
||||
if (found.first) {
|
||||
call(found.first, false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto indicesCapacity = 1UL << indicesCapacityLog2;
|
||||
|
||||
// The number of slots in use is elementCount + 1, since the capacity also
|
||||
// takes a slot.
|
||||
auto emptyCount = indicesCapacity - (elementCount + 1);
|
||||
auto proportion = indicesCapacity / emptyCount;
|
||||
if (proportion >= ResizeProportion) {
|
||||
indices = resize(indices, indicesCapacityLog2, elementsPtr);
|
||||
found = find(key, indices, elementCount, elementsPtr);
|
||||
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
|
||||
}
|
||||
|
||||
if (!elements || elementCount >= elements->Capacity) {
|
||||
elements = resize(elements, elementCount);
|
||||
}
|
||||
auto *element = &elements->data()[elementCount];
|
||||
|
||||
// Order matters: fill out the element, then update the count,
|
||||
// then update the index.
|
||||
bool keep = call(element, true);
|
||||
if (keep) {
|
||||
assert(hash_value(key) == hash_value(*element) &&
|
||||
"Element must have the same hash code as its key.");
|
||||
ElementCount.store(elementCount + 1, std::memory_order_release);
|
||||
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
|
||||
std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Implicitly trivial constructor/destructor.
|
||||
ConcurrentReadableHashMap() = default;
|
||||
@@ -729,48 +684,6 @@ public:
|
||||
return Snapshot(this, indices, elementsPtr, elementCount);
|
||||
}
|
||||
|
||||
/// A wrapper that allows performing several `getOrInsert` operations under
|
||||
/// the same lock.
|
||||
class GetOrInsertManyScope {
|
||||
GetOrInsertManyScope() = delete;
|
||||
GetOrInsertManyScope(const GetOrInsertManyScope &) = delete;
|
||||
GetOrInsertManyScope &operator=(const GetOrInsertManyScope &) = delete;
|
||||
GetOrInsertManyScope(GetOrInsertManyScope &&) = delete;
|
||||
GetOrInsertManyScope &operator=(GetOrInsertManyScope &&) = delete;
|
||||
|
||||
ConcurrentReadableHashMap ⤅
|
||||
|
||||
public:
|
||||
GetOrInsertManyScope(ConcurrentReadableHashMap &map) : Map(map) {
|
||||
Map.WriterLock.lock();
|
||||
}
|
||||
|
||||
~GetOrInsertManyScope() {
|
||||
Map.deallocateFreeListIfSafe();
|
||||
Map.WriterLock.unlock();
|
||||
}
|
||||
|
||||
/// Get an element by key, or insert a new element for that key if one is
|
||||
/// not already present. Invoke `call` with the pointer to the element.
|
||||
///
|
||||
/// `call` is passed the following parameters:
|
||||
/// - `element`: the pointer to the element corresponding to `key`
|
||||
/// - `created`: true if the element is newly created, false if it already
|
||||
/// exists
|
||||
/// `call` returns a `bool`. When `created` is `true`, the return values
|
||||
/// mean:
|
||||
/// - `true` the new entry is to be kept
|
||||
/// - `false` indicates that the new entry is discarded
|
||||
/// If the new entry is kept, then the new element MUST be initialized, and
|
||||
/// have a hash value that matches the hash value of `key`.
|
||||
///
|
||||
/// The return value is ignored when `created` is `false`.
|
||||
template <class KeyTy, typename Call>
|
||||
void getOrInsert(KeyTy key, const Call &call) {
|
||||
Map.getOrInsertExternallyLocked(key, call);
|
||||
}
|
||||
};
|
||||
|
||||
/// Get an element by key, or insert a new element for that key if one is not
|
||||
/// already present. Invoke `call` with the pointer to the element. BEWARE:
|
||||
/// `call` is invoked with the internal writer lock held, keep work to a
|
||||
@@ -790,7 +703,48 @@ public:
|
||||
template <class KeyTy, typename Call>
|
||||
void getOrInsert(KeyTy key, const Call &call) {
|
||||
typename MutexTy::ScopedLock guard(WriterLock);
|
||||
getOrInsertExternallyLocked(key, call);
|
||||
|
||||
auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
|
||||
auto indicesCapacityLog2 = indices.getCapacityLog2();
|
||||
auto elementCount = ElementCount.load(std::memory_order_relaxed);
|
||||
auto *elements = Elements.load(std::memory_order_relaxed);
|
||||
auto *elementsPtr = elements ? elements->data() : nullptr;
|
||||
|
||||
auto found = this->find(key, indices, elementCount, elementsPtr);
|
||||
if (found.first) {
|
||||
call(found.first, false);
|
||||
deallocateFreeListIfSafe();
|
||||
return;
|
||||
}
|
||||
|
||||
auto indicesCapacity = 1UL << indicesCapacityLog2;
|
||||
|
||||
// The number of slots in use is elementCount + 1, since the capacity also
|
||||
// takes a slot.
|
||||
auto emptyCount = indicesCapacity - (elementCount + 1);
|
||||
auto proportion = indicesCapacity / emptyCount;
|
||||
if (proportion >= ResizeProportion) {
|
||||
indices = resize(indices, indicesCapacityLog2, elementsPtr);
|
||||
found = find(key, indices, elementCount, elementsPtr);
|
||||
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
|
||||
}
|
||||
|
||||
if (!elements || elementCount >= elements->Capacity) {
|
||||
elements = resize(elements, elementCount);
|
||||
}
|
||||
auto *element = &elements->data()[elementCount];
|
||||
|
||||
// Order matters: fill out the element, then update the count,
|
||||
// then update the index.
|
||||
bool keep = call(element, true);
|
||||
if (keep) {
|
||||
assert(hash_value(key) == hash_value(*element) &&
|
||||
"Element must have the same hash code as its key.");
|
||||
ElementCount.store(elementCount + 1, std::memory_order_release);
|
||||
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
deallocateFreeListIfSafe();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,11 +62,6 @@ VARIABLE(SWIFT_DEBUG_ENABLE_SHARED_CACHE_PROTOCOL_CONFORMANCES, bool, true,
|
||||
|
||||
#endif
|
||||
|
||||
VARIABLE(SWIFT_DEBUG_ENABLE_CACHE_PROTOCOL_CONFORMANCES_BY_TYPE_DESCRIPTOR,
|
||||
bool, true,
|
||||
"Enable caching protocol conformances by type descriptor in addition "
|
||||
"to the cache by metadata.")
|
||||
|
||||
VARIABLE(SWIFT_DEBUG_CONCURRENCY_ENABLE_COOPERATIVE_QUEUES, bool, true,
|
||||
"Enable dispatch cooperative queues in the global executor.")
|
||||
|
||||
@@ -75,10 +70,6 @@ VARIABLE(SWIFT_DEBUG_CONCURRENCY_ENABLE_COOPERATIVE_QUEUES, bool, true,
|
||||
VARIABLE(SWIFT_DEBUG_RUNTIME_EXCLUSIVITY_LOGGING, bool, false,
|
||||
"Enable the an asserts runtime to emit logging as it works.")
|
||||
|
||||
VARIABLE(SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG,
|
||||
bool, false,
|
||||
"Enable logs for the conformance lookup routine.")
|
||||
|
||||
#endif
|
||||
|
||||
VARIABLE(SWIFT_BINARY_COMPATIBILITY_VERSION, uint32_t, 0,
|
||||
|
||||
@@ -117,15 +117,6 @@ void ProtocolDescriptorFlags::dump() const {
|
||||
|
||||
#endif
|
||||
|
||||
static bool IsDebugLog() {
|
||||
#ifndef NDEBUG
|
||||
return runtime::environment::
|
||||
SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) && SWIFT_OBJC_INTEROP
|
||||
#include <objc/runtime.h>
|
||||
|
||||
@@ -531,29 +522,16 @@ namespace {
|
||||
};
|
||||
|
||||
struct ConformanceCacheKey {
|
||||
llvm::PointerUnion<const Metadata *, const TypeContextDescriptor *>
|
||||
TypeOrDescriptor;
|
||||
const Metadata *Type;
|
||||
const ProtocolDescriptor *Proto;
|
||||
|
||||
ConformanceCacheKey(const Metadata *type, const ProtocolDescriptor *proto)
|
||||
: TypeOrDescriptor(type), Proto(proto) {
|
||||
: Type(type), Proto(proto) {
|
||||
assert(type);
|
||||
}
|
||||
|
||||
ConformanceCacheKey(llvm::PointerUnion<const Metadata *, const TypeContextDescriptor *> typeOrDescriptor, const ProtocolDescriptor *proto)
|
||||
: Proto(proto) {
|
||||
TypeOrDescriptor = typeOrDescriptor;
|
||||
assert(typeOrDescriptor);
|
||||
}
|
||||
|
||||
ConformanceCacheKey(const TypeContextDescriptor *typeDescriptor, const ProtocolDescriptor *proto)
|
||||
: TypeOrDescriptor(typeDescriptor), Proto(proto) {
|
||||
assert(typeDescriptor);
|
||||
}
|
||||
|
||||
friend llvm::hash_code hash_value(const ConformanceCacheKey &key) {
|
||||
return llvm::hash_combine(key.TypeOrDescriptor.getOpaqueValue(),
|
||||
key.Proto);
|
||||
return llvm::hash_combine(key.Type, key.Proto);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -576,33 +554,28 @@ namespace {
|
||||
ExtendedStorage *next = nullptr;
|
||||
};
|
||||
|
||||
llvm::PointerUnion<const Metadata *, const TypeContextDescriptor *>
|
||||
TypeOrDescriptor;
|
||||
const Metadata *Type;
|
||||
llvm::PointerUnion<const ProtocolDescriptor *, ExtendedStorage *>
|
||||
ProtoOrStorage;
|
||||
|
||||
union {
|
||||
/// The witness table. Used for type cache records.
|
||||
const WitnessTable *Witness;
|
||||
|
||||
/// The conformance. Used for type descriptor cache records.
|
||||
const ProtocolConformanceDescriptor *Conformance;
|
||||
};
|
||||
/// The witness table.
|
||||
const WitnessTable *Witness;
|
||||
|
||||
public:
|
||||
ConformanceCacheEntry(const Metadata *type, const ProtocolDescriptor *proto,
|
||||
ConformanceCacheEntry(ConformanceCacheKey key,
|
||||
ConformanceLookupResult result,
|
||||
std::atomic<ExtendedStorage *> &storageHead)
|
||||
: TypeOrDescriptor(type), Witness(result.witnessTable) {
|
||||
: Type(key.Type), Witness(result.witnessTable)
|
||||
{
|
||||
if (!result.globalActorIsolationType) {
|
||||
ProtoOrStorage = proto;
|
||||
ProtoOrStorage = key.Proto;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate extended storage.
|
||||
void *memory = malloc(sizeof(ExtendedStorage));
|
||||
auto storage = new (memory) ExtendedStorage{
|
||||
proto, result.globalActorIsolationType,
|
||||
key.Proto, result.globalActorIsolationType,
|
||||
result.globalActorIsolationWitnessTable
|
||||
};
|
||||
|
||||
@@ -620,17 +593,8 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
ConformanceCacheEntry(const TypeContextDescriptor *typeDescriptor,
|
||||
const ProtocolDescriptor *proto,
|
||||
const ProtocolConformanceDescriptor *conformance)
|
||||
: TypeOrDescriptor(typeDescriptor), ProtoOrStorage(proto),
|
||||
Conformance(conformance) {
|
||||
assert(TypeOrDescriptor);
|
||||
assert(ProtoOrStorage);
|
||||
}
|
||||
|
||||
bool matchesKey(const ConformanceCacheKey &key) const {
|
||||
return TypeOrDescriptor == key.TypeOrDescriptor && getProtocol() == key.Proto;
|
||||
return Type == key.Type && getProtocol() == key.Proto;
|
||||
}
|
||||
|
||||
friend llvm::hash_code hash_value(const ConformanceCacheEntry &entry) {
|
||||
@@ -650,7 +614,7 @@ namespace {
|
||||
|
||||
/// Get the conformance cache key.
|
||||
ConformanceCacheKey getKey() const {
|
||||
return ConformanceCacheKey(TypeOrDescriptor, getProtocol());
|
||||
return ConformanceCacheKey(Type, getProtocol());
|
||||
}
|
||||
|
||||
/// Get the cached witness table, or null if we cached failure.
|
||||
@@ -673,14 +637,9 @@ namespace {
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
static bool CanCacheTypeByDescriptor(const TypeContextDescriptor &descriptor) {
|
||||
return descriptor.isGeneric();
|
||||
}
|
||||
|
||||
// Conformance Cache.
|
||||
struct ConformanceState {
|
||||
using CacheType = ConcurrentReadableHashMap<ConformanceCacheEntry>;
|
||||
CacheType Cache;
|
||||
ConcurrentReadableHashMap<ConformanceCacheEntry> Cache;
|
||||
ConcurrentReadableArray<ConformanceSection> SectionsToScan;
|
||||
|
||||
/// The head of an intrusive linked list that keeps track of all of the
|
||||
@@ -688,7 +647,6 @@ struct ConformanceState {
|
||||
std::atomic<ConformanceCacheEntry::ExtendedStorage *> ExtendedStorageHead{nullptr};
|
||||
|
||||
bool scanSectionsBackwards;
|
||||
bool envAllowCacheByDescriptors;
|
||||
|
||||
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
|
||||
uintptr_t dyldSharedCacheStart;
|
||||
@@ -713,8 +671,6 @@ struct ConformanceState {
|
||||
ConformanceState() {
|
||||
scanSectionsBackwards =
|
||||
runtime::bincompat::useLegacyProtocolConformanceReverseIteration();
|
||||
envAllowCacheByDescriptors = runtime::environment::
|
||||
SWIFT_DEBUG_ENABLE_CACHE_PROTOCOL_CONFORMANCES_BY_TYPE_DESCRIPTOR();
|
||||
|
||||
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
|
||||
if (__builtin_available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)) {
|
||||
@@ -750,56 +706,38 @@ struct ConformanceState {
|
||||
}
|
||||
|
||||
void cacheResult(const Metadata *type, const ProtocolDescriptor *proto,
|
||||
ConformanceLookupResult result, size_t sectionsCount,
|
||||
bool allowSaveDescriptor) {
|
||||
CacheType::GetOrInsertManyScope lockedCache(Cache);
|
||||
|
||||
// Check the current sections count against what was
|
||||
// passed in. If a section count was passed in and they
|
||||
// don't match, then this is not an authoritative entry
|
||||
// and it may have been obsoleted, because the new
|
||||
// sections could contain a conformance in a more
|
||||
// specific type.
|
||||
//
|
||||
// If they DO match, then we can safely add. Another
|
||||
// thread might be adding new sections at this point,
|
||||
// but we will not race with them. That other thread
|
||||
// will add the new sections, then clear the cache.
|
||||
// When it clears the cache, it will block waiting for
|
||||
// this code to complete and relinquish Cache's writer
|
||||
// lock. If we cache a stale entry, it will be
|
||||
// immediately cleared.
|
||||
if (sectionsCount > 0 &&
|
||||
SectionsToScan.snapshot().count() != sectionsCount)
|
||||
return; // abandon the new entry
|
||||
ConformanceLookupResult result, size_t sectionsCount) {
|
||||
Cache.getOrInsert(ConformanceCacheKey(type, proto),
|
||||
[&](ConformanceCacheEntry *entry, bool created) {
|
||||
// Create the entry if needed. If it already exists,
|
||||
// we're done.
|
||||
if (!created)
|
||||
return false;
|
||||
|
||||
lockedCache.getOrInsert(ConformanceCacheKey(type, proto),
|
||||
[&](ConformanceCacheEntry *entry, bool created) {
|
||||
// Create the entry if needed. If it already
|
||||
// exists, we're done.
|
||||
if (!created)
|
||||
return false;
|
||||
// Check the current sections count against what was
|
||||
// passed in. If a section count was passed in and they
|
||||
// don't match, then this is not an authoritative entry
|
||||
// and it may have been obsoleted, because the new
|
||||
// sections could contain a conformance in a more
|
||||
// specific type.
|
||||
//
|
||||
// If they DO match, then we can safely add. Another
|
||||
// thread might be adding new sections at this point,
|
||||
// but we will not race with them. That other thread
|
||||
// will add the new sections, then clear the cache. When
|
||||
// it clears the cache, it will block waiting for this
|
||||
// code to complete and relinquish Cache's writer lock.
|
||||
// If we cache a stale entry, it will be immediately
|
||||
// cleared.
|
||||
if (sectionsCount > 0 &&
|
||||
SectionsToScan.snapshot().count() != sectionsCount)
|
||||
return false; // abandon the new entry
|
||||
|
||||
::new (entry) ConformanceCacheEntry(
|
||||
type, proto, result, ExtendedStorageHead);
|
||||
return true; // keep the new entry
|
||||
});
|
||||
|
||||
if (auto typeDescriptor = type->getTypeContextDescriptor();
|
||||
envAllowCacheByDescriptors && allowSaveDescriptor &&
|
||||
typeDescriptor && result.witnessTable &&
|
||||
CanCacheTypeByDescriptor(*typeDescriptor)) {
|
||||
auto conformance = result.witnessTable->getDescription();
|
||||
lockedCache.getOrInsert(ConformanceCacheKey(typeDescriptor, proto),
|
||||
[&](ConformanceCacheEntry *entry, bool created) {
|
||||
if (!created)
|
||||
return false;
|
||||
|
||||
::new (entry) ConformanceCacheEntry(
|
||||
typeDescriptor, proto, conformance);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
::new (entry) ConformanceCacheEntry(
|
||||
ConformanceCacheKey(type, proto), result,
|
||||
ExtendedStorageHead);
|
||||
return true; // keep the new entry
|
||||
});
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -920,40 +858,12 @@ swift::swift_registerProtocolConformances(const ProtocolConformanceRecord *begin
|
||||
_registerProtocolConformances(C, ConformanceSection{begin, end});
|
||||
}
|
||||
|
||||
// Result of `searchInConformanceCache`
|
||||
struct SearchInConformanceCacheResult {
|
||||
enum class Source {
|
||||
None,
|
||||
TypeMetadata,
|
||||
TypeDescriptor,
|
||||
};
|
||||
|
||||
/// `IsAuthoritative` is `true` if the result is for the type itself and not a
|
||||
/// superclass. If `false` then we cached a conformance on a superclass, but
|
||||
/// that may be overridden.
|
||||
bool IsAuthoritative;
|
||||
ConformanceLookupResult Result;
|
||||
#ifndef NDEBUG
|
||||
Source Source; // For logging purpose
|
||||
#endif
|
||||
|
||||
SearchInConformanceCacheResult(bool isAuthoritative,
|
||||
ConformanceLookupResult result,
|
||||
enum Source source)
|
||||
: IsAuthoritative(isAuthoritative), Result(result)
|
||||
#ifndef NDEBUG
|
||||
, Source(source)
|
||||
#endif
|
||||
{}
|
||||
|
||||
static SearchInConformanceCacheResult NotFound() {
|
||||
return SearchInConformanceCacheResult(false, {},
|
||||
Source::None);
|
||||
}
|
||||
};
|
||||
|
||||
/// Search for a conformance descriptor in the ConformanceCache.
|
||||
static SearchInConformanceCacheResult
|
||||
/// First element of the return value is `true` if the result is authoritative
|
||||
/// i.e. the result is for the type itself and not a superclass. If `false`
|
||||
/// then we cached a conformance on a superclass, but that may be overridden.
|
||||
/// A return value of `{ false, { } }` indicates nothing was cached.
|
||||
static std::pair<bool, ConformanceLookupResult>
|
||||
searchInConformanceCache(const Metadata *type,
|
||||
const ProtocolDescriptor *protocol,
|
||||
bool instantiateSuperclassMetadata) {
|
||||
@@ -964,47 +874,13 @@ searchInConformanceCache(const Metadata *type,
|
||||
MaybeIncompleteSuperclassIterator superclassIterator{
|
||||
type, instantiateSuperclassMetadata};
|
||||
for (; auto type = superclassIterator.metadata; ++superclassIterator) {
|
||||
if (auto *cacheEntry = snapshot.find(ConformanceCacheKey(type, protocol))) {
|
||||
return SearchInConformanceCacheResult(
|
||||
type == origType, cacheEntry->getResult(),
|
||||
SearchInConformanceCacheResult::Source::TypeMetadata);
|
||||
}
|
||||
if (auto *typeDescriptor = type->getTypeContextDescriptor();
|
||||
typeDescriptor && CanCacheTypeByDescriptor(*typeDescriptor)) {
|
||||
auto *cacheEntry =
|
||||
snapshot.find(ConformanceCacheKey(typeDescriptor, protocol));
|
||||
if (!cacheEntry)
|
||||
continue;
|
||||
auto conformanceDescriptor = cacheEntry->Conformance;
|
||||
auto result =
|
||||
ConformanceLookupResult::fromConformance(type, conformanceDescriptor);
|
||||
// In case we couldn't get a witness table from the cached conformance
|
||||
// for this type. While it's possible we could find another conformance
|
||||
// that satisfies the requirements, we do NOT attempt to find it.
|
||||
// We cache it and return the result immediatelly.
|
||||
// This aligns with the current logic of the scanning:
|
||||
// When we find a conformance for the given type and protocol we attempt
|
||||
// to get the witness table and cache it and put into `foundWitnesses` no
|
||||
// matter if the witness is nullptr. If we find another conformance in
|
||||
// subsequent iterations we will get the witness and attempt to insert it
|
||||
// into the cache and `foundWitnesses` as well, but both of them will NOT
|
||||
// override the existing entry saved earlier. Later we select the first
|
||||
// witness in `foundWitnesses` in order of hierarchy even if it's null.
|
||||
// See the test case `(GenericSubClass<Int>() as Any as! Hello).hello()`
|
||||
// in `test/multifile/protocol-conformance-redundant.swift` for example.
|
||||
auto sectionsCount = C.SectionsToScan.snapshot().count();
|
||||
bool allowSaveDescriptor = false;
|
||||
C.cacheResult(type, protocol, result, sectionsCount,
|
||||
allowSaveDescriptor);
|
||||
auto isAuthoritative = origType == type;
|
||||
return SearchInConformanceCacheResult(
|
||||
isAuthoritative, result,
|
||||
SearchInConformanceCacheResult::Source::TypeDescriptor);
|
||||
if (auto *Value = snapshot.find(ConformanceCacheKey(type, protocol))) {
|
||||
return { type == origType, Value->getResult() };
|
||||
}
|
||||
}
|
||||
|
||||
// We did not find a cache entry.
|
||||
return SearchInConformanceCacheResult::NotFound();
|
||||
return { false, ConformanceLookupResult{} };
|
||||
}
|
||||
|
||||
/// Get the appropriate context descriptor for a type. If the descriptor is a
|
||||
@@ -1400,43 +1276,19 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
|
||||
return {dyldCachedWitnessTable, false};
|
||||
}
|
||||
|
||||
auto debugLogResult = [&](bool found, const char *source) {
|
||||
if (IsDebugLog()) {
|
||||
auto typeName = swift_getTypeName(type, true);
|
||||
const char *status = found ? "found" : "not found";
|
||||
fprintf(stderr, "Check confomance %.*s to %s: %s, source: %s\n",
|
||||
(int)typeName.length, typeName.data, protocol->Name.get(), status,
|
||||
source);
|
||||
}
|
||||
};
|
||||
|
||||
// See if we have an authoritative cached conformance. The
|
||||
// ConcurrentReadableHashMap data structure allows us to search the map
|
||||
// concurrently without locking.
|
||||
if (auto cacheSearchResult = searchInConformanceCache(
|
||||
type, protocol, instantiateSuperclassMetadata);
|
||||
cacheSearchResult.IsAuthoritative) {
|
||||
auto found =
|
||||
searchInConformanceCache(type, protocol, instantiateSuperclassMetadata);
|
||||
if (found.first) {
|
||||
// An authoritative negative result can be overridden by a result from dyld.
|
||||
if (!cacheSearchResult.Result.witnessTable) {
|
||||
if (!found.second.witnessTable) {
|
||||
if (dyldCachedWitnessTable)
|
||||
return {dyldCachedWitnessTable, false};
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
const char *source;
|
||||
switch (cacheSearchResult.Source) {
|
||||
case SearchInConformanceCacheResult::Source::None:
|
||||
source = "unknown";
|
||||
break;
|
||||
case SearchInConformanceCacheResult::Source::TypeMetadata:
|
||||
source = "cache by type metadata";
|
||||
break;
|
||||
case SearchInConformanceCacheResult::Source::TypeDescriptor:
|
||||
source = "cache by type descriptor";
|
||||
break;
|
||||
}
|
||||
debugLogResult(cacheSearchResult.Result.witnessTable != nullptr, source);
|
||||
#endif
|
||||
return {cacheSearchResult.Result, false};
|
||||
|
||||
return {found.second, false};
|
||||
}
|
||||
|
||||
if (dyldCachedConformanceDescriptor) {
|
||||
@@ -1446,8 +1298,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
|
||||
assert(matchingType);
|
||||
auto witness = ConformanceLookupResult::fromConformance(
|
||||
matchingType, dyldCachedConformanceDescriptor);
|
||||
bool allowSaveDescriptor = false; // already have it in the dyld cache
|
||||
C.cacheResult(type, protocol, witness, /*always cache*/ 0, allowSaveDescriptor);
|
||||
C.cacheResult(type, protocol, witness, /*always cache*/ 0);
|
||||
DYLD_CONFORMANCES_LOG("Caching generic conformance to %s found by DYLD",
|
||||
protocol->Name.get());
|
||||
return {witness, false};
|
||||
@@ -1474,8 +1325,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
|
||||
if (matchingType) {
|
||||
auto witness = ConformanceLookupResult::fromConformance(
|
||||
matchingType, &descriptor);
|
||||
bool allowSaveDescriptor = true;
|
||||
C.cacheResult(matchingType, protocol, witness, /*always cache*/ 0, allowSaveDescriptor);
|
||||
C.cacheResult(matchingType, protocol, witness, /*always cache*/ 0);
|
||||
foundWitnesses.insert({matchingType, witness});
|
||||
}
|
||||
};
|
||||
@@ -1508,10 +1358,10 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
|
||||
MaybeIncompleteSuperclassIterator superclassIterator{
|
||||
type, instantiateSuperclassMetadata};
|
||||
for (; auto searchType = superclassIterator.metadata; ++superclassIterator) {
|
||||
const auto witnessIt = foundWitnesses.find(searchType);
|
||||
if (witnessIt != foundWitnesses.end()) {
|
||||
auto witness = foundWitnesses.lookup(searchType);
|
||||
if (witness) {
|
||||
if (!foundType) {
|
||||
foundWitness = witnessIt->getSecond(); // may be null
|
||||
foundWitness = witness;
|
||||
foundType = searchType;
|
||||
} else {
|
||||
auto foundName = swift_getTypeName(foundType, true);
|
||||
@@ -1536,16 +1386,13 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
|
||||
// Do not cache negative results if there were uninstantiated superclasses
|
||||
// we didn't search. They might have a conformance that will be found later.
|
||||
if (foundWitness || !hasUninstantiatedSuperclass)
|
||||
C.cacheResult(type, protocol, foundWitness, snapshot.count(), /* allowSaveDescriptor */ false);
|
||||
C.cacheResult(type, protocol, foundWitness, snapshot.count());
|
||||
|
||||
// A negative result can be overridden by a result from dyld.
|
||||
if (!foundWitness) {
|
||||
if (dyldCachedWitnessTable) {
|
||||
debugLogResult(true, "dyld cache");
|
||||
if (dyldCachedWitnessTable)
|
||||
return {dyldCachedWitnessTable, false};
|
||||
}
|
||||
}
|
||||
debugLogResult(static_cast<bool>(foundWitness), "section scan");
|
||||
return {foundWitness, hasUninstantiatedSuperclass};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift %s -o %t/a.out
|
||||
// RUN: env SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG=1 %target-run %t/a.out 2>&1 | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: objc_interop
|
||||
// REQUIRES: swift_stdlib_asserts
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=ios
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=tvos
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=watchos
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=xros
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
|
||||
// The optimizer will remove many of these conformance checks due to statically
|
||||
// knowing the result.
|
||||
// UNSUPPORTED: swift_test_mode_optimize
|
||||
// UNSUPPORTED: swift_test_mode_optimize_size
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Proto {}
|
||||
extension Proto {
|
||||
static var selfType: Any.Type { Self.self }
|
||||
}
|
||||
|
||||
func conformsToProto<T>(_ type: T.Type) -> Bool {
|
||||
(type as? Proto.Type)?.selfType == type
|
||||
}
|
||||
|
||||
func doesNotConformToProto<T>(_ type: T) -> Bool {
|
||||
(type as? Proto.Type) == nil
|
||||
}
|
||||
|
||||
@objc class BaseClass: NSObject, Proto {}
|
||||
@objc class DerivedClass: BaseClass {}
|
||||
@objc class NonConformingClass: NSObject {}
|
||||
|
||||
// CHECK: Check confomance a.BaseClass to Proto: found, source: section scan
|
||||
assert(conformsToProto(BaseClass.self))
|
||||
// CHECK: Check confomance a.BaseClass to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(BaseClass.self))
|
||||
|
||||
// CHECK: Check confomance a.DerivedClass to Proto: found, source: section scan
|
||||
assert(conformsToProto(DerivedClass.self))
|
||||
// CHECK: Check confomance a.DerivedClass to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(DerivedClass.self))
|
||||
|
||||
// CHECK: Check confomance a.NonConformingClass to Proto: not found, source: section scan
|
||||
assert(doesNotConformToProto(NonConformingClass.self))
|
||||
// CHECK: Check confomance a.NonConformingClass to Proto: not found, source: cache by type metadata
|
||||
assert(doesNotConformToProto(NonConformingClass.self))
|
||||
@@ -1,121 +0,0 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift %s -o %t/a.out
|
||||
// RUN: env SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG=1 %target-run %t/a.out 2>&1 | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: swift_stdlib_asserts
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=ios
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=tvos
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=watchos
|
||||
// UNSUPPORTED: DARWIN_SIMULATOR=xros
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
|
||||
// The optimizer will remove many of these conformance checks due to statically
|
||||
// knowing the result.
|
||||
// UNSUPPORTED: swift_test_mode_optimize
|
||||
// UNSUPPORTED: swift_test_mode_optimize_size
|
||||
|
||||
protocol Proto {}
|
||||
extension Proto {
|
||||
static var selfType: Any.Type { Self.self }
|
||||
}
|
||||
|
||||
func conformsToProto<T>(_ type: T.Type) -> Bool {
|
||||
(type as? Proto.Type)?.selfType == type
|
||||
}
|
||||
|
||||
func doesNotConformToProto<T>(_ type: T) -> Bool {
|
||||
(type as? Proto.Type) == nil
|
||||
}
|
||||
|
||||
extension Array: Proto {}
|
||||
extension Dictionary: Proto where Key: Proto {}
|
||||
extension Int: Proto {}
|
||||
|
||||
// CHECK: Check confomance Swift.Int to Proto: found, source: section scan
|
||||
assert(conformsToProto(Int.self))
|
||||
// CHECK: Check confomance Swift.Int to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(Int.self))
|
||||
|
||||
// CHECK: Check confomance Swift.String to Proto: not found, source: section scan
|
||||
assert(doesNotConformToProto(String.self))
|
||||
// CHECK: Check confomance Swift.String to Proto: not found, source: cache by type metadata
|
||||
assert(doesNotConformToProto(String.self))
|
||||
|
||||
// CHECK: Check confomance Swift.Array<Swift.Int> to Proto: found, source: section scan
|
||||
assert(conformsToProto([Int].self))
|
||||
// CHECK: Check confomance Swift.Array<Swift.Int> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto([Int].self))
|
||||
|
||||
// CHECK: Check confomance Swift.Array<Swift.String> to Proto: found, source: cache by type descriptor
|
||||
assert(conformsToProto([String].self))
|
||||
// CHECK: Check confomance Swift.Array<Swift.String> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto([String].self))
|
||||
|
||||
// CHECK: Check confomance Swift.Dictionary<Swift.Int, Swift.Int> to Proto: found, source: section scan
|
||||
assert(conformsToProto([Int: Int].self))
|
||||
|
||||
// CHECK: Check confomance Swift.Dictionary<Swift.String, Swift.Int> to Proto: not found, source: cache by type descriptor
|
||||
assert(doesNotConformToProto([String: Int].self))
|
||||
// CHECK: Check confomance Swift.Dictionary<Swift.String, Swift.Int> to Proto: not found, source: cache by type metadata
|
||||
assert(doesNotConformToProto([String: Int].self))
|
||||
|
||||
|
||||
class BaseClass: Proto {}
|
||||
class DerivedClass: BaseClass {}
|
||||
class GenericClass<T>: Proto {}
|
||||
class GenericClassConditional<T> {}
|
||||
extension GenericClassConditional: Proto where T: Proto {}
|
||||
|
||||
// CHECK: Check confomance a.BaseClass to Proto: found, source: section scan
|
||||
assert(conformsToProto(BaseClass.self))
|
||||
// CHECK: Check confomance a.BaseClass to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(BaseClass.self))
|
||||
|
||||
// CHECK: Check confomance a.DerivedClass to Proto: found, source: section scan
|
||||
assert(conformsToProto(DerivedClass.self))
|
||||
// CHECK: Check confomance a.DerivedClass to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(DerivedClass.self))
|
||||
|
||||
// CHECK: Check confomance a.GenericClass<Swift.Int> to Proto: found, source: section scan
|
||||
assert(conformsToProto(GenericClass<Int>.self))
|
||||
// CHECK: Check confomance a.GenericClass<Swift.Int> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(GenericClass<Int>.self))
|
||||
|
||||
// CHECK: Check confomance a.GenericClass<Swift.String> to Proto: found, source: cache by type descriptor
|
||||
assert(conformsToProto(GenericClass<String>.self))
|
||||
// CHECK: Check confomance a.GenericClass<Swift.String> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(GenericClass<String>.self))
|
||||
|
||||
// CHECK: Check confomance a.GenericClassConditional<Swift.Int> to Proto: found, source: section scan
|
||||
assert(conformsToProto(GenericClassConditional<Int>.self))
|
||||
// CHECK: Check confomance a.GenericClassConditional<Swift.Int> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(GenericClassConditional<Int>.self))
|
||||
|
||||
// CHECK: Check confomance a.GenericClassConditional<Swift.String> to Proto: not found, source: cache by type descriptor
|
||||
assert(doesNotConformToProto(GenericClassConditional<String>.self))
|
||||
// CHECK: Check confomance a.GenericClassConditional<Swift.String> to Proto: not found, source: cache by type metadata
|
||||
assert(doesNotConformToProto(GenericClassConditional<String>.self))
|
||||
|
||||
enum Enum: Proto {}
|
||||
extension Optional: Proto where Wrapped: Proto {}
|
||||
|
||||
// CHECK: Check confomance a.Enum to Proto: found, source: section scan
|
||||
assert(conformsToProto(Enum.self))
|
||||
// CHECK: Check confomance a.Enum to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(Enum.self))
|
||||
|
||||
// CHECK: Check confomance Swift.Optional<a.Enum> to Proto: found, source: section scan
|
||||
assert(conformsToProto(Enum?.self))
|
||||
// CHECK: Check confomance Swift.Optional<a.Enum> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(Enum?.self))
|
||||
|
||||
// CHECK: Check confomance Swift.Optional<Swift.Int> to Proto: found, source: cache by type descriptor
|
||||
assert(conformsToProto(Int?.self))
|
||||
// CHECK: Check confomance Swift.Optional<Swift.Int> to Proto: found, source: cache by type metadata
|
||||
assert(conformsToProto(Int?.self))
|
||||
|
||||
// CHECK: Check confomance Swift.Optional<Swift.String> to Proto: not found, source: cache by type descriptor
|
||||
assert(doesNotConformToProto(String?.self))
|
||||
// CHECK: Check confomance Swift.Optional<Swift.String> to Proto: not found, source: cache by type metadata
|
||||
assert(doesNotConformToProto(String?.self))
|
||||
@@ -5,11 +5,3 @@ public protocol Hello {
|
||||
open class Super {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
open class GenericSuperClass<T> {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct GenericStruct<T> {
|
||||
public init() {}
|
||||
}
|
||||
@@ -1,19 +1,7 @@
|
||||
import Def
|
||||
|
||||
extension Super: @retroactive Hello {
|
||||
extension Super : Hello {
|
||||
public func hello() {
|
||||
print("Hello from Ext")
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericSuperClass: @retroactive Hello {
|
||||
public func hello() {
|
||||
print("Hello from Ext")
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericStruct: @retroactive Hello {
|
||||
public func hello() {
|
||||
print("Hello from Ext")
|
||||
print("Hello")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
// RUN: %target-build-swift-dylib(%t/%target-library-name(Ext)) -module-name Ext -emit-module -emit-module-path %t/Ext.swiftmodule -I%t -L%t -lDef %S/Inputs/protocol-conformance-redundant-ext.swift
|
||||
// RUN: %target-build-swift -I%t -L%t -lDef -o %t/main %target-rpath(%t) %s
|
||||
// RUN: %target-codesign %t/main %t/%target-library-name(Def) %t/%target-library-name(Ext)
|
||||
// RUN: %target-run %t/main %t/%target-library-name(Def) %t/%target-library-name(Ext) 2> >(%FileCheck %s -check-prefix=CHECK-STDERR) | %FileCheck %s
|
||||
// RUN: %target-run %t/main %t/%target-library-name(Def) %t/%target-library-name(Ext) 2>&1 | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// XFAIL: OS=windows-msvc
|
||||
|
||||
// CHECK: Warning: 'main.Sub' conforms to protocol 'Hello', but it also inherits conformance from 'Def.Super'. Relying on a particular conformance is undefined behaviour.
|
||||
// CHECK: Hello
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
#if canImport(Darwin)
|
||||
@@ -32,40 +35,7 @@ class Sub : Super, Hello {
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-STDERR: Warning: 'main.Sub' conforms to protocol 'Hello', but it also inherits conformance from 'Def.Super'. Relying on a particular conformance is undefined behaviour.
|
||||
let s = Sub() as AnyObject as! Hello
|
||||
// CHECK: Hello
|
||||
|
||||
s.hello()
|
||||
|
||||
extension GenericStruct: @retroactive Hello where T == String {
|
||||
public func hello() {
|
||||
print("Hello from main")
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-STDERR: Warning: 'main.GenericSubClass<Swift.String>' conforms to protocol 'Hello', but it also inherits conformance from 'Def.GenericSuperClass<Swift.String>'. Relying on a particular conformance is undefined behaviour.
|
||||
// CHECK: Hello from main
|
||||
(GenericStruct<String>() as Any as! Hello).hello()
|
||||
|
||||
assert(GenericStruct<Int>() as Any as? Hello == nil)
|
||||
|
||||
class GenericSubClass<T>: GenericSuperClass<T> {}
|
||||
extension GenericSubClass: Hello where T == String {
|
||||
func hello() {
|
||||
print("Hello from main")
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK: Hello from main
|
||||
(GenericSubClass<String>() as Any as! Hello).hello()
|
||||
|
||||
// https://github.com/swiftlang/swift/issues/82889
|
||||
// CHECK: Expected nil
|
||||
// CHECK: Expected nil
|
||||
for _ in 0..<2 {
|
||||
if GenericSubClass<Int>() as Any as? Hello != nil {
|
||||
print("Unexpected successful cast")
|
||||
} else {
|
||||
print("Expected nil")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user