xref: /freebsd/contrib/llvm-project/clang/lib/Serialization/MultiOnDiskHashTable.h (revision 0b57cec536236d46e3dba9bd041533462f33dbb7)
1*0b57cec5SDimitry Andric //===- MultiOnDiskHashTable.h - Merged set of hash tables -------*- C++ -*-===//
2*0b57cec5SDimitry Andric //
3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0b57cec5SDimitry Andric //
7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
8*0b57cec5SDimitry Andric //
9*0b57cec5SDimitry Andric //  This file provides a hash table data structure suitable for incremental and
10*0b57cec5SDimitry Andric //  distributed storage across a set of files.
11*0b57cec5SDimitry Andric //
12*0b57cec5SDimitry Andric //  Multiple hash tables from different files are implicitly merged to improve
13*0b57cec5SDimitry Andric //  performance, and on reload the merged table will override those from other
14*0b57cec5SDimitry Andric //  files.
15*0b57cec5SDimitry Andric //
16*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
17*0b57cec5SDimitry Andric 
18*0b57cec5SDimitry Andric #ifndef LLVM_CLANG_LIB_SERIALIZATION_MULTIONDISKHASHTABLE_H
19*0b57cec5SDimitry Andric #define LLVM_CLANG_LIB_SERIALIZATION_MULTIONDISKHASHTABLE_H
20*0b57cec5SDimitry Andric 
21*0b57cec5SDimitry Andric #include "llvm/ADT/DenseMap.h"
22*0b57cec5SDimitry Andric #include "llvm/ADT/DenseSet.h"
23*0b57cec5SDimitry Andric #include "llvm/ADT/PointerUnion.h"
24*0b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h"
25*0b57cec5SDimitry Andric #include "llvm/ADT/SmallVector.h"
26*0b57cec5SDimitry Andric #include "llvm/ADT/TinyPtrVector.h"
27*0b57cec5SDimitry Andric #include "llvm/ADT/iterator_range.h"
28*0b57cec5SDimitry Andric #include "llvm/Support/Endian.h"
29*0b57cec5SDimitry Andric #include "llvm/Support/EndianStream.h"
30*0b57cec5SDimitry Andric #include "llvm/Support/OnDiskHashTable.h"
31*0b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
32*0b57cec5SDimitry Andric #include <algorithm>
33*0b57cec5SDimitry Andric #include <cstdint>
34*0b57cec5SDimitry Andric #include <vector>
35*0b57cec5SDimitry Andric 
36*0b57cec5SDimitry Andric namespace clang {
37*0b57cec5SDimitry Andric namespace serialization {
38*0b57cec5SDimitry Andric 
39*0b57cec5SDimitry Andric /// A collection of on-disk hash tables, merged when relevant for performance.
40*0b57cec5SDimitry Andric template<typename Info> class MultiOnDiskHashTable {
41*0b57cec5SDimitry Andric public:
42*0b57cec5SDimitry Andric   /// A handle to a file, used when overriding tables.
43*0b57cec5SDimitry Andric   using file_type = typename Info::file_type;
44*0b57cec5SDimitry Andric 
45*0b57cec5SDimitry Andric   /// A pointer to an on-disk representation of the hash table.
46*0b57cec5SDimitry Andric   using storage_type = const unsigned char *;
47*0b57cec5SDimitry Andric 
48*0b57cec5SDimitry Andric   using external_key_type = typename Info::external_key_type;
49*0b57cec5SDimitry Andric   using internal_key_type = typename Info::internal_key_type;
50*0b57cec5SDimitry Andric   using data_type = typename Info::data_type;
51*0b57cec5SDimitry Andric   using data_type_builder = typename Info::data_type_builder;
52*0b57cec5SDimitry Andric   using hash_value_type = unsigned;
53*0b57cec5SDimitry Andric 
54*0b57cec5SDimitry Andric private:
55*0b57cec5SDimitry Andric   /// The generator is permitted to read our merged table.
56*0b57cec5SDimitry Andric   template<typename ReaderInfo, typename WriterInfo>
57*0b57cec5SDimitry Andric   friend class MultiOnDiskHashTableGenerator;
58*0b57cec5SDimitry Andric 
59*0b57cec5SDimitry Andric   /// A hash table stored on disk.
60*0b57cec5SDimitry Andric   struct OnDiskTable {
61*0b57cec5SDimitry Andric     using HashTable = llvm::OnDiskIterableChainedHashTable<Info>;
62*0b57cec5SDimitry Andric 
63*0b57cec5SDimitry Andric     file_type File;
64*0b57cec5SDimitry Andric     HashTable Table;
65*0b57cec5SDimitry Andric 
66*0b57cec5SDimitry Andric     OnDiskTable(file_type File, unsigned NumBuckets, unsigned NumEntries,
67*0b57cec5SDimitry Andric                 storage_type Buckets, storage_type Payload, storage_type Base,
68*0b57cec5SDimitry Andric                 const Info &InfoObj)
69*0b57cec5SDimitry Andric         : File(File),
70*0b57cec5SDimitry Andric           Table(NumBuckets, NumEntries, Buckets, Payload, Base, InfoObj) {}
71*0b57cec5SDimitry Andric   };
72*0b57cec5SDimitry Andric 
73*0b57cec5SDimitry Andric   struct MergedTable {
74*0b57cec5SDimitry Andric     std::vector<file_type> Files;
75*0b57cec5SDimitry Andric     llvm::DenseMap<internal_key_type, data_type> Data;
76*0b57cec5SDimitry Andric   };
77*0b57cec5SDimitry Andric 
78*0b57cec5SDimitry Andric   using Table = llvm::PointerUnion<OnDiskTable *, MergedTable *>;
79*0b57cec5SDimitry Andric   using TableVector = llvm::TinyPtrVector<void *>;
80*0b57cec5SDimitry Andric 
81*0b57cec5SDimitry Andric   /// The current set of on-disk and merged tables.
82*0b57cec5SDimitry Andric   /// We manually store the opaque value of the Table because TinyPtrVector
83*0b57cec5SDimitry Andric   /// can't cope with holding a PointerUnion directly.
84*0b57cec5SDimitry Andric   /// There can be at most one MergedTable in this vector, and if present,
85*0b57cec5SDimitry Andric   /// it is the first table.
86*0b57cec5SDimitry Andric   TableVector Tables;
87*0b57cec5SDimitry Andric 
88*0b57cec5SDimitry Andric   /// Files corresponding to overridden tables that we've not yet
89*0b57cec5SDimitry Andric   /// discarded.
90*0b57cec5SDimitry Andric   llvm::TinyPtrVector<file_type> PendingOverrides;
91*0b57cec5SDimitry Andric 
92*0b57cec5SDimitry Andric   struct AsOnDiskTable {
93*0b57cec5SDimitry Andric     using result_type = OnDiskTable *;
94*0b57cec5SDimitry Andric 
95*0b57cec5SDimitry Andric     result_type operator()(void *P) const {
96*0b57cec5SDimitry Andric       return Table::getFromOpaqueValue(P).template get<OnDiskTable *>();
97*0b57cec5SDimitry Andric     }
98*0b57cec5SDimitry Andric   };
99*0b57cec5SDimitry Andric 
100*0b57cec5SDimitry Andric   using table_iterator =
101*0b57cec5SDimitry Andric       llvm::mapped_iterator<TableVector::iterator, AsOnDiskTable>;
102*0b57cec5SDimitry Andric   using table_range = llvm::iterator_range<table_iterator>;
103*0b57cec5SDimitry Andric 
104*0b57cec5SDimitry Andric   /// The current set of on-disk tables.
105*0b57cec5SDimitry Andric   table_range tables() {
106*0b57cec5SDimitry Andric     auto Begin = Tables.begin(), End = Tables.end();
107*0b57cec5SDimitry Andric     if (getMergedTable())
108*0b57cec5SDimitry Andric       ++Begin;
109*0b57cec5SDimitry Andric     return llvm::make_range(llvm::map_iterator(Begin, AsOnDiskTable()),
110*0b57cec5SDimitry Andric                             llvm::map_iterator(End, AsOnDiskTable()));
111*0b57cec5SDimitry Andric   }
112*0b57cec5SDimitry Andric 
113*0b57cec5SDimitry Andric   MergedTable *getMergedTable() const {
114*0b57cec5SDimitry Andric     // If we already have a merged table, it's the first one.
115*0b57cec5SDimitry Andric     return Tables.empty() ? nullptr : Table::getFromOpaqueValue(*Tables.begin())
116*0b57cec5SDimitry Andric                                           .template dyn_cast<MergedTable*>();
117*0b57cec5SDimitry Andric   }
118*0b57cec5SDimitry Andric 
119*0b57cec5SDimitry Andric   /// Delete all our current on-disk tables.
120*0b57cec5SDimitry Andric   void clear() {
121*0b57cec5SDimitry Andric     for (auto *T : tables())
122*0b57cec5SDimitry Andric       delete T;
123*0b57cec5SDimitry Andric     if (auto *M = getMergedTable())
124*0b57cec5SDimitry Andric       delete M;
125*0b57cec5SDimitry Andric     Tables.clear();
126*0b57cec5SDimitry Andric   }
127*0b57cec5SDimitry Andric 
128*0b57cec5SDimitry Andric   void removeOverriddenTables() {
129*0b57cec5SDimitry Andric     llvm::DenseSet<file_type> Files;
130*0b57cec5SDimitry Andric     Files.insert(PendingOverrides.begin(), PendingOverrides.end());
131*0b57cec5SDimitry Andric     // Explicitly capture Files to work around an MSVC 2015 rejects-valid bug.
132*0b57cec5SDimitry Andric     auto ShouldRemove = [&Files](void *T) -> bool {
133*0b57cec5SDimitry Andric       auto *ODT = Table::getFromOpaqueValue(T).template get<OnDiskTable *>();
134*0b57cec5SDimitry Andric       bool Remove = Files.count(ODT->File);
135*0b57cec5SDimitry Andric       if (Remove)
136*0b57cec5SDimitry Andric         delete ODT;
137*0b57cec5SDimitry Andric       return Remove;
138*0b57cec5SDimitry Andric     };
139*0b57cec5SDimitry Andric     Tables.erase(std::remove_if(tables().begin().getCurrent(), Tables.end(),
140*0b57cec5SDimitry Andric                                 ShouldRemove),
141*0b57cec5SDimitry Andric                  Tables.end());
142*0b57cec5SDimitry Andric     PendingOverrides.clear();
143*0b57cec5SDimitry Andric   }
144*0b57cec5SDimitry Andric 
145*0b57cec5SDimitry Andric   void condense() {
146*0b57cec5SDimitry Andric     MergedTable *Merged = getMergedTable();
147*0b57cec5SDimitry Andric     if (!Merged)
148*0b57cec5SDimitry Andric       Merged = new MergedTable;
149*0b57cec5SDimitry Andric 
150*0b57cec5SDimitry Andric     // Read in all the tables and merge them together.
151*0b57cec5SDimitry Andric     // FIXME: Be smarter about which tables we merge.
152*0b57cec5SDimitry Andric     for (auto *ODT : tables()) {
153*0b57cec5SDimitry Andric       auto &HT = ODT->Table;
154*0b57cec5SDimitry Andric       Info &InfoObj = HT.getInfoObj();
155*0b57cec5SDimitry Andric 
156*0b57cec5SDimitry Andric       for (auto I = HT.data_begin(), E = HT.data_end(); I != E; ++I) {
157*0b57cec5SDimitry Andric         auto *LocalPtr = I.getItem();
158*0b57cec5SDimitry Andric 
159*0b57cec5SDimitry Andric         // FIXME: Don't rely on the OnDiskHashTable format here.
160*0b57cec5SDimitry Andric         auto L = InfoObj.ReadKeyDataLength(LocalPtr);
161*0b57cec5SDimitry Andric         const internal_key_type &Key = InfoObj.ReadKey(LocalPtr, L.first);
162*0b57cec5SDimitry Andric         data_type_builder ValueBuilder(Merged->Data[Key]);
163*0b57cec5SDimitry Andric         InfoObj.ReadDataInto(Key, LocalPtr + L.first, L.second,
164*0b57cec5SDimitry Andric                              ValueBuilder);
165*0b57cec5SDimitry Andric       }
166*0b57cec5SDimitry Andric 
167*0b57cec5SDimitry Andric       Merged->Files.push_back(ODT->File);
168*0b57cec5SDimitry Andric       delete ODT;
169*0b57cec5SDimitry Andric     }
170*0b57cec5SDimitry Andric 
171*0b57cec5SDimitry Andric     Tables.clear();
172*0b57cec5SDimitry Andric     Tables.push_back(Table(Merged).getOpaqueValue());
173*0b57cec5SDimitry Andric   }
174*0b57cec5SDimitry Andric 
175*0b57cec5SDimitry Andric public:
176*0b57cec5SDimitry Andric   MultiOnDiskHashTable() = default;
177*0b57cec5SDimitry Andric 
178*0b57cec5SDimitry Andric   MultiOnDiskHashTable(MultiOnDiskHashTable &&O)
179*0b57cec5SDimitry Andric       : Tables(std::move(O.Tables)),
180*0b57cec5SDimitry Andric         PendingOverrides(std::move(O.PendingOverrides)) {
181*0b57cec5SDimitry Andric     O.Tables.clear();
182*0b57cec5SDimitry Andric   }
183*0b57cec5SDimitry Andric 
184*0b57cec5SDimitry Andric   MultiOnDiskHashTable &operator=(MultiOnDiskHashTable &&O) {
185*0b57cec5SDimitry Andric     if (&O == this)
186*0b57cec5SDimitry Andric       return *this;
187*0b57cec5SDimitry Andric     clear();
188*0b57cec5SDimitry Andric     Tables = std::move(O.Tables);
189*0b57cec5SDimitry Andric     O.Tables.clear();
190*0b57cec5SDimitry Andric     PendingOverrides = std::move(O.PendingOverrides);
191*0b57cec5SDimitry Andric     return *this;
192*0b57cec5SDimitry Andric   }
193*0b57cec5SDimitry Andric 
194*0b57cec5SDimitry Andric   ~MultiOnDiskHashTable() { clear(); }
195*0b57cec5SDimitry Andric 
196*0b57cec5SDimitry Andric   /// Add the table \p Data loaded from file \p File.
197*0b57cec5SDimitry Andric   void add(file_type File, storage_type Data, Info InfoObj = Info()) {
198*0b57cec5SDimitry Andric     using namespace llvm::support;
199*0b57cec5SDimitry Andric 
200*0b57cec5SDimitry Andric     storage_type Ptr = Data;
201*0b57cec5SDimitry Andric 
202*0b57cec5SDimitry Andric     uint32_t BucketOffset = endian::readNext<uint32_t, little, unaligned>(Ptr);
203*0b57cec5SDimitry Andric 
204*0b57cec5SDimitry Andric     // Read the list of overridden files.
205*0b57cec5SDimitry Andric     uint32_t NumFiles = endian::readNext<uint32_t, little, unaligned>(Ptr);
206*0b57cec5SDimitry Andric     // FIXME: Add a reserve() to TinyPtrVector so that we don't need to make
207*0b57cec5SDimitry Andric     // an additional copy.
208*0b57cec5SDimitry Andric     llvm::SmallVector<file_type, 16> OverriddenFiles;
209*0b57cec5SDimitry Andric     OverriddenFiles.reserve(NumFiles);
210*0b57cec5SDimitry Andric     for (/**/; NumFiles != 0; --NumFiles)
211*0b57cec5SDimitry Andric       OverriddenFiles.push_back(InfoObj.ReadFileRef(Ptr));
212*0b57cec5SDimitry Andric     PendingOverrides.insert(PendingOverrides.end(), OverriddenFiles.begin(),
213*0b57cec5SDimitry Andric                             OverriddenFiles.end());
214*0b57cec5SDimitry Andric 
215*0b57cec5SDimitry Andric     // Read the OnDiskChainedHashTable header.
216*0b57cec5SDimitry Andric     storage_type Buckets = Data + BucketOffset;
217*0b57cec5SDimitry Andric     auto NumBucketsAndEntries =
218*0b57cec5SDimitry Andric         OnDiskTable::HashTable::readNumBucketsAndEntries(Buckets);
219*0b57cec5SDimitry Andric 
220*0b57cec5SDimitry Andric     // Register the table.
221*0b57cec5SDimitry Andric     Table NewTable = new OnDiskTable(File, NumBucketsAndEntries.first,
222*0b57cec5SDimitry Andric                                      NumBucketsAndEntries.second,
223*0b57cec5SDimitry Andric                                      Buckets, Ptr, Data, std::move(InfoObj));
224*0b57cec5SDimitry Andric     Tables.push_back(NewTable.getOpaqueValue());
225*0b57cec5SDimitry Andric   }
226*0b57cec5SDimitry Andric 
227*0b57cec5SDimitry Andric   /// Find and read the lookup results for \p EKey.
228*0b57cec5SDimitry Andric   data_type find(const external_key_type &EKey) {
229*0b57cec5SDimitry Andric     data_type Result;
230*0b57cec5SDimitry Andric 
231*0b57cec5SDimitry Andric     if (!PendingOverrides.empty())
232*0b57cec5SDimitry Andric       removeOverriddenTables();
233*0b57cec5SDimitry Andric 
234*0b57cec5SDimitry Andric     if (Tables.size() > static_cast<unsigned>(Info::MaxTables))
235*0b57cec5SDimitry Andric       condense();
236*0b57cec5SDimitry Andric 
237*0b57cec5SDimitry Andric     internal_key_type Key = Info::GetInternalKey(EKey);
238*0b57cec5SDimitry Andric     auto KeyHash = Info::ComputeHash(Key);
239*0b57cec5SDimitry Andric 
240*0b57cec5SDimitry Andric     if (MergedTable *M = getMergedTable()) {
241*0b57cec5SDimitry Andric       auto It = M->Data.find(Key);
242*0b57cec5SDimitry Andric       if (It != M->Data.end())
243*0b57cec5SDimitry Andric         Result = It->second;
244*0b57cec5SDimitry Andric     }
245*0b57cec5SDimitry Andric 
246*0b57cec5SDimitry Andric     data_type_builder ResultBuilder(Result);
247*0b57cec5SDimitry Andric 
248*0b57cec5SDimitry Andric     for (auto *ODT : tables()) {
249*0b57cec5SDimitry Andric       auto &HT = ODT->Table;
250*0b57cec5SDimitry Andric       auto It = HT.find_hashed(Key, KeyHash);
251*0b57cec5SDimitry Andric       if (It != HT.end())
252*0b57cec5SDimitry Andric         HT.getInfoObj().ReadDataInto(Key, It.getDataPtr(), It.getDataLen(),
253*0b57cec5SDimitry Andric                                      ResultBuilder);
254*0b57cec5SDimitry Andric     }
255*0b57cec5SDimitry Andric 
256*0b57cec5SDimitry Andric     return Result;
257*0b57cec5SDimitry Andric   }
258*0b57cec5SDimitry Andric 
259*0b57cec5SDimitry Andric   /// Read all the lookup results into a single value. This only makes
260*0b57cec5SDimitry Andric   /// sense if merging values across keys is meaningful.
261*0b57cec5SDimitry Andric   data_type findAll() {
262*0b57cec5SDimitry Andric     data_type Result;
263*0b57cec5SDimitry Andric     data_type_builder ResultBuilder(Result);
264*0b57cec5SDimitry Andric 
265*0b57cec5SDimitry Andric     if (!PendingOverrides.empty())
266*0b57cec5SDimitry Andric       removeOverriddenTables();
267*0b57cec5SDimitry Andric 
268*0b57cec5SDimitry Andric     if (MergedTable *M = getMergedTable()) {
269*0b57cec5SDimitry Andric       for (auto &KV : M->Data)
270*0b57cec5SDimitry Andric         Info::MergeDataInto(KV.second, ResultBuilder);
271*0b57cec5SDimitry Andric     }
272*0b57cec5SDimitry Andric 
273*0b57cec5SDimitry Andric     for (auto *ODT : tables()) {
274*0b57cec5SDimitry Andric       auto &HT = ODT->Table;
275*0b57cec5SDimitry Andric       Info &InfoObj = HT.getInfoObj();
276*0b57cec5SDimitry Andric       for (auto I = HT.data_begin(), E = HT.data_end(); I != E; ++I) {
277*0b57cec5SDimitry Andric         auto *LocalPtr = I.getItem();
278*0b57cec5SDimitry Andric 
279*0b57cec5SDimitry Andric         // FIXME: Don't rely on the OnDiskHashTable format here.
280*0b57cec5SDimitry Andric         auto L = InfoObj.ReadKeyDataLength(LocalPtr);
281*0b57cec5SDimitry Andric         const internal_key_type &Key = InfoObj.ReadKey(LocalPtr, L.first);
282*0b57cec5SDimitry Andric         InfoObj.ReadDataInto(Key, LocalPtr + L.first, L.second, ResultBuilder);
283*0b57cec5SDimitry Andric       }
284*0b57cec5SDimitry Andric     }
285*0b57cec5SDimitry Andric 
286*0b57cec5SDimitry Andric     return Result;
287*0b57cec5SDimitry Andric   }
288*0b57cec5SDimitry Andric };
289*0b57cec5SDimitry Andric 
290*0b57cec5SDimitry Andric /// Writer for the on-disk hash table.
291*0b57cec5SDimitry Andric template<typename ReaderInfo, typename WriterInfo>
292*0b57cec5SDimitry Andric class MultiOnDiskHashTableGenerator {
293*0b57cec5SDimitry Andric   using BaseTable = MultiOnDiskHashTable<ReaderInfo>;
294*0b57cec5SDimitry Andric   using Generator = llvm::OnDiskChainedHashTableGenerator<WriterInfo>;
295*0b57cec5SDimitry Andric 
296*0b57cec5SDimitry Andric   Generator Gen;
297*0b57cec5SDimitry Andric 
298*0b57cec5SDimitry Andric public:
299*0b57cec5SDimitry Andric   MultiOnDiskHashTableGenerator() : Gen() {}
300*0b57cec5SDimitry Andric 
301*0b57cec5SDimitry Andric   void insert(typename WriterInfo::key_type_ref Key,
302*0b57cec5SDimitry Andric               typename WriterInfo::data_type_ref Data, WriterInfo &Info) {
303*0b57cec5SDimitry Andric     Gen.insert(Key, Data, Info);
304*0b57cec5SDimitry Andric   }
305*0b57cec5SDimitry Andric 
306*0b57cec5SDimitry Andric   void emit(llvm::SmallVectorImpl<char> &Out, WriterInfo &Info,
307*0b57cec5SDimitry Andric             const BaseTable *Base) {
308*0b57cec5SDimitry Andric     using namespace llvm::support;
309*0b57cec5SDimitry Andric 
310*0b57cec5SDimitry Andric     llvm::raw_svector_ostream OutStream(Out);
311*0b57cec5SDimitry Andric 
312*0b57cec5SDimitry Andric     // Write our header information.
313*0b57cec5SDimitry Andric     {
314*0b57cec5SDimitry Andric       endian::Writer Writer(OutStream, little);
315*0b57cec5SDimitry Andric 
316*0b57cec5SDimitry Andric       // Reserve four bytes for the bucket offset.
317*0b57cec5SDimitry Andric       Writer.write<uint32_t>(0);
318*0b57cec5SDimitry Andric 
319*0b57cec5SDimitry Andric       if (auto *Merged = Base ? Base->getMergedTable() : nullptr) {
320*0b57cec5SDimitry Andric         // Write list of overridden files.
321*0b57cec5SDimitry Andric         Writer.write<uint32_t>(Merged->Files.size());
322*0b57cec5SDimitry Andric         for (const auto &F : Merged->Files)
323*0b57cec5SDimitry Andric           Info.EmitFileRef(OutStream, F);
324*0b57cec5SDimitry Andric 
325*0b57cec5SDimitry Andric         // Add all merged entries from Base to the generator.
326*0b57cec5SDimitry Andric         for (auto &KV : Merged->Data) {
327*0b57cec5SDimitry Andric           if (!Gen.contains(KV.first, Info))
328*0b57cec5SDimitry Andric             Gen.insert(KV.first, Info.ImportData(KV.second), Info);
329*0b57cec5SDimitry Andric         }
330*0b57cec5SDimitry Andric       } else {
331*0b57cec5SDimitry Andric         Writer.write<uint32_t>(0);
332*0b57cec5SDimitry Andric       }
333*0b57cec5SDimitry Andric     }
334*0b57cec5SDimitry Andric 
335*0b57cec5SDimitry Andric     // Write the table itself.
336*0b57cec5SDimitry Andric     uint32_t BucketOffset = Gen.Emit(OutStream, Info);
337*0b57cec5SDimitry Andric 
338*0b57cec5SDimitry Andric     // Replace the first four bytes with the bucket offset.
339*0b57cec5SDimitry Andric     endian::write32le(Out.data(), BucketOffset);
340*0b57cec5SDimitry Andric   }
341*0b57cec5SDimitry Andric };
342*0b57cec5SDimitry Andric 
343*0b57cec5SDimitry Andric } // namespace serialization
344*0b57cec5SDimitry Andric } // namespace clang
345*0b57cec5SDimitry Andric 
346*0b57cec5SDimitry Andric #endif // LLVM_CLANG_LIB_SERIALIZATION_MULTIONDISKHASHTABLE_H
347