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:
Dmitrii Galimzianov
2025-08-16 01:39:16 +02:00
parent 12abf7ee37
commit 4509b22415
8 changed files with 114 additions and 544 deletions

View File

@@ -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 &Map;
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();
}

View File

@@ -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,

View File

@@ -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};
}

View File

@@ -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))

View File

@@ -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))

View File

@@ -5,11 +5,3 @@ public protocol Hello {
open class Super {
public init() {}
}
open class GenericSuperClass<T> {
public init() {}
}
public struct GenericStruct<T> {
public init() {}
}

View File

@@ -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")
}
}

View File

@@ -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")
}
}