A performance assessment has led me to look into code paths that lead to swift_conformsToProtocol. One of the common callers is swift::_checkGenericRequirements. From a previous forum post ([SE-0143] Dynamic casting for conditional conformances), @Douglas_Gregor describes how conditional conformance leads to a runtime check of generic requirements.
In our code, a primary source of calls to swift::_checkGenericRequirements is coming from RxSwift. However these calls don't seem to be due to conditional conformance. I've reduced some RxSwift code into the below code. This code results in a call to _checkGenericRequirements when the Sink is constructed, and it's not clear to me why a runtime check conformance is needed.
I'd like to learn which patterns of code lead to runtime conformance checks, and ideally how to write code that avoids it. Any directions for further reading/viewing on this subject are much appreciated.
In this code, the Sink<T: ObserverType> class by itself does not trigger _checkGenericRequirements, it's also the presence of let disposable: Disposable. Removing either the protocol constraint, or removing the existential property, will prevent/bypass the generic requirements check at runtime.
protocol ObserverType {}
protocol Disposable {}
class Sink<T: ObserverType> {
let disposable: Disposable
init(disposable: Disposable) {
self.disposable = disposable
}
}
class Observer: ObserverType {}
class Disposer: Disposable {}
_ = Sink<Observer>(disposable: Disposer())
The stack trace that leads to the swift::_checkGenericRequirements call is:
frame #0: swift_conformsToProtocol
frame #1: swift::_conformsToProtocol(swift::OpaqueValue const*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetProtocolDescriptorRef<swift::InProcess>, swift::TargetWitnessTable<swift::InProcess> const**)
frame #2: swift::_checkGenericRequirements(llvm::ArrayRef<swift::TargetGenericRequirementDescriptor<swift::InProcess> >, llvm::SmallVectorImpl<void const*>&, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>)
frame #3: _gatherGenericParameters(swift::TargetContextDescriptor<swift::InProcess> const*, llvm::ArrayRef<swift::TargetMetadata<swift::InProcess> const*>, swift::TargetMetadata<swift::InProcess> const*, llvm::SmallVectorImpl<unsigned int>&, llvm::SmallVectorImpl<void const*>&, swift::Demangle::Demangler&)
frame #4: (anonymous namespace)::DecodedMetadataBuilder::createBoundGenericType(swift::TargetContextDescriptor<swift::InProcess> const*, llvm::ArrayRef<swift::TargetMetadata<swift::InProcess> const*>, swift::TargetMetadata<swift::InProcess> const*) const
frame #5: swift::Demangle::TypeDecoder<(anonymous namespace)::DecodedMetadataBuilder>::decodeMangledType(swift::Demangle::Node*)
frame #6: swift_getTypeByMangledNodeImpl(swift::MetadataRequest, swift::Demangle::Demangler&, swift::Demangle::Node*, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>)
frame #7: swift::swift_getTypeByMangledNode(swift::MetadataRequest, swift::Demangle::Demangler&, swift::Demangle::Node*, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>)
frame #8: swift_getTypeByMangledNameImpl(swift::MetadataRequest, llvm::StringRef, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>)
frame #9: swift::swift_getTypeByMangledName(swift::MetadataRequest, llvm::StringRef, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>)
frame #10: swift_getTypeByMangledNameInContext
frame #11: ConformsToProtocol`__swift_instantiateConcreteTypeFromMangledName
frame #12: ConformsToProtocol`main [inlined] generic specialization <ConformsToProtocol.Observer> of ConformsToProtocol.Sink.__allocating_init(disposable: ConformsToProtocol.Disposable) -> ConformsToProtocol.Sink<A> at main.swift:0 [opt]