Files
gps/tools/local/circt/include/llvm/CAS/OnDiskGraphDB.h
T
2026-07-12 11:01:51 +03:00

528 lines
18 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
/// \file
/// This declares OnDiskGraphDB, an ondisk CAS database with a fixed length
/// hash. This is the class that implements the database storage scheme without
/// exposing the hashing algorithm.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CAS_ONDISKGRAPHDB_H
#define LLVM_CAS_ONDISKGRAPHDB_H
#include "llvm/ADT/PointerUnion.h"
#include "llvm/CAS/OnDiskCASLogger.h"
#include "llvm/CAS/OnDiskDataAllocator.h"
#include "llvm/CAS/OnDiskTrieRawHashMap.h"
#include <atomic>
namespace llvm::cas::ondisk {
/// Standard 8 byte reference inside OnDiskGraphDB.
class InternalRef {
public:
FileOffset getFileOffset() const { return FileOffset(Data); }
uint64_t getRawData() const { return Data; }
static InternalRef getFromRawData(uint64_t Data) { return InternalRef(Data); }
static InternalRef getFromOffset(FileOffset Offset) {
return InternalRef(Offset.get());
}
friend bool operator==(InternalRef LHS, InternalRef RHS) {
return LHS.Data == RHS.Data;
}
private:
InternalRef(FileOffset Offset) : Data((uint64_t)Offset.get()) {}
InternalRef(uint64_t Data) : Data(Data) {}
uint64_t Data;
};
/// Compact 4 byte reference inside OnDiskGraphDB for smaller references.
class InternalRef4B {
public:
FileOffset getFileOffset() const { return FileOffset(Data); }
uint32_t getRawData() const { return Data; }
/// Shrink to 4B reference.
static std::optional<InternalRef4B> tryToShrink(InternalRef Ref) {
uint64_t Offset = Ref.getRawData();
if (Offset > UINT32_MAX)
return std::nullopt;
return InternalRef4B(Offset);
}
operator InternalRef() const {
return InternalRef::getFromOffset(getFileOffset());
}
private:
friend class InternalRef;
InternalRef4B(uint32_t Data) : Data(Data) {}
uint32_t Data;
};
/// Array of internal node references.
class InternalRefArrayRef {
public:
size_t size() const { return Size; }
bool empty() const { return !Size; }
class iterator
: public iterator_facade_base<iterator, std::random_access_iterator_tag,
const InternalRef> {
public:
bool operator==(const iterator &RHS) const { return I == RHS.I; }
InternalRef operator*() const {
if (auto *Ref = dyn_cast<const InternalRef *>(I))
return *Ref;
return InternalRef(*cast<const InternalRef4B *>(I));
}
bool operator<(const iterator &RHS) const {
assert(isa<const InternalRef *>(I) == isa<const InternalRef *>(RHS.I));
if (auto *Ref = dyn_cast<const InternalRef *>(I))
return Ref < cast<const InternalRef *>(RHS.I);
return cast<const InternalRef4B *>(I) -
cast<const InternalRef4B *>(RHS.I);
}
ptrdiff_t operator-(const iterator &RHS) const {
assert(isa<const InternalRef *>(I) == isa<const InternalRef *>(RHS.I));
if (auto *Ref = dyn_cast<const InternalRef *>(I))
return Ref - cast<const InternalRef *>(RHS.I);
return cast<const InternalRef4B *>(I) -
cast<const InternalRef4B *>(RHS.I);
}
iterator &operator+=(ptrdiff_t N) {
if (auto *Ref = dyn_cast<const InternalRef *>(I))
I = Ref + N;
else
I = cast<const InternalRef4B *>(I) + N;
return *this;
}
iterator &operator-=(ptrdiff_t N) {
if (auto *Ref = dyn_cast<const InternalRef *>(I))
I = Ref - N;
else
I = cast<const InternalRef4B *>(I) - N;
return *this;
}
InternalRef operator[](ptrdiff_t N) const { return *(this->operator+(N)); }
iterator() = default;
uint64_t getOpaqueData() const { return uintptr_t(I.getOpaqueValue()); }
static iterator fromOpaqueData(uint64_t Opaque) {
return iterator(
PointerUnion<const InternalRef *,
const InternalRef4B *>::getFromOpaqueValue((void *)
Opaque));
}
private:
friend class InternalRefArrayRef;
explicit iterator(
PointerUnion<const InternalRef *, const InternalRef4B *> I)
: I(I) {}
PointerUnion<const InternalRef *, const InternalRef4B *> I;
};
bool operator==(const InternalRefArrayRef &RHS) const {
return size() == RHS.size() && std::equal(begin(), end(), RHS.begin());
}
iterator begin() const { return iterator(Begin); }
iterator end() const { return begin() + Size; }
/// Array accessor.
InternalRef operator[](ptrdiff_t N) const { return begin()[N]; }
bool is4B() const { return isa<const InternalRef4B *>(Begin); }
bool is8B() const { return isa<const InternalRef *>(Begin); }
ArrayRef<uint8_t> getBuffer() const {
if (is4B()) {
auto *B = cast<const InternalRef4B *>(Begin);
return ArrayRef((const uint8_t *)B, sizeof(InternalRef4B) * Size);
}
auto *B = cast<const InternalRef *>(Begin);
return ArrayRef((const uint8_t *)B, sizeof(InternalRef) * Size);
}
InternalRefArrayRef(std::nullopt_t = std::nullopt) {
// This is useful so that all the casts in the \p iterator functions can
// operate without needing to check for a null value.
static InternalRef PlaceHolder = InternalRef::getFromRawData(0);
Begin = &PlaceHolder;
}
InternalRefArrayRef(ArrayRef<InternalRef> Refs)
: Begin(Refs.begin()), Size(Refs.size()) {}
InternalRefArrayRef(ArrayRef<InternalRef4B> Refs)
: Begin(Refs.begin()), Size(Refs.size()) {}
private:
PointerUnion<const InternalRef *, const InternalRef4B *> Begin;
size_t Size = 0;
};
/// Reference to a node. The node's data may not be stored in the database.
/// An \p ObjectID instance can only be used with the \p OnDiskGraphDB instance
/// it came from. \p ObjectIDs from different \p OnDiskGraphDB instances are not
/// comparable.
class ObjectID {
public:
uint64_t getOpaqueData() const { return Opaque; }
static ObjectID fromOpaqueData(uint64_t Opaque) { return ObjectID(Opaque); }
friend bool operator==(const ObjectID &LHS, const ObjectID &RHS) {
return LHS.Opaque == RHS.Opaque;
}
friend bool operator!=(const ObjectID &LHS, const ObjectID &RHS) {
return !(LHS == RHS);
}
private:
explicit ObjectID(uint64_t Opaque) : Opaque(Opaque) {}
uint64_t Opaque;
};
/// Handle for a loaded node object.
class ObjectHandle {
public:
explicit ObjectHandle(uint64_t Opaque) : Opaque(Opaque) {}
uint64_t getOpaqueData() const { return Opaque; }
LLVM_ABI static ObjectHandle fromFileOffset(FileOffset Offset);
LLVM_ABI static ObjectHandle fromMemory(uintptr_t Ptr);
friend bool operator==(const ObjectHandle &LHS, const ObjectHandle &RHS) {
return LHS.Opaque == RHS.Opaque;
}
friend bool operator!=(const ObjectHandle &LHS, const ObjectHandle &RHS) {
return !(LHS == RHS);
}
private:
uint64_t Opaque;
};
/// Iterator for ObjectID.
class object_refs_iterator
: public iterator_facade_base<object_refs_iterator,
std::random_access_iterator_tag, ObjectID> {
public:
bool operator==(const object_refs_iterator &RHS) const { return I == RHS.I; }
ObjectID operator*() const {
return ObjectID::fromOpaqueData((*I).getRawData());
}
bool operator<(const object_refs_iterator &RHS) const { return I < RHS.I; }
ptrdiff_t operator-(const object_refs_iterator &RHS) const {
return I - RHS.I;
}
object_refs_iterator &operator+=(ptrdiff_t N) {
I += N;
return *this;
}
object_refs_iterator &operator-=(ptrdiff_t N) {
I -= N;
return *this;
}
ObjectID operator[](ptrdiff_t N) const { return *(this->operator+(N)); }
object_refs_iterator() = default;
object_refs_iterator(InternalRefArrayRef::iterator I) : I(I) {}
uint64_t getOpaqueData() const { return I.getOpaqueData(); }
static object_refs_iterator fromOpaqueData(uint64_t Opaque) {
return InternalRefArrayRef::iterator::fromOpaqueData(Opaque);
}
private:
InternalRefArrayRef::iterator I;
};
using object_refs_range = llvm::iterator_range<object_refs_iterator>;
/// On-disk CAS nodes database, independent of a particular hashing algorithm.
class OnDiskGraphDB {
public:
/// Associate data & references with a particular object ID. If there is
/// already a record for this object the operation is a no-op. \param ID the
/// object ID to associate the data & references with. \param Refs references
/// \param Data data buffer.
LLVM_ABI_FOR_TEST Error store(ObjectID ID, ArrayRef<ObjectID> Refs,
ArrayRef<char> Data);
/// Associates the data of a file with a particular object ID. If there is
/// already a record for this object the operation is a no-op.
///
/// This is more than a convenience variant of \c store(), \c storeFile() can
/// perform optimizations that reduce I/O and disk space consumption.
///
/// If there are any concurrent modifications to the file, the contents in the
/// CAS may be corrupt.
///
/// \param ID the object ID to associate the data with.
/// \param FilePath the path of the file data.
LLVM_ABI_FOR_TEST Error storeFile(ObjectID ID, StringRef FilePath);
/// \returns \p nullopt if the object associated with \p Ref does not exist.
LLVM_ABI_FOR_TEST Expected<std::optional<ObjectHandle>> load(ObjectID Ref);
/// \returns the hash bytes digest for the object reference.
ArrayRef<uint8_t> getDigest(ObjectID Ref) const {
// ObjectID should be valid to fetch Digest.
return cantFail(getDigest(getInternalRef(Ref)));
}
/// Form a reference for the provided hash. The reference can be used as part
/// of a CAS object even if it's not associated with an object yet.
LLVM_ABI_FOR_TEST Expected<ObjectID> getReference(ArrayRef<uint8_t> Hash);
/// Get an existing reference to the object \p Digest.
///
/// Returns \p nullopt if the object is not stored in this CAS.
LLVM_ABI_FOR_TEST std::optional<ObjectID>
getExistingReference(ArrayRef<uint8_t> Digest, bool CheckUpstream = true);
/// Check whether the object associated with \p Ref is stored in the CAS.
/// Note that this function will fault-in according to the policy.
LLVM_ABI Expected<bool> isMaterialized(ObjectID Ref);
/// Check whether the object associated with \p Ref is stored in the CAS.
/// Note that this function does not fault-in.
bool containsObject(ObjectID Ref, bool CheckUpstream = true) const {
auto Presence = getObjectPresence(Ref, CheckUpstream);
if (!Presence) {
consumeError(Presence.takeError());
return false;
}
switch (*Presence) {
case ObjectPresence::Missing:
return false;
case ObjectPresence::InPrimaryDB:
return true;
case ObjectPresence::OnlyInUpstreamDB:
return true;
}
llvm_unreachable("Unknown ObjectPresence enum");
}
/// \returns the data part of the provided object handle.
LLVM_ABI_FOR_TEST ArrayRef<char> getObjectData(ObjectHandle Node) const;
/// \returns the object referenced by the provided object handle.
object_refs_range getObjectRefs(ObjectHandle Node) const {
InternalRefArrayRef Refs = getInternalRefs(Node);
return make_range(Refs.begin(), Refs.end());
}
/// Encapsulates file info for an underlying object node.
struct FileBackedData {
/// The data of the object node.
ArrayRef<char> Data;
struct FileInfoTy {
/// The file path of the object node.
std::string FilePath;
/// Whether the file of the object leaf node has an extra nul appended at
/// the end. If the file is copied the extra nul needs to be removed.
bool IsFileNulTerminated;
};
/// File information for the object, if available.
std::optional<FileInfoTy> FileInfo;
};
/// Provides access to the underlying file path, that represents an object
/// leaf node, when available.
///
/// This enables reducing I/O and disk space consumption, i.e. instead of
/// loading the data in memory and then writing it to a file, the client could
/// clone the underlying file directly. The client *must not* write to or
/// delete the underlying file, the path is provided only for reading/copying.
LLVM_ABI FileBackedData
getInternalFileBackedObjectData(ObjectHandle Node) const;
/// \returns Total size of stored objects.
///
/// NOTE: There's a possibility that the returned size is not including a
/// large object if the process crashed right at the point of inserting it.
LLVM_ABI_FOR_TEST size_t getStorageSize() const;
/// \returns The precentage of space utilization of hard space limits.
///
/// Return value is an integer between 0 and 100 for percentage.
LLVM_ABI unsigned getHardStorageLimitUtilization() const;
LLVM_ABI void print(raw_ostream &OS) const;
/// Hashing function type for validation.
using HashingFuncT = function_ref<void(
ArrayRef<ArrayRef<uint8_t>>, ArrayRef<char>, SmallVectorImpl<uint8_t> &)>;
/// Validate the OnDiskGraphDB.
///
/// \param Deep if true, rehash all the objects to ensure no data
/// corruption in stored objects, otherwise just validate the structure of
/// CAS database.
/// \param Hasher is the hashing function used for objects inside CAS.
LLVM_ABI Error validate(bool Deep, HashingFuncT Hasher) const;
/// Checks that \p ID exists in the index. It is allowed to not have data
/// associated with it.
LLVM_ABI_FOR_TEST Error validateObjectID(ObjectID ID) const;
/// How to fault-in nodes if an upstream database is used.
enum class FaultInPolicy {
/// Copy only the requested node.
SingleNode,
/// Copy the the entire graph of a node.
FullTree,
};
/// Open the on-disk store from a directory.
///
/// \param Path directory for the on-disk store. The directory will be created
/// if it doesn't exist.
/// \param HashName Identifier name for the hashing algorithm that is going to
/// be used.
/// \param HashByteSize Size for the object digest hash bytes.
/// \param UpstreamDB Optional on-disk store to be used for faulting-in nodes
/// if they don't exist in the primary store. The upstream store is only used
/// for reading nodes, new nodes are only written to the primary store. User
/// need to make sure \p UpstreamDB outlives current instance of
/// OnDiskGraphDB and the common usage is to have an \p UnifiedOnDiskCache to
/// manage both.
/// \param Policy If \p UpstreamDB is provided, controls how nodes are copied
/// to primary store. This is recorded at creation time and subsequent opens
/// need to pass the same policy otherwise the \p open will fail.
LLVM_ABI_FOR_TEST static Expected<std::unique_ptr<OnDiskGraphDB>>
open(StringRef Path, StringRef HashName, unsigned HashByteSize,
OnDiskGraphDB *UpstreamDB = nullptr,
std::shared_ptr<OnDiskCASLogger> Logger = nullptr,
FaultInPolicy Policy = FaultInPolicy::FullTree);
LLVM_ABI_FOR_TEST ~OnDiskGraphDB();
private:
/// Forward declaration for a proxy for an ondisk index record.
struct IndexProxy;
enum class ObjectPresence {
Missing,
InPrimaryDB,
OnlyInUpstreamDB,
};
/// Check if object exists and if it is on upstream only.
LLVM_ABI_FOR_TEST Expected<ObjectPresence>
getObjectPresence(ObjectID Ref, bool CheckUpstream) const;
/// When \p load is called for a node that doesn't exist, this function tries
/// to load it from the upstream store and copy it to the primary one.
Expected<std::optional<ObjectHandle>> faultInFromUpstream(ObjectID PrimaryID);
/// Import the entire tree from upstream with \p UpstreamNode as root.
Error importFullTree(ObjectID PrimaryID, ObjectHandle UpstreamNode);
/// Import only the \param UpstreamNode.
Error importSingleNode(ObjectID PrimaryID, ObjectHandle UpstreamNode);
Error importUpstreamData(ObjectID PrimaryID, ArrayRef<ObjectID> PrimaryRefs,
ObjectHandle UpstreamNode);
enum class InternalUpstreamImportKind { Leaf, Leaf0 };
/// Private \c storeFile than optimizes internal upstream database imports.
Error storeFile(ObjectID ID, StringRef FilePath,
std::optional<InternalUpstreamImportKind> ImportKind);
/// Found the IndexProxy for the hash.
Expected<IndexProxy> indexHash(ArrayRef<uint8_t> Hash);
/// Get path for creating standalone data file.
void getStandalonePath(StringRef FileSuffix, FileOffset IndexOffset,
SmallVectorImpl<char> &Path) const;
/// Create a standalone leaf file.
Error createStandaloneLeaf(IndexProxy &I, ArrayRef<char> Data);
/// \name Helper functions for internal data structures.
/// \{
static InternalRef getInternalRef(ObjectID Ref) {
return InternalRef::getFromRawData(Ref.getOpaqueData());
}
static ObjectID getExternalReference(InternalRef Ref) {
return ObjectID::fromOpaqueData(Ref.getRawData());
}
static ObjectID getExternalReference(const IndexProxy &I);
static InternalRef makeInternalRef(FileOffset IndexOffset);
LLVM_ABI_FOR_TEST Expected<ArrayRef<uint8_t>>
getDigest(InternalRef Ref) const;
ArrayRef<uint8_t> getDigest(const IndexProxy &I) const;
Expected<IndexProxy> getIndexProxyFromRef(InternalRef Ref) const;
IndexProxy
getIndexProxyFromPointer(OnDiskTrieRawHashMap::ConstOnDiskPtr P) const;
LLVM_ABI_FOR_TEST InternalRefArrayRef
getInternalRefs(ObjectHandle Node) const;
/// \}
/// Get the atomic variable that keeps track of the standalone data storage
/// size.
std::atomic<uint64_t> &standaloneStorageSize() const;
/// Increase the standalone data size.
void recordStandaloneSizeIncrease(size_t SizeIncrease);
/// Get the standalone data size.
uint64_t getStandaloneStorageSize() const;
// Private constructor.
OnDiskGraphDB(StringRef RootPath, OnDiskTrieRawHashMap Index,
OnDiskDataAllocator DataPool, OnDiskGraphDB *UpstreamDB,
FaultInPolicy Policy, std::shared_ptr<OnDiskCASLogger> Logger);
/// Mapping from hash to object reference.
///
/// Data type is TrieRecord.
OnDiskTrieRawHashMap Index;
/// Storage for most objects.
///
/// Data type is DataRecordHandle.
OnDiskDataAllocator DataPool;
/// A StandaloneDataMap.
void *StandaloneData = nullptr;
/// Path to the root directory.
std::string RootPath;
/// Optional on-disk store to be used for faulting-in nodes.
OnDiskGraphDB *UpstreamDB = nullptr;
/// The policy used to fault in data from upstream.
FaultInPolicy FIPolicy;
/// Debug Logger.
std::shared_ptr<OnDiskCASLogger> Logger;
};
} // namespace llvm::cas::ondisk
#endif // LLVM_CAS_ONDISKGRAPHDB_H