Skip to content

Commit e15783e

Browse files
committed
[Runtime] Fix concurrent lookups of types by name (again).
Fix a potential false positive if we check a class for a protocol conformance and its superclasses aren't yet instantiated. This was partially fixed before, but we were incorrectly saying that all superclasses had been instantiated if they had been instatiated by the LAST check in conformsToProtocol. That left us open for a possible false positive in an earlier check to go unnoticed. This change fixes it by checking for uninstantiated superclasses after each iteration over superclasses, not just the last one. This change also makes the concurrentTypeByName test much more robust. The original test caught this bug only rarely, but the new one catches it it reliably. We now look up 1000 types per test run. Testing locally, we typically hit the race after less than 100 types. rdar://82364236
1 parent 2415288 commit e15783e

File tree

2 files changed

+1060
-44
lines changed

2 files changed

+1060
-44
lines changed

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -283,18 +283,6 @@ struct MaybeIncompleteSuperclassIterator {
283283
}
284284
};
285285

286-
/// Return a range that will iterate over the given metadata and all its
287-
/// superclasses in order. If the metadata is not a class, iteration will
288-
/// provide that metadata and then stop.
289-
iterator_range<MaybeIncompleteSuperclassIterator>
290-
iterateMaybeIncompleteSuperclasses(const Metadata *metadata,
291-
bool instantiateSuperclassMetadata) {
292-
return iterator_range<MaybeIncompleteSuperclassIterator>(
293-
MaybeIncompleteSuperclassIterator(metadata,
294-
instantiateSuperclassMetadata),
295-
MaybeIncompleteSuperclassIterator(nullptr, false));
296-
}
297-
298286
/// Take the type reference inside a protocol conformance record and fetch the
299287
/// canonical metadata pointer for the type it refers to.
300288
/// Returns nil for universal or generic type references.
@@ -635,8 +623,9 @@ searchInConformanceCache(const Metadata *type,
635623
auto origType = type;
636624
auto snapshot = C.Cache.snapshot();
637625

638-
for (auto type : iterateMaybeIncompleteSuperclasses(
639-
type, instantiateSuperclassMetadata)) {
626+
MaybeIncompleteSuperclassIterator superclassIterator{
627+
type, instantiateSuperclassMetadata};
628+
for (; auto type = superclassIterator.metadata; ++superclassIterator) {
640629
if (auto *Value = snapshot.find(ConformanceCacheKey(type, protocol))) {
641630
return {type == origType, Value->getWitnessTable()};
642631
}
@@ -723,16 +712,20 @@ namespace {
723712

724713
/// Retrieve the type that matches the conformance candidate, which may
725714
/// be a superclass of the given type. Returns null if this type does not
726-
/// match this conformance.
727-
const Metadata *getMatchingType(const Metadata *conformingType,
728-
bool instantiateSuperclassMetadata) const {
729-
for (auto conformingType : iterateMaybeIncompleteSuperclasses(
730-
conformingType, instantiateSuperclassMetadata)) {
715+
/// match this conformance, along with the final metadata state of the
716+
/// superclass iterator.
717+
std::pair<const Metadata *, llvm::Optional<MetadataState>>
718+
getMatchingType(const Metadata *conformingType,
719+
bool instantiateSuperclassMetadata) const {
720+
MaybeIncompleteSuperclassIterator superclassIterator{
721+
conformingType, instantiateSuperclassMetadata};
722+
for (; auto conformingType = superclassIterator.metadata;
723+
++superclassIterator) {
731724
if (matches(conformingType))
732-
return conformingType;
725+
return {conformingType, llvm::None};
733726
}
734727

735-
return nullptr;
728+
return {nullptr, superclassIterator.state};
736729
}
737730
};
738731
}
@@ -755,7 +748,8 @@ static void validateDyldResults(
755748
continue;
756749

757750
ConformanceCandidate candidate(descriptor);
758-
if (candidate.getMatchingType(type, instantiateSuperclassMetadata))
751+
if (std::get<const Metadata *>(
752+
candidate.getMatchingType(type, instantiateSuperclassMetadata)))
759753
conformances.push_back(&descriptor);
760754
}
761755
}
@@ -915,8 +909,9 @@ findConformanceWithDyld(ConformanceState &C, const Metadata *type,
915909
dyldResult.value);
916910

917911
assert(conformanceDescriptor->getProtocol() == protocol);
918-
assert(ConformanceCandidate{*conformanceDescriptor}.getMatchingType(
919-
type, instantiateSuperclassMetadata));
912+
assert(std::get<const Metadata *>(
913+
ConformanceCandidate{*conformanceDescriptor}.getMatchingType(
914+
type, instantiateSuperclassMetadata)));
920915

921916
if (conformanceDescriptor->getGenericWitnessTable()) {
922917
DYLD_CONFORMANCES_LOG(
@@ -980,11 +975,29 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
980975
const ProtocolConformanceDescriptor *dyldCachedConformanceDescriptor =
981976
nullptr;
982977

978+
// Track whether we have uninstantiated superclasses. Each time we iterate
979+
// over our superclasses, we check the final state to see if there are more
980+
// superclasses we haven't instantiated by calling noteFinalMetadataState.
981+
// If we ever see Abstract, that means there are more superclasses we can't
982+
// check yet, and we might get a false negative. We have to do this after each
983+
// iteration (really, just the first iteration, but it's hard to keep track of
984+
// which iteration is the first time), because another thread might
985+
// instantiate the superclass while we're in the middle of searching. If we
986+
// only look at the state after the last iteration, we might have hit a false
987+
// negative before that no longer shows up.
988+
bool hasUninstantiatedSuperclass = false;
989+
auto noteFinalMetadataState = [&](llvm::Optional<MetadataState> state) {
990+
hasUninstantiatedSuperclass =
991+
hasUninstantiatedSuperclass || state == MetadataState::Abstract;
992+
};
993+
983994
// Search the shared cache tables for a conformance for this type, and for
984995
// superclasses (if it's a class).
985996
if (C.dyldOptimizationsActive()) {
986-
for (auto dyldSearchType : iterateMaybeIncompleteSuperclasses(
987-
type, instantiateSuperclassMetadata)) {
997+
MaybeIncompleteSuperclassIterator superclassIterator{
998+
type, instantiateSuperclassMetadata};
999+
for (; auto dyldSearchType = superclassIterator.metadata;
1000+
++superclassIterator) {
9881001
bool definitiveFailure;
9891002
std::tie(dyldCachedWitnessTable, dyldCachedConformanceDescriptor,
9901003
definitiveFailure) =
@@ -997,6 +1010,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
9971010
if (dyldCachedWitnessTable || dyldCachedConformanceDescriptor)
9981011
break;
9991012
}
1013+
noteFinalMetadataState(superclassIterator.state);
10001014

10011015
validateDyldResults(C, type, protocol, dyldCachedWitnessTable,
10021016
dyldCachedConformanceDescriptor,
@@ -1025,8 +1039,8 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
10251039

10261040
if (dyldCachedConformanceDescriptor) {
10271041
ConformanceCandidate candidate(*dyldCachedConformanceDescriptor);
1028-
auto *matchingType =
1029-
candidate.getMatchingType(type, instantiateSuperclassMetadata);
1042+
auto *matchingType = std::get<const Metadata *>(
1043+
candidate.getMatchingType(type, instantiateSuperclassMetadata));
10301044
assert(matchingType);
10311045
auto witness = dyldCachedConformanceDescriptor->getWitnessTable(matchingType);
10321046
C.cacheResult(type, protocol, witness, /*always cache*/ 0);
@@ -1048,8 +1062,12 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
10481062
// The matching type is exact, so they can't go stale, and we should
10491063
// always cache them.
10501064
ConformanceCandidate candidate(descriptor);
1051-
if (auto *matchingType =
1052-
candidate.getMatchingType(type, instantiateSuperclassMetadata)) {
1065+
const Metadata *matchingType;
1066+
llvm::Optional<MetadataState> finalState;
1067+
std::tie(matchingType, finalState) =
1068+
candidate.getMatchingType(type, instantiateSuperclassMetadata);
1069+
noteFinalMetadataState(finalState);
1070+
if (matchingType) {
10531071
auto witness = descriptor.getWitnessTable(matchingType);
10541072
C.cacheResult(matchingType, protocol, witness, /*always cache*/ 0);
10551073
foundWitnesses.insert({matchingType, witness});
@@ -1078,9 +1096,6 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
10781096
const WitnessTable *foundWitness = nullptr;
10791097
const Metadata *foundType = nullptr;
10801098

1081-
// Use MaybeIncompleteSuperclassIterator directly so we can examine its final
1082-
// state. Complete indicates that we finished normally, Abstract indicates
1083-
// that there's an uninstantiated superclass we didn't iterate over.
10841099
MaybeIncompleteSuperclassIterator superclassIterator{
10851100
type, instantiateSuperclassMetadata};
10861101
for (; auto searchType = superclassIterator.metadata; ++superclassIterator) {
@@ -1102,15 +1117,13 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
11021117
}
11031118
}
11041119
}
1105-
1106-
// Do not cache negative results if there were uninstantiated superclasses we
1107-
// didn't search. They might have a conformance that will be found later.
1108-
bool hasUninstantiatedSuperclass =
1109-
superclassIterator.state == MetadataState::Abstract;
1120+
noteFinalMetadataState(superclassIterator.state);
11101121

11111122
// If it's for a superclass or if we didn't find anything, then add an
11121123
// authoritative entry for this type.
11131124
if (foundType != type)
1125+
// Do not cache negative results if there were uninstantiated superclasses
1126+
// we didn't search. They might have a conformance that will be found later.
11141127
if (foundWitness || !hasUninstantiatedSuperclass)
11151128
C.cacheResult(type, protocol, foundWitness, snapshot.count());
11161129

@@ -1378,8 +1391,8 @@ const Metadata *swift::findConformingSuperclass(
13781391
// Figure out which type we're looking for.
13791392
ConformanceCandidate candidate(*conformance);
13801393

1381-
const Metadata *conformingType =
1382-
candidate.getMatchingType(type, true /*instantiateSuperclassMetadata*/);
1394+
const Metadata *conformingType = std::get<const Metadata *>(
1395+
candidate.getMatchingType(type, true /*instantiateSuperclassMetadata*/));
13831396
assert(conformingType);
13841397
return conformingType;
13851398
}

0 commit comments

Comments
 (0)