| 1 | //===----------------------------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | /// \file |
| 10 | /// This file declares the common interface for a DatabaseFile that is used to |
| 11 | /// implement OnDiskCAS. |
| 12 | // |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | |
| 15 | #ifndef LLVM_LIB_CAS_DATABASEFILE_H |
| 16 | #define LLVM_LIB_CAS_DATABASEFILE_H |
| 17 | |
| 18 | #include "llvm/ADT/StringRef.h" |
| 19 | #include "llvm/CAS/MappedFileRegionArena.h" |
| 20 | #include "llvm/CAS/OnDiskCASLogger.h" |
| 21 | #include "llvm/Support/Error.h" |
| 22 | |
| 23 | namespace llvm::cas::ondisk { |
| 24 | |
| 25 | using MappedFileRegion = MappedFileRegionArena::RegionT; |
| 26 | |
| 27 | /// Generic handle for a table. |
| 28 | /// |
| 29 | /// Generic table header layout: |
| 30 | /// - 2-bytes: TableKind |
| 31 | /// - 2-bytes: TableNameSize |
| 32 | /// - 4-bytes: TableNameRelOffset (relative to header) |
| 33 | class TableHandle { |
| 34 | public: |
| 35 | enum class TableKind : uint16_t { |
| 36 | TrieRawHashMap = 1, |
| 37 | DataAllocator = 2, |
| 38 | }; |
| 39 | struct Header { |
| 40 | TableKind Kind; |
| 41 | uint16_t NameSize; |
| 42 | int32_t NameRelOffset; ///< Relative to Header. |
| 43 | }; |
| 44 | |
| 45 | explicit operator bool() const { return H; } |
| 46 | const Header &getHeader() const { return *H; } |
| 47 | MappedFileRegion &getRegion() const { return *Region; } |
| 48 | |
| 49 | template <class T> static void check() { |
| 50 | static_assert( |
| 51 | std::is_same<decltype(T::Header::GenericHeader), Header>::value, |
| 52 | "T::GenericHeader should be of type TableHandle::Header" ); |
| 53 | static_assert(offsetof(typename T::Header, GenericHeader) == 0, |
| 54 | "T::GenericHeader must be the head of T::Header" ); |
| 55 | } |
| 56 | template <class T> bool is() const { return T::Kind == H->Kind; } |
| 57 | template <class T> T dyn_cast() const { |
| 58 | check<T>(); |
| 59 | if (is<T>()) |
| 60 | return T(*Region, *reinterpret_cast<typename T::Header *>(H)); |
| 61 | return T(); |
| 62 | } |
| 63 | template <class T> T cast() const { |
| 64 | assert(is<T>()); |
| 65 | return dyn_cast<T>(); |
| 66 | } |
| 67 | |
| 68 | StringRef getName() const { |
| 69 | auto *Begin = reinterpret_cast<const char *>(H) + H->NameRelOffset; |
| 70 | return StringRef(Begin, H->NameSize); |
| 71 | } |
| 72 | |
| 73 | TableHandle() = default; |
| 74 | TableHandle(MappedFileRegion &Region, Header &H) : Region(&Region), H(&H) {} |
| 75 | TableHandle(MappedFileRegion &Region, intptr_t ) |
| 76 | : TableHandle(Region, |
| 77 | *reinterpret_cast<Header *>(Region.data() + HeaderOffset)) { |
| 78 | } |
| 79 | |
| 80 | private: |
| 81 | MappedFileRegion *Region = nullptr; |
| 82 | Header *H = nullptr; |
| 83 | }; |
| 84 | |
| 85 | /// Encapsulate a database file, which: |
| 86 | /// - Sets/checks magic. |
| 87 | /// - Sets/checks version. |
| 88 | /// - Points at an arbitrary root table. |
| 89 | /// - Sets up a MappedFileRegionArena for allocation. |
| 90 | /// |
| 91 | /// Top-level layout: |
| 92 | /// - 4-bytes: Magic |
| 93 | /// - 4-bytes: Version |
| 94 | /// - 8-bytes: RootTableOffset (16-bits: Kind; 48-bits: Offset) |
| 95 | /// - 8-bytes: BumpPtr from MappedFileRegionArena |
| 96 | class DatabaseFile { |
| 97 | public: |
| 98 | static constexpr uint32_t getMagic() { return 0xDA7ABA53UL; } |
| 99 | static constexpr uint32_t getVersion() { return 1UL; } |
| 100 | struct { |
| 101 | uint32_t ; |
| 102 | uint32_t ; |
| 103 | std::atomic<int64_t> ; |
| 104 | }; |
| 105 | |
| 106 | const Header &() { return *H; } |
| 107 | MappedFileRegionArena &getAlloc() { return Alloc; } |
| 108 | MappedFileRegion &getRegion() { return Alloc.getRegion(); } |
| 109 | |
| 110 | /// Add a table. This is currently not thread safe and should be called inside |
| 111 | /// NewDBConstructor. |
| 112 | Error addTable(TableHandle Table); |
| 113 | |
| 114 | /// Find a table. May return null. |
| 115 | std::optional<TableHandle> findTable(StringRef Name); |
| 116 | |
| 117 | /// Create the DatabaseFile at Path with Capacity. |
| 118 | static Expected<DatabaseFile> |
| 119 | create(const Twine &Path, uint64_t Capacity, |
| 120 | std::shared_ptr<OnDiskCASLogger> Logger, |
| 121 | function_ref<Error(DatabaseFile &)> NewDBConstructor); |
| 122 | |
| 123 | size_t size() const { return Alloc.size(); } |
| 124 | |
| 125 | private: |
| 126 | static Expected<DatabaseFile> |
| 127 | get(std::unique_ptr<MappedFileRegionArena> Alloc) { |
| 128 | if (Error E = validate(Region&: Alloc->getRegion())) |
| 129 | return std::move(E); |
| 130 | return DatabaseFile(std::move(Alloc)); |
| 131 | } |
| 132 | |
| 133 | static Error validate(MappedFileRegion &Region); |
| 134 | |
| 135 | DatabaseFile(MappedFileRegionArena &Alloc) |
| 136 | : H(reinterpret_cast<Header *>(Alloc.data())), Alloc(Alloc) {} |
| 137 | DatabaseFile(std::unique_ptr<MappedFileRegionArena> Alloc) |
| 138 | : DatabaseFile(*Alloc) { |
| 139 | OwnedAlloc = std::move(Alloc); |
| 140 | } |
| 141 | |
| 142 | Header *H = nullptr; |
| 143 | MappedFileRegionArena &Alloc; |
| 144 | std::unique_ptr<MappedFileRegionArena> OwnedAlloc; |
| 145 | }; |
| 146 | |
| 147 | Error createTableConfigError(std::errc ErrC, StringRef Path, |
| 148 | StringRef TableName, const Twine &Msg); |
| 149 | |
| 150 | Error checkTable(StringRef Label, size_t Expected, size_t Observed, |
| 151 | StringRef Path, StringRef TrieName); |
| 152 | |
| 153 | } // namespace llvm::cas::ondisk |
| 154 | |
| 155 | #endif |
| 156 | |