Skip to content

Commit 67506f5

Browse files
authored
Merge pull request #82818 from DmT021/wp/conformance-cache-by-descriptor
Protocol conformance cache for generic types
2 parents a4ad806 + 7989dbe commit 67506f5

8 files changed

+536
-116
lines changed

include/swift/Runtime/Concurrent.h

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,51 @@ struct ConcurrentReadableHashMap {
593593
}
594594
}
595595

596+
// Common implementation for `getOrInsert` and `GetOrInsertManyScope`
597+
template <class KeyTy, typename Call>
598+
void getOrInsertExternallyLocked(KeyTy key, const Call &call) {
599+
auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
600+
auto indicesCapacityLog2 = indices.getCapacityLog2();
601+
auto elementCount = ElementCount.load(std::memory_order_relaxed);
602+
auto *elements = Elements.load(std::memory_order_relaxed);
603+
auto *elementsPtr = elements ? elements->data() : nullptr;
604+
605+
606+
auto found = this->find(key, indices, elementCount, elementsPtr);
607+
if (found.first) {
608+
call(found.first, false);
609+
return;
610+
}
611+
612+
auto indicesCapacity = 1UL << indicesCapacityLog2;
613+
614+
// The number of slots in use is elementCount + 1, since the capacity also
615+
// takes a slot.
616+
auto emptyCount = indicesCapacity - (elementCount + 1);
617+
auto proportion = indicesCapacity / emptyCount;
618+
if (proportion >= ResizeProportion) {
619+
indices = resize(indices, indicesCapacityLog2, elementsPtr);
620+
found = find(key, indices, elementCount, elementsPtr);
621+
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
622+
}
623+
624+
if (!elements || elementCount >= elements->Capacity) {
625+
elements = resize(elements, elementCount);
626+
}
627+
auto *element = &elements->data()[elementCount];
628+
629+
// Order matters: fill out the element, then update the count,
630+
// then update the index.
631+
bool keep = call(element, true);
632+
if (keep) {
633+
assert(hash_value(key) == hash_value(*element) &&
634+
"Element must have the same hash code as its key.");
635+
ElementCount.store(elementCount + 1, std::memory_order_release);
636+
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
637+
std::memory_order_release);
638+
}
639+
}
640+
596641
public:
597642
// Implicitly trivial constructor/destructor.
598643
ConcurrentReadableHashMap() = default;
@@ -684,6 +729,48 @@ struct ConcurrentReadableHashMap {
684729
return Snapshot(this, indices, elementsPtr, elementCount);
685730
}
686731

