Skip to content

Commit d9bb81b

Browse files
committed
[Runtime] Cache protocol conformance descriptors, not witness tables.
The conformance cache was caching the witness table for a conformance `T: P`, where `T` is a concrete type and `P` is a protocol. However, it essentially picked one of potentially many witness tables for that conformance, because retroactive conformances might produce different results from different modules. Make the conformance cache what is says it is: a cache of the conformance descriptor for a given `T: P`, potentially filtered by a module (when requested). Clients of the conformance cache can choose how to interpret the protocol conformance descriptor, e.g., by instantiating a witness table. We can bring back a specific conformance cache for swift_conformsToProtocol() if it is profitable. (cherry picked from commit 0af2af00a739a4d912d2a9c3b196449e4164484f)
1 parent 17699d4 commit d9bb81b

File tree

2 files changed

+49
-41
lines changed

2 files changed

+49
-41
lines changed

stdlib/public/runtime/Private.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,12 @@ class TypeInfo {
507507
const Metadata *type,
508508
const ProtocolConformanceDescriptor *conformance);
509509

510+
/// Determine whether the given type conforms to the given Swift protocol,
511+
/// returning the appropriate protocol conformance descriptor when it does.
512+
const ProtocolConformanceDescriptor *
513+
_conformsToSwiftProtocol(const Metadata * const type,
514+
const ProtocolDescriptor *protocol);
515+
510516
/// Retrieve an associated type witness from the given witness table.
511517
///
512518
/// \param wtable The witness table.

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,14 @@ namespace {
200200
private:
201201
const void *Type;
202202
const ProtocolDescriptor *Proto;
203-
std::atomic<const WitnessTable *> Table;
203+
std::atomic<const ProtocolConformanceDescriptor *> Description;
204204
std::atomic<size_t> FailureGeneration;
205205

206206
public:
207207
ConformanceCacheEntry(ConformanceCacheKey key,
208-
const WitnessTable *table,
208+
const ProtocolConformanceDescriptor *description,
209209
size_t failureGeneration)
210-
: Type(key.Type), Proto(key.Proto), Table(table),
210+
: Type(key.Type), Proto(key.Proto), Description(description),
211211
FailureGeneration(failureGeneration) {
212212
}
213213

@@ -227,22 +227,22 @@ namespace {
227227
}
228228

229229
bool isSuccessful() const {
230-
return Table.load(std::memory_order_relaxed) != nullptr;
230+
return Description.load(std::memory_order_relaxed) != nullptr;
231231
}
232232

233-
void makeSuccessful(const WitnessTable *table) {
234-
Table.store(table, std::memory_order_release);
233+
void makeSuccessful(const ProtocolConformanceDescriptor *description) {
234+
Description.store(description, std::memory_order_release);
235235
}
236236

237237
void updateFailureGeneration(size_t failureGeneration) {
238238
assert(!isSuccessful());
239239
FailureGeneration.store(failureGeneration, std::memory_order_relaxed);
240240
}
241-
242-
/// Get the cached witness table, if successful.
243-
const WitnessTable *getWitnessTable() const {
241+
242+
/// Get the cached conformance descriptor, if successful.
243+
const ProtocolConformanceDescriptor *getDescription() const {
244244
assert(isSuccessful());
245-
return Table.load(std::memory_order_acquire);
245+
return Description.load(std::memory_order_acquire);
246246
}
247247

248248
/// Get the generation in which this lookup failed.
@@ -263,21 +263,22 @@ struct ConformanceState {
263263
}
264264

265265
void cacheSuccess(const void *type, const ProtocolDescriptor *proto,
266-
const WitnessTable *witness) {
266+
const ProtocolConformanceDescriptor *description) {
267267
auto result = Cache.getOrInsert(ConformanceCacheKey(type, proto),
268-
witness, 0);
268+
description, 0);
269269

270270
// If the entry was already present, we may need to update it.
271271
if (!result.second) {
272-
result.first->makeSuccessful(witness);
272+
result.first->makeSuccessful(description);
273273
}
274274
}
275275

276276
void cacheFailure(const void *type, const ProtocolDescriptor *proto,
277277
size_t failureGeneration) {
278-
auto result = Cache.getOrInsert(ConformanceCacheKey(type, proto),
279-
(const WitnessTable *) nullptr,
280-
failureGeneration);
278+
auto result =
279+
Cache.getOrInsert(ConformanceCacheKey(type, proto),
280+
(const ProtocolConformanceDescriptor *) nullptr,
281+
failureGeneration);
281282

282283
// If the entry was already present, we may need to update it.
283284
if (!result.second) {
@@ -344,21 +345,22 @@ swift::swift_registerProtocolConformances(const ProtocolConformanceRecord *begin
344345

345346

346347
struct ConformanceCacheResult {
347-
// true if witnessTable is an authoritative result as-is.
348+
// true if description is an authoritative result as-is.
348349
// false if more searching is required (for example, because a cached
349350
// failure was returned in failureEntry but it is out-of-date.
350351
bool isAuthoritative;
351352

352-
// The matching witness table, or null if no cached conformance was found.
353-
const WitnessTable *witnessTable;
353+
// The matching conformance descriptor, or null if no cached conformance
354+
// was found.
355+
const ProtocolConformanceDescriptor *description;
354356

355357
// If the search fails, this may be the negative cache entry for the
356358
// queried type itself. This entry may be null or out-of-date.
357359
ConformanceCacheEntry *failureEntry;
358360

359361
static ConformanceCacheResult
360-
cachedSuccess(const WitnessTable *table) {
361-
return ConformanceCacheResult { true, table, nullptr };
362+
cachedSuccess(const ProtocolConformanceDescriptor *description) {
363+
return ConformanceCacheResult { true, description, nullptr };
362364
}
363365

364366
static ConformanceCacheResult
@@ -381,7 +383,7 @@ static const void *getConformanceCacheTypeKey(const Metadata *type) {
381383
return type;
382384
}
383385

384-
/// Search for a witness table in the ConformanceCache.
386+
/// Search for a conformance descriptor in the ConformanceCache.
385387
static
386388
ConformanceCacheResult
387389
searchInConformanceCache(const Metadata *type,
@@ -396,7 +398,7 @@ searchInConformanceCache(const Metadata *type,
396398
if (auto *Value = C.findCached(type, protocol)) {
397399
if (Value->isSuccessful()) {
398400
// Found a conformance on the type or some superclass. Return it.
399-
return ConformanceCacheResult::cachedSuccess(Value->getWitnessTable());
401+
return ConformanceCacheResult::cachedSuccess(Value->getDescription());
400402
}
401403

402404
// Found a negative cache entry.
@@ -438,7 +440,7 @@ searchInConformanceCache(const Metadata *type,
438440
// Hash and lookup the type-protocol pair in the cache.
439441
if (auto *Value = C.findCached(typeKey, protocol)) {
440442
if (Value->isSuccessful())
441-
return ConformanceCacheResult::cachedSuccess(Value->getWitnessTable());
443+
return ConformanceCacheResult::cachedSuccess(Value->getDescription());
442444

443445
// We don't try to cache negative responses for generic
444446
// patterns.
@@ -530,17 +532,17 @@ namespace {
530532
};
531533
}
532534

533-
static const WitnessTable *
534-
swift_conformsToProtocolImpl(const Metadata * const type,
535-
const ProtocolDescriptor *protocol) {
535+
const ProtocolConformanceDescriptor *
536+
swift::_conformsToSwiftProtocol(const Metadata * const type,
537+
const ProtocolDescriptor *protocol) {
536538
auto &C = Conformances.get();
537539

538540
// See if we have a cached conformance. The ConcurrentMap data structure
539541
// allows us to insert and search the map concurrently without locking.
540542
auto FoundConformance = searchInConformanceCache(type, protocol);
541543
// If the result (positive or negative) is authoritative, return it.
542544
if (FoundConformance.isAuthoritative)
543-
return FoundConformance.witnessTable;
545+
return FoundConformance.description;
544546

545547
auto failureEntry = FoundConformance.failureEntry;
546548

@@ -560,16 +562,6 @@ swift_conformsToProtocolImpl(const Metadata * const type,
560562
return nullptr;
561563
}
562564

563-
/// Local function to retrieve the witness table and record the result.
564-
auto recordWitnessTable = [&](const ProtocolConformanceDescriptor &descriptor,
565-
const Metadata *type) {
566-
auto witnessTable = descriptor.getWitnessTable(type);
567-
if (witnessTable)
568-
C.cacheSuccess(type, protocol, witnessTable);
569-
else
570-
C.cacheFailure(type, protocol, snapshot.count());
571-
};
572-
573565
// Really scan conformance records.
574566
for (size_t i = startIndex; i < endIndex; i++) {
575567
auto &section = snapshot.Start[i];
@@ -588,23 +580,33 @@ swift_conformsToProtocolImpl(const Metadata * const type,
588580
if (!matchingType)
589581
matchingType = type;
590582

591-
recordWitnessTable(descriptor, matchingType);
583+
C.cacheSuccess(matchingType, protocol, &descriptor);
592584
}
593585
}
594586
}
595587

596588
// Conformance scan is complete.
597-
// Search the cache once more, and this time update the cache if necessary.
598589

590+
// Search the cache once more, and this time update the cache if necessary.
599591
FoundConformance = searchInConformanceCache(type, protocol);
600592
if (FoundConformance.isAuthoritative) {
601-
return FoundConformance.witnessTable;
593+
return FoundConformance.description;
602594
} else {
603595
C.cacheFailure(type, protocol, snapshot.count());
604596
return nullptr;
605597
}
606598
}
607599

600+
static const WitnessTable *
601+
swift_conformsToProtocolImpl(const Metadata * const type,
602+
const ProtocolDescriptor *protocol) {
603+
auto description = _conformsToSwiftProtocol(type, protocol);
604+
if (!description)
605+
return nullptr;
606+
607+
return description->getWitnessTable(type);
608+
}
609+
608610
const ContextDescriptor *
609611
swift::_searchConformancesByMangledTypeName(Demangle::NodePointer node) {
610612
auto &C = Conformances.get();

0 commit comments

Comments
 (0)