Skip to content

Protocol conformance cache for generic types #82818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 88 additions & 42 deletions include/swift/Runtime/Concurrent.h
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,51 @@ struct ConcurrentReadableHashMap {
}
}

// Common implementation for `getOrInsert` and `GetOrInsertManyScope`
template <class KeyTy, typename Call>
void getOrInsertExternallyLocked(KeyTy key, const Call &call) {
auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
auto indicesCapacityLog2 = indices.getCapacityLog2();
auto elementCount = ElementCount.load(std::memory_order_relaxed);
auto *elements = Elements.load(std::memory_order_relaxed);
auto *elementsPtr = elements ? elements->data() : nullptr;


auto found = this->find(key, indices, elementCount, elementsPtr);
if (found.first) {
call(found.first, false);
return;
}

auto indicesCapacity = 1UL << indicesCapacityLog2;

// The number of slots in use is elementCount + 1, since the capacity also
// takes a slot.
auto emptyCount = indicesCapacity - (elementCount + 1);
auto proportion = indicesCapacity / emptyCount;
if (proportion >= ResizeProportion) {
indices = resize(indices, indicesCapacityLog2, elementsPtr);
found = find(key, indices, elementCount, elementsPtr);
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
}

if (!elements || elementCount >= elements->Capacity) {
elements = resize(elements, elementCount);
}
auto *element = &elements->data()[elementCount];

// Order matters: fill out the element, then update the count,
// then update the index.
bool keep = call(element, true);
if (keep) {
assert(hash_value(key) == hash_value(*element) &&
"Element must have the same hash code as its key.");
ElementCount.store(elementCount + 1, std::memory_order_release);
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
std::memory_order_release);
}
}

public:
// Implicitly trivial constructor/destructor.
ConcurrentReadableHashMap() = default;
Expand Down Expand Up @@ -684,6 +729,48 @@ struct ConcurrentReadableHashMap {
return Snapshot(this, indices, elementsPtr, elementCount);
}

/// A wrapper that allows performing several `getOrInsert` operations under
/// the same lock.
class GetOrInsertManyScope {
GetOrInsertManyScope() = delete;
GetOrInsertManyScope(const GetOrInsertManyScope &) = delete;
GetOrInsertManyScope &operator=(const GetOrInsertManyScope &) = delete;
GetOrInsertManyScope(GetOrInsertManyScope &&) = delete;
GetOrInsertManyScope &operator=(GetOrInsertManyScope &&) = delete;

ConcurrentReadableHashMap &Map;

public:
GetOrInsertManyScope(ConcurrentReadableHashMap &map) : Map(map) {
Map.WriterLock.lock();
}

~GetOrInsertManyScope() {
Map.deallocateFreeListIfSafe();
Map.WriterLock.unlock();
}

/// Get an element by key, or insert a new element for that key if one is
/// not already present. Invoke `call` with the pointer to the element.
///
/// `call` is passed the following parameters:
/// - `element`: the pointer to the element corresponding to `key`
/// - `created`: true if the element is newly created, false if it already
/// exists
/// `call` returns a `bool`. When `created` is `true`, the return values
/// mean:
/// - `true` the new entry is to be kept
/// - `false` indicates that the new entry is discarded
/// If the new entry is kept, then the new element MUST be initialized, and
/// have a hash value that matches the hash value of `key`.
///
/// The return value is ignored when `created` is `false`.
template <class KeyTy, typename Call>
void getOrInsert(KeyTy key, const Call &call) {
Map.getOrInsertExternallyLocked(key, call);
}
};

/// Get an element by key, or insert a new element for that key if one is not
/// already present. Invoke `call` with the pointer to the element. BEWARE:
/// `call` is invoked with the internal writer lock held, keep work to a
Expand All @@ -703,48 +790,7 @@ struct ConcurrentReadableHashMap {
template <class KeyTy, typename Call>
void getOrInsert(KeyTy key, const Call &call) {
typename MutexTy::ScopedLock guard(WriterLock);

auto indices = IndexStorage{Indices.load(std::memory_order_relaxed)};
auto indicesCapacityLog2 = indices.getCapacityLog2();
auto elementCount = ElementCount.load(std::memory_order_relaxed);
auto *elements = Elements.load(std::memory_order_relaxed);
auto *elementsPtr = elements ? elements->data() : nullptr;

auto found = this->find(key, indices, elementCount, elementsPtr);
if (found.first) {
call(found.first, false);
deallocateFreeListIfSafe();
return;
}

auto indicesCapacity = 1UL << indicesCapacityLog2;

// The number of slots in use is elementCount + 1, since the capacity also
// takes a slot.
auto emptyCount = indicesCapacity - (elementCount + 1);
auto proportion = indicesCapacity / emptyCount;
if (proportion >= ResizeProportion) {
indices = resize(indices, indicesCapacityLog2, elementsPtr);
found = find(key, indices, elementCount, elementsPtr);
assert(!found.first && "Shouldn't suddenly find the key after rehashing");
}

if (!elements || elementCount >= elements->Capacity) {
elements = resize(elements, elementCount);
}
auto *element = &elements->data()[elementCount];

// Order matters: fill out the element, then update the count,
// then update the index.
bool keep = call(element, true);
if (keep) {
assert(hash_value(key) == hash_value(*element) &&
"Element must have the same hash code as its key.");
ElementCount.store(elementCount + 1, std::memory_order_release);
indices.storeIndexAt(&Indices, elementCount + 1, found.second,
std::memory_order_release);
}

getOrInsertExternallyLocked(key, call);
deallocateFreeListIfSafe();
}

Expand Down
9 changes: 9 additions & 0 deletions stdlib/public/runtime/EnvironmentVariables.def
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ VARIABLE(SWIFT_DEBUG_ENABLE_SHARED_CACHE_PROTOCOL_CONFORMANCES, bool, true,

#endif

VARIABLE(SWIFT_DEBUG_ENABLE_CACHE_PROTOCOL_CONFORMANCES_BY_TYPE_DESCRIPTOR,
bool, true,
"Enable caching protocol conformances by type descriptor in addition "
"to the cache by metadata.")

VARIABLE(SWIFT_DEBUG_CONCURRENCY_ENABLE_COOPERATIVE_QUEUES, bool, true,
"Enable dispatch cooperative queues in the global executor.")

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

VARIABLE(SWIFT_DEBUG_ENABLE_PROTOCOL_CONFORMANCES_LOOKUP_LOG,
bool, false,
"Enable logs for the conformance lookup routine.")

#endif

VARIABLE(SWIFT_BINARY_COMPATIBILITY_VERSION, uint32_t, 0,
Expand Down
Loading