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
23namespace llvm::cas::ondisk {
24
25using 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)
33class TableHandle {
34public:
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 HeaderOffset)
76 : TableHandle(Region,
77 *reinterpret_cast<Header *>(Region.data() + HeaderOffset)) {
78 }
79
80private:
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
96class DatabaseFile {
97public:
98 static constexpr uint32_t getMagic() { return 0xDA7ABA53UL; }
99 static constexpr uint32_t getVersion() { return 1UL; }
100 struct Header {
101 uint32_t Magic;
102 uint32_t Version;
103 std::atomic<int64_t> RootTableOffset;
104 };
105
106 const Header &getHeader() { 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
125private:
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
147Error createTableConfigError(std::errc ErrC, StringRef Path,
148 StringRef TableName, const Twine &Msg);
149
150Error checkTable(StringRef Label, size_t Expected, size_t Observed,
151 StringRef Path, StringRef TrieName);
152
153} // namespace llvm::cas::ondisk
154
155#endif
156