Files
swift-mirror/stdlib/public/runtime/ProtocolConformance.cpp

2494 lines
94 KiB
C++

//===--- ProtocolConformance.cpp - Swift protocol conformance checking ----===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Checking and caching of Swift protocol conformances.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringExtras.h"
#include "swift/ABI/TypeIdentity.h"
#include "swift/Basic/Lazy.h"
#include "swift/Basic/STLExtras.h"
#include "swift/Demangling/Demangle.h"
#include "swift/Runtime/Bincompat.h"
#include "swift/Runtime/Casting.h"
#include "swift/Runtime/Concurrent.h"
#include "swift/Runtime/EnvironmentVariables.h"
#include "swift/Runtime/HeapObject.h"
#include "swift/Runtime/Metadata.h"
#include "swift/Basic/Unreachable.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "../CompatibilityOverride/CompatibilityOverride.h"
#include "ImageInspection.h"
#include "Private.h"
#include "Tracing.h"
#include <new>
#include <vector>
#if __has_include(<mach-o/dyld_priv.h>)
#include <mach-o/dyld_priv.h>
#define DYLD_EXPECTED_SWIFT_OPTIMIZATIONS_VERSION 1u
// Redeclare these functions as weak so we can build against a macOS 12 SDK and
// still test on macOS 11.
LLVM_ATTRIBUTE_WEAK
struct _dyld_protocol_conformance_result
_dyld_find_protocol_conformance(const void *protocolDescriptor,
const void *metadataType,
const void *typeDescriptor);
LLVM_ATTRIBUTE_WEAK
struct _dyld_protocol_conformance_result
_dyld_find_foreign_type_protocol_conformance(const void *protocol,
const char *foreignTypeIdentityStart,
size_t foreignTypeIdentityLength);
LLVM_ATTRIBUTE_WEAK
uint32_t _dyld_swift_optimizations_version(void);
#if DYLD_FIND_PROTOCOL_ON_DISK_CONFORMANCE_DEFINED
// Redeclare these functions as weak as well.
LLVM_ATTRIBUTE_WEAK bool _dyld_has_preoptimized_swift_protocol_conformances(
const struct mach_header *mh);
LLVM_ATTRIBUTE_WEAK struct _dyld_protocol_conformance_result
_dyld_find_protocol_conformance_on_disk(const void *protocolDescriptor,
const void *metadataType,
const void *typeDescriptor,
uint32_t flags);
LLVM_ATTRIBUTE_WEAK struct _dyld_protocol_conformance_result
_dyld_find_foreign_type_protocol_conformance_on_disk(
const void *protocol, const char *foreignTypeIdentityStart,
size_t foreignTypeIdentityLength, uint32_t flags);
#endif // DYLD_FIND_PROTOCOL_ON_DISK_CONFORMANCE_DEFINED
#endif // __has_include(<mach-o/dyld_priv.h>)
// Set this to 1 to enable logging of calls to the dyld shared cache conformance
// table
#if 0
#define DYLD_CONFORMANCES_LOG(fmt, ...) \
fprintf(stderr, "PROTOCOL CONFORMANCE: " fmt "\n", __VA_ARGS__)
#define SHARED_CACHE_LOG_ENABLED 1
#else
#define DYLD_CONFORMANCES_LOG(fmt, ...) (void)0
#endif
// Enable dyld shared cache acceleration only when it's available and we have
// ObjC interop.
#if DYLD_FIND_PROTOCOL_CONFORMANCE_DEFINED && SWIFT_OBJC_INTEROP
#define USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES 1
#endif
using namespace swift;
#ifndef NDEBUG
template <> SWIFT_USED void ProtocolDescriptor::dump() const {
printf("TargetProtocolDescriptor.\n"
"Name: \"%s\".\n",
Name.get());
}
void ProtocolDescriptorFlags::dump() const {
printf("ProtocolDescriptorFlags.\n");
printf("Is Swift: %s.\n", (isSwift() ? "true" : "false"));
printf("Needs Witness Table: %s.\n",
(needsWitnessTable() ? "true" : "false"));
printf("Is Resilient: %s.\n", (isResilient() ? "true" : "false"));
printf("Special Protocol: %s.\n",
(bool(getSpecialProtocol()) ? "Error" : "None"));
printf("Class Constraint: %s.\n",
(bool(getClassConstraint()) ? "Class" : "Any"));
printf("Dispatch Strategy: %s.\n",
(bool(getDispatchStrategy()) ? "Swift" : "ObjC"));
}
#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>
static const char *class_getName(const ClassMetadata* type) {
return class_getName(
reinterpret_cast<Class>(const_cast<ClassMetadata*>(type)));
}
template<> void ProtocolConformanceDescriptor::dump() const {
std::optional<SymbolInfo> info;
auto symbolName = [&](const void *addr) -> const char * {
info = SymbolInfo::lookup(addr);
if (info.has_value() && info->getSymbolName()) {
return info->getSymbolName();
}
return "<unknown addr>";
};
switch (getTypeKind()) {
case TypeReferenceKind::DirectObjCClassName:
printf("direct Objective-C class name %s", getDirectObjCClassName());
break;
case TypeReferenceKind::IndirectObjCClass:
printf("indirect Objective-C class %s",
class_getName(*getIndirectObjCClass()));
break;
case TypeReferenceKind::DirectTypeDescriptor:
case TypeReferenceKind::IndirectTypeDescriptor:
printf("unique nominal type descriptor %s", symbolName(getTypeDescriptor()));
break;
}
printf(" => ");
printf("witness table pattern (%p) %s\n", getWitnessTablePattern(), symbolName(getWitnessTablePattern()));
}
#endif
#ifndef NDEBUG
template <> SWIFT_USED void ProtocolConformanceDescriptor::verify() const {
auto typeKind = unsigned(getTypeKind());
assert(((unsigned(TypeReferenceKind::First_Kind) <= typeKind) &&
(unsigned(TypeReferenceKind::Last_Kind) >= typeKind)) &&
"Corrupted type metadata record kind");
}
#endif
#if SWIFT_OBJC_INTEROP
template <>
const ClassMetadata *TypeReference::getObjCClass(TypeReferenceKind kind) const {
switch (kind) {
case TypeReferenceKind::IndirectObjCClass:
return *getIndirectObjCClass(kind);
case TypeReferenceKind::DirectObjCClassName:
return reinterpret_cast<const ClassMetadata *>(
objc_lookUpClass(getDirectObjCClassName(kind)));
case TypeReferenceKind::DirectTypeDescriptor:
case TypeReferenceKind::IndirectTypeDescriptor:
return nullptr;
}
swift_unreachable("Unhandled TypeReferenceKind in switch.");
}
#endif
static MetadataState
tryGetCompleteMetadataNonblocking(const Metadata *metadata) {
return swift_checkMetadataState(
MetadataRequest(MetadataState::Complete, /*isNonBlocking*/ true),
metadata)
.State;
}
/// Get the superclass of metadata, which may be incomplete. When the metadata
/// is not sufficiently complete, then we fall back to demangling the superclass
/// in the nominal type descriptor, which is slow but works. Return {NULL,
/// MetadataState::Complete} if the metadata is not a class, or has no
/// superclass.
///
/// If the metadata's current state is known, it may be passed in as
/// knownMetadataState. This saves the cost of retrieving that info separately.
///
/// When instantiateSuperclassMetadata is true, this function will instantiate
/// superclass metadata when necessary. When false, this will return {NULL,
/// MetadataState::Abstract} to indicate that there's an uninstantiated
/// superclass that was not returned.
static MetadataResponse getSuperclassForMaybeIncompleteMetadata(
const Metadata *metadata, std::optional<MetadataState> knownMetadataState,
bool instantiateSuperclassMetadata) {
const ClassMetadata *classMetadata = dyn_cast<ClassMetadata>(metadata);
if (!classMetadata)
return {_swift_class_getSuperclass(metadata), MetadataState::Complete};
#if SWIFT_OBJC_INTEROP
// Artificial subclasses are not valid type metadata and
// tryGetCompleteMetadataNonblocking will crash on them. However, they're
// always fully set up, so we can just skip it and fetch the Subclass field.
if (classMetadata->isTypeMetadata() && classMetadata->isArtificialSubclass())
return {classMetadata->Superclass, MetadataState::Complete};
// Pure ObjC classes are already set up, and the code below will not be
// happy with them.
if (!classMetadata->isTypeMetadata())
return {classMetadata->Superclass, MetadataState::Complete};
#endif
MetadataState metadataState;
if (knownMetadataState)
metadataState = *knownMetadataState;
else
metadataState = tryGetCompleteMetadataNonblocking(classMetadata);
if (metadataState == MetadataState::Complete) {
// The subclass metadata is complete. Fetch and return the superclass.
auto *superMetadata = getMetadataForClass(classMetadata->Superclass);
return {superMetadata, MetadataState::Complete};
} else if (metadataState == MetadataState::NonTransitiveComplete) {
// The subclass metadata is complete, but, unlike above, not transitively.
// Its Superclass field is valid, so just read that field to get to the
// superclass to proceed to the next step.
auto *superMetadata = getMetadataForClass(classMetadata->Superclass);
auto superState = tryGetCompleteMetadataNonblocking(superMetadata);
return {superMetadata, superState};
} else if (instantiateSuperclassMetadata) {
// The subclass metadata is either LayoutComplete or Abstract, so the
// Superclass field is not valid. To get to the superclass, make the
// expensive call to getSuperclassMetadata which demangles the superclass
// name from the nominal type descriptor to get the metadata for the
// superclass.
MetadataRequest request(MetadataState::Abstract,
/*non-blocking*/ true);
return getSuperclassMetadata(request, classMetadata);
} else {
// The Superclass field is not valid and the caller did not request
// instantiation. Return a NULL superclass and Abstract to indicate that a
// superclass exists but is not yet instantiated.
return {nullptr, MetadataState::Abstract};
}
}
struct MaybeIncompleteSuperclassIterator {
const Metadata *metadata;
std::optional<MetadataState> state;
bool instantiateSuperclassMetadata;
MaybeIncompleteSuperclassIterator(const Metadata *metadata,
bool instantiateSuperclassMetadata)
: metadata(metadata), state(std::nullopt),
instantiateSuperclassMetadata(instantiateSuperclassMetadata) {}
MaybeIncompleteSuperclassIterator &operator++() {
auto response = getSuperclassForMaybeIncompleteMetadata(
metadata, state, instantiateSuperclassMetadata);
metadata = response.Value;
state = response.State;
return *this;
}
const Metadata *operator*() const { return metadata; }
bool operator!=(const MaybeIncompleteSuperclassIterator rhs) const {
return metadata != rhs.metadata;
}
};
/// 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 *
ProtocolConformanceDescriptor::getCanonicalTypeMetadata() const {
switch (getTypeKind()) {
case TypeReferenceKind::IndirectObjCClass:
case TypeReferenceKind::DirectObjCClassName:
#if SWIFT_OBJC_INTEROP
// 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 cls = TypeRef.getObjCClass(getTypeKind()))
return getMetadataForClass(cls);
#endif
return nullptr;
case TypeReferenceKind::DirectTypeDescriptor:
case TypeReferenceKind::IndirectTypeDescriptor: {
if (auto anyType = getTypeDescriptor()) {
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
if (!type->isGeneric()) {
if (auto accessFn = type->getAccessFunction())
return accessFn(MetadataState::Abstract).Value;
}
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
return _getSimpleProtocolTypeMetadata(protocol);
}
}
return nullptr;
}
}
swift_unreachable("Unhandled TypeReferenceKind in switch.");
}
namespace {
/// Describes the result of looking in the conformance cache.
struct ConformanceLookupResult {
/// The actual witness table, which will be NULL if the type does not
/// conform.
const WitnessTable *witnessTable = nullptr;
/// The global actor to which this conformance is isolated, or NULL for
/// a nonisolated conformances.
const Metadata *globalActorIsolationType = nullptr;
/// When the conformance is global-actor-isolated, this is the conformance
/// of globalActorIsolationType to GlobalActor.
const WitnessTable *globalActorIsolationWitnessTable = nullptr;
ConformanceLookupResult() { }
ConformanceLookupResult(std::nullptr_t) { }
ConformanceLookupResult(const WitnessTable *witnessTable,
const Metadata *globalActorIsolationType,
const WitnessTable *globalActorIsolationWitnessTable)
: witnessTable(witnessTable),
globalActorIsolationType(globalActorIsolationType),
globalActorIsolationWitnessTable(globalActorIsolationWitnessTable) { }
explicit operator bool() const { return witnessTable != nullptr; }
/// Given a type and conformance descriptor, form a conformance lookup
/// result.
static ConformanceLookupResult fromConformance(
const Metadata *type,
const ProtocolConformanceDescriptor *conformanceDescriptor);
};
}
/// Determine the global actor isolation for the given witness table.
///
/// Returns true if an error occurred, false if global actor isolation was
/// successfully computed (which can mean "not isolated").
static bool _checkWitnessTableIsolation(
const Metadata *type,
const WitnessTable *wtable,
llvm::ArrayRef<const void *> conditionalArgs,
ConformanceExecutionContext &context
);
template<>
const WitnessTable *
ProtocolConformanceDescriptor::getWitnessTable(
const Metadata *type,
ConformanceExecutionContext &context
) const {
// If needed, check the conditional requirements.
llvm::SmallVector<const void *, 8> conditionalArgs;
llvm::ArrayRef<GenericParamDescriptor> genericParams;
if (auto typeDescriptor = type->getTypeContextDescriptor())
genericParams = typeDescriptor->getGenericParams();
if (hasConditionalRequirements() || !genericParams.empty()) {
SubstGenericParametersFromMetadata substitutions(type);
auto error = _checkGenericRequirements(
genericParams, getConditionalRequirements(), conditionalArgs,
[&substitutions](unsigned depth, unsigned index) {
return substitutions.getMetadata(depth, index).Ptr;
},
[&substitutions](unsigned fullOrdinal, unsigned keyOrdinal) {
return substitutions.getMetadataKeyArgOrdinal(keyOrdinal).Ptr;
},
[&substitutions](const Metadata *type, unsigned index) {
return substitutions.getWitnessTable(type, index);
},
&context);
if (error)
return nullptr;
}
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
auto wtable = (const WitnessTable *)
swift_getWitnessTableRelative(this, type, conditionalArgs.data());
#else
auto wtable = swift_getWitnessTable(this, type, conditionalArgs.data());
#endif
if (!wtable)
return nullptr;
// Check the global-actor isolation for this conformance, combining it with
// any global-actor isolation determined based on the conditional
// requirements above.
if (_checkWitnessTableIsolation(type, wtable, conditionalArgs, context))
return nullptr;
return wtable;
}
ConformanceLookupResult ConformanceLookupResult::fromConformance(
const Metadata *type,
const ProtocolConformanceDescriptor *conformanceDescriptor) {
ConformanceExecutionContext context;
auto wtable = conformanceDescriptor->getWitnessTable(type, context);
return {
wtable,
context.globalActorIsolationType,
context.globalActorIsolationWitnessTable
};
}
/// Determine the global actor isolation for the given witness table.
///
/// Returns true if an error occurred, false if global actor isolation was
/// successfully computed (which can mean "not isolated").
static bool _checkWitnessTableIsolation(
const Metadata *type,
const WitnessTable *wtable,
llvm::ArrayRef<const void *> conditionalArgs,
ConformanceExecutionContext &context
) {
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
auto description = lookThroughOptionalConditionalWitnessTable(
reinterpret_cast<const RelativeWitnessTable *>(wtable))
->getDescription();
#else
auto description = wtable->getDescription();
#endif
// If there's no protocol conformance descriptor, do nothing.
if (!description)
return false;
// If this conformance doesn't have global actor isolation, we're done.
if (!description->hasGlobalActorIsolation())
return false;
// Resolve the global actor type.
SubstGenericParametersFromMetadata substitutions(type);
auto result = swift_getTypeByMangledName(
MetadataState::Abstract, description->getGlobalActorType(),
conditionalArgs.data(),
[&substitutions](unsigned depth, unsigned index) {
return substitutions.getMetadata(depth, index).Ptr;
},
[&substitutions](const Metadata *type, unsigned index) {
return substitutions.getWitnessTable(type, index);
});
if (result.isError())
return true;
auto myGlobalActorIsolationType = result.getType().getMetadata();
if (!myGlobalActorIsolationType)
return true;
// If the global actor isolation from this conformance conflicts with
// the one we already have, fail.
if (context.globalActorIsolationType &&
context.globalActorIsolationType != myGlobalActorIsolationType)
return true;
// Dig out the witness table.
auto myConformance = description->getGlobalActorConformance();
if (!myConformance)
return true;
auto myWitnessTable = ConformanceLookupResult::fromConformance(
myGlobalActorIsolationType, myConformance);
if (!myWitnessTable)
return true;
context.globalActorIsolationType = myGlobalActorIsolationType;
context.globalActorIsolationWitnessTable = myWitnessTable.witnessTable;
return false;
}
namespace {
struct ConformanceSection {
const ProtocolConformanceRecord *Begin, *End;
ConformanceSection(const ProtocolConformanceRecord *begin,
const ProtocolConformanceRecord *end)
: Begin(begin), End(end) {}
ConformanceSection(const void *ptr, uintptr_t size) {
auto bytes = reinterpret_cast<const char *>(ptr);
Begin = reinterpret_cast<const ProtocolConformanceRecord *>(ptr);
End = reinterpret_cast<const ProtocolConformanceRecord *>(bytes + size);
}
const ProtocolConformanceRecord *begin() const {
return Begin;
}
const ProtocolConformanceRecord *end() const {
return End;
}
};
struct ConformanceCacheKey {
llvm::PointerUnion<const Metadata *, const TypeContextDescriptor *>
TypeOrDescriptor;
const ProtocolDescriptor *Proto;
ConformanceCacheKey(const Metadata *type, const ProtocolDescriptor *proto)
: TypeOrDescriptor(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);
}
};
struct ConformanceCacheEntry {
public:
/// Storage used when we have global actor isolation on the conformance.
struct ExtendedStorage {
/// The protocol to which the type conforms.
const ProtocolDescriptor *Proto;
/// The global actor to which this conformance is isolated, or NULL for
/// a nonisolated conformances.
const Metadata *globalActorIsolationType = nullptr;
/// When the conformance is global-actor-isolated, this is the conformance
/// of globalActorIsolationType to GlobalActor.
const WitnessTable *globalActorIsolationWitnessTable = nullptr;
/// The next pointer in the list of extended storage allocations.
ExtendedStorage *next = nullptr;
};
llvm::PointerUnion<const Metadata *, const TypeContextDescriptor *>
TypeOrDescriptor;
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;
};
public:
ConformanceCacheEntry(const Metadata *type, const ProtocolDescriptor *proto,
ConformanceLookupResult result,
std::atomic<ExtendedStorage *> &storageHead)
: TypeOrDescriptor(type), Witness(result.witnessTable) {
if (!result.globalActorIsolationType) {
ProtoOrStorage = proto;
return;
}
// Allocate extended storage.
void *memory = malloc(sizeof(ExtendedStorage));
auto storage = new (memory) ExtendedStorage{
proto, result.globalActorIsolationType,
result.globalActorIsolationWitnessTable
};
ProtoOrStorage = storage;
// Add the storage pointer to the list of extended storage allocations
// so that we can free them later.
auto head = storageHead.load(std::memory_order_relaxed);
while (true) {
storage->next = head;
if (storageHead.compare_exchange_weak(
head, storage, std::memory_order_release,
std::memory_order_relaxed))
break;
};
}
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;
}
friend llvm::hash_code hash_value(const ConformanceCacheEntry &entry) {
return hash_value(entry.getKey());
}
/// Get the protocol.
const ProtocolDescriptor *getProtocol() const {
if (auto proto = ProtoOrStorage.dyn_cast<const ProtocolDescriptor *>())
return proto;
if (auto storage = ProtoOrStorage.dyn_cast<ExtendedStorage *>())
return storage->Proto;
return nullptr;
}
/// Get the conformance cache key.
ConformanceCacheKey getKey() const {
return ConformanceCacheKey(TypeOrDescriptor, getProtocol());
}
/// Get the cached witness table, or null if we cached failure.
const WitnessTable *getWitnessTable() const {
return Witness;
}
ConformanceLookupResult getResult() const {
if (ProtoOrStorage.is<const ProtocolDescriptor *>())
return ConformanceLookupResult { Witness, nullptr, nullptr };
if (auto storage = ProtoOrStorage.dyn_cast<ExtendedStorage *>()) {
return ConformanceLookupResult(
Witness, storage->globalActorIsolationType,
storage->globalActorIsolationWitnessTable);
}
return nullptr;
}
};
} // end anonymous namespace
static bool CanCacheTypeByDescriptor(const TypeContextDescriptor &descriptor) {
return descriptor.isGeneric();
}
// Conformance Cache.
struct ConformanceState {
using CacheType = ConcurrentReadableHashMap<ConformanceCacheEntry>;
CacheType Cache;
ConcurrentReadableArray<ConformanceSection> SectionsToScan;
/// The head of an intrusive linked list that keeps track of all of the
/// conformance cache entries that require extended storage.
std::atomic<ConformanceCacheEntry::ExtendedStorage *> ExtendedStorageHead{nullptr};
bool scanSectionsBackwards;
bool envAllowCacheByDescriptors;
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
uintptr_t dyldSharedCacheStart;
uintptr_t dyldSharedCacheEnd;
bool hasOverriddenImage;
bool validateDyldResults;
// Only populated when validateDyldResults is enabled.
ConcurrentReadableArray<ConformanceSection> DyldOptimizedSections;
bool inSharedCache(const void *ptr) {
auto uintPtr = reinterpret_cast<uintptr_t>(ptr);
return dyldSharedCacheStart <= uintPtr && uintPtr < dyldSharedCacheEnd;
}
bool dyldOptimizationsActive() { return dyldSharedCacheStart != 0; }
#else
bool dyldOptimizationsActive() { return false; }
#endif
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, *)) {
if (runtime::environment::SWIFT_DEBUG_ENABLE_SHARED_CACHE_PROTOCOL_CONFORMANCES()) {
if (&_dyld_swift_optimizations_version) {
if (_dyld_swift_optimizations_version() ==
DYLD_EXPECTED_SWIFT_OPTIMIZATIONS_VERSION) {
size_t length;
dyldSharedCacheStart =
(uintptr_t)_dyld_get_shared_cache_range(&length);
dyldSharedCacheEnd =
dyldSharedCacheStart ? dyldSharedCacheStart + length : 0;
validateDyldResults = runtime::environment::
SWIFT_DEBUG_VALIDATE_SHARED_CACHE_PROTOCOL_CONFORMANCES();
DYLD_CONFORMANCES_LOG("Shared cache range is %#lx-%#lx",
dyldSharedCacheStart, dyldSharedCacheEnd);
} else {
DYLD_CONFORMANCES_LOG("Disabling dyld protocol conformance "
"optimizations due to unknown "
"optimizations version %u",
_dyld_swift_optimizations_version());
dyldSharedCacheStart = 0;
dyldSharedCacheEnd = 0;
}
}
}
}
#endif
// This must run last, as it triggers callbacks that require
// ConformanceState to be set up.
initializeProtocolConformanceLookup();
}
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
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;
::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)) {
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
auto conformance = lookThroughOptionalConditionalWitnessTable(
reinterpret_cast<const RelativeWitnessTable *>(result.witnessTable))
->getDescription();
#else
auto conformance = result.witnessTable->getDescription();
#endif
lockedCache.getOrInsert(ConformanceCacheKey(typeDescriptor, proto),
[&](ConformanceCacheEntry *entry, bool created) {
if (!created)
return false;
::new (entry) ConformanceCacheEntry(
typeDescriptor, proto, conformance);
return true;
});
}
}
#ifndef NDEBUG
void verify() const SWIFT_USED;
#endif
};
#ifndef NDEBUG
void ConformanceState::verify() const {
// Iterate over all of the sections and verify all of the protocol
// descriptors.
auto &Self = const_cast<ConformanceState &>(*this);
for (const auto &Section : Self.SectionsToScan.snapshot()) {
for (const auto &Record : Section) {
Record.get()->verify();
}
}
}
#endif
static Lazy<ConformanceState> Conformances;
const void * const swift::_swift_debug_protocolConformanceStatePointer =
&Conformances;
static void _registerProtocolConformances(ConformanceState &C,
ConformanceSection section) {
C.SectionsToScan.push_back(section);
// Blow away the conformances cache to get rid of any negative entries that
// may now be obsolete.
C.Cache.clear([&](ConcurrentFreeListNode *&freeListHead) {
// The extended storage for conformance entries will need to be freed
// eventually. Put it on the concurrent free list so the cache will do so.
auto storageHead = C.ExtendedStorageHead.load(std::memory_order_relaxed);
while (storageHead) {
auto current = storageHead;
auto newHead = current->next;
if (C.ExtendedStorageHead.compare_exchange_weak(
storageHead, newHead, std::memory_order_release,
std::memory_order_relaxed)) {
ConcurrentFreeListNode::add(&freeListHead, current);
}
}
});
}
void swift::addImageProtocolConformanceBlockCallbackUnsafe(
const void *baseAddress,
const void *conformances, uintptr_t conformancesSize) {
assert(conformancesSize % sizeof(ProtocolConformanceRecord) == 0 &&
"conformances section not a multiple of ProtocolConformanceRecord");
// Conformance cache should always be sufficiently initialized by this point.
auto &C = Conformances.unsafeGetAlreadyInitialized();
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
// If any image in the shared cache is overridden, we need to scan all
// conformance sections in the shared cache. The pre-built table does NOT work
// if the protocol, type, or descriptor are in overridden images. Example:
//
// libX.dylib: struct S {}
// libY.dylib: protocol P {}
// libZ.dylib: extension S: P {}
//
// If libX or libY are overridden, then dyld will not return the S: P
// conformance from libZ. But that conformance still exists and we must still
// return it! Therefore we must scan libZ (and all other dylibs) even though
// it is not overridden.
if (!dyld_shared_cache_some_image_overridden()) {
// Sections in the shared cache are ignored in favor of the shared cache's
// pre-built tables.
if (C.inSharedCache(conformances)) {
DYLD_CONFORMANCES_LOG(
"Skipping conformances section %p in the shared cache", conformances);
if (C.validateDyldResults)
C.DyldOptimizedSections.push_back(
ConformanceSection{conformances, conformancesSize});
return;
#if DYLD_FIND_PROTOCOL_ON_DISK_CONFORMANCE_DEFINED
} else if (&_dyld_has_preoptimized_swift_protocol_conformances &&
_dyld_has_preoptimized_swift_protocol_conformances(
reinterpret_cast<const mach_header *>(baseAddress))) {
// dyld may optimize images outside the shared cache. Skip those too.
DYLD_CONFORMANCES_LOG(
"Skipping conformances section %p optimized by dyld", conformances);
if (C.validateDyldResults)
C.DyldOptimizedSections.push_back(
ConformanceSection{conformances, conformancesSize});
return;
#endif
} else {
DYLD_CONFORMANCES_LOG(
"Adding conformances section %p outside the shared cache",
conformances);
}
}
#endif
// If we have a section, enqueue the conformances for lookup.
_registerProtocolConformances(
C, ConformanceSection{conformances, conformancesSize});
}
void swift::addImageProtocolConformanceBlockCallback(
const void *baseAddress,
const void *conformances, uintptr_t conformancesSize) {
Conformances.get();
addImageProtocolConformanceBlockCallbackUnsafe(baseAddress,
conformances,
conformancesSize);
}
void
swift::swift_registerProtocolConformances(const ProtocolConformanceRecord *begin,
const ProtocolConformanceRecord *end){
auto &C = Conformances.get();
_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
searchInConformanceCache(const Metadata *type,
const ProtocolDescriptor *protocol,
bool instantiateSuperclassMetadata) {
auto &C = Conformances.get();
auto origType = type;
auto snapshot = C.Cache.snapshot();
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);
}
}
// We did not find a cache entry.
return SearchInConformanceCacheResult::NotFound();
}
/// Get the appropriate context descriptor for a type. If the descriptor is a
/// foreign type descriptor, also return its identity string.
static std::pair<const ContextDescriptor *, llvm::StringRef>
getContextDescriptor(const Metadata *conformingType) {
const auto *description = conformingType->getTypeContextDescriptor();
if (description) {
if (description->hasForeignMetadataInitialization()) {
auto identity = ParsedTypeIdentity::parse(description).FullIdentity;
return {description, identity};
}
return {description, {}};
}
// Handle single-protocol existential types for self-conformance.
auto *existentialType = dyn_cast<ExistentialTypeMetadata>(conformingType);
if (existentialType == nullptr ||
existentialType->getProtocols().size() != 1 ||
existentialType->getSuperclassConstraint() != nullptr)
return {nullptr, {}};
auto proto = existentialType->getProtocols()[0];
#if SWIFT_OBJC_INTEROP
if (proto.isObjC())
return {nullptr, {}};
#endif
return {proto.getSwiftProtocol(), {}};
}
namespace {
/// Describes a protocol conformance "candidate" that can be checked
/// against a type metadata.
class ConformanceCandidate {
const void *candidate;
bool candidateIsMetadata;
public:
ConformanceCandidate() : candidate(0), candidateIsMetadata(false) { }
ConformanceCandidate(const ProtocolConformanceDescriptor &conformance)
: ConformanceCandidate()
{
if (auto description = conformance.getTypeDescriptor()) {
candidate = description;
candidateIsMetadata = false;
return;
}
if (auto metadata = conformance.getCanonicalTypeMetadata()) {
candidate = metadata;
candidateIsMetadata = true;
return;
}
}
/// Whether the conforming type exactly matches the conformance candidate.
bool matches(const Metadata *conformingType) const {
// Check whether the types match.
if (candidateIsMetadata && conformingType == candidate)
return true;
// Check whether the nominal type descriptors match.
if (!candidateIsMetadata) {
const auto *description = std::get<const ContextDescriptor *>(
getContextDescriptor(conformingType));
auto candidateDescription =
static_cast<const ContextDescriptor *>(candidate);
if (description && equalContexts(description, candidateDescription))
return true;
}
return false;
}
/// Retrieve the type that matches the conformance candidate, which may
/// be a superclass of the given type. Returns null if this type does not
/// match this conformance, along with the final metadata state of the
/// superclass iterator.
std::pair<const Metadata *, std::optional<MetadataState>>
getMatchingType(const Metadata *conformingType,
bool instantiateSuperclassMetadata) const {
MaybeIncompleteSuperclassIterator superclassIterator{
conformingType, instantiateSuperclassMetadata};
for (; auto conformingType = superclassIterator.metadata;
++superclassIterator) {
if (matches(conformingType))
return {conformingType, std::nullopt};
}
return {nullptr, superclassIterator.state};
}
};
}
static void validateDyldResults(
ConformanceState &C, const Metadata *type,
const ProtocolDescriptor *protocol,
ConformanceLookupResult dyldCachedWitnessTable,
const ProtocolConformanceDescriptor *dyldCachedConformanceDescriptor,
bool instantiateSuperclassMetadata) {
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
if (!C.dyldOptimizationsActive() || !C.validateDyldResults)
return;
llvm::SmallVector<const ProtocolConformanceDescriptor *, 8> conformances;
for (auto &section : C.DyldOptimizedSections.snapshot()) {
for (const auto &record : section) {
auto &descriptor = *record.get();
if (descriptor.getProtocol() != protocol)
continue;
ConformanceCandidate candidate(descriptor);
if (std::get<const Metadata *>(
candidate.getMatchingType(type, instantiateSuperclassMetadata)))
conformances.push_back(&descriptor);
}
}
auto conformancesString = [&]() -> std::string {
std::string result = "";
for (auto *conformance : conformances) {
if (!result.empty())
result += ", ";
result += "0x";
result += llvm::utohexstr(reinterpret_cast<uint64_t>(conformance));
}
return result;
};
if (dyldCachedConformanceDescriptor) {
if (std::find(conformances.begin(), conformances.end(),
dyldCachedConformanceDescriptor) == conformances.end()) {
auto typeName = swift_getTypeName(type, true);
swift::fatalError(
0,
"Checking conformance of %.*s %p to %s %p - dyld cached conformance "
"descriptor %p not found in conformance records: (%s)\n",
(int)typeName.length, typeName.data, type, protocol->Name.get(),
protocol, dyldCachedConformanceDescriptor,
conformancesString().c_str());
}
} else {
if (!conformances.empty()) {
auto typeName = swift_getTypeName(type, true);
swift::fatalError(
0,
"Checking conformance of %.*s %p to %s %p - dyld found no "
"conformance descriptor, but matching descriptors exist: (%s)\n",
(int)typeName.length, typeName.data, type, protocol->Name.get(),
protocol, conformancesString().c_str());
}
}
#endif
}
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
static _dyld_protocol_conformance_result getDyldSharedCacheConformance(
ConformanceState &C, const ProtocolDescriptor *protocol,
const ClassMetadata *objcClassMetadata,
const ContextDescriptor *description, llvm::StringRef foreignTypeIdentity) {
// Protocols that aren't in the shared cache will never be found in the shared
// cache conformances, skip the call.
if (!C.inSharedCache(protocol)) {
DYLD_CONFORMANCES_LOG(
"Skipping shared cache lookup, protocol %p is not in shared cache.",
protocol);
return {_dyld_protocol_conformance_result_kind_not_found, nullptr};
}
if (!foreignTypeIdentity.empty()) {
// Foreign types are non-unique so those can still be found in the shared
// cache even if the identity string is outside.
DYLD_CONFORMANCES_LOG(
"_dyld_find_foreign_type_protocol_conformance(%p, %.*s, %zu)", protocol,
(int)foreignTypeIdentity.size(), foreignTypeIdentity.data(),
foreignTypeIdentity.size());
return _dyld_find_foreign_type_protocol_conformance(
protocol, foreignTypeIdentity.data(), foreignTypeIdentity.size());
} else {
// If both the ObjC class metadata and description are outside the shared
// cache, then we'll never find a shared cache conformance, skip the call.
// We can still find a shared cache conformance if one is inside and one is
// outside.
if (!C.inSharedCache(objcClassMetadata) && !C.inSharedCache(description)) {
DYLD_CONFORMANCES_LOG("Skipping shared cache lookup, class %p and "
"description %p are not in shared cache.",
objcClassMetadata, description);
return {_dyld_protocol_conformance_result_kind_not_found, nullptr};
}
DYLD_CONFORMANCES_LOG("_dyld_find_protocol_conformance(%p, %p, %p)",
protocol, objcClassMetadata, description);
return _dyld_find_protocol_conformance(protocol, objcClassMetadata,
description);
}
}
static _dyld_protocol_conformance_result getDyldOnDiskConformance(
ConformanceState &C, const ProtocolDescriptor *protocol,
const ClassMetadata *objcClassMetadata,
const ContextDescriptor *description, llvm::StringRef foreignTypeIdentity) {
#if DYLD_FIND_PROTOCOL_ON_DISK_CONFORMANCE_DEFINED
if (&_dyld_find_foreign_type_protocol_conformance_on_disk &&
&_dyld_find_protocol_conformance_on_disk) {
if (!foreignTypeIdentity.empty()) {
DYLD_CONFORMANCES_LOG(
"_dyld_find_foreign_type_protocol_conformance_on_disk(%"
"p, %.*s, %zu, 0)",
protocol, (int)foreignTypeIdentity.size(), foreignTypeIdentity.data(),
foreignTypeIdentity.size());
return _dyld_find_foreign_type_protocol_conformance_on_disk(
protocol, foreignTypeIdentity.data(), foreignTypeIdentity.size(), 0);
} else {
DYLD_CONFORMANCES_LOG(
"_dyld_find_protocol_conformance_on_disk(%p, %p, %p, 0)", protocol,
objcClassMetadata, description);
return _dyld_find_protocol_conformance_on_disk(
protocol, objcClassMetadata, description, 0);
}
}
#endif
return {_dyld_protocol_conformance_result_kind_not_found, nullptr};
}
#endif
/// Query dyld for a protocol conformance, if supported. The return
/// value is a tuple consisting of the found witness table (if any), the found
/// conformance descriptor (if any), and a bool that's true if a failure is
/// definitive.
static std::tuple<ConformanceLookupResult,
const ProtocolConformanceDescriptor *,
bool>
findConformanceWithDyld(ConformanceState &C, const Metadata *type,
const ProtocolDescriptor *protocol,
bool instantiateSuperclassMetadata) {
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
const ContextDescriptor *description;
llvm::StringRef foreignTypeIdentity;
std::tie(description, foreignTypeIdentity) = getContextDescriptor(type);
// dyld expects the ObjC class, if any, as the second parameter.
auto objcClassMetadata = swift_getObjCClassFromMetadataConditional(type);
#if SHARED_CACHE_LOG_ENABLED
auto typeName = swift_getTypeName(type, true);
DYLD_CONFORMANCES_LOG("Looking up conformance of %.*s (type=%p, "
"objcClassMetadata=%p, description=%p) to %s (%p)",
(int)typeName.length, typeName.data, type,
objcClassMetadata, description, protocol->Name.get(),
protocol);
#endif
_dyld_protocol_conformance_result dyldResult;
if (C.scanSectionsBackwards) {
// Search "on disk" first, then shared cache.
dyldResult = getDyldOnDiskConformance(C, protocol, objcClassMetadata,
description, foreignTypeIdentity);
if (dyldResult.kind == _dyld_protocol_conformance_result_kind_not_found)
dyldResult = getDyldSharedCacheConformance(
C, protocol, objcClassMetadata, description, foreignTypeIdentity);
} else {
// In normal operation, search the shared cache first.
dyldResult = getDyldSharedCacheConformance(
C, protocol, objcClassMetadata, description, foreignTypeIdentity);
if (dyldResult.kind == _dyld_protocol_conformance_result_kind_not_found)
dyldResult = getDyldOnDiskConformance(C, protocol, objcClassMetadata,
description, foreignTypeIdentity);
}
switch (dyldResult.kind) {
case _dyld_protocol_conformance_result_kind_found_descriptor: {
auto *conformanceDescriptor =
reinterpret_cast<const ProtocolConformanceDescriptor *>(
dyldResult.value);
assert(conformanceDescriptor->getProtocol() == protocol);
assert(std::get<const Metadata *>(
ConformanceCandidate{*conformanceDescriptor}.getMatchingType(
type, instantiateSuperclassMetadata)));
if (conformanceDescriptor->getGenericWitnessTable()) {
DYLD_CONFORMANCES_LOG(
"DYLD found generic conformance descriptor %p for %s, continuing",
conformanceDescriptor, protocol->Name.get());
return std::make_tuple(nullptr, conformanceDescriptor, false);
} else {
// When there are no generics, we can retrieve the witness table cheaply,
// so do it up front.
DYLD_CONFORMANCES_LOG("DYLD Found conformance descriptor %p for %s",
conformanceDescriptor, protocol->Name.get());
auto result = ConformanceLookupResult::fromConformance(
type, conformanceDescriptor);
return std::make_tuple(result, conformanceDescriptor, false);
}
break;
}
case _dyld_protocol_conformance_result_kind_found_witness_table: {
// If we found a witness table then we're done.
DYLD_CONFORMANCES_LOG("DYLD found witness table %p for conformance to %s",
dyldResult.value, protocol->Name.get());
auto result = ConformanceLookupResult{
reinterpret_cast<const WitnessTable *>(dyldResult.value), nullptr,
nullptr};
return std::make_tuple(result, nullptr, false);
}
case _dyld_protocol_conformance_result_kind_not_found:
// If nothing is found, then we'll proceed with checking the runtime's
// caches and scanning conformance records.
DYLD_CONFORMANCES_LOG("DYLD did not find conformance to %s",
protocol->Name.get());
return std::make_tuple(nullptr, nullptr, false);
break;
case _dyld_protocol_conformance_result_kind_definitive_failure:
// This type is known not to conform to this protocol. Return failure
// without any further checks.
DYLD_CONFORMANCES_LOG("DYLD found definitive failure for %s",
protocol->Name.get());
return std::make_tuple(nullptr, nullptr, true);
default:
// Other values may be added. Consider them equivalent to not_found until
// we implement code to handle them.
DYLD_CONFORMANCES_LOG(
"Unknown result kind %lu from _dyld_find_protocol_conformance()",
(unsigned long)dyldResult.kind);
return std::make_tuple(nullptr, nullptr, false);
}
#else
return std::make_tuple(nullptr, nullptr, false);
#endif
}
/// Check if a type conforms to a protocol, possibly instantiating superclasses
/// that have not yet been instantiated. The return value is a pair consisting
/// of the the result of the lookup (which evaluates false if no conformance was
/// found), and a boolean indicating whether there are uninstantiated
/// superclasses that were not searched.
static std::pair<ConformanceLookupResult, bool>
swift_conformsToProtocolMaybeInstantiateSuperclasses(
const Metadata *const type, const ProtocolDescriptor *protocol,
bool instantiateSuperclassMetadata) {
auto &C = Conformances.get();
ConformanceLookupResult dyldCachedWitnessTable;
const ProtocolConformanceDescriptor *dyldCachedConformanceDescriptor =
nullptr;
// Track whether we have uninstantiated superclasses. Each time we iterate
// over our superclasses, we check the final state to see if there are more
// superclasses we haven't instantiated by calling noteFinalMetadataState.
// If we ever see Abstract, that means there are more superclasses we can't
// check yet, and we might get a false negative. We have to do this after each
// iteration (really, just the first iteration, but it's hard to keep track of
// which iteration is the first time), because another thread might
// instantiate the superclass while we're in the middle of searching. If we
// only look at the state after the last iteration, we might have hit a false
// negative before that no longer shows up.
bool hasUninstantiatedSuperclass = false;
auto noteFinalMetadataState = [&](std::optional<MetadataState> state) {
hasUninstantiatedSuperclass =
hasUninstantiatedSuperclass || state == MetadataState::Abstract;
};
// Search the shared cache tables for a conformance for this type, and for
// superclasses (if it's a class).
if (C.dyldOptimizationsActive()) {
MaybeIncompleteSuperclassIterator superclassIterator{
type, instantiateSuperclassMetadata};
for (; auto dyldSearchType = superclassIterator.metadata;
++superclassIterator) {
bool definitiveFailure;
std::tie(dyldCachedWitnessTable, dyldCachedConformanceDescriptor,
definitiveFailure) =
findConformanceWithDyld(C, dyldSearchType, protocol,
instantiateSuperclassMetadata);
if (definitiveFailure)
return {ConformanceLookupResult{}, false};
if (dyldCachedWitnessTable || dyldCachedConformanceDescriptor)
break;
}
noteFinalMetadataState(superclassIterator.state);
validateDyldResults(C, type, protocol, dyldCachedWitnessTable,
dyldCachedConformanceDescriptor,
instantiateSuperclassMetadata);
// Return a cached result if we got a witness table. We can't do this if
// scanSectionsBackwards is set, since a scanned conformance can override a
// cached result in that case.
if (!C.scanSectionsBackwards)
if (dyldCachedWitnessTable)
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) {
// An authoritative negative result can be overridden by a result from dyld.
if (!cacheSearchResult.Result.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};
}
if (dyldCachedConformanceDescriptor) {
ConformanceCandidate candidate(*dyldCachedConformanceDescriptor);
auto *matchingType = std::get<const Metadata *>(
candidate.getMatchingType(type, instantiateSuperclassMetadata));
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);
DYLD_CONFORMANCES_LOG("Caching generic conformance to %s found by DYLD",
protocol->Name.get());
return {witness, false};
}
// Scan conformance records.
llvm::SmallDenseMap<const Metadata *, ConformanceLookupResult> foundWitnesses;
auto processSection = [&](const ConformanceSection &section) {
// Eagerly pull records for nondependent witnesses into our cache.
auto processDescriptor = [&](const ProtocolConformanceDescriptor &descriptor) {
// We only care about conformances for this protocol.
if (descriptor.getProtocol() != protocol)
return;
// If there's a matching type, record the positive result and return it.
// The matching type is exact, so they can't go stale, and we should
// always cache them.
ConformanceCandidate candidate(descriptor);
const Metadata *matchingType;
std::optional<MetadataState> finalState;
std::tie(matchingType, finalState) =
candidate.getMatchingType(type, instantiateSuperclassMetadata);
noteFinalMetadataState(finalState);
if (matchingType) {
auto witness = ConformanceLookupResult::fromConformance(
matchingType, &descriptor);
bool allowSaveDescriptor = true;
C.cacheResult(matchingType, protocol, witness, /*always cache*/ 0, allowSaveDescriptor);
foundWitnesses.insert({matchingType, witness});
}
};
if (C.scanSectionsBackwards) {
for (const auto &record : llvm::reverse(section))
processDescriptor(*record.get());
} else {
for (const auto &record : section)
processDescriptor(*record.get());
}
};
auto traceState =
runtime::trace::protocol_conformance_scan_begin(type, protocol);
auto snapshot = C.SectionsToScan.snapshot();
if (C.scanSectionsBackwards) {
for (auto &section : llvm::reverse(snapshot))
processSection(section);
} else {
for (auto &section : snapshot)
processSection(section);
}
// Find the most specific conformance that was scanned.
ConformanceLookupResult foundWitness = nullptr;
const Metadata *foundType = nullptr;
MaybeIncompleteSuperclassIterator superclassIterator{
type, instantiateSuperclassMetadata};
for (; auto searchType = superclassIterator.metadata; ++superclassIterator) {
const auto witnessIt = foundWitnesses.find(searchType);
if (witnessIt != foundWitnesses.end()) {
if (!foundType) {
foundWitness = witnessIt->getSecond(); // may be null
foundType = searchType;
} else {
auto foundName = swift_getTypeName(foundType, true);
auto searchName = swift_getTypeName(searchType, true);
swift::warning(RuntimeErrorFlagNone,
"Warning: '%.*s' conforms to protocol '%s', but it also "
"inherits conformance from '%.*s'. Relying on a "
"particular conformance is undefined behaviour.\n",
(int)foundName.length, foundName.data,
protocol->Name.get(),
(int)searchName.length, searchName.data);
}
}
}
noteFinalMetadataState(superclassIterator.state);
traceState.end(foundWitness.witnessTable);
// If it's for a superclass or if we didn't find anything, then add an
// authoritative entry for this type.
if (foundType != type)
// 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);
// A negative result can be overridden by a result from dyld.
if (!foundWitness) {
if (dyldCachedWitnessTable) {
debugLogResult(true, "dyld cache");
return {dyldCachedWitnessTable, false};
}
}
debugLogResult(static_cast<bool>(foundWitness), "section scan");
return {foundWitness, hasUninstantiatedSuperclass};
}
static const WitnessTable *
swift_conformsToProtocolWithExecutionContextImpl(
const Metadata *const type,
const ProtocolDescriptor *protocol,
ConformanceExecutionContext *context) {
ConformanceLookupResult found;
bool hasUninstantiatedSuperclass;
// First, try without instantiating any new superclasses. This avoids
// an infinite loop for cases like `class Sub: Super<Sub>`. In cases like
// that, the conformance must exist on the subclass (or at least somewhere
// in the chain before we get to an uninstantiated superclass) so this search
// will succeed without trying to instantiate Super while it's already being
// instantiated.=
std::tie(found, hasUninstantiatedSuperclass) =
swift_conformsToProtocolMaybeInstantiateSuperclasses(
type, protocol, false /*instantiateSuperclassMetadata*/);
// If no conformance was found, and there is an uninstantiated superclass that
// was not searched, then try the search again and instantiate all
// superclasses.
if (!found && hasUninstantiatedSuperclass)
std::tie(found, hasUninstantiatedSuperclass) =
swift_conformsToProtocolMaybeInstantiateSuperclasses(
type, protocol, true /*instantiateSuperclassMetadata*/);
// Check for isolated conformances.
if (found.globalActorIsolationType && context) {
// If the existing global actor isolation differs from the one we
// computed, it's a conflict. Fail.
if (context->globalActorIsolationType &&
context->globalActorIsolationType != found.globalActorIsolationType)
return nullptr;
// Report the global actor isolation.
context->globalActorIsolationType = found.globalActorIsolationType;
context->globalActorIsolationWitnessTable =
found.globalActorIsolationWitnessTable;
}
return found.witnessTable;
}
static const WitnessTable *
swift_conformsToProtocolCommonImpl(
const Metadata *const type,
const ProtocolDescriptor *protocol) {
return swift_conformsToProtocolWithExecutionContextImpl(
type, protocol, nullptr);
}
static const WitnessTable *
swift_conformsToProtocol2Impl(const Metadata *const type,
const ProtocolDescriptor *protocol) {
protocol = swift_auth_data_non_address(
protocol, SpecialPointerAuthDiscriminators::ProtocolDescriptor);
return swift_conformsToProtocolCommonImpl(type, protocol);
}
static const WitnessTable *
swift_conformsToProtocolImpl(const Metadata *const type,
const void *protocol) {
// This call takes `protocol` without a ptrauth signature. We declare
// it as `void *` to avoid the implicit ptrauth we get from the
// ptrauth_struct attribute. The static_cast implicitly signs the
// pointer when we call through to the implementation in
// swift_conformsToProtocolCommon.
return swift_conformsToProtocolCommonImpl(
type, static_cast<const ProtocolDescriptor *>(protocol));
}
static bool swift_isInConformanceExecutionContextImpl(
const Metadata *type,
const ConformanceExecutionContext *context) {
if (!context)
return true;
if (context->globalActorIsolationType) {
// If the hook is not installed, assume we're on the right actor.
if (!_swift_task_isCurrentGlobalActorHook)
return true;
// Check whether we are running on this global actor.
if (!_swift_task_isCurrentGlobalActorHook(
context->globalActorIsolationType,
context->globalActorIsolationWitnessTable))
return false;
}
return true;
}
const ContextDescriptor *
swift::_searchConformancesByMangledTypeName(Demangle::NodePointer node) {
auto traceState = runtime::trace::protocol_conformance_scan_begin(node);
auto &C = Conformances.get();
for (auto &section : C.SectionsToScan.snapshot()) {
for (const auto &record : section) {
if (auto ntd = record->getTypeDescriptor()) {
if (_contextDescriptorMatchesMangling(ntd, node))
return traceState.end(ntd);
}
}
}
return nullptr;
}
template <typename HandleObjc>
bool isSwiftClassMetadataSubclass(const ClassMetadata *subclass,
const ClassMetadata *superclass,
HandleObjc handleObjc) {
assert(subclass);
assert(superclass);
std::optional<MetadataState> subclassState = std::nullopt;
while (true) {
auto response = getSuperclassForMaybeIncompleteMetadata(
subclass, subclassState, true /*instantiateSuperclassMetadata*/);
if (response.Value == superclass)
return true;
if (!response.Value)
return false;
subclass = dyn_cast<ClassMetadata>(response.Value);
if (!subclass || subclass->isPureObjC())
return handleObjc(response.Value, superclass);
}
}
// Whether the provided `subclass` is metadata for a subclass* of the superclass
// whose metadata is specified.
//
// The function is robust against incomplete metadata for both subclass and
// superclass. In the worst case, each intervening class between subclass and
// superclass is demangled. Besides that slow path, there are a number of fast
// paths:
// - both classes are ObjC: swift_dynamicCastMetatype
// - Complete subclass metadata: loop over Superclass fields
// - NonTransitiveComplete: read the Superclass field once
//
// * A non-strict subclass; that is, given a class X, isSubclass(X.self, X.self)
// is true.
static bool isSubclass(const Metadata *subclass, const Metadata *superclass) {
assert(subclass);
assert(superclass);
assert(subclass->isAnyClass());
assert(superclass->isAnyClass());
if (subclass == superclass)
return true;
if (!isa<ClassMetadata>(subclass)) {
if (!isa<ClassMetadata>(superclass)) {
// Only ClassMetadata can be incomplete; when the class metadata is not
// ClassMetadata, just use swift_dynamicCastMetatype.
return swift_dynamicCastMetatype(subclass, superclass);
} else {
// subclass is ObjC, but superclass is not; since it is not possible for
// any ObjC class to be a subclass of any Swift class, this subclass is
// not a subclass of this superclass.
return false;
}
}
const ClassMetadata *swiftSubclass = cast<ClassMetadata>(subclass);
#if SWIFT_OBJC_INTEROP
if (auto *objcSuperclass = dyn_cast<ObjCClassWrapperMetadata>(superclass)) {
// Walk up swiftSubclass's ancestors until we get to an ObjC class, then
// kick over to swift_dynamicCastMetatype.
return isSwiftClassMetadataSubclass(
swiftSubclass, objcSuperclass->Class,
[](const Metadata *intermediate, const Metadata *superclass) {
// Intermediate is an ObjC class, and superclass is an ObjC class;
// as above, just use swift_dynamicCastMetatype.
return swift_dynamicCastMetatype(intermediate, superclass);
});
return false;
}
#endif
if (isa<ForeignClassMetadata>(superclass)) {
// superclass is foreign, but subclass is not (if it were, the above
// !isa<ClassMetadata> condition would have been entered). Since it is not
// possible for any Swift class to be a subclass of any foreign superclass,
// this subclass is not a subclass of this superclass.
return false;
}
auto swiftSuperclass = cast<ClassMetadata>(superclass);
return isSwiftClassMetadataSubclass(swiftSubclass, swiftSuperclass,
[](const Metadata *, const Metadata *) {
// Because (1) no ObjC classes inherit
// from Swift classes and (2)
// `superclass` is not ObjC, if some
// ancestor of `subclass` is ObjC, then
// `subclass` cannot descend from
// `superclass` (otherwise at some point
// some ObjC class would have to inherit
// from a Swift class).
return false;
});
}
static bool isSubclassOrExistential(const Metadata *subclass,
const Metadata *superclass) {
// If the type which is constrained to a base class is an existential
// type, and if that existential type includes a superclass constraint,
// just require that the superclass by which the existential is
// constrained is a subclass of the base class.
if (auto *existential = dyn_cast<ExistentialTypeMetadata>(subclass)) {
if (auto *superclassConstraint = existential->getSuperclassConstraint())
subclass = superclassConstraint;
}
return isSubclass(subclass, superclass);
}
static std::optional<TypeLookupError>
satisfiesLayoutConstraint(const GenericRequirementDescriptor &req,
const Metadata *subjectType) {
switch (req.getLayout()) {
case GenericRequirementLayoutKind::Class:
if (!subjectType->satisfiesClassConstraint()) {
return TYPE_LOOKUP_ERROR_FMT(
"subject type %.*s does not satisfy class constraint",
(int)req.getParam().size(), req.getParam().data());
}
return std::nullopt;
}
// Unknown layout.
return TYPE_LOOKUP_ERROR_FMT("unknown layout kind %u",
static_cast<uint32_t>(req.getLayout()));
}
SWIFT_CC(swift)
SWIFT_RUNTIME_STDLIB_SPI
bool swift::_swift_class_isSubclass(const Metadata *subclass,
const Metadata *superclass) {
return isSubclass(subclass, superclass);
}
static std::optional<TypeLookupError>
checkInvertibleRequirements(const Metadata *type,
InvertibleProtocolSet ignored);
static std::optional<TypeLookupError>
checkGenericRequirement(
const GenericRequirementDescriptor &req,
llvm::SmallVectorImpl<const void *> &extraArguments,
SubstGenericParameterFn substGenericParam,
SubstDependentWitnessTableFn substWitnessTable,
llvm::SmallVectorImpl<InvertibleProtocolSet> &suppressed,
ConformanceExecutionContext *context) {
assert(!req.getFlags().isPackRequirement());
// Make sure we understand the requirement we're dealing with.
if (!req.hasKnownKind())
return TypeLookupError("unknown kind");
// Resolve the subject generic parameter.
auto result = swift_getTypeByMangledName(
MetadataState::Abstract, req.getParam(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
const Metadata *subjectType = result.getType().getMetadata();
// Check the requirement.
switch (req.getKind()) {
case GenericRequirementKind::Protocol: {
const WitnessTable *witnessTable = nullptr;
if (!_conformsToProtocol(nullptr, subjectType, req.getProtocol(),
&witnessTable, context)) {
const char *protoName =
req.getProtocol() ? req.getProtocol().getName() : "<null>";
return TYPE_LOOKUP_ERROR_FMT(
"subject type %.*s does not conform to protocol %s",
(int)req.getParam().size(), req.getParam().data(), protoName);
}
// If we need a witness table, add it.
if (req.getProtocol().needsWitnessTable()) {
assert(witnessTable);
extraArguments.push_back(witnessTable);
}
return std::nullopt;
}
case GenericRequirementKind::SameType: {
// Demangle the second type under the given substitutions.
auto result = swift_getTypeByMangledName(
MetadataState::Abstract, req.getMangledTypeName(),
extraArguments.data(), substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
auto otherType = result.getType().getMetadata();
// Check that the types are equivalent.
if (subjectType != otherType) {
return TYPE_LOOKUP_ERROR_FMT(
"subject type %.*s does not match %.*s", (int)req.getParam().size(),
req.getParam().data(), (int)req.getMangledTypeName().size(),
req.getMangledTypeName().data());
}
return std::nullopt;
}
case GenericRequirementKind::Layout: {
return satisfiesLayoutConstraint(req, subjectType);
}
case GenericRequirementKind::BaseClass: {
// Demangle the base type under the given substitutions.
auto result = swift_getTypeByMangledName(
MetadataState::Abstract, req.getMangledTypeName(),
extraArguments.data(), substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
auto baseType = result.getType().getMetadata();
if (!isSubclassOrExistential(subjectType, baseType))
return TYPE_LOOKUP_ERROR_FMT(
"%.*s is not subclass of %.*s", (int)req.getParam().size(),
req.getParam().data(), (int)req.getMangledTypeName().size(),
req.getMangledTypeName().data());
return std::nullopt;
}
case GenericRequirementKind::SameConformance: {
// FIXME: Implement this check.
return std::nullopt;
}
case GenericRequirementKind::SameShape: {
return TYPE_LOOKUP_ERROR_FMT("can't have same-shape requirement where "
"subject type is not a pack");
}
case GenericRequirementKind::InvertedProtocols: {
uint16_t index = req.getInvertedProtocolsGenericParamIndex();
if (index == 0xFFFF) {
return checkInvertibleRequirements(subjectType,
req.getInvertedProtocols());
}
// Expand the suppression set so we can record these protocols.
if (index >= suppressed.size()) {
suppressed.resize(index + 1, InvertibleProtocolSet());
}
// Record these suppressed protocols for this generic parameter.
suppressed[index] |= req.getInvertedProtocols();
return std::nullopt;
}
}
// Unknown generic requirement kind.
return TYPE_LOOKUP_ERROR_FMT("unknown generic requirement kind %u",
(unsigned)req.getKind());
}
static std::optional<TypeLookupError>
checkGenericPackRequirement(
const GenericRequirementDescriptor &req,
llvm::SmallVectorImpl<const void *> &extraArguments,
SubstGenericParameterFn substGenericParam,
SubstDependentWitnessTableFn substWitnessTable,
llvm::SmallVectorImpl<InvertibleProtocolSet> &suppressed,
ConformanceExecutionContext *context) {
assert(req.getFlags().isPackRequirement());
// Make sure we understand the requirement we're dealing with.
if (!req.hasKnownKind())
return TypeLookupError("unknown kind");
// Resolve the subject generic parameter.
auto result = swift::getTypePackByMangledName(
req.getParam(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
MetadataPackPointer subjectType = result.getType();
assert(subjectType.getLifetime() == PackLifetime::OnHeap);
// Check the requirement.
switch (req.getKind()) {
case GenericRequirementKind::Protocol: {
llvm::SmallVector<const WitnessTable *, 4> witnessTables;
// Look up the conformance of each pack element to the protocol.
for (size_t i = 0, e = subjectType.getNumElements(); i < e; ++i) {
const Metadata *elt = subjectType.getElements()[i];
const WitnessTable *witnessTable = nullptr;
if (!_conformsToProtocol(nullptr, elt, req.getProtocol(),
&witnessTable, context)) {
const char *protoName =
req.getProtocol() ? req.getProtocol().getName() : "<null>";
return TYPE_LOOKUP_ERROR_FMT(
"subject type %.*s does not conform to protocol %s at pack index %zu",
(int)req.getParam().size(), req.getParam().data(), protoName, i);
}
if (req.getProtocol().needsWitnessTable())
witnessTables.push_back(witnessTable);
}
// If we need a witness table, add it.
if (req.getProtocol().needsWitnessTable()) {
assert(witnessTables.size() == subjectType.getNumElements());
auto *pack = swift_allocateWitnessTablePack(witnessTables.data(),
witnessTables.size());
extraArguments.push_back(pack);
}
return std::nullopt;
}
case GenericRequirementKind::SameType: {
// Resolve the constraint generic parameter.
auto result = swift::getTypePackByMangledName(
req.getMangledTypeName(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
MetadataPackPointer constraintType = result.getType();
assert(constraintType.getLifetime() == PackLifetime::OnHeap);
if (subjectType.getNumElements() != constraintType.getNumElements()) {
return TYPE_LOOKUP_ERROR_FMT(
"mismatched pack lengths in same-type pack requirement %.*s: %zu vs %zu",
(int)req.getParam().size(), req.getParam().data(),
subjectType.getNumElements(), constraintType.getNumElements());
}
for (size_t i = 0, e = subjectType.getNumElements(); i < e; ++i) {
auto *subjectElt = subjectType.getElements()[i];
auto *constraintElt = constraintType.getElements()[i];
if (subjectElt != constraintElt) {
return TYPE_LOOKUP_ERROR_FMT(
"subject type %.*s does not match %.*s at pack index %zu",
(int)req.getParam().size(),
req.getParam().data(), (int)req.getMangledTypeName().size(),
req.getMangledTypeName().data(), i);
}
}
return std::nullopt;
}
case GenericRequirementKind::Layout: {
for (size_t i = 0, e = subjectType.getNumElements(); i < e; ++i) {
const Metadata *elt = subjectType.getElements()[i];
if (auto result = satisfiesLayoutConstraint(req, elt))
return result;
}
return std::nullopt;
}
case GenericRequirementKind::BaseClass: {
// Demangle the base type under the given substitutions.
auto result = swift_getTypeByMangledName(
MetadataState::Abstract, req.getMangledTypeName(),
extraArguments.data(), substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
auto baseType = result.getType().getMetadata();
// Check that each pack element inherits from the base class.
for (size_t i = 0, e = subjectType.getNumElements(); i < e; ++i) {
const Metadata *elt = subjectType.getElements()[i];
if (!isSubclassOrExistential(elt, baseType))
return TYPE_LOOKUP_ERROR_FMT(
"%.*s is not subclass of %.*s at pack index %zu",
(int)req.getParam().size(),
req.getParam().data(), (int)req.getMangledTypeName().size(),
req.getMangledTypeName().data(), i);
}
return std::nullopt;
}
case GenericRequirementKind::SameConformance: {
// FIXME: Implement this check.
return std::nullopt;
}
case GenericRequirementKind::SameShape: {
auto result = swift::getTypePackByMangledName(
req.getMangledTypeName(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
MetadataPackPointer otherType = result.getType();
assert(otherType.getLifetime() == PackLifetime::OnHeap);
if (subjectType.getNumElements() != otherType.getNumElements()) {
return TYPE_LOOKUP_ERROR_FMT("same-shape requirement unsatisfied; "
"%zu != %zu",
subjectType.getNumElements(),
otherType.getNumElements() );
}
return std::nullopt;
}
case GenericRequirementKind::InvertedProtocols: {
uint16_t index = req.getInvertedProtocolsGenericParamIndex();
if (index == 0xFFFF) {
// Check that each pack element meets the invertible requirements.
for (size_t i = 0, e = subjectType.getNumElements(); i < e; ++i) {
const Metadata *elt = subjectType.getElements()[i];
if (auto error = checkInvertibleRequirements(
elt, req.getInvertedProtocols()))
return error;
}
return std::nullopt;
}
// Expand the suppression set so we can record these protocols.
if (index >= suppressed.size()) {
suppressed.resize(index + 1, InvertibleProtocolSet());
}
// Record these suppressed protocols for this generic parameter.
suppressed[index] |= req.getInvertedProtocols();
return std::nullopt;
}
}
// Unknown generic requirement kind.
return TYPE_LOOKUP_ERROR_FMT("unknown generic requirement kind %u",
(unsigned)req.getKind());
}
static std::optional<TypeLookupError>
checkGenericValueRequirement(const GenericRequirementDescriptor &req,
llvm::SmallVectorImpl<const void *> &extraArguments,
SubstGenericParameterFn substGenericParam,
SubstDependentWitnessTableFn substWitnessTable,
llvm::SmallVectorImpl<InvertibleProtocolSet> &suppressed) {
assert(req.getFlags().isValueRequirement());
// Make sure we understand the requirement we're dealing with.
if (!req.hasKnownKind())
return TypeLookupError("unknown kind");
// Resolve the subject generic value.
auto result = swift::getTypeValueByMangledName(
req.getParam(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
auto subjectValue = result.getType();
// Check the requirement.
switch (req.getKind()) {
case GenericRequirementKind::SameType: {
// Resolve the constraint generic value.
auto result = swift::getTypeValueByMangledName(
req.getMangledTypeName(), extraArguments.data(),
substGenericParam, substWitnessTable);
if (result.getError())
return *result.getError();
auto constraintValue = result.getType();
if (subjectValue != constraintValue) {
return TYPE_LOOKUP_ERROR_FMT(
"subject value %" PRIiPTR " does not match constraint value %" PRIiPTR,
subjectValue,
constraintValue);
}
return std::nullopt;
}
default: {
// Value requirements can only be same type'd at the moment.
return TYPE_LOOKUP_ERROR_FMT("unknown value generic requirement kind %u",
(unsigned)req.getKind());
}
}
}
static std::optional<TypeLookupError>
checkInvertibleRequirementsStructural(const Metadata *type,
InvertibleProtocolSet ignored) {
switch (type->getKind()) {
case MetadataKind::Class:
case MetadataKind::Struct:
case MetadataKind::Enum:
case MetadataKind::Optional:
case MetadataKind::ForeignClass:
case MetadataKind::ForeignReferenceType:
case MetadataKind::ObjCClassWrapper:
// All handled via context descriptor in the caller.
return std::nullopt;
case MetadataKind::HeapLocalVariable:
case MetadataKind::Opaque:
case MetadataKind::HeapGenericLocalVariable:
case MetadataKind::ErrorObject:
case MetadataKind::Task:
case MetadataKind::Job:
// Not part of the user-visible type system; assumed to handle all
// invertible requirements.
return std::nullopt;
case MetadataKind::Tuple: {
// Check every element type in the tuple.
auto tupleMetadata = cast<TupleTypeMetadata>(type);
for (unsigned i = 0, n = tupleMetadata->NumElements; i != n; ++i) {
if (auto error =
checkInvertibleRequirements(&*tupleMetadata->getElement(i).Type,
ignored))
return error;
}
return std::nullopt;
}
case MetadataKind::Function: {
auto functionMetadata = cast<FunctionTypeMetadata>(type);
// Determine the set of protocols that are suppressed by the function
// type.
InvertibleProtocolSet suppressed;
if (functionMetadata->hasExtendedFlags()) {
suppressed = functionMetadata->getExtendedFlags()
.getInvertedProtocols();
}
// Map the existing "noescape" bit as a suppressed protocol, when
// appropriate.
switch (functionMetadata->getConvention()) {
case FunctionMetadataConvention::Swift:
// Swift function types can be non-escaping, so honor the bit.
if (!functionMetadata->isEscaping())
suppressed.insert(InvertibleProtocolKind::Escapable);
break;
case FunctionMetadataConvention::Block:
// Objective-C block types don't encode non-escaping-ness in metadata,
// so we assume that they are always escaping.
break;
case FunctionMetadataConvention::Thin:
case FunctionMetadataConvention::CFunctionPointer:
// Thin and C function pointers have no captures, so whether they
// escape is irrelevant.
break;
}
auto missing = suppressed - ignored;
if (!missing.empty()) {
return TYPE_LOOKUP_ERROR_FMT(
"function type missing invertible protocols %x", missing.rawBits());
}
return std::nullopt;
}
case MetadataKind::ExtendedExistential: {
auto existential = cast<ExtendedExistentialTypeMetadata>(type);
auto &shape = *existential->Shape;
// If this is an extended existential metatype, then just allow it. Metatypes
// are always copyable and escapable so there can't possibly be a
// suppression issue.
if (shape.Flags.isMetatypeConstrained()) {
return std::nullopt;
}
llvm::ArrayRef<GenericRequirementDescriptor> reqs(
shape.getReqSigRequirements(), shape.getNumReqSigRequirements());
// Look for any suppressed protocol requirements. If the existential
// has suppressed a protocol that is not ignored, then the existential
// does not meet the specified requirements.
for (const auto& req : reqs) {
if (req.getKind() != GenericRequirementKind::InvertedProtocols)
continue;
auto suppressed = req.getInvertedProtocols();
auto missing = suppressed - ignored;
if (!missing.empty()) {
return TYPE_LOOKUP_ERROR_FMT(
"existential type missing invertible protocols %x",
missing.rawBits());
}
}
return std::nullopt;
}
case MetadataKind::Metatype:
case MetadataKind::ExistentialMetatype:
// Metatypes themselves can't have invertible protocols.
return std::nullopt;
case MetadataKind::Existential:
// The existential representation has no room for specifying any
// suppressed requirements, so it always succeeds.
return std::nullopt;
case MetadataKind::FixedArray:
// Builtin.FixedArray has no conformances of its own.
return std::nullopt;
case MetadataKind::LastEnumerated:
break;
}
// Just accept any unknown types.
return std::nullopt;
}
/// Check that the given `type` meets all invertible protocol requirements
/// that haven't been explicitly suppressed by `ignored`.
std::optional<TypeLookupError>
checkInvertibleRequirements(const Metadata *type,
InvertibleProtocolSet ignored) {
auto contextDescriptor = type->getTypeContextDescriptor();
if (!contextDescriptor)
return checkInvertibleRequirementsStructural(type, ignored);
// If no conformances are suppressed, then it conforms to everything.
if (!contextDescriptor->hasInvertibleProtocols()) {
return std::nullopt;
}
// If this type has suppressed conformances, but we can't find them...
// bail out.
auto InvertedProtocols = contextDescriptor->getInvertedProtocols();
if (!InvertedProtocols) {
return TYPE_LOOKUP_ERROR_FMT("unable to find suppressed protocols");
}
// Determine the set of invertible conformances that the type has
// suppressed but aren't being ignored. These are missing conformances
// based on the primary definition of the type.
InvertibleProtocolSet missingConformances = *InvertedProtocols - ignored;
if (missingConformances.empty())
return std::nullopt;
// If the context descriptor is not generic, there are no conditional
// conformances: fail.
if (!contextDescriptor->isGeneric()) {
return TYPE_LOOKUP_ERROR_FMT("type missing invertible conformances %x",
missingConformances.rawBits());
}
auto genericContext = contextDescriptor->getGenericContext();
if (!genericContext ||
!genericContext->hasConditionalInvertedProtocols()) {
return TYPE_LOOKUP_ERROR_FMT("type missing invertible conformances %x",
missingConformances.rawBits());
}
// If there are missing conformances that do not have corresponding
// conditional conformances, then the nominal type does not satisfy these
// suppressed conformances. We're done.
auto conditionalSuppressed =
genericContext->getConditionalInvertedProtocols();
auto alwaysMissingConformances = missingConformances - conditionalSuppressed;
if (!alwaysMissingConformances.empty()) {
return TYPE_LOOKUP_ERROR_FMT("type missing invertible conformances %x",
alwaysMissingConformances.rawBits());
}
// Now we need to check the conditional conformances for each of the
// missing conformances.
for (auto invertibleKind : missingConformances) {
// Get the conditional requirements.
// Note: This will end up being quadratic in the number of invertible
// protocols. That number is small (currently 2) and cannot be more than 16,
// but if it's a problem we can switch to a different strategy.
auto condReqs =
genericContext->getConditionalInvertibleProtocolRequirementsFor(
invertibleKind);
// Check the conditional requirements.
llvm::ArrayRef<GenericRequirementDescriptor> requirements(
reinterpret_cast<const GenericRequirementDescriptor *>(condReqs.data()),
condReqs.size());
SubstGenericParametersFromMetadata substFn(type);
llvm::SmallVector<const void *, 1> extraArguments;
auto error = _checkGenericRequirements(
genericContext->getGenericParams(),
requirements, extraArguments,
[&substFn](unsigned depth, unsigned index) {
return substFn.getMetadata(depth, index).Ptr;
},
[&substFn](unsigned fullOrdinal, unsigned keyOrdinal) {
return substFn.getMetadataKeyArgOrdinal(keyOrdinal).Ptr;
},
[&substFn](const Metadata *type, unsigned index) {
return substFn.getWitnessTable(type, index);
},
nullptr);
if (error)
return error;
}
return std::nullopt;
}
std::optional<TypeLookupError> swift::_checkGenericRequirements(
llvm::ArrayRef<GenericParamDescriptor> genericParams,
llvm::ArrayRef<GenericRequirementDescriptor> requirements,
llvm::SmallVectorImpl<const void *> &extraArguments,
SubstGenericParameterFn substGenericParam,
SubstGenericParameterOrdinalFn substGenericParamOrdinal,
SubstDependentWitnessTableFn substWitnessTable,
ConformanceExecutionContext *context) {
// The suppressed conformances for each generic parameter.
llvm::SmallVector<InvertibleProtocolSet, 4> allSuppressed;
for (const auto &req : requirements) {
if (req.getFlags().isPackRequirement()) {
auto error = checkGenericPackRequirement(req, extraArguments,
substGenericParam,
substWitnessTable,
allSuppressed,
context);
if (error)
return error;
} else if (req.getFlags().isValueRequirement()) {
auto error = checkGenericValueRequirement(req, extraArguments,
substGenericParam,
substWitnessTable,
allSuppressed);
if (error)
return error;
} else {
auto error = checkGenericRequirement(req, extraArguments,
substGenericParam,
substWitnessTable,
allSuppressed,
context);
if (error)
return error;
}
}
// Now, check all of the generic arguments for invertible protocols.
unsigned numGenericParams = genericParams.size();
unsigned keyIndex = 0;
for (unsigned index = 0; index != numGenericParams; ++index) {
// Non-key arguments don't need to be checked, because they are
// aliased to another type.
if (!genericParams[index].hasKeyArgument())
continue;
InvertibleProtocolSet suppressed;
if (index < allSuppressed.size())
suppressed = allSuppressed[index];
MetadataPackOrValue MetadataPackOrValue(substGenericParamOrdinal(index, keyIndex));
switch (genericParams[index].getKind()) {
case GenericParamKind::Type: {
if (!MetadataPackOrValue || MetadataPackOrValue.isMetadataPack()) {
return TYPE_LOOKUP_ERROR_FMT(
"unexpected pack for generic parameter %u", index);
}
auto metadata = MetadataPackOrValue.getMetadata();
if (auto error = checkInvertibleRequirements(metadata, suppressed))
return error;
break;
}
case GenericParamKind::TypePack: {
// NULL can be used to indicate an empty pack.
if (!MetadataPackOrValue)
break;
if (MetadataPackOrValue.isMetadata()) {
return TYPE_LOOKUP_ERROR_FMT(
"unexpected metadata for generic pack parameter %u", index);
}
auto pack = MetadataPackOrValue.getMetadataPack();
if (pack.getElements() != 0) {
llvm::ArrayRef<const Metadata *> elements(
pack.getElements(), pack.getNumElements());
for (auto element : elements) {
if (auto error = checkInvertibleRequirements(element, suppressed))
return error;
}
}
break;
}
case GenericParamKind::Value: {
// Value parameter can never conform to protocols or the inverse thereof.
break;
}
default:
return TYPE_LOOKUP_ERROR_FMT("unknown generic parameter kind %u",
index);
}
keyIndex++;
}
// Success!
return std::nullopt;
}
const Metadata *swift::findConformingSuperclass(
const Metadata *type,
const ProtocolConformanceDescriptor *conformance) {
// Figure out which type we're looking for.
ConformanceCandidate candidate(*conformance);
const Metadata *conformingType = std::get<const Metadata *>(
candidate.getMatchingType(type, true /*instantiateSuperclassMetadata*/));
assert(conformingType);
return conformingType;
}
size_t swift::swift_ConformanceExecutionContextSize =
sizeof(ConformanceExecutionContext);
#define OVERRIDE_PROTOCOLCONFORMANCE COMPATIBILITY_OVERRIDE
#include "../CompatibilityOverride/CompatibilityOverrideIncludePath.h"