Files
swift-mirror/stdlib/public/runtime/MetadataCache.h
2023-05-31 15:18:08 -07:00

1597 lines
56 KiB
C++

//===--- MetadataCache.h - Implements the metadata cache --------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_RUNTIME_METADATACACHE_H
#define SWIFT_RUNTIME_METADATACACHE_H
#include "swift/Runtime/AtomicWaitQueue.h"
#include "swift/Runtime/Concurrent.h"
#include "swift/Runtime/Metadata.h"
#include "swift/Threading/Mutex.h"
#include "swift/shims/Visibility.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/STLExtras.h"
#include <condition_variable>
#include <atomic>
#ifndef SWIFT_DEBUG_RUNTIME
#define SWIFT_DEBUG_RUNTIME 0
#endif
namespace swift {
RelativeWitnessTable *lookThroughOptionalConditionalWitnessTable(const RelativeWitnessTable *);
#if !SWIFT_STDLIB_PASSTHROUGH_METADATA_ALLOCATOR
class MetadataAllocator : public llvm::AllocatorBase<MetadataAllocator> {
private:
uint16_t Tag;
public:
constexpr MetadataAllocator(uint16_t tag) : Tag(tag) {}
MetadataAllocator() = delete;
void Reset() {}
SWIFT_RETURNS_NONNULL SWIFT_NODISCARD
void *Allocate(size_t size, size_t alignment);
using AllocatorBase<MetadataAllocator>::Allocate;
void Deallocate(const void *Ptr, size_t size, size_t Alignment);
using AllocatorBase<MetadataAllocator>::Deallocate;
void PrintStats() const {}
MetadataAllocator withTag(uint16_t Tag) {
MetadataAllocator Allocator = *this;
Allocator.Tag = Tag;
return Allocator;
}
};
#else
class MetadataAllocator {
public:
MetadataAllocator(uint16_t tag) {}
SWIFT_RETURNS_NONNULL SWIFT_NODISCARD
void *Allocate(size_t size, size_t alignment) {
if (alignment < sizeof(void*)) alignment = sizeof(void*);
void *ptr = nullptr;
if (SWIFT_UNLIKELY(posix_memalign(&ptr, alignment, size) != 0 || !ptr)) {
swift::crash("Could not allocate memory for type metadata.");
}
return ptr;
}
void Deallocate(const void *ptr, size_t size = 0, size_t Alignment = 0) {
return free(const_cast<void *>(ptr));
}
};
#endif
template <uint16_t StaticTag>
class TaggedMetadataAllocator : public MetadataAllocator {
public:
constexpr TaggedMetadataAllocator() : MetadataAllocator(StaticTag) {}
};
using RawPrivateMetadataState = uint8_t;
enum class PrivateMetadataState : RawPrivateMetadataState {
/// The metadata is being allocated.
Allocating,
/// The metadata has been allocated, but is not yet complete for
/// external layout: that is, it does not have a size.
Abstract,
/// The metadata has a complete external layout, but may not have
/// been fully initialized.
LayoutComplete,
/// The metadata has a complete external layout and has been fully
/// initialized, but has not yet satisfied its transitive completeness
/// requirements.
NonTransitiveComplete,
/// The metadata is fully complete. There should no longer be waiters.
Complete
};
inline bool operator<(PrivateMetadataState lhs, PrivateMetadataState rhs) {
return RawPrivateMetadataState(lhs) < RawPrivateMetadataState(rhs);
}
inline bool operator<=(PrivateMetadataState lhs, PrivateMetadataState rhs) {
return RawPrivateMetadataState(lhs) <= RawPrivateMetadataState(rhs);
}
inline bool operator>(PrivateMetadataState lhs, PrivateMetadataState rhs) {
return RawPrivateMetadataState(lhs) > RawPrivateMetadataState(rhs);
}
inline bool operator>=(PrivateMetadataState lhs, PrivateMetadataState rhs) {
return RawPrivateMetadataState(lhs) >= RawPrivateMetadataState(rhs);
}
inline bool satisfies(PrivateMetadataState state, MetadataState requirement) {
switch (requirement) {
case MetadataState::Abstract:
return state >= PrivateMetadataState::Abstract;
case MetadataState::LayoutComplete:
return state >= PrivateMetadataState::LayoutComplete;
case MetadataState::NonTransitiveComplete:
return state >= PrivateMetadataState::NonTransitiveComplete;
case MetadataState::Complete:
return state >= PrivateMetadataState::Complete;
}
swift_unreachable("unsupported requirement kind");
}
inline MetadataState getAccomplishedRequestState(PrivateMetadataState state) {
switch (state) {
case PrivateMetadataState::Allocating:
swift_unreachable("cannot call on allocating state");
case PrivateMetadataState::Abstract:
return MetadataState::Abstract;
case PrivateMetadataState::LayoutComplete:
return MetadataState::LayoutComplete;
case PrivateMetadataState::NonTransitiveComplete:
return MetadataState::NonTransitiveComplete;
case PrivateMetadataState::Complete:
return MetadataState::Complete;
}
swift_unreachable("bad state");
}
struct MetadataStateWithDependency {
/// The current state of the metadata.
PrivateMetadataState NewState;
/// The known dependency that the metadata has, if any.
MetadataDependency Dependency;
};
/// A typedef for simple global caches with stable addresses for the entries.
template <class EntryTy, uint16_t Tag>
using SimpleGlobalCache =
StableAddressConcurrentReadableHashMap<EntryTy,
TaggedMetadataAllocator<Tag>>;
struct ConcurrencyControl {
using LockType = SmallMutex;
LockType Lock;
};
template <class EntryType, uint16_t Tag>
class LockingConcurrentMapStorage {
// This class must fit within
// TargetGenericMetadataInstantiationCache::PrivateData. On 32-bit archs, that
// space is not large enough to accommodate a Mutex along with everything
// else. There, use a SmallMutex to squeeze into the available space.
using MutexTy = std::conditional_t<sizeof(void *) == 8 && sizeof(Mutex) <= 56,
Mutex, SmallMutex>;
StableAddressConcurrentReadableHashMap<EntryType,
TaggedMetadataAllocator<Tag>, MutexTy>
Map;
ConcurrencyControl Concurrency;
public:
LockingConcurrentMapStorage() {}
ConcurrencyControl &getConcurrency() { return Concurrency; }
template <class KeyType, class... ArgTys>
std::pair<EntryType*, bool>
getOrInsert(KeyType key, ArgTys &&...args) {
return Map.getOrInsert(key, args...);
}
template <class KeyType>
EntryType *find(KeyType key) {
return Map.find(key);
}
/// A default implementation for resolveEntry that assumes that the
/// key type is a lookup key for the map.
template <class KeyType>
EntryType *resolveExistingEntry(KeyType key) {
auto entry = Map.find(key);
assert(entry && "entry doesn't already exist!");
return entry;
}
};
/// A map for which there is a phase of initialization that is guaranteed
/// to be performed exclusively.
///
/// In addition to the requirements of ConcurrentMap, the entry type must
/// provide the following members:
///
/// /// An encapsulation of the status of the entry. The result type
/// /// of most operations.
/// using Status = ...;
///
/// /// Given that this is not the thread currently responsible for
/// /// initializing the entry, wait for the entry to complete.
/// Status await(ConcurrencyControl &concurrency, ArgTys...);
///
/// /// Perform allocation. If this returns a status, initialization
/// /// is skipped.
/// Optional<Status>
/// beginAllocation(WaitQueue::Worker &worker, ArgTys...);
///
/// /// Attempt to initialize an entry. This is called once for the entry,
/// /// immediately after construction, by the thread that successfully
/// /// constructed the entry.
/// Status beginInitialization(WaitQueue::Worker &worker, ArgTys...);
///
/// /// Perform a checkDependency operation. This only needs to be
/// /// implemented if checkDependency is called on the map.
/// MetadataStateWithDependency
/// checkDependency(ConcurrencyControl &concurrency, ArgTys...);
template <class EntryType,
class StorageType = LockingConcurrentMapStorage<EntryType, true>>
class LockingConcurrentMap {
StorageType Storage;
using Status = typename EntryType::Status;
using WaitQueue = typename EntryType::WaitQueue;
using Worker = typename WaitQueue::Worker;
using Waiter = typename WaitQueue::Waiter;
public:
LockingConcurrentMap() = default;
template <class KeyType, class... ArgTys>
std::pair<EntryType*, Status>
getOrInsert(KeyType key, ArgTys &&...args) {
Worker worker(Storage.getConcurrency().Lock);
auto result = Storage.getOrInsert(key, worker, args...);
auto entry = result.first;
// If we are not inserting the entry, we need to potentially block on
// currently satisfies our conditions.
if (!result.second) {
auto status =
entry->await(Storage.getConcurrency(), std::forward<ArgTys>(args)...);
return { entry, status };
}
// Okay, we inserted. We are responsible for allocating and
// subsequently trying to initialize the entry.
// Insertion should have called worker.createQueue(); tell the Worker
// object that we published it.
worker.flagCreatedQueueIsPublished();
// Allocation. This can fast-path and bypass initialization by returning
// a status.
if (auto status = entry->beginAllocation(worker, args...)) {
return { entry, *status };
}
// Initialization.
auto status = entry->beginInitialization(worker,
std::forward<ArgTys>(args)...);
return { entry, status };
}
template <class KeyType>
EntryType *find(KeyType key) {
return Storage.find(key);
}
/// Given that an entry already exists, await it.
template <class KeyType, class... ArgTys>
Status await(KeyType key, ArgTys &&...args) {
EntryType *entry = Storage.resolveExistingEntry(key);
return entry->await(Storage.getConcurrency(),
std::forward<ArgTys>(args)...);
}
/// If an entry already exists, await it; otherwise report failure.
template <class KeyType, class... ArgTys>
llvm::Optional<Status> tryAwaitExisting(KeyType key, ArgTys &&... args) {
EntryType *entry = Storage.find(key);
if (!entry) return None;
return entry->await(Storage.getConcurrency(),
std::forward<ArgTys>(args)...);
}
/// Given that an entry already exists, check whether it has an active
/// dependency.
template <class KeyType, class... ArgTys>
MetadataStateWithDependency
checkDependency(KeyType key, ArgTys &&...args) {
EntryType *entry = Storage.resolveExistingEntry(key);
return entry->checkDependency(Storage.getConcurrency(),
std::forward<ArgTys>(args)...);
}
};
/// A base class for metadata cache entries which supports an unfailing
/// one-phase allocation strategy that should not be done by trial.
///
/// In addition to the requirements of ConcurrentMap, subclasses should
/// provide:
///
/// /// Allocate the cached entry. This is not allowed to fail.
/// ValueType allocate(ArgTys...);
template <class Impl, class ValueType>
class SimpleLockingCacheEntryBase {
public:
using WaitQueue = SimpleAtomicWaitQueue<ConcurrencyControl::LockType>;
private:
static_assert(std::is_pointer<ValueType>::value,
"value type must be a pointer type");
static const uintptr_t IsWaitQueue = 1;
static WaitQueue *getAsWaitQueue(uintptr_t value) {
if (value & IsWaitQueue)
return reinterpret_cast<WaitQueue*>(value & ~IsWaitQueue);
return nullptr;
}
static ValueType castAsValue(uintptr_t value) {
assert(!(value & IsWaitQueue));
return reinterpret_cast<ValueType>(value);
}
std::atomic<uintptr_t> Value;
protected:
Impl &asImpl() { return static_cast<Impl &>(*this); }
const Impl &asImpl() const { return static_cast<const Impl &>(*this); }
SimpleLockingCacheEntryBase(WaitQueue::Worker &worker)
: Value(reinterpret_cast<uintptr_t>(worker.createQueue()) | IsWaitQueue) {}
public:
using Status = ValueType;
template <class... ArgTys>
Status await(ConcurrencyControl &concurrency, ArgTys &&...args) {
WaitQueue::Waiter waiter(concurrency.Lock);
// Load the value. If this is not a queue, we're done.
auto value = Value.load(std::memory_order_acquire);
if (getAsWaitQueue(value)) {
bool waited = waiter.tryReloadAndWait([&] {
// We can use a relaxed load because we're already ordered
// by the lock.
value = Value.load(std::memory_order_relaxed);
return getAsWaitQueue(value);
});
if (waited) {
// This load can be relaxed because we acquired the wait queue
// lock, which was released by the worker thread after
// initializing Value to the value.
value = Value.load(std::memory_order_relaxed);
assert(!getAsWaitQueue(value));
}
}
return castAsValue(value);
}
template <class... ArgTys>
llvm::Optional<Status> beginAllocation(WaitQueue::Worker &worker,
ArgTys &&... args) {
// Delegate to the implementation class.
ValueType origValue =
asImpl().allocate(std::forward<ArgTys>(args)...);
auto value = reinterpret_cast<uintptr_t>(origValue);
assert(!getAsWaitQueue(value) && "allocate returned an unaligned value");
// Publish the value, which unpublishes the queue.
worker.finishAndUnpublishQueue([&] {
Value.store(value, std::memory_order_release);
});
return origValue;
}
template <class... ArgTys>
Status beginInitialization(WaitQueue::Worker &worker,
ArgTys &&...args) {
swift_unreachable("beginAllocation always short-circuits");
}
};
/// A summary of the information from a generic signature that's
/// sufficient to compare arguments.
template<typename Runtime>
struct GenericSignatureLayout {
uint16_t NumKeyParameters = 0;
uint16_t NumWitnessTables = 0;
uint16_t NumPacks = 0;
uint16_t NumShapeClasses = 0;
const GenericPackShapeDescriptor *PackShapeDescriptors = nullptr;
GenericSignatureLayout(const RuntimeGenericSignature<Runtime> &sig)
: NumPacks(sig.getGenericPackShapeHeader().NumPacks),
NumShapeClasses(sig.getGenericPackShapeHeader().NumShapeClasses),
PackShapeDescriptors(sig.getGenericPackShapeDescriptors().data()) {
#ifndef NDEBUG
unsigned packIdx = 0;
#endif
for (const auto &gp : sig.getParams()) {
if (gp.hasKeyArgument()) {
#ifndef NDEBUG
if (gp.getKind() == GenericParamKind::TypePack) {
assert(packIdx < NumPacks);
assert(PackShapeDescriptors[packIdx].Kind
== GenericPackKind::Metadata);
assert(PackShapeDescriptors[packIdx].Index
== NumShapeClasses + NumKeyParameters);
assert(PackShapeDescriptors[packIdx].ShapeClass
< NumShapeClasses);
++packIdx;
}
#endif
++NumKeyParameters;
}
}
for (const auto &reqt : sig.getRequirements()) {
if (reqt.Flags.hasKeyArgument() &&
reqt.getKind() == GenericRequirementKind::Protocol) {
#ifndef NDEBUG
if (reqt.getFlags().isPackRequirement()) {
assert(packIdx < NumPacks);
assert(PackShapeDescriptors[packIdx].Kind
== GenericPackKind::WitnessTable);
assert(PackShapeDescriptors[packIdx].Index
== NumShapeClasses + NumKeyParameters + NumWitnessTables);
assert(PackShapeDescriptors[packIdx].ShapeClass
< NumShapeClasses);
++packIdx;
}
#endif
++NumWitnessTables;
}
}
#ifndef NDEBUG
assert(packIdx == NumPacks);
#endif
}
size_t sizeInWords() const {
return NumShapeClasses + NumKeyParameters + NumWitnessTables;
}
friend bool operator==(const GenericSignatureLayout<Runtime> &lhs,
const GenericSignatureLayout<Runtime> &rhs) {
if (lhs.NumKeyParameters != rhs.NumKeyParameters ||
lhs.NumWitnessTables != rhs.NumWitnessTables ||
lhs.NumShapeClasses != rhs.NumShapeClasses ||
lhs.NumPacks != rhs.NumPacks) {
return false;
}
for (unsigned i = 0; i < lhs.NumPacks; ++i) {
const auto &lhsElt = lhs.PackShapeDescriptors[i];
const auto &rhsElt = rhs.PackShapeDescriptors[i];
if (lhsElt.Kind != rhsElt.Kind ||
lhsElt.Index != rhsElt.Index ||
lhsElt.ShapeClass != rhsElt.ShapeClass)
return false;
}
return true;
}
friend bool operator!=(const GenericSignatureLayout<Runtime> &lhs,
const GenericSignatureLayout<Runtime> &rhs) {
return !(lhs == rhs);
}
};
/// A key value as provided to the concurrent map.
class MetadataCacheKey {
const void * const *Data;
GenericSignatureLayout<InProcess> Layout;
uint32_t Hash;
public:
/// Compare two witness tables, which may involving checking the
/// contents of their conformance descriptors.
static bool areWitnessTablesEqual(const WitnessTable *awt,
const WitnessTable *bwt) {
if (awt == bwt)
return true;
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
auto *aDescription = lookThroughOptionalConditionalWitnessTable(
reinterpret_cast<const RelativeWitnessTable*>(awt))->getDescription();
auto *bDescription = lookThroughOptionalConditionalWitnessTable(
reinterpret_cast<const RelativeWitnessTable*>(bwt))->getDescription();
#else
auto *aDescription = awt->getDescription();
auto *bDescription = bwt->getDescription();
#endif
return areConformanceDescriptorsEqual(aDescription, bDescription);
}
static void installGenericArguments(uint16_t numKeyArguments, uint16_t numPacks,
const GenericPackShapeDescriptor *packShapeDescriptors,
const void **dst, const void * const *src);
/// Compare two conformance descriptors, checking their contents if necessary.
static bool areConformanceDescriptorsEqual(
const ProtocolConformanceDescriptor *aDescription,
const ProtocolConformanceDescriptor *bDescription) {
if (aDescription == bDescription)
return true;
if (!aDescription->isSynthesizedNonUnique() ||
!bDescription->isSynthesizedNonUnique())
return aDescription == bDescription;
auto aType = aDescription->getCanonicalTypeMetadata();
auto bType = bDescription->getCanonicalTypeMetadata();
if (!aType || !bType)
return aDescription == bDescription;
return (aType == bType &&
aDescription->getProtocol() == bDescription->getProtocol());
}
private:
static bool areMetadataPacksEqual(const void *lhsPtr,
const void *rhsPtr,
uintptr_t count) {
MetadataPackPointer lhs(lhsPtr);
MetadataPackPointer rhs(rhsPtr);
// lhs is the user-supplied key, which might be on the stack.
// rhs is the stored key in the cache.
assert(rhs.getLifetime() == PackLifetime::OnHeap);
auto *lhsElt = lhs.getElements();
auto *rhsElt = rhs.getElements();
for (uintptr_t i = 0; i < count; ++i) {
if (lhsElt[i] != rhsElt[i])
return false;
}
return true;
}
static bool areWitnessTablePacksEqual(const void *lhsPtr,
const void *rhsPtr,
uintptr_t count) {
WitnessTablePackPointer lhs(lhsPtr);
WitnessTablePackPointer rhs(rhsPtr);
// lhs is the user-supplied key, which might be on the stack.
// rhs is the stored key in the cache.
assert(rhs.getLifetime() == PackLifetime::OnHeap);
auto *lhsElt = lhs.getElements();
auto *rhsElt = rhs.getElements();
for (uintptr_t i = 0; i < count; ++i) {
if (!areWitnessTablesEqual(lhsElt[i], rhsElt[i]))
return false;
}
return true;
}
public:
MetadataCacheKey(const GenericSignatureLayout<InProcess> &layout,
const void *const *data)
: Data(data), Layout(layout), Hash(computeHash()) {}
MetadataCacheKey(const GenericSignatureLayout<InProcess> &layout,
const void *const *data, uint32_t hash)
: Data(data), Layout(layout), Hash(hash) {}
bool operator==(const MetadataCacheKey &rhs) const {
// Compare the hashes.
if (hash() != rhs.hash()) return false;
// Compare the layouts.
if (Layout != rhs.Layout) return false;
// Compare the content.
auto *adata = begin();
auto *bdata = rhs.begin();
const uintptr_t *packCounts = reinterpret_cast<const uintptr_t *>(adata);
unsigned argIdx = 0;
// Compare pack lengths for shape classes.
for (unsigned i = 0; i != Layout.NumShapeClasses; ++i) {
if (adata[argIdx] != bdata[argIdx])
return false;
++argIdx;
}
auto *packs = Layout.PackShapeDescriptors;
unsigned packIdx = 0;
// Compare generic arguments for key parameters.
for (unsigned i = 0; i != Layout.NumKeyParameters; ++i) {
// Is this entry a metadata pack?
if (packIdx < Layout.NumPacks &&
packs[packIdx].Kind == GenericPackKind::Metadata &&
argIdx == packs[packIdx].Index) {
assert(packs[packIdx].ShapeClass < Layout.NumShapeClasses);
uintptr_t count = packCounts[packs[packIdx].ShapeClass];
if (!areMetadataPacksEqual(adata[argIdx], bdata[argIdx], count))
return false;
++packIdx;
++argIdx;
continue;
}
if (adata[argIdx] != bdata[argIdx])
return false;
++argIdx;
}
// Compare witness tables.
for (unsigned i = 0; i != Layout.NumWitnessTables; ++i) {
// Is this entry a witness table pack?
if (packIdx < Layout.NumPacks &&
packs[packIdx].Kind == GenericPackKind::WitnessTable &&
argIdx == packs[packIdx].Index) {
assert(packs[packIdx].ShapeClass < Layout.NumShapeClasses);
uintptr_t count = packCounts[packs[packIdx].ShapeClass];
if (!areWitnessTablePacksEqual(adata[argIdx], bdata[argIdx], count))
return false;
++packIdx;
++argIdx;
continue;
}
if (!areWitnessTablesEqual((const WitnessTable *)adata[argIdx],
(const WitnessTable *)bdata[argIdx]))
return false;
++argIdx;
}
assert(packIdx == Layout.NumPacks && "Missed a pack");
return true;
}
uint32_t hash() const {
return Hash;
}
const GenericSignatureLayout<InProcess> &layout() const { return Layout; }
friend llvm::hash_code hash_value(const MetadataCacheKey &key) {
return key.Hash;
}
const void * const *begin() const { return Data; }
const void * const *end() const { return Data + size(); }
unsigned size() const { return Layout.sizeInWords(); }
void installInto(const void **buffer) const {
MetadataCacheKey::installGenericArguments(
Layout.sizeInWords(),
Layout.NumPacks,
Layout.PackShapeDescriptors,
buffer, Data);
}
private:
uint32_t computeHash() const {
size_t H = 0x56ba80d1u * Layout.NumKeyParameters;
auto *packs = Layout.PackShapeDescriptors;
unsigned packIdx = 0;
auto update = [&H](uintptr_t value) {
H = (H >> 10) | (H << ((sizeof(uintptr_t) * 8) - 10));
H ^= (value ^ (value >> 19));
};
// FIXME: The first NumShapeClasses entries are pack counts;
// incorporate them into the hash
for (unsigned i = Layout.NumShapeClasses,
e = Layout.NumShapeClasses + Layout.NumKeyParameters;
i != e; ++i) {
// Is this entry a metadata pack?
if (packIdx < Layout.NumPacks &&
packs[packIdx].Kind == GenericPackKind::Metadata &&
i == packs[packIdx].Index) {
assert(packs[packIdx].ShapeClass < Layout.NumShapeClasses);
auto count = reinterpret_cast<uintptr_t>(Data[packs[packIdx].ShapeClass]);
++packIdx;
MetadataPackPointer pack(Data[i]);
for (unsigned j = 0; j < count; ++j)
update(reinterpret_cast<uintptr_t>(pack.getElements()[j]));
continue;
}
update(reinterpret_cast<uintptr_t>(Data[i]));
}
H *= 0x27d4eb2d;
// Rotate right by 10 and then truncate to 32 bits.
return uint32_t((H >> 10) | (H << ((sizeof(uintptr_t) * 8) - 10)));
}
};
/// A helper class for ConcurrentMap entry types which allows trailing objects
/// objects and automatically implements the getExtraAllocationSize methods
/// in terms of numTrailingObjects calls.
///
/// For each trailing object type T, the subclass must provide:
/// size_t numTrailingObjects(OverloadToken<T>) const;
/// static size_t numTrailingObjects(OverloadToken<T>, ...) const;
/// where the arguments to the latter are the arguments to getOrInsert,
/// including the key.
template <class Impl, class... Objects>
struct ConcurrentMapTrailingObjectsEntry
: swift::ABI::TrailingObjects<Impl, Objects...> {
protected:
using TrailingObjects =
swift::ABI::TrailingObjects<Impl, Objects...>;
Impl &asImpl() { return static_cast<Impl &>(*this); }
const Impl &asImpl() const { return static_cast<const Impl &>(*this); }
template<typename T>
using OverloadToken = typename TrailingObjects::template OverloadToken<T>;
public:
template <class KeyType, class... Args>
static size_t getExtraAllocationSize(const KeyType &key,
Args &&...args) {
return TrailingObjects::template additionalSizeToAlloc<Objects...>(
Impl::numTrailingObjects(OverloadToken<Objects>(), key, args...)...);
}
size_t getExtraAllocationSize() const {
return TrailingObjects::template additionalSizeToAlloc<Objects...>(
asImpl().numTrailingObjects(OverloadToken<Objects>())...);
}
};
/// Reserve the runtime extra space to use for its own tracking.
struct PrivateMetadataCompletionContext {
MetadataCompletionContext Public;
};
/// The alignment required for objects that will be stored in
/// PrivateMetadataTrackingInfo.
const size_t PrivateMetadataTrackingAlignment = 16;
/// The wait queue object that we create for metadata that are
/// being actively initialized right now.
struct alignas(PrivateMetadataTrackingAlignment) MetadataWaitQueue :
public AtomicWaitQueue<MetadataWaitQueue, ConcurrencyControl::LockType> {
/// A pointer to the completion context being used to complete this
/// metadata. This is only actually filled in if:
///
/// - the initializing thread is unable to complete the metadata,
/// but its request doesn't need it to, and
/// - the current completion context is non-zero. (Completion contexts
/// are initially zeroed, so this only happens if the initialization
/// actually stores to the context, which is uncommon.)
///
/// This should only be touched by the initializing thread, i.e. the
/// thread that holds the lock embedded in this object.
std::unique_ptr<PrivateMetadataCompletionContext> PersistentContext;
/// The dependency that is currently blocking this initialization.
/// This should only be touched while holding the global lock
/// for this metadata cache.
MetadataDependency BlockingDependency;
class Worker : public AtomicWaitQueue::Worker {
using super = AtomicWaitQueue::Worker;
PrivateMetadataState State = PrivateMetadataState::Allocating;
public:
Worker(ConcurrencyControl::LockType &globalLock) : super(globalLock) {}
void flagCreatedQueueIsPublished() {
// This method is called after successfully inserting an entry into
// the atomic storage, at a point that just assumes that a queue
// was created. However, we may not have created a queue if the
// metadata was completed during construction.
//
// Testing CurrentQueue to see if we published a queue is generally
// suspect because we might be looping and calling createQueue()
// on each iteration. However, the metadata cache system won't do
// this, at least on the path leading to the call to this method,
// so this works in this one case.
if (CurrentQueue) {
assert(State < PrivateMetadataState::Complete);
super::flagCreatedQueueIsPublished();
} else {
assert(State == PrivateMetadataState::Complete);
}
}
void setState(PrivateMetadataState newState) {
// It would be nice to assert isWorkerThread() here, but we need
// this to be callable before we've published the queue.
State = newState;
}
PrivateMetadataState getState() const {
assert(isWorkerThread() || State == PrivateMetadataState::Complete);
return State;
}
};
};
/// A record used to store information about an attempt to
/// complete a metadata when there's no active worker thread.
struct alignas(PrivateMetadataTrackingAlignment) SuspendedMetadataCompletion {
MetadataDependency BlockingDependency;
std::unique_ptr<PrivateMetadataCompletionContext> PersistentContext;
SuspendedMetadataCompletion(MetadataDependency blockingDependency,
PrivateMetadataCompletionContext *context)
: BlockingDependency(blockingDependency),
PersistentContext(context) {}
};
class PrivateMetadataTrackingInfo {
public:
using RawType = uintptr_t;
private:
enum : RawType {
StateMask = 0x7,
PointerIsWaitQueueMask = 0x8,
AllBitsMask = StateMask | PointerIsWaitQueueMask,
PointerMask = ~AllBitsMask,
};
static_assert(AllBitsMask < PrivateMetadataTrackingAlignment,
"too many bits for alignment");
RawType Data;
public:
// Some std::atomic implementations require a default constructor
// for no apparent reason.
PrivateMetadataTrackingInfo() : Data(0) {}
explicit PrivateMetadataTrackingInfo(PrivateMetadataState state)
: Data(RawType(state)) {}
explicit PrivateMetadataTrackingInfo(PrivateMetadataState state,
MetadataWaitQueue *queue)
: Data(RawType(state) | reinterpret_cast<RawType>(queue)
| PointerIsWaitQueueMask) {
assert(queue);
assert(!(reinterpret_cast<RawType>(queue) & AllBitsMask));
}
explicit PrivateMetadataTrackingInfo(PrivateMetadataState state,
SuspendedMetadataCompletion *suspended)
: Data(RawType(state) | reinterpret_cast<RawType>(suspended)) {
assert(!(reinterpret_cast<RawType>(suspended) & AllBitsMask));
}
static PrivateMetadataTrackingInfo
initial(MetadataWaitQueue::Worker &worker,
PrivateMetadataState initialState) {
worker.setState(initialState);
if (initialState != PrivateMetadataState::Complete)
return PrivateMetadataTrackingInfo(initialState, worker.createQueue());
return PrivateMetadataTrackingInfo(initialState);
}
PrivateMetadataState getState() const {
return PrivateMetadataState(Data & StateMask);
}
/// Does the state mean that we've allocated metadata?
bool hasAllocatedMetadata() const {
return getState() != PrivateMetadataState::Allocating;
}
bool isComplete() const {
return getState() == PrivateMetadataState::Complete;
}
bool hasWaitQueue() const {
return Data & PointerIsWaitQueueMask;
}
MetadataWaitQueue *getWaitQueue() const {
if (hasWaitQueue())
return reinterpret_cast<MetadataWaitQueue*>(Data & PointerMask);
return nullptr;
}
SuspendedMetadataCompletion *getSuspendedCompletion() const {
if (!hasWaitQueue())
return reinterpret_cast<SuspendedMetadataCompletion*>(Data & PointerMask);
return nullptr;
}
/// Return the blocking dependency for this metadata. Should only
/// be called while holding the global lock for the metadata cache.
MetadataDependency getBlockingDependency_locked() const {
if (auto queue = getWaitQueue())
return queue->BlockingDependency;
if (auto dependency = getSuspendedCompletion())
return dependency->BlockingDependency;
return MetadataDependency();
}
bool satisfies(MetadataState requirement) {
return swift::satisfies(getState(), requirement);
}
enum CheckResult {
/// The request is satisfied.
Satisfied,
/// The request is not satisfied, and the requesting thread
/// should report that immediately.
Unsatisfied,
/// The request is not satisfied, and the requesting thread
/// must wait for another thread to complete the initialization.
Wait,
/// The request is not satisfied, and the requesting thread
/// should try to complete the initialization itself.
Resume,
};
CheckResult check(MetadataRequest request) {
switch (getState()) {
// Always wait if the metadata is still allocating. Non-blocking
// requests still need to allocate abstract metadata that
// downstream consumers can report a dependency on.
case PrivateMetadataState::Allocating:
return Wait;
// We never need to do anything if we're complete. This is the
// most common result.
case PrivateMetadataState::Complete:
return Satisfied;
case PrivateMetadataState::Abstract:
case PrivateMetadataState::LayoutComplete:
case PrivateMetadataState::NonTransitiveComplete:
// If the request is satisfied, we don't need to do anything.
if (satisfies(request.getState()))
return Satisfied;
// If there isn't an running thread, we should take over
// initialization.
if (!hasWaitQueue())
return Resume;
// If this is a blocking request, we should wait.
if (request.isBlocking())
return Wait;
// Otherwise, we should return that the request is unsatisfied.
return Unsatisfied;
}
swift_unreachable("bad state");
}
};
/// Given that this is the initializing thread, and we've reached the
/// given state, should we block wait for further initialization?
inline bool shouldBlockInitialization(PrivateMetadataState currentState,
MetadataRequest request) {
switch (currentState) {
case PrivateMetadataState::Allocating:
swift_unreachable("initialization hasn't allocated?");
case PrivateMetadataState::Complete:
return false;
case PrivateMetadataState::Abstract:
case PrivateMetadataState::LayoutComplete:
case PrivateMetadataState::NonTransitiveComplete:
if (satisfies(currentState, request.getState()))
return false;
return request.isBlocking();
}
swift_unreachable("bad state");
}
/// Block until the dependency is satisfied.
void blockOnMetadataDependency(MetadataDependency request,
MetadataDependency dependency);
/// A cache entry class which provides the basic mechanisms for two-phase
/// metadata initialization. Suitable for more heavyweight metadata kinds
/// such as generic types and tuples. Does not provide the lookup-related
/// members.
///
/// The value type may be an arbitrary type, but it must be contextually
/// convertible to bool, and it must be default-constructible in a false
/// state.
///
/// In addition to the lookup members required by ConcurrentMap, concrete
/// implementations should provide:
///
/// /// A name describing the map; used in debugging diagnostics.
/// static const char *getName();
///
/// /// A constructor which should set up an entry. Note that this phase
/// /// of initialization may race with other threads attempting to set up
/// /// the same entry; do not do anything during it which might block or
/// /// need to be reverted.
/// /// The extra arguments are those provided to getOrInsert.
/// Entry(MetadataCacheKey key, ExtraArgTys...);
///
/// /// Allocate the metadata.
/// AllocationResult allocate(ExtraArgTys...);
///
/// /// Try to initialize the metadata.
/// MetadataStateWithDependency tryInitialize(Metadata *metadata,
/// PrivateMetadataState state,
/// PrivateMetadataCompletionContext *ctxt);
template <class Impl, class... Objects>
class MetadataCacheEntryBase
: public ConcurrentMapTrailingObjectsEntry<Impl, Objects...> {
using super = ConcurrentMapTrailingObjectsEntry<Impl, Objects...>;
public:
using ValueType = Metadata *;
using Status = MetadataResponse;
using WaitQueue = MetadataWaitQueue;
protected:
using TrailingObjectsEntry = super;
using super::asImpl;
private:
/// The current state of this metadata cache entry.
///
/// All modifications of this field are performed while holding
/// the global lock associated with this metadata cache. This is
/// because these modifications all coincide with changes to the wait
/// queue reference: either installing, removing, or replacing it.
/// The proper reference-counting of the queue object requires the
/// lock to be held during these operations. However, this field
/// can be read without holding the global lock, as part of the fast
/// path of several operations on the entry, most importantly
/// requesting the metadata.
///
/// Acquiring and releasing the global lock provides a certain
/// amount of memory ordering. Thus:
/// - Reads from the field performed in fast paths without holding
/// the lock must be acquires in order to properly order memory
/// with the initializing thread.
/// - Reads from the field that are performed under the lock can
/// be relaxed because the lock will properly order them.
/// - Modifications of the field can be stores rather than
/// compare-exchanges, although they must still use release
/// ordering to guarantee proper ordering with code in the
/// fast paths.
std::atomic<PrivateMetadataTrackingInfo> TrackingInfo;
static constexpr std::memory_order TrackingInfoIsLockedOrder =
std::memory_order_relaxed;
public:
MetadataCacheEntryBase(MetadataWaitQueue::Worker &worker,
PrivateMetadataState initialState =
PrivateMetadataState::Allocating)
: TrackingInfo(PrivateMetadataTrackingInfo::initial(worker, initialState)) {
}
// Note that having an explicit destructor here is important to make this
// a non-POD class and allow subclass fields to be allocated in our
// tail-padding.
~MetadataCacheEntryBase() {
}
/// Given that this thread doesn't own the right to initialize the
/// metadata, await the metadata being in the right state.
template <class... Args>
Status await(ConcurrencyControl &concurrency, MetadataRequest request,
Args &&...extraArgs) {
return awaitSatisfyingState(concurrency, request);
}
Status getStatusToReturn(PrivateMetadataState state) {
assert(state != PrivateMetadataState::Allocating);
return { asImpl().getValue(), getAccomplishedRequestState(state) };
}
/// The expected return type of allocate.
struct AllocationResult {
Metadata *Value;
PrivateMetadataState State;
};
/// Perform the allocation operation.
template <class... Args>
llvm::Optional<Status> beginAllocation(MetadataWaitQueue::Worker &worker,
MetadataRequest request,
Args &&... args) {
// Returning a non-None value here will preempt initialization, so we
// should only do it if we're reached PrivateMetadataState::Complete.
// We can skip allocation if we were allocated during construction.
auto state = worker.getState();
if (state != PrivateMetadataState::Allocating) {
#ifndef NDEBUG
// We've already published the metadata as part of construction,
// so we can verify that the mangled name round-trips.
if (asImpl().allowMangledNameVerification(std::forward<Args>(args)...))
verifyMangledNameRoundtrip(asImpl().getValue());
#endif
// Skip initialization, too, if we're fully complete.
if (state == PrivateMetadataState::Complete) {
assert(!worker.isWorkerThread());
return Status{asImpl().getValue(), MetadataState::Complete};
}
// Otherwise, go directly to the initialization phase.
assert(worker.isWorkerThread());
return None;
}
assert(worker.isWorkerThread());
// Allocate the metadata.
AllocationResult allocationResult =
asImpl().allocate(std::forward<Args>(args)...);
state = allocationResult.State;
worker.setState(state);
// Set the self-link before publishing the new status.
auto value = const_cast<ValueType>(allocationResult.Value);
asImpl().setValue(value);
// If allocation gave us complete metadata, we can short-circuit
// initialization; publish and report that we've finished.
if (state == PrivateMetadataState::Complete) {
finishAndPublishProgress(worker, MetadataDependency(), nullptr);
#ifndef NDEBUG
// Now that we've published the allocated metadata, verify that
// the mangled name round-trips.
if (asImpl().allowMangledNameVerification(std::forward<Args>(args)...))
verifyMangledNameRoundtrip(value);
#endif
return Status{allocationResult.Value, MetadataState::Complete};
}
// Otherwise, we always try at least one round of initialization
// even if the request is for abstract metadata, just to avoid
// doing more unnecessary bookkeeping. Publish the current
// state so that e.g. recursive uses of this metadata are
// satisfiable.
notifyWaitingThreadsOfProgress(worker, MetadataDependency());
#ifndef NDEBUG
// Now that we've published the allocated metadata, verify that
// the mangled name round-trips.
if (asImpl().allowMangledNameVerification(std::forward<Args>(args)...))
verifyMangledNameRoundtrip(value);
#endif
return None;
}
template <class... Args>
static bool allowMangledNameVerification(Args &&...args) {
// By default, always allow mangled name verification.
return true;
}
/// Begin initialization immediately after allocation.
template <class... Args>
Status beginInitialization(WaitQueue::Worker &worker,
MetadataRequest request, Args &&...args) {
// Note that we ignore the extra arguments; those are just for the
// constructor and allocation.
return doInitialization(worker, request);
}
private:
/// Try to complete the metadata.
///
/// This is the initializing thread. The lock is not held.
Status doInitialization(WaitQueue::Worker &worker,
MetadataRequest request) {
assert(worker.isWorkerThread());
assert(worker.getState() > PrivateMetadataState::Allocating);
auto value = asImpl().getValue();
auto queue = worker.getPublishedQueue();
// Figure out a completion context to use.
static const constexpr PrivateMetadataCompletionContext zeroContext = {};
PrivateMetadataCompletionContext scratchContext;
PrivateMetadataCompletionContext *context;
if (auto persistent = queue->PersistentContext.get()) {
context = persistent;
} else {
// Initialize the scratch context to zero.
scratchContext = zeroContext;
context = &scratchContext;
}
// Try the complete the metadata. This only loops if initialization
// has a dependency, but the new dependency is resolved when we go to
// add ourselves to its queue.
while (true) {
assert(worker.getState() < PrivateMetadataState::Complete);
// Try a round of initialization.
auto oldState = worker.getState();
MetadataStateWithDependency MetadataStateWithDependency =
asImpl().tryInitialize(value, oldState, context);
auto newState = MetadataStateWithDependency.NewState;
auto dependency = MetadataStateWithDependency.Dependency;
worker.setState(newState);
assert(oldState <= newState &&
"initialization regressed to an earlier state");
// If we don't have a dependency, we're finished.
bool done, willWait;
if (!dependency) {
assert(newState == PrivateMetadataState::Complete &&
"initialization didn't report a dependency but isn't complete");
done = true;
willWait = false;
} else {
assert(newState != PrivateMetadataState::Complete &&
"initialization reported a dependency but is complete");
done = false;
willWait = shouldBlockInitialization(newState, request);
}
// If we're not going to wait, but we're not done, and the
// completion context is no longer zero, copy the completion
// context into the persistent state (if it isn't already there).
if (!willWait && !done && !queue->PersistentContext) {
if (memcmp(&scratchContext, &zeroContext, sizeof(zeroContext)) != 0)
queue->PersistentContext.reset(
new PrivateMetadataCompletionContext(scratchContext));
}
// If we're not going to wait, publish the new state and finish
// execution.
if (!willWait) {
finishAndPublishProgress(worker, dependency,
queue->PersistentContext.release());
return getStatusToReturn(newState);
}
// We're going to wait. If we've made progress, make sure we notify
// any waiting threads about that progress; if they're satisfied
// by that progress, they shouldn't be blocked.
if (oldState < newState) {
notifyWaitingThreadsOfProgress(worker, dependency);
// This might change the queue pointer.
queue = worker.getPublishedQueue();
assert(!queue->PersistentContext ||
queue->PersistentContext.get() == context);
}
// Block on the target dependency.
blockOnDependency(worker, request, MetadataStateWithDependency.Dependency);
// Go back and try initialization again.
}
}
/// Publish a new metadata state. Wake waiters if we had any.
void finishAndPublishProgress(MetadataWaitQueue::Worker &worker,
MetadataDependency dependency,
PrivateMetadataCompletionContext *context) {
auto newState = worker.getState();
// Create a suspended completion if there's something to record there.
// This will be deallocated when some other thread takes over
// initialization.
SuspendedMetadataCompletion *suspended = nullptr;
if (dependency || context) {
assert(newState != PrivateMetadataState::Complete);
suspended = new SuspendedMetadataCompletion(dependency, context);
}
// We're done with this worker thread; replace the wait queue
// with the dependency record. We still want to do these stores
// under the lock, though.
worker.finishAndUnpublishQueue([&] {
auto newInfo = PrivateMetadataTrackingInfo(newState, suspended);
assert(newInfo.hasAllocatedMetadata());
// Set the new state and unpublish the reference to the queue.
TrackingInfo.store(newInfo, std::memory_order_release);
});
}
/// Notify any waiting threads that metadata has made progress.
void notifyWaitingThreadsOfProgress(MetadataWaitQueue::Worker &worker,
MetadataDependency dependency) {
worker.maybeReplaceQueue([&] {
MetadataWaitQueue *oldQueue = worker.getPublishedQueue();
MetadataWaitQueue *newQueue;
// If there aren't any other references to the existing queue,
// we don't need to replace anything.
if (oldQueue->isUniquelyReferenced_locked()) {
newQueue = oldQueue;
// Otherwise, make a new queue. Cycling queues this way allows
// waiting threads to unblock if they are satisfied with the given
// progress. If they aren't, they'll wait on the new queue.
} else {
newQueue = worker.createReplacementQueue();
newQueue->PersistentContext = std::move(oldQueue->PersistentContext);
}
// Update the current blocking dependency.
newQueue->BlockingDependency = dependency;
// Only the worker thread modifies TrackingInfo, so we can do a
// simple store instead of a compare-exchange.
PrivateMetadataTrackingInfo newTrackingInfo =
PrivateMetadataTrackingInfo(worker.getState(), newQueue);
TrackingInfo.store(newTrackingInfo, std::memory_order_release);
// We signal to maybeReplaceQueue that replacement is required by
// returning a non-null queue.
return (newQueue != oldQueue ? newQueue : nullptr);
});
}
/// Given that the request is not satisfied by the current state of
/// the metadata, wait for the request to be satisfied.
///
/// If there's a thread that currently owns initialization for this
/// metadata (i.e. it has published a wait queue into TrackingInfo),
/// we simply wait on that thread. Otherwise, we take over
/// initialization on the current thread.
///
/// If the request is non-blocking, we do not wait, but we may need
/// to take over initialization.
Status awaitSatisfyingState(ConcurrencyControl &concurrency,
MetadataRequest request) {
// Try loading the current state before acquiring the lock.
auto trackingInfo = TrackingInfo.load(std::memory_order_acquire);
// Return if the current state says to do so.
auto checkResult = trackingInfo.check(request);
if (checkResult == PrivateMetadataTrackingInfo::Satisfied ||
checkResult == PrivateMetadataTrackingInfo::Unsatisfied)
return getStatusToReturn(trackingInfo.getState());
MetadataWaitQueue::Worker worker(concurrency.Lock);
std::unique_ptr<SuspendedMetadataCompletion> suspendedCompletionToDelete;
worker.withLock([&](MetadataWaitQueue::Worker::Operation &op) {
assert(!worker.isWorkerThread());
// Reload the tracking info, since it might have been
// changed by a concurrent worker thread.
trackingInfo = TrackingInfo.load(TrackingInfoIsLockedOrder);
checkResult = trackingInfo.check(request);
switch (checkResult) {
// Either the request is satisfied or we should tell the
// requester immediately that it isn't.
case PrivateMetadataTrackingInfo::Satisfied:
case PrivateMetadataTrackingInfo::Unsatisfied:
return;
// There's currently an initializing thread for this metadata,
// and either we've got a blocking request that isn't yet
// satisfied or the metadata hasn't even been allocated yet.
// Wait on the thread and then call this lambda again.
case PrivateMetadataTrackingInfo::Wait:
assert(trackingInfo.hasWaitQueue());
return op.waitAndRepeat(trackingInfo.getWaitQueue());
// There isn't a thread currently building the metadata,
// and the request isn't satisfied. Become the initializing
// thread and try to build the metadata ourselves.
case PrivateMetadataTrackingInfo::Resume: {
assert(!trackingInfo.hasWaitQueue());
// Create a queue and publish it, taking over execution.
auto queue = op.createQueue();
// Copy the information from the suspended completion, if any,
// into the queue.
if (auto suspendedCompletion =
trackingInfo.getSuspendedCompletion()) {
queue->BlockingDependency =
suspendedCompletion->BlockingDependency;
queue->PersistentContext =
std::move(suspendedCompletion->PersistentContext);
// Make sure we delete the suspended completion later.
suspendedCompletionToDelete.reset(suspendedCompletion);
}
// Publish the wait queue we just made.
auto newTrackingInfo =
PrivateMetadataTrackingInfo(trackingInfo.getState(), queue);
TrackingInfo.store(newTrackingInfo, std::memory_order_release);
return op.flagQueueIsPublished(queue);
}
}
});
// If the check result wasn't Resume, it must have been Satisfied
// or Unsatisfied, and we should return immediately.
if (checkResult != PrivateMetadataTrackingInfo::Resume) {
assert(checkResult == PrivateMetadataTrackingInfo::Satisfied ||
checkResult == PrivateMetadataTrackingInfo::Unsatisfied);
return getStatusToReturn(trackingInfo.getState());
}
// Otherwise, we published and are now the worker thread owning
// this metadata's initialization. Do the initialization.
worker.setState(trackingInfo.getState());
return doInitialization(worker, request);
}
/// Given that we are the active worker thread for this initialization,
/// block until the given dependency is satisfied.
void blockOnDependency(MetadataWaitQueue::Worker &worker,
MetadataRequest request,
MetadataDependency dependency) {
assert(worker.isWorkerThread());
assert(request.isBlocking());
// Formulate the request for this metadata as a dependency.
auto requestDependency = MetadataDependency(asImpl().getValue(),
request.getState());
// Block on the metadata dependency.
blockOnMetadataDependency(requestDependency, dependency);
}
public:
/// Check whether this metadata has reached the given state and,
/// if not, return a further metadata dependency if possible.
///
/// It's possible for this to not return a dependency, but only if some
/// other thread is currently still attempting to complete the first
/// full round of attempted initialization. It's also possible
/// for the reported dependency to be out of date.
MetadataStateWithDependency
checkDependency(ConcurrencyControl &concurrency, MetadataState requirement) {
// Do a quick check while not holding the lock.
auto curInfo = TrackingInfo.load(std::memory_order_acquire);
if (curInfo.satisfies(requirement))
return { curInfo.getState(), MetadataDependency() };
// Alright, try again while holding the lock, which is required
// in order to safely read the blocking dependency.
return concurrency.Lock.withLock([&]() -> MetadataStateWithDependency {
curInfo = TrackingInfo.load(TrackingInfoIsLockedOrder);
if (curInfo.satisfies(requirement))
return { curInfo.getState(), MetadataDependency() };
return { curInfo.getState(), curInfo.getBlockingDependency_locked() };
});
}
};
/// An convenient subclass of MetadataCacheEntryBase which provides
/// metadata lookup using a variadic key.
template <class Impl, class... Objects>
class VariadicMetadataCacheEntryBase :
public MetadataCacheEntryBase<Impl, const void *, Objects...> {
using super = MetadataCacheEntryBase<Impl, const void *, Objects...>;
protected:
using super::asImpl;
using ValueType = typename super::ValueType;
using TrailingObjectsEntry = typename super::TrailingObjectsEntry;
friend TrailingObjectsEntry;
using TrailingObjects = typename super::TrailingObjects;
friend TrailingObjects;
template<typename T>
using OverloadToken = typename TrailingObjects::template OverloadToken<T>;
size_t numTrailingObjects(OverloadToken<const void *>) const {
return Layout.sizeInWords();
}
template <class... Args>
static size_t numTrailingObjects(OverloadToken<const void *>,
const MetadataCacheKey &key,
Args &&...extraArgs) {
return key.size();
}
private:
/// These are set during construction and never changed.
const GenericSignatureLayout<InProcess> Layout;
const uint32_t Hash;
/// Valid if TrackingInfo.getState() >= PrivateMetadataState::Abstract.
ValueType Value;
friend super;
ValueType getValue() {
return Value;
}
void setValue(ValueType value) {
Value = value;
}
public:
VariadicMetadataCacheEntryBase(const MetadataCacheKey &key,
MetadataWaitQueue::Worker &worker,
PrivateMetadataState initialState,
ValueType value)
: super(worker, initialState),
Layout(key.layout()),
Hash(key.hash()),
Value(value) {
assert((value != nullptr) ==
(initialState != PrivateMetadataState::Allocating));
key.installInto(this->template getTrailingObjects<const void *>());
}
MetadataCacheKey getKey() const {
return MetadataCacheKey(Layout,
this->template getTrailingObjects<const void*>(),
Hash);
}
intptr_t getKeyIntValueForDump() const {
return Hash;
}
friend llvm::hash_code hash_value(const VariadicMetadataCacheEntryBase<Impl, Objects...> &value) {
return hash_value(value.getKey());
}
bool matchesKey(const MetadataCacheKey &key) const {
return key == getKey();
}
};
template <class EntryType, uint16_t Tag>
class MetadataCache :
public LockingConcurrentMap<EntryType,
LockingConcurrentMapStorage<EntryType, Tag>> {
};
} // namespace swift
#endif // SWIFT_RUNTIME_METADATACACHE_H