732+
/// A wrapper that allows performing several `getOrInsert` operations under
733+
/// the same lock.
734+
class GetOrInsertManyScope {
735+
GetOrInsertManyScope() = delete;
736+
GetOrInsertManyScope(const GetOrInsertManyScope &) = delete;
737+
GetOrInsertManyScope &operator=(const GetOrInsertManyScope &) = delete;
738+
GetOrInsertManyScope(GetOrInsertManyScope &&) = delete;
739+
GetOrInsertManyScope &operator=(GetOrInsertManyScope &&) = delete;
740+
741+
ConcurrentReadableHashMap &Map;
742+
743+
public:
744+
GetOrInsertManyScope(ConcurrentReadableHashMap &map) : Map(map) {
745+
Map.WriterLock.lock();
746+
}
747+
748+
~GetOrInsertManyScope() {
749+
Map.deallocateFreeListIfSafe();
750+
Map.WriterLock.unlock();
751+
}
752+
753+
/// Get an element by key, or insert a new element for that key if one is
754+
/// not already present. Invoke `call` with the pointer to the element.
755+
///
756+
/// `call` is passed the following parameters:
757+
/// - `element`: the pointer to the element corresponding to `key`
758+
/// - `created`: true if the element is newly created, false if it already
759+
/// exists
760+
/// `call` returns a `bool`. When `created` is `true`, the return values
761+
/// mean:
762+
/// - `true` the new entry is to be kept
763+
/// - `false` indicates that the new entry is discarded
764+
/// If the new entry is kept, then the new element MUST be initialized, and
765+
/// have a hash value that matches the hash value of `key`.
766+
///
767+
/// The return value is ignored when `created` is `false`.
768+
template <class KeyTy, typename Call>
769+
void getOrInsert(KeyTy key, const Call &call) {
770+
Map.getOrInsertExternallyLocked(key, call);
771+
}
772+
};
773+
687774
/// Get an element by key, or insert a new element for that key if one is not
688775
/// already present. Invoke `call` with the pointer to the element. BEWARE:
689776
/// `call` is invoked with the internal writer lock held, keep work to a
@@ -703,48 +790,7 @@ struct ConcurrentReadableHashMap {
703790
template <class KeyTy, typename Call>
704791
void getOrInsert(KeyTy key, const Call &call) {
705792
typename MutexTy::ScopedLock guard(WriterLock);
706-
707-
auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
708-
auto indicesCapacityLog2 = indices.getCapacityLog2();
709-
auto elementCount = ElementCount.load(std::memory_order_relaxed);
710-
auto *elements = Elements.load(std::memory_order_relaxed);
711-
auto *elementsPtr = elements ? elements->data() : nullptr;
712-
713-
auto found = this->find(key, indices, elementCount, elementsPtr);
714-
if (found.first) {
715-
call(found.first, false);
716-
deallocateFreeListIfSafe();
717-
return;
718-
}
719-
720-
auto indicesCapacity = 1UL << indicesCapacityLog2;
721-
722-
// The number of slots in use is elementCount + 1, since the capacity also
723-
// takes a slot.
724-
auto emptyCount = indicesCapacity - (elementCount + 1);
725-
auto proportion = indicesCapacity / emptyCount;
726-
if (proportion >= ResizeProportion) {
727-
indices = resize(indices, indicesCapacityLog2, elementsPtr);
728-
found = find(key, indices, elementCount, elementsPtr);
729-
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
730-
}
731-
732-
if (!elements || elementCount >= elements->Capacity) {
733-
elements = resize(elements, elementCount);
734-
}
735-
auto *element = &elements->data()[elementCount];
736-
737-
// Order matters: fill out the element, then update the count,
738-
// then update the index.
739-
bool keep = call(element, true);
740-
if (keep) {
741-
assert(hash_value(key) == hash_value(*element) &&
742-
"Element must have the same hash code as its key.");
743-
ElementCount.store(elementCount + 1, std::memory_order_release);
744-
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
745-
std::memory_order_release);
746-
}
747-
793+
getOrInsertExternallyLocked(key, call);
748794
deallocateFreeListIfSafe();
749795
}
750796

stdlib/public/runtime/EnvironmentVariables.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ VARIABLE(SWIFT_DEBUG_ENABLE_SHARED_CACHE_PROTOCOL_CONFORMANCES, bool, true,
6262

6363
#endif
6464

65+
VARIABLE(SWIFT_DEBUG_ENABLE_CACHE_PROTOCOL_CONFORMANCES_BY_TYPE_DESCRIPTOR,
66+
bool, true,
67+
"Enable caching protocol conformances by type descriptor in addition "
68+
"to the cache by metadata.")
69+
6570
VARIABLE(SWIFT_DEBUG_CONCURRENCY_ENABLE_COOPERATIVE_QUEUES, bool, true,
6671
"Enable dispatch cooperative queues in the global executor.")
6772

@@ -70,6 +75,10 @@ VARIABLE(SWIFT_DEBUG_CONCURRENCY_ENABLE_COOPERATIVE_QUEUES, bool, true,
7075
VARIABLE(SWIFT_DEBUG_RUNTIME_EXCLUSIVITY_LOGGING, bool, false,
7176
"Enable the an asserts runtime to emit logging as it works.")
7277

78+
VARIABLE(SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG,
79+
bool, false,
80+
"Enable logs for the conformance lookup routine.")
81+
7382
#endif
7483

7584
VARIABLE(SWIFT_BINARY_COMPATIBILITY_VERSION, uint32_t, 0,

0 commit comments

Comments
 (0)