Skip to content

Commit ecd6d4d

Browse files
committed
Add a new ConcurrentReadableHashMap type. Switch the protocol conformance cache
to use it. ConcurrentReadableHashMap is lock-free for readers, with writers using a lock to ensure mutual exclusion amongst each other. The intent is to eventually replace all uses ConcurrentMap with ConcurrentReadableHashMap. ConcurrentReadableHashMap provides for relatively quick lookups by using a hash table. Rearders perform an atomic increment/decrement in order to inform writers that there are active readers. The design attempts to minimize wasted memory by storing the actual elements out-of-line, and having the table store indices into a separate array of elements. The protocol conformance cache now uses ConcurrentReadableHashMap, which provides faster lookups and less memory use than the previous ConcurrentMap implementation. The previous implementation caches ProtocolConformanceDescriptors and extracts the WitnessTable after the cache lookup. The new implementation directly caches the WitnessTable, removing an extra step (potentially a quite slow one) from the fast path. The previous implementation used a generational scheme to detect when negative cache entries became obsolete due to new dynamic libraries being loaded, and update them in place. The new implementation just clears the entire cache when libraries are loaded, greatly simplifying the code and saving the memory needed to track the current generation in each negative cache entry. This means we need to re-cache all requested conformances after loading a dynamic library, but loading libraries at runtime is rare and slow anyway. rdar://problem/67268325
1 parent 4cfaeee commit ecd6d4d

File tree

14 files changed

+1483
-238
lines changed

14 files changed

+1483
-238
lines changed

include/swift/Reflection/ReflectionContext.h

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,8 @@ class ReflectionContext
879879
std::function<void(StoredPointer Type, StoredPointer Proto)> Call) {
880880
if (!NodePtr)
881881
return;
882-
auto NodeBytes = getReader().readBytes(RemoteAddress(NodePtr), sizeof(Node));
882+
auto NodeBytes = getReader().readBytes(RemoteAddress(NodePtr),
883+
sizeof(ConformanceNode<Runtime>));
883884
auto NodeData =
884885
reinterpret_cast<const ConformanceNode<Runtime> *>(NodeBytes.get());
885886
if (!NodeData)
@@ -889,6 +890,33 @@ class ReflectionContext
889890
iterateConformanceTree(NodeData->Right, Call);
890891
}
891892

893+
void IterateConformanceTable(
894+
RemoteAddress ConformancesPtr,
895+
std::function<void(StoredPointer Type, StoredPointer Proto)> Call) {
896+
auto MapBytes = getReader().readBytes(RemoteAddress(ConformancesPtr),
897+
sizeof(ConcurrentHashMap<Runtime>));
898+
auto MapData =
899+
reinterpret_cast<const ConcurrentHashMap<Runtime> *>(MapBytes.get());
900+
if (!MapData)
901+
return;
902+
903+
auto Count = MapData->ElementCount;
904+
auto Size = Count * sizeof(ConformanceCacheEntry<Runtime>);
905+
906+
auto ElementsBytes =
907+
getReader().readBytes(RemoteAddress(MapData->Elements), Size);
908+
auto ElementsData =
909+
reinterpret_cast<const ConformanceCacheEntry<Runtime> *>(
910+
ElementsBytes.get());
911+
if (!ElementsData)
912+
return;
913+
914+
for (StoredSize i = 0; i < Count; i++) {
915+
auto &Element = ElementsData[i];
916+
Call(Element.Type, Element.Proto);
917+
}
918+
}
919+
892920
/// Iterate the protocol conformance cache in the target process, calling Call
893921
/// with the type and protocol of each conformance. Returns None on success,
894922
/// and a string describing the error on failure.
@@ -908,7 +936,26 @@ class ReflectionContext
908936

909937
auto Root = getReader().readPointer(ConformancesAddr->getResolvedAddress(),
910938
sizeof(StoredPointer));
911-
iterateConformanceTree(Root->getResolvedAddress().getAddressData(), Call);
939+
auto ReaderCount = Root->getResolvedAddress().getAddressData();
940+
941+
// ReaderCount will be the root pointer if the conformance cache is a
942+
// ConcurrentMap. It's very unlikely that there would ever be more readers
943+
// than the least valid pointer value, so compare with that to distinguish.
944+
// TODO: once the old conformance cache is gone for good, remove that code.
945+
uint64_t LeastValidPointerValue;
946+
if (!getReader().queryDataLayout(
947+
DataLayoutQueryType::DLQ_GetLeastValidPointerValue, nullptr,
948+
&LeastValidPointerValue)) {
949+
return std::string("unable to query least valid pointer value");
950+
}
951+
952+
if (ReaderCount < LeastValidPointerValue)
953+
IterateConformanceTable(ConformancesAddr->getResolvedAddress(), Call);
954+
else {
955+
// The old code has the root address at this location.
956+
auto RootAddr = ReaderCount;
957+
iterateConformanceTree(RootAddr, Call);
958+
}
912959
return llvm::None;
913960
}
914961

include/swift/Reflection/RuntimeInternals.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ template <typename Runtime> struct MetadataCacheNode {
4646
typename Runtime::StoredPointer Right;
4747
};
4848

49+
template <typename Runtime> struct ConcurrentHashMap {
50+
typename Runtime::StoredSize ReaderCount;
51+
typename Runtime::StoredSize ElementCount;
52+
typename Runtime::StoredPointer Elements;
53+
typename Runtime::StoredPointer Indices;
54+
// We'll ignore the remaining fields for now....
55+
};
56+
57+
template <typename Runtime> struct ConformanceCacheEntry {
58+
typename Runtime::StoredPointer Type;
59+
typename Runtime::StoredPointer Proto;
60+
typename Runtime::StoredPointer Witness;
61+
};
62+
4963
} // end namespace reflection
5064
} // end namespace swift
5165

0 commit comments

Comments
 (0)