Skip to content

Commit a80fe85

Browse files
committed
[Runtime] Fix concurrent _typeByName with generic arguments, protocol conformances, and superclasses.
When constructing generic type metadata, we can end up checking protocol conformances for metadata that isn't fully built, and in particular the superclass field may not be set up yet. Handle this case properly by falling back to looking up the superclass by mangled name from the nominal type descriptor when necessary. Previously we were just chasing Superclass fields, which would cause us to terminate the search early sometimes when concurrently looking up the same type from multiple threads. When a protocol conformance is on a superclass, this causes the search to fail incorrectly, causing type lookup failures. This only arises on the very first attempt to instantiate a given type, since the lookup will be cached (and all metadata fully initialized) after the first success. rdar://72583931
1 parent bfef818 commit a80fe85

File tree

2 files changed

+1169
-74
lines changed

2 files changed

+1169
-74
lines changed

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 110 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,90 @@ const ClassMetadata *TypeReference::getObjCClass(TypeReferenceKind kind) const {
147147
}
148148
#endif
149149

150+
static MetadataState
151+
tryGetCompleteMetadataNonblocking(const Metadata *metadata) {
152+
return swift_checkMetadataState(
153+
MetadataRequest(MetadataState::Complete, /*isNonBlocking*/ true),
154+
metadata)
155+
.State;
156+
}
157+
158+
/// Get the superclass of metadata, which may be incomplete. When the metadata
159+
/// is not sufficiently complete, then we fall back to demangling the superclass
160+
/// in the nominal type descriptor, which is slow but works. Return NULL if the
161+
/// metadata is not a class.
162+
///
163+
/// If the metadata's current state is known, it may be passed in as
164+
/// knownMetadataState. This saves the cost of retrieving that info separately.
165+
static MetadataResponse getSuperclassForMaybeIncompleteMetadata(
166+
const Metadata *metadata,
167+
llvm::Optional<MetadataState> knownMetadataState) {
168+
const ClassMetadata *classMetadata = dyn_cast<ClassMetadata>(metadata);
169+
if (!classMetadata)
170+
return {_swift_class_getSuperclass(metadata), MetadataState::Complete};
171+
172+
MetadataState metadataState;
173+
if (knownMetadataState)
174+
metadataState = *knownMetadataState;
175+
else
176+
metadataState = tryGetCompleteMetadataNonblocking(classMetadata);
177+
178+
if (metadataState == MetadataState::Complete) {
179+
// The subclass metadata is complete. Fetch and return the superclass.
180+
auto *superMetadata = getMetadataForClass(classMetadata->Superclass);
181+
return {superMetadata, MetadataState::Complete};
182+
}
183+
if (metadataState == MetadataState::NonTransitiveComplete) {
184+
// The subclass metadata is complete, but, unlike above, not transitively.
185+
// Its Superclass field is valid, so just read that field to get to the
186+
// superclass to proceed to the next step.
187+
auto *superMetadata = getMetadataForClass(classMetadata->Superclass);
188+
auto superState = tryGetCompleteMetadataNonblocking(superMetadata);
189+
return {superMetadata, superState};
190+
} else {
191+
// The subclass metadata is either LayoutComplete or Abstract, so the
192+
// Superclass field is not valid. To get to the superclass, make the
193+
// expensive call to getSuperclassMetadata which demangles the superclass
194+
// name from the nominal type descriptor to get the metadata for the
195+
// superclass.
196+
MetadataRequest request(MetadataState::Complete,
197+
/*non-blocking*/ true);
198+
return getSuperclassMetadata(request, classMetadata);
199+
}
200+
}
201+
202+
class MaybeIncompleteSuperclassIterator {
203+
const Metadata *metadata;
204+
llvm::Optional<MetadataState> state;
205+
206+
public:
207+
MaybeIncompleteSuperclassIterator(const Metadata *metadata)
208+
: metadata(metadata), state(llvm::None) {}
209+
210+
MaybeIncompleteSuperclassIterator &operator++() {
211+
auto response = getSuperclassForMaybeIncompleteMetadata(metadata, state);
212+
metadata = response.Value;
213+
state = response.State;
214+
return *this;
215+
}
216+
217+
const Metadata *operator*() const { return metadata; }
218+
219+
bool operator!=(const MaybeIncompleteSuperclassIterator rhs) const {
220+
return metadata != rhs.metadata;
221+
}
222+
};
223+
224+
/// Return a range that will iterate over the given metadata and all its
225+
/// superclasses in order. If the metadata is not a class, iteration will
226+
/// provide that metadata and then stop.
227+
iterator_range<MaybeIncompleteSuperclassIterator>
228+
iterateMaybeIncompleteSuperclasses(const Metadata *metadata) {
229+
return iterator_range<MaybeIncompleteSuperclassIterator>(
230+
MaybeIncompleteSuperclassIterator(metadata),
231+
MaybeIncompleteSuperclassIterator(nullptr));
232+
}
233+
150234
/// Take the type reference inside a protocol conformance record and fetch the
151235
/// canonical metadata pointer for the type it refers to.
152236
/// Returns nil for universal or generic type references.
@@ -470,13 +554,10 @@ searchInConformanceCache(const Metadata *type,
470554
auto origType = type;
471555
auto snapshot = C.Cache.snapshot();
472556

473-
while (type) {
557+
for (auto type : iterateMaybeIncompleteSuperclasses(type)) {
474558
if (auto *Value = snapshot.find(ConformanceCacheKey(type, protocol))) {
475559
return {type == origType, Value->getWitnessTable()};
476560
}
477-
478-
// If there is a superclass, look there.
479-
type = _swift_class_getSuperclass(type);
480561
}
481562

482563
// We did not find a cache entry.
@@ -562,13 +643,10 @@ namespace {
562643
/// be a superclass of the given type. Returns null if this type does not
563644
/// match this conformance.
564645
const Metadata *getMatchingType(const Metadata *conformingType) const {
565-
while (conformingType) {
566-
// Check for a match.
646+
for (auto conformingType :
647+
iterateMaybeIncompleteSuperclasses(conformingType)) {
567648
if (matches(conformingType))
568649
return conformingType;
569-
570-
// Look for a superclass.
571-
conformingType = _swift_class_getSuperclass(conformingType);
572650
}
573651

574652
return nullptr;
@@ -739,8 +817,7 @@ swift_conformsToProtocolImpl(const Metadata *const type,
739817
// Search the shared cache tables for a conformance for this type, and for
740818
// superclasses (if it's a class).
741819
if (C.sharedCacheOptimizationsActive()) {
742-
const Metadata *dyldSearchType = type;
743-
do {
820+
for (auto dyldSearchType : iterateMaybeIncompleteSuperclasses(type)) {
744821
bool definitiveFailure;
745822
std::tie(dyldCachedWitnessTable, dyldCachedConformanceDescriptor,
746823
definitiveFailure) =
@@ -749,9 +826,9 @@ swift_conformsToProtocolImpl(const Metadata *const type,
749826
if (definitiveFailure)
750827
return nullptr;
751828

752-
dyldSearchType = _swift_class_getSuperclass(dyldSearchType);
753-
} while (dyldSearchType && !dyldCachedWitnessTable &&
754-
!dyldCachedConformanceDescriptor);
829+
if (dyldCachedWitnessTable || dyldCachedConformanceDescriptor)
830+
break;
831+
}
755832

756833
validateSharedCacheResults(C, type, protocol, dyldCachedWitnessTable,
757834
dyldCachedConformanceDescriptor);
@@ -827,18 +904,18 @@ swift_conformsToProtocolImpl(const Metadata *const type,
827904

828905
// Find the most specific conformance that was scanned.
829906
const WitnessTable *foundWitness = nullptr;
830-
const Metadata *searchType = type;
831-
while (!foundWitness && searchType) {
907+
const Metadata *foundType = nullptr;
908+
for (auto searchType : iterateMaybeIncompleteSuperclasses(type)) {
832909
foundWitness = foundWitnesses.lookup(searchType);
833-
834-
// If there's no entry here, move up to the superclass (if any).
835-
if (!foundWitness)
836-
searchType = _swift_class_getSuperclass(searchType);
910+
if (foundWitness) {
911+
foundType = searchType;
912+
break;
913+
}
837914
}
838915

839916
// If it's for a superclass or if we didn't find anything, then add an
840917
// authoritative entry for this type.
841-
if (searchType != type)
918+
if (foundType != type)
842919
C.cacheResult(type, protocol, foundWitness, snapshot.count());
843920

844921
// A negative result can be overridden by a result from dyld.
@@ -864,67 +941,26 @@ swift::_searchConformancesByMangledTypeName(Demangle::NodePointer node) {
864941
return nullptr;
865942
}
866943

867-
static MetadataState
868-
tryGetCompleteMetadataNonblocking(const Metadata *metadata) {
869-
return swift_checkMetadataState(
870-
MetadataRequest(MetadataState::Complete, /*isNonBlocking*/ true),
871-
metadata)
872-
.State;
873-
}
874-
875944
template <typename HandleObjc>
876945
bool isSwiftClassMetadataSubclass(const ClassMetadata *subclass,
877946
const ClassMetadata *superclass,
878947
HandleObjc handleObjc) {
879948
assert(subclass);
880949
assert(superclass);
881950

882-
MetadataState subclassState = tryGetCompleteMetadataNonblocking(subclass);
883-
884-
do {
885-
if (subclassState == MetadataState::Complete) {
886-
// The subclass metadata is complete. That means not just that its
887-
// Superclass field is valid, but that the Superclass field of the
888-
// referenced class metadata is valid, and the Superclass field of the
889-
// class metadata referenced there, and so on transitively.
890-
//
891-
// Scan the superclass chains in the ClassMetadata looking for a match.
892-
while ((subclass = subclass->Superclass)) {
893-
if (subclass == superclass)
894-
return true;
895-
}
896-
return false;
897-
}
898-
if (subclassState == MetadataState::NonTransitiveComplete) {
899-
// The subclass metadata is complete, but, unlike above, not transitively.
900-
// Its Superclass field is valid, so just read that field to get to the
901-
// superclass to proceed to the next step.
902-
subclass = subclass->Superclass;
903-
if (subclass->isPureObjC()) {
904-
return handleObjc(subclass, superclass);
905-
}
906-
subclassState = tryGetCompleteMetadataNonblocking(subclass);
907-
} else {
908-
// The subclass metadata is either LayoutComplete or Abstract, so the
909-
// Superclass field is not valid. To get to the superclass, make the
910-
// expensive call to getSuperclassMetadata which demangles the superclass
911-
// name from the nominal type descriptor to get the metadata for the
912-
// superclass.
913-
MetadataRequest request(MetadataState::Complete,
914-
/*non-blocking*/ true);
915-
auto response = getSuperclassMetadata(request, subclass);
916-
auto newMetadata = response.Value;
917-
if (auto newSubclass = dyn_cast<ClassMetadata>(newMetadata)) {
918-
subclass = newSubclass;
919-
subclassState = response.State;
920-
} else {
921-
return handleObjc(newMetadata, superclass);
922-
}
923-
}
924-
if (subclass == superclass)
951+
llvm::Optional<MetadataState> subclassState = llvm::None;
952+
while (true) {
953+
auto response =
954+
getSuperclassForMaybeIncompleteMetadata(subclass, subclassState);
955+
if (response.Value == superclass)
925956
return true;
926-
} while (subclass);
927-
return false;
957+
if (!response.Value)
958+
return false;
959+
960+
subclass = dyn_cast<ClassMetadata>(response.Value);
961+
if (!subclass || subclass->isPureObjC())
962+
return handleObjc(response.Value, superclass);
963+
}
928964
}
929965

930966
// Whether the provided `subclass` is metadata for a subclass* of the superclass

0 commit comments

Comments
 (0)