1 | //===- InstrProfWriter.cpp - Instrumented profiling writer ----------------===// |
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 | // This file contains support for writing profiling data for clang's |
10 | // instrumentation based PGO and coverage. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "llvm/ProfileData/InstrProfWriter.h" |
15 | #include "llvm/ADT/STLExtras.h" |
16 | #include "llvm/ADT/SetVector.h" |
17 | #include "llvm/ADT/StringRef.h" |
18 | #include "llvm/IR/ProfileSummary.h" |
19 | #include "llvm/ProfileData/DataAccessProf.h" |
20 | #include "llvm/ProfileData/IndexedMemProfData.h" |
21 | #include "llvm/ProfileData/InstrProf.h" |
22 | #include "llvm/ProfileData/ProfileCommon.h" |
23 | #include "llvm/Support/Compression.h" |
24 | #include "llvm/Support/EndianStream.h" |
25 | #include "llvm/Support/Error.h" |
26 | #include "llvm/Support/MemoryBuffer.h" |
27 | #include "llvm/Support/OnDiskHashTable.h" |
28 | #include "llvm/Support/raw_ostream.h" |
29 | #include <cstdint> |
30 | #include <ctime> |
31 | #include <memory> |
32 | #include <string> |
33 | #include <tuple> |
34 | #include <utility> |
35 | #include <vector> |
36 | |
37 | using namespace llvm; |
38 | |
39 | namespace llvm { |
40 | |
41 | class InstrProfRecordWriterTrait { |
42 | public: |
43 | using key_type = StringRef; |
44 | using key_type_ref = StringRef; |
45 | |
46 | using data_type = const InstrProfWriter::ProfilingData *const; |
47 | using data_type_ref = const InstrProfWriter::ProfilingData *const; |
48 | |
49 | using hash_value_type = uint64_t; |
50 | using offset_type = uint64_t; |
51 | |
52 | llvm::endianness ValueProfDataEndianness = llvm::endianness::little; |
53 | InstrProfSummaryBuilder *SummaryBuilder; |
54 | InstrProfSummaryBuilder *CSSummaryBuilder; |
55 | |
56 | InstrProfRecordWriterTrait() = default; |
57 | |
58 | static hash_value_type ComputeHash(key_type_ref K) { |
59 | return IndexedInstrProf::ComputeHash(K); |
60 | } |
61 | |
62 | static std::pair<offset_type, offset_type> |
63 | EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) { |
64 | using namespace support; |
65 | |
66 | endian::Writer LE(Out, llvm::endianness::little); |
67 | |
68 | offset_type N = K.size(); |
69 | LE.write<offset_type>(Val: N); |
70 | |
71 | offset_type M = 0; |
72 | for (const auto &ProfileData : *V) { |
73 | const InstrProfRecord &ProfRecord = ProfileData.second; |
74 | M += sizeof(uint64_t); // The function hash |
75 | M += sizeof(uint64_t); // The size of the Counts vector |
76 | M += ProfRecord.Counts.size() * sizeof(uint64_t); |
77 | M += sizeof(uint64_t); // The size of the Bitmap vector |
78 | M += ProfRecord.BitmapBytes.size() * sizeof(uint64_t); |
79 | |
80 | // Value data |
81 | M += ValueProfData::getSize(Record: ProfileData.second); |
82 | } |
83 | LE.write<offset_type>(Val: M); |
84 | |
85 | return std::make_pair(x&: N, y&: M); |
86 | } |
87 | |
88 | void EmitKey(raw_ostream &Out, key_type_ref K, offset_type N) { |
89 | Out.write(Ptr: K.data(), Size: N); |
90 | } |
91 | |
92 | void EmitData(raw_ostream &Out, key_type_ref, data_type_ref V, offset_type) { |
93 | using namespace support; |
94 | |
95 | endian::Writer LE(Out, llvm::endianness::little); |
96 | for (const auto &ProfileData : *V) { |
97 | const InstrProfRecord &ProfRecord = ProfileData.second; |
98 | if (NamedInstrProfRecord::hasCSFlagInHash(FuncHash: ProfileData.first)) |
99 | CSSummaryBuilder->addRecord(ProfRecord); |
100 | else |
101 | SummaryBuilder->addRecord(ProfRecord); |
102 | |
103 | LE.write<uint64_t>(Val: ProfileData.first); // Function hash |
104 | LE.write<uint64_t>(Val: ProfRecord.Counts.size()); |
105 | for (uint64_t I : ProfRecord.Counts) |
106 | LE.write<uint64_t>(Val: I); |
107 | |
108 | LE.write<uint64_t>(Val: ProfRecord.BitmapBytes.size()); |
109 | for (uint64_t I : ProfRecord.BitmapBytes) |
110 | LE.write<uint64_t>(Val: I); |
111 | |
112 | // Write value data |
113 | std::unique_ptr<ValueProfData> VDataPtr = |
114 | ValueProfData::serializeFrom(Record: ProfileData.second); |
115 | uint32_t S = VDataPtr->getSize(); |
116 | VDataPtr->swapBytesFromHost(Endianness: ValueProfDataEndianness); |
117 | Out.write(Ptr: (const char *)VDataPtr.get(), Size: S); |
118 | } |
119 | } |
120 | }; |
121 | |
122 | } // end namespace llvm |
123 | |
124 | InstrProfWriter::InstrProfWriter( |
125 | bool Sparse, uint64_t TemporalProfTraceReservoirSize, |
126 | uint64_t MaxTemporalProfTraceLength, bool WritePrevVersion, |
127 | memprof::IndexedVersion MemProfVersionRequested, bool MemProfFullSchema, |
128 | bool MemprofGenerateRandomHotness, |
129 | unsigned MemprofGenerateRandomHotnessSeed) |
130 | : Sparse(Sparse), MaxTemporalProfTraceLength(MaxTemporalProfTraceLength), |
131 | TemporalProfTraceReservoirSize(TemporalProfTraceReservoirSize), |
132 | InfoObj(new InstrProfRecordWriterTrait()), |
133 | WritePrevVersion(WritePrevVersion), |
134 | MemProfVersionRequested(MemProfVersionRequested), |
135 | MemProfFullSchema(MemProfFullSchema), |
136 | MemprofGenerateRandomHotness(MemprofGenerateRandomHotness) { |
137 | // Set up the random number seed if requested. |
138 | if (MemprofGenerateRandomHotness) { |
139 | unsigned seed = MemprofGenerateRandomHotnessSeed |
140 | ? MemprofGenerateRandomHotnessSeed |
141 | : std::time(timer: nullptr); |
142 | errs() << "random hotness seed = " << seed << "\n" ; |
143 | std::srand(seed: seed); |
144 | } |
145 | } |
146 | |
147 | InstrProfWriter::~InstrProfWriter() { delete InfoObj; } |
148 | |
149 | // Internal interface for testing purpose only. |
150 | void InstrProfWriter::setValueProfDataEndianness(llvm::endianness Endianness) { |
151 | InfoObj->ValueProfDataEndianness = Endianness; |
152 | } |
153 | |
154 | void InstrProfWriter::setOutputSparse(bool Sparse) { this->Sparse = Sparse; } |
155 | |
156 | void InstrProfWriter::addRecord(NamedInstrProfRecord &&I, uint64_t Weight, |
157 | function_ref<void(Error)> Warn) { |
158 | auto Name = I.Name; |
159 | auto Hash = I.Hash; |
160 | addRecord(Name, Hash, I: std::move(I), Weight, Warn); |
161 | } |
162 | |
163 | void InstrProfWriter::overlapRecord(NamedInstrProfRecord &&Other, |
164 | OverlapStats &Overlap, |
165 | OverlapStats &FuncLevelOverlap, |
166 | const OverlapFuncFilters &FuncFilter) { |
167 | auto Name = Other.Name; |
168 | auto Hash = Other.Hash; |
169 | Other.accumulateCounts(Sum&: FuncLevelOverlap.Test); |
170 | auto It = FunctionData.find(Key: Name); |
171 | if (It == FunctionData.end()) { |
172 | Overlap.addOneUnique(UniqueFunc: FuncLevelOverlap.Test); |
173 | return; |
174 | } |
175 | if (FuncLevelOverlap.Test.CountSum < 1.0f) { |
176 | Overlap.Overlap.NumEntries += 1; |
177 | return; |
178 | } |
179 | auto &ProfileDataMap = It->second; |
180 | auto [Where, NewFunc] = ProfileDataMap.try_emplace(Key: Hash); |
181 | if (NewFunc) { |
182 | Overlap.addOneMismatch(MismatchFunc: FuncLevelOverlap.Test); |
183 | return; |
184 | } |
185 | InstrProfRecord &Dest = Where->second; |
186 | |
187 | uint64_t ValueCutoff = FuncFilter.ValueCutoff; |
188 | if (!FuncFilter.NameFilter.empty() && Name.contains(Other: FuncFilter.NameFilter)) |
189 | ValueCutoff = 0; |
190 | |
191 | Dest.overlap(Other, Overlap, FuncLevelOverlap, ValueCutoff); |
192 | } |
193 | |
194 | void InstrProfWriter::addRecord(StringRef Name, uint64_t Hash, |
195 | InstrProfRecord &&I, uint64_t Weight, |
196 | function_ref<void(Error)> Warn) { |
197 | auto &ProfileDataMap = FunctionData[Name]; |
198 | |
199 | auto [Where, NewFunc] = ProfileDataMap.try_emplace(Key: Hash); |
200 | InstrProfRecord &Dest = Where->second; |
201 | |
202 | auto MapWarn = [&](instrprof_error E) { |
203 | Warn(make_error<InstrProfError>(Args&: E)); |
204 | }; |
205 | |
206 | if (NewFunc) { |
207 | // We've never seen a function with this name and hash, add it. |
208 | Dest = std::move(I); |
209 | if (Weight > 1) |
210 | Dest.scale(N: Weight, D: 1, Warn: MapWarn); |
211 | } else { |
212 | // We're updating a function we've seen before. |
213 | Dest.merge(Other&: I, Weight, Warn: MapWarn); |
214 | } |
215 | |
216 | Dest.sortValueData(); |
217 | } |
218 | |
219 | void InstrProfWriter::addMemProfRecord( |
220 | const Function::GUID Id, const memprof::IndexedMemProfRecord &Record) { |
221 | auto NewRecord = Record; |
222 | // Provoke random hotness values if requested. We specify the lifetime access |
223 | // density and lifetime length that will result in a cold or not cold hotness. |
224 | // See the logic in getAllocType() in Analysis/MemoryProfileInfo.cpp. |
225 | if (MemprofGenerateRandomHotness) { |
226 | for (auto &Alloc : NewRecord.AllocSites) { |
227 | // To get a not cold context, set the lifetime access density to the |
228 | // maximum value and the lifetime to 0. |
229 | uint64_t NewTLAD = std::numeric_limits<uint64_t>::max(); |
230 | uint64_t NewTL = 0; |
231 | bool IsCold = std::rand() % 2; |
232 | if (IsCold) { |
233 | // To get a cold context, set the lifetime access density to 0 and the |
234 | // lifetime to the maximum value. |
235 | NewTLAD = 0; |
236 | NewTL = std::numeric_limits<uint64_t>::max(); |
237 | } |
238 | Alloc.Info.setTotalLifetimeAccessDensity(NewTLAD); |
239 | Alloc.Info.setTotalLifetime(NewTL); |
240 | } |
241 | } |
242 | MemProfSumBuilder.addRecord(NewRecord); |
243 | auto [Iter, Inserted] = MemProfData.Records.insert(KV: {Id, NewRecord}); |
244 | // If we inserted a new record then we are done. |
245 | if (Inserted) { |
246 | return; |
247 | } |
248 | memprof::IndexedMemProfRecord &Existing = Iter->second; |
249 | Existing.merge(Other: NewRecord); |
250 | } |
251 | |
252 | bool InstrProfWriter::addMemProfFrame(const memprof::FrameId Id, |
253 | const memprof::Frame &Frame, |
254 | function_ref<void(Error)> Warn) { |
255 | auto [Iter, Inserted] = MemProfData.Frames.insert(KV: {Id, Frame}); |
256 | // If a mapping already exists for the current frame id and it does not |
257 | // match the new mapping provided then reset the existing contents and bail |
258 | // out. We don't support the merging of memprof data whose Frame -> Id |
259 | // mapping across profiles is inconsistent. |
260 | if (!Inserted && Iter->second != Frame) { |
261 | Warn(make_error<InstrProfError>(Args: instrprof_error::malformed, |
262 | Args: "frame to id mapping mismatch" )); |
263 | return false; |
264 | } |
265 | return true; |
266 | } |
267 | |
268 | bool InstrProfWriter::addMemProfCallStack( |
269 | const memprof::CallStackId CSId, |
270 | const llvm::SmallVector<memprof::FrameId> &CallStack, |
271 | function_ref<void(Error)> Warn) { |
272 | auto [Iter, Inserted] = MemProfData.CallStacks.insert(KV: {CSId, CallStack}); |
273 | // If a mapping already exists for the current call stack id and it does not |
274 | // match the new mapping provided then reset the existing contents and bail |
275 | // out. We don't support the merging of memprof data whose CallStack -> Id |
276 | // mapping across profiles is inconsistent. |
277 | if (!Inserted && Iter->second != CallStack) { |
278 | Warn(make_error<InstrProfError>(Args: instrprof_error::malformed, |
279 | Args: "call stack to id mapping mismatch" )); |
280 | return false; |
281 | } |
282 | return true; |
283 | } |
284 | |
285 | bool InstrProfWriter::addMemProfData(memprof::IndexedMemProfData Incoming, |
286 | function_ref<void(Error)> Warn) { |
287 | // Return immediately if everything is empty. |
288 | if (Incoming.Frames.empty() && Incoming.CallStacks.empty() && |
289 | Incoming.Records.empty()) |
290 | return true; |
291 | |
292 | // Otherwise, every component must be non-empty. |
293 | assert(!Incoming.Frames.empty() && !Incoming.CallStacks.empty() && |
294 | !Incoming.Records.empty()); |
295 | |
296 | if (MemProfData.Frames.empty()) |
297 | MemProfData.Frames = std::move(Incoming.Frames); |
298 | else |
299 | for (const auto &[Id, F] : Incoming.Frames) |
300 | if (addMemProfFrame(Id, Frame: F, Warn)) |
301 | return false; |
302 | |
303 | if (MemProfData.CallStacks.empty()) |
304 | MemProfData.CallStacks = std::move(Incoming.CallStacks); |
305 | else |
306 | for (const auto &[CSId, CS] : Incoming.CallStacks) |
307 | if (addMemProfCallStack(CSId, CallStack: CS, Warn)) |
308 | return false; |
309 | |
310 | // Add one record at a time if randomization is requested. |
311 | if (MemProfData.Records.empty() && !MemprofGenerateRandomHotness) { |
312 | // Need to manually add each record to the builder, which is otherwise done |
313 | // in addMemProfRecord. |
314 | for (const auto &[GUID, Record] : Incoming.Records) |
315 | MemProfSumBuilder.addRecord(Record); |
316 | MemProfData.Records = std::move(Incoming.Records); |
317 | } else { |
318 | for (const auto &[GUID, Record] : Incoming.Records) |
319 | addMemProfRecord(Id: GUID, Record); |
320 | } |
321 | |
322 | return true; |
323 | } |
324 | |
325 | void InstrProfWriter::addBinaryIds(ArrayRef<llvm::object::BuildID> BIs) { |
326 | llvm::append_range(C&: BinaryIds, R&: BIs); |
327 | } |
328 | |
329 | void InstrProfWriter::addDataAccessProfData( |
330 | std::unique_ptr<memprof::DataAccessProfData> DataAccessProfDataIn) { |
331 | DataAccessProfileData = std::move(DataAccessProfDataIn); |
332 | } |
333 | |
334 | void InstrProfWriter::addTemporalProfileTrace(TemporalProfTraceTy Trace) { |
335 | assert(Trace.FunctionNameRefs.size() <= MaxTemporalProfTraceLength); |
336 | assert(!Trace.FunctionNameRefs.empty()); |
337 | if (TemporalProfTraceStreamSize < TemporalProfTraceReservoirSize) { |
338 | // Simply append the trace if we have not yet hit our reservoir size limit. |
339 | TemporalProfTraces.push_back(Elt: std::move(Trace)); |
340 | } else { |
341 | // Otherwise, replace a random trace in the stream. |
342 | std::uniform_int_distribution<uint64_t> Distribution( |
343 | 0, TemporalProfTraceStreamSize); |
344 | uint64_t RandomIndex = Distribution(RNG); |
345 | if (RandomIndex < TemporalProfTraces.size()) |
346 | TemporalProfTraces[RandomIndex] = std::move(Trace); |
347 | } |
348 | ++TemporalProfTraceStreamSize; |
349 | } |
350 | |
351 | void InstrProfWriter::addTemporalProfileTraces( |
352 | SmallVectorImpl<TemporalProfTraceTy> &SrcTraces, uint64_t SrcStreamSize) { |
353 | for (auto &Trace : SrcTraces) |
354 | if (Trace.FunctionNameRefs.size() > MaxTemporalProfTraceLength) |
355 | Trace.FunctionNameRefs.resize(new_size: MaxTemporalProfTraceLength); |
356 | llvm::erase_if(C&: SrcTraces, P: [](auto &T) { return T.FunctionNameRefs.empty(); }); |
357 | // Assume that the source has the same reservoir size as the destination to |
358 | // avoid needing to record it in the indexed profile format. |
359 | bool IsDestSampled = |
360 | (TemporalProfTraceStreamSize > TemporalProfTraceReservoirSize); |
361 | bool IsSrcSampled = (SrcStreamSize > TemporalProfTraceReservoirSize); |
362 | if (!IsDestSampled && IsSrcSampled) { |
363 | // If one of the traces are sampled, ensure that it belongs to Dest. |
364 | std::swap(LHS&: TemporalProfTraces, RHS&: SrcTraces); |
365 | std::swap(a&: TemporalProfTraceStreamSize, b&: SrcStreamSize); |
366 | std::swap(a&: IsDestSampled, b&: IsSrcSampled); |
367 | } |
368 | if (!IsSrcSampled) { |
369 | // If the source stream is not sampled, we add each source trace normally. |
370 | for (auto &Trace : SrcTraces) |
371 | addTemporalProfileTrace(Trace: std::move(Trace)); |
372 | return; |
373 | } |
374 | // Otherwise, we find the traces that would have been removed if we added |
375 | // the whole source stream. |
376 | SmallSetVector<uint64_t, 8> IndicesToReplace; |
377 | for (uint64_t I = 0; I < SrcStreamSize; I++) { |
378 | std::uniform_int_distribution<uint64_t> Distribution( |
379 | 0, TemporalProfTraceStreamSize); |
380 | uint64_t RandomIndex = Distribution(RNG); |
381 | if (RandomIndex < TemporalProfTraces.size()) |
382 | IndicesToReplace.insert(X: RandomIndex); |
383 | ++TemporalProfTraceStreamSize; |
384 | } |
385 | // Then we insert a random sample of the source traces. |
386 | llvm::shuffle(first: SrcTraces.begin(), last: SrcTraces.end(), g&: RNG); |
387 | for (const auto &[Index, Trace] : llvm::zip(t&: IndicesToReplace, u&: SrcTraces)) |
388 | TemporalProfTraces[Index] = std::move(Trace); |
389 | } |
390 | |
391 | void InstrProfWriter::mergeRecordsFromWriter(InstrProfWriter &&IPW, |
392 | function_ref<void(Error)> Warn) { |
393 | for (auto &I : IPW.FunctionData) |
394 | for (auto &Func : I.getValue()) |
395 | addRecord(Name: I.getKey(), Hash: Func.first, I: std::move(Func.second), Weight: 1, Warn); |
396 | |
397 | BinaryIds.reserve(n: BinaryIds.size() + IPW.BinaryIds.size()); |
398 | for (auto &I : IPW.BinaryIds) |
399 | addBinaryIds(BIs: I); |
400 | |
401 | addTemporalProfileTraces(SrcTraces&: IPW.TemporalProfTraces, |
402 | SrcStreamSize: IPW.TemporalProfTraceStreamSize); |
403 | |
404 | MemProfData.Frames.reserve(NumEntries: IPW.MemProfData.Frames.size()); |
405 | for (auto &[FrameId, Frame] : IPW.MemProfData.Frames) { |
406 | // If we weren't able to add the frame mappings then it doesn't make sense |
407 | // to try to merge the records from this profile. |
408 | if (!addMemProfFrame(Id: FrameId, Frame, Warn)) |
409 | return; |
410 | } |
411 | |
412 | MemProfData.CallStacks.reserve(NumEntries: IPW.MemProfData.CallStacks.size()); |
413 | for (auto &[CSId, CallStack] : IPW.MemProfData.CallStacks) { |
414 | if (!addMemProfCallStack(CSId, CallStack, Warn)) |
415 | return; |
416 | } |
417 | |
418 | MemProfData.Records.reserve(NumEntries: IPW.MemProfData.Records.size()); |
419 | for (auto &[GUID, Record] : IPW.MemProfData.Records) { |
420 | addMemProfRecord(Id: GUID, Record); |
421 | } |
422 | } |
423 | |
424 | bool InstrProfWriter::shouldEncodeData(const ProfilingData &PD) { |
425 | if (!Sparse) |
426 | return true; |
427 | for (const auto &Func : PD) { |
428 | const InstrProfRecord &IPR = Func.second; |
429 | if (llvm::any_of(Range: IPR.Counts, P: [](uint64_t Count) { return Count > 0; })) |
430 | return true; |
431 | if (llvm::any_of(Range: IPR.BitmapBytes, P: [](uint8_t Byte) { return Byte > 0; })) |
432 | return true; |
433 | } |
434 | return false; |
435 | } |
436 | |
437 | static void setSummary(IndexedInstrProf::Summary *TheSummary, |
438 | ProfileSummary &PS) { |
439 | using namespace IndexedInstrProf; |
440 | |
441 | const std::vector<ProfileSummaryEntry> &Res = PS.getDetailedSummary(); |
442 | TheSummary->NumSummaryFields = Summary::NumKinds; |
443 | TheSummary->NumCutoffEntries = Res.size(); |
444 | TheSummary->set(K: Summary::MaxFunctionCount, V: PS.getMaxFunctionCount()); |
445 | TheSummary->set(K: Summary::MaxBlockCount, V: PS.getMaxCount()); |
446 | TheSummary->set(K: Summary::MaxInternalBlockCount, V: PS.getMaxInternalCount()); |
447 | TheSummary->set(K: Summary::TotalBlockCount, V: PS.getTotalCount()); |
448 | TheSummary->set(K: Summary::TotalNumBlocks, V: PS.getNumCounts()); |
449 | TheSummary->set(K: Summary::TotalNumFunctions, V: PS.getNumFunctions()); |
450 | for (unsigned I = 0; I < Res.size(); I++) |
451 | TheSummary->setEntry(I, E: Res[I]); |
452 | } |
453 | |
454 | uint64_t InstrProfWriter::(const IndexedInstrProf::Header &, |
455 | const bool WritePrevVersion, |
456 | ProfOStream &OS) { |
457 | // Only write out the first four fields. |
458 | for (int I = 0; I < 4; I++) |
459 | OS.write(V: reinterpret_cast<const uint64_t *>(&Header)[I]); |
460 | |
461 | // Remember the offset of the remaining fields to allow back patching later. |
462 | auto BackPatchStartOffset = OS.tell(); |
463 | |
464 | // Reserve the space for back patching later. |
465 | OS.write(V: 0); // HashOffset |
466 | OS.write(V: 0); // MemProfOffset |
467 | OS.write(V: 0); // BinaryIdOffset |
468 | OS.write(V: 0); // TemporalProfTracesOffset |
469 | if (!WritePrevVersion) |
470 | OS.write(V: 0); // VTableNamesOffset |
471 | |
472 | return BackPatchStartOffset; |
473 | } |
474 | |
475 | Error InstrProfWriter::writeBinaryIds(ProfOStream &OS) { |
476 | // BinaryIdSection has two parts: |
477 | // 1. uint64_t BinaryIdsSectionSize |
478 | // 2. list of binary ids that consist of: |
479 | // a. uint64_t BinaryIdLength |
480 | // b. uint8_t BinaryIdData |
481 | // c. uint8_t Padding (if necessary) |
482 | // Calculate size of binary section. |
483 | uint64_t BinaryIdsSectionSize = 0; |
484 | |
485 | // Remove duplicate binary ids. |
486 | llvm::sort(C&: BinaryIds); |
487 | BinaryIds.erase(first: llvm::unique(R&: BinaryIds), last: BinaryIds.end()); |
488 | |
489 | for (const auto &BI : BinaryIds) { |
490 | // Increment by binary id length data type size. |
491 | BinaryIdsSectionSize += sizeof(uint64_t); |
492 | // Increment by binary id data length, aligned to 8 bytes. |
493 | BinaryIdsSectionSize += alignToPowerOf2(Value: BI.size(), Align: sizeof(uint64_t)); |
494 | } |
495 | // Write binary ids section size. |
496 | OS.write(V: BinaryIdsSectionSize); |
497 | |
498 | for (const auto &BI : BinaryIds) { |
499 | uint64_t BILen = BI.size(); |
500 | // Write binary id length. |
501 | OS.write(V: BILen); |
502 | // Write binary id data. |
503 | for (unsigned K = 0; K < BILen; K++) |
504 | OS.writeByte(V: BI[K]); |
505 | // Write padding if necessary. |
506 | uint64_t PaddingSize = alignToPowerOf2(Value: BILen, Align: sizeof(uint64_t)) - BILen; |
507 | for (unsigned K = 0; K < PaddingSize; K++) |
508 | OS.writeByte(V: 0); |
509 | } |
510 | |
511 | return Error::success(); |
512 | } |
513 | |
514 | Error InstrProfWriter::writeVTableNames(ProfOStream &OS) { |
515 | std::vector<std::string> VTableNameStrs; |
516 | for (StringRef VTableName : VTableNames.keys()) |
517 | VTableNameStrs.push_back(x: VTableName.str()); |
518 | |
519 | std::string CompressedVTableNames; |
520 | if (!VTableNameStrs.empty()) |
521 | if (Error E = collectGlobalObjectNameStrings( |
522 | NameStrs: VTableNameStrs, doCompression: compression::zlib::isAvailable(), |
523 | Result&: CompressedVTableNames)) |
524 | return E; |
525 | |
526 | const uint64_t CompressedStringLen = CompressedVTableNames.length(); |
527 | |
528 | // Record the length of compressed string. |
529 | OS.write(V: CompressedStringLen); |
530 | |
531 | // Write the chars in compressed strings. |
532 | for (auto &c : CompressedVTableNames) |
533 | OS.writeByte(V: static_cast<uint8_t>(c)); |
534 | |
535 | // Pad up to a multiple of 8. |
536 | // InstrProfReader could read bytes according to 'CompressedStringLen'. |
537 | const uint64_t PaddedLength = alignTo(Value: CompressedStringLen, Align: 8); |
538 | |
539 | for (uint64_t K = CompressedStringLen; K < PaddedLength; K++) |
540 | OS.writeByte(V: 0); |
541 | |
542 | return Error::success(); |
543 | } |
544 | |
545 | Error InstrProfWriter::writeImpl(ProfOStream &OS) { |
546 | using namespace IndexedInstrProf; |
547 | using namespace support; |
548 | |
549 | OnDiskChainedHashTableGenerator<InstrProfRecordWriterTrait> Generator; |
550 | |
551 | InstrProfSummaryBuilder ISB(ProfileSummaryBuilder::DefaultCutoffs); |
552 | InfoObj->SummaryBuilder = &ISB; |
553 | InstrProfSummaryBuilder CSISB(ProfileSummaryBuilder::DefaultCutoffs); |
554 | InfoObj->CSSummaryBuilder = &CSISB; |
555 | |
556 | // Populate the hash table generator. |
557 | SmallVector<std::pair<StringRef, const ProfilingData *>> OrderedData; |
558 | for (const auto &I : FunctionData) |
559 | if (shouldEncodeData(PD: I.getValue())) |
560 | OrderedData.emplace_back(Args: (I.getKey()), Args: &I.getValue()); |
561 | llvm::sort(C&: OrderedData, Comp: less_first()); |
562 | for (const auto &I : OrderedData) |
563 | Generator.insert(Key: I.first, Data: I.second); |
564 | |
565 | // Write the header. |
566 | IndexedInstrProf::Header ; |
567 | Header.Version = WritePrevVersion |
568 | ? IndexedInstrProf::ProfVersion::Version11 |
569 | : IndexedInstrProf::ProfVersion::CurrentVersion; |
570 | // The WritePrevVersion handling will either need to be removed or updated |
571 | // if the version is advanced beyond 12. |
572 | static_assert(IndexedInstrProf::ProfVersion::CurrentVersion == |
573 | IndexedInstrProf::ProfVersion::Version12); |
574 | if (static_cast<bool>(ProfileKind & InstrProfKind::IRInstrumentation)) |
575 | Header.Version |= VARIANT_MASK_IR_PROF; |
576 | if (static_cast<bool>(ProfileKind & InstrProfKind::ContextSensitive)) |
577 | Header.Version |= VARIANT_MASK_CSIR_PROF; |
578 | if (static_cast<bool>(ProfileKind & |
579 | InstrProfKind::FunctionEntryInstrumentation)) |
580 | Header.Version |= VARIANT_MASK_INSTR_ENTRY; |
581 | if (static_cast<bool>(ProfileKind & |
582 | InstrProfKind::LoopEntriesInstrumentation)) |
583 | Header.Version |= VARIANT_MASK_INSTR_LOOP_ENTRIES; |
584 | if (static_cast<bool>(ProfileKind & InstrProfKind::SingleByteCoverage)) |
585 | Header.Version |= VARIANT_MASK_BYTE_COVERAGE; |
586 | if (static_cast<bool>(ProfileKind & InstrProfKind::FunctionEntryOnly)) |
587 | Header.Version |= VARIANT_MASK_FUNCTION_ENTRY_ONLY; |
588 | if (static_cast<bool>(ProfileKind & InstrProfKind::MemProf)) |
589 | Header.Version |= VARIANT_MASK_MEMPROF; |
590 | if (static_cast<bool>(ProfileKind & InstrProfKind::TemporalProfile)) |
591 | Header.Version |= VARIANT_MASK_TEMPORAL_PROF; |
592 | |
593 | const uint64_t BackPatchStartOffset = |
594 | writeHeader(Header, WritePrevVersion, OS); |
595 | |
596 | // Reserve space to write profile summary data. |
597 | uint32_t NumEntries = ProfileSummaryBuilder::DefaultCutoffs.size(); |
598 | uint32_t SummarySize = Summary::getSize(NumSumFields: Summary::NumKinds, NumCutoffEntries: NumEntries); |
599 | // Remember the summary offset. |
600 | uint64_t SummaryOffset = OS.tell(); |
601 | for (unsigned I = 0; I < SummarySize / sizeof(uint64_t); I++) |
602 | OS.write(V: 0); |
603 | uint64_t CSSummaryOffset = 0; |
604 | uint64_t CSSummarySize = 0; |
605 | if (static_cast<bool>(ProfileKind & InstrProfKind::ContextSensitive)) { |
606 | CSSummaryOffset = OS.tell(); |
607 | CSSummarySize = SummarySize / sizeof(uint64_t); |
608 | for (unsigned I = 0; I < CSSummarySize; I++) |
609 | OS.write(V: 0); |
610 | } |
611 | |
612 | // Write the hash table. |
613 | uint64_t HashTableStart = Generator.Emit(Out&: OS.OS, InfoObj&: *InfoObj); |
614 | |
615 | // Write the MemProf profile data if we have it. |
616 | uint64_t MemProfSectionStart = 0; |
617 | if (static_cast<bool>(ProfileKind & InstrProfKind::MemProf)) { |
618 | MemProfSectionStart = OS.tell(); |
619 | |
620 | if (auto E = writeMemProf( |
621 | OS, MemProfData, MemProfVersionRequested, MemProfFullSchema, |
622 | DataAccessProfileData: std::move(DataAccessProfileData), MemProfSum: MemProfSumBuilder.getSummary())) |
623 | return E; |
624 | } |
625 | |
626 | uint64_t BinaryIdSectionStart = OS.tell(); |
627 | if (auto E = writeBinaryIds(OS)) |
628 | return E; |
629 | |
630 | uint64_t VTableNamesSectionStart = OS.tell(); |
631 | |
632 | if (!WritePrevVersion) |
633 | if (Error E = writeVTableNames(OS)) |
634 | return E; |
635 | |
636 | uint64_t TemporalProfTracesSectionStart = 0; |
637 | if (static_cast<bool>(ProfileKind & InstrProfKind::TemporalProfile)) { |
638 | TemporalProfTracesSectionStart = OS.tell(); |
639 | OS.write(V: TemporalProfTraces.size()); |
640 | OS.write(V: TemporalProfTraceStreamSize); |
641 | for (auto &Trace : TemporalProfTraces) { |
642 | OS.write(V: Trace.Weight); |
643 | OS.write(V: Trace.FunctionNameRefs.size()); |
644 | for (auto &NameRef : Trace.FunctionNameRefs) |
645 | OS.write(V: NameRef); |
646 | } |
647 | } |
648 | |
649 | // Allocate space for data to be serialized out. |
650 | std::unique_ptr<IndexedInstrProf::Summary> TheSummary = |
651 | IndexedInstrProf::allocSummary(TotalSize: SummarySize); |
652 | // Compute the Summary and copy the data to the data |
653 | // structure to be serialized out (to disk or buffer). |
654 | std::unique_ptr<ProfileSummary> PS = ISB.getSummary(); |
655 | setSummary(TheSummary: TheSummary.get(), PS&: *PS); |
656 | InfoObj->SummaryBuilder = nullptr; |
657 | |
658 | // For Context Sensitive summary. |
659 | std::unique_ptr<IndexedInstrProf::Summary> TheCSSummary = nullptr; |
660 | if (static_cast<bool>(ProfileKind & InstrProfKind::ContextSensitive)) { |
661 | TheCSSummary = IndexedInstrProf::allocSummary(TotalSize: SummarySize); |
662 | std::unique_ptr<ProfileSummary> CSPS = CSISB.getSummary(); |
663 | setSummary(TheSummary: TheCSSummary.get(), PS&: *CSPS); |
664 | } |
665 | InfoObj->CSSummaryBuilder = nullptr; |
666 | |
667 | SmallVector<uint64_t, 8> = {HashTableStart, MemProfSectionStart, |
668 | BinaryIdSectionStart, |
669 | TemporalProfTracesSectionStart}; |
670 | if (!WritePrevVersion) |
671 | HeaderOffsets.push_back(Elt: VTableNamesSectionStart); |
672 | |
673 | PatchItem PatchItems[] = { |
674 | // Patch the Header fields |
675 | {.Pos: BackPatchStartOffset, .D: HeaderOffsets}, |
676 | // Patch the summary data. |
677 | {.Pos: SummaryOffset, |
678 | .D: ArrayRef<uint64_t>(reinterpret_cast<uint64_t *>(TheSummary.get()), |
679 | SummarySize / sizeof(uint64_t))}, |
680 | {.Pos: CSSummaryOffset, |
681 | .D: ArrayRef<uint64_t>(reinterpret_cast<uint64_t *>(TheCSSummary.get()), |
682 | CSSummarySize)}}; |
683 | |
684 | OS.patch(P: PatchItems); |
685 | |
686 | for (const auto &I : FunctionData) |
687 | for (const auto &F : I.getValue()) |
688 | if (Error E = validateRecord(Func: F.second)) |
689 | return E; |
690 | |
691 | return Error::success(); |
692 | } |
693 | |
694 | Error InstrProfWriter::write(raw_fd_ostream &OS) { |
695 | // Write the hash table. |
696 | ProfOStream POS(OS); |
697 | return writeImpl(OS&: POS); |
698 | } |
699 | |
700 | Error InstrProfWriter::write(raw_string_ostream &OS) { |
701 | ProfOStream POS(OS); |
702 | return writeImpl(OS&: POS); |
703 | } |
704 | |
705 | std::unique_ptr<MemoryBuffer> InstrProfWriter::writeBuffer() { |
706 | std::string Data; |
707 | raw_string_ostream OS(Data); |
708 | // Write the hash table. |
709 | if (Error E = write(OS)) |
710 | return nullptr; |
711 | // Return this in an aligned memory buffer. |
712 | return MemoryBuffer::getMemBufferCopy(InputData: Data); |
713 | } |
714 | |
715 | static const char *ValueProfKindStr[] = { |
716 | #define VALUE_PROF_KIND(Enumerator, Value, Descr) #Enumerator, |
717 | #include "llvm/ProfileData/InstrProfData.inc" |
718 | }; |
719 | |
720 | Error InstrProfWriter::validateRecord(const InstrProfRecord &Func) { |
721 | for (uint32_t VK = 0; VK <= IPVK_Last; VK++) { |
722 | if (VK == IPVK_IndirectCallTarget || VK == IPVK_VTableTarget) |
723 | continue; |
724 | uint32_t NS = Func.getNumValueSites(ValueKind: VK); |
725 | for (uint32_t S = 0; S < NS; S++) { |
726 | DenseSet<uint64_t> SeenValues; |
727 | for (const auto &V : Func.getValueArrayForSite(ValueKind: VK, Site: S)) |
728 | if (!SeenValues.insert(V: V.Value).second) |
729 | return make_error<InstrProfError>(Args: instrprof_error::invalid_prof); |
730 | } |
731 | } |
732 | |
733 | return Error::success(); |
734 | } |
735 | |
736 | void InstrProfWriter::writeRecordInText(StringRef Name, uint64_t Hash, |
737 | const InstrProfRecord &Func, |
738 | InstrProfSymtab &Symtab, |
739 | raw_fd_ostream &OS) { |
740 | OS << Name << "\n" ; |
741 | OS << "# Func Hash:\n" << Hash << "\n" ; |
742 | OS << "# Num Counters:\n" << Func.Counts.size() << "\n" ; |
743 | OS << "# Counter Values:\n" ; |
744 | for (uint64_t Count : Func.Counts) |
745 | OS << Count << "\n" ; |
746 | |
747 | if (Func.BitmapBytes.size() > 0) { |
748 | OS << "# Num Bitmap Bytes:\n$" << Func.BitmapBytes.size() << "\n" ; |
749 | OS << "# Bitmap Byte Values:\n" ; |
750 | for (uint8_t Byte : Func.BitmapBytes) { |
751 | OS << "0x" ; |
752 | OS.write_hex(N: Byte); |
753 | OS << "\n" ; |
754 | } |
755 | OS << "\n" ; |
756 | } |
757 | |
758 | uint32_t NumValueKinds = Func.getNumValueKinds(); |
759 | if (!NumValueKinds) { |
760 | OS << "\n" ; |
761 | return; |
762 | } |
763 | |
764 | OS << "# Num Value Kinds:\n" << Func.getNumValueKinds() << "\n" ; |
765 | for (uint32_t VK = 0; VK < IPVK_Last + 1; VK++) { |
766 | uint32_t NS = Func.getNumValueSites(ValueKind: VK); |
767 | if (!NS) |
768 | continue; |
769 | OS << "# ValueKind = " << ValueProfKindStr[VK] << ":\n" << VK << "\n" ; |
770 | OS << "# NumValueSites:\n" << NS << "\n" ; |
771 | for (uint32_t S = 0; S < NS; S++) { |
772 | auto VD = Func.getValueArrayForSite(ValueKind: VK, Site: S); |
773 | OS << VD.size() << "\n" ; |
774 | for (const auto &V : VD) { |
775 | if (VK == IPVK_IndirectCallTarget || VK == IPVK_VTableTarget) |
776 | OS << Symtab.getFuncOrVarNameIfDefined(MD5Hash: V.Value) << ":" << V.Count |
777 | << "\n" ; |
778 | else |
779 | OS << V.Value << ":" << V.Count << "\n" ; |
780 | } |
781 | } |
782 | } |
783 | |
784 | OS << "\n" ; |
785 | } |
786 | |
787 | Error InstrProfWriter::writeText(raw_fd_ostream &OS) { |
788 | // Check CS first since it implies an IR level profile. |
789 | if (static_cast<bool>(ProfileKind & InstrProfKind::ContextSensitive)) |
790 | OS << "# CSIR level Instrumentation Flag\n:csir\n" ; |
791 | else if (static_cast<bool>(ProfileKind & InstrProfKind::IRInstrumentation)) |
792 | OS << "# IR level Instrumentation Flag\n:ir\n" ; |
793 | |
794 | if (static_cast<bool>(ProfileKind & |
795 | InstrProfKind::FunctionEntryInstrumentation)) |
796 | OS << "# Always instrument the function entry block\n:entry_first\n" ; |
797 | if (static_cast<bool>(ProfileKind & |
798 | InstrProfKind::LoopEntriesInstrumentation)) |
799 | OS << "# Always instrument the loop entry " |
800 | "blocks\n:instrument_loop_entries\n" ; |
801 | if (static_cast<bool>(ProfileKind & InstrProfKind::SingleByteCoverage)) |
802 | OS << "# Instrument block coverage\n:single_byte_coverage\n" ; |
803 | InstrProfSymtab Symtab; |
804 | |
805 | using FuncPair = detail::DenseMapPair<uint64_t, InstrProfRecord>; |
806 | using RecordType = std::pair<StringRef, FuncPair>; |
807 | SmallVector<RecordType, 4> OrderedFuncData; |
808 | |
809 | for (const auto &I : FunctionData) { |
810 | if (shouldEncodeData(PD: I.getValue())) { |
811 | if (Error E = Symtab.addFuncName(FuncName: I.getKey())) |
812 | return E; |
813 | for (const auto &Func : I.getValue()) |
814 | OrderedFuncData.push_back(Elt: std::make_pair(x: I.getKey(), y: Func)); |
815 | } |
816 | } |
817 | |
818 | for (const auto &VTableName : VTableNames) |
819 | if (Error E = Symtab.addVTableName(VTableName: VTableName.getKey())) |
820 | return E; |
821 | |
822 | if (static_cast<bool>(ProfileKind & InstrProfKind::TemporalProfile)) |
823 | writeTextTemporalProfTraceData(OS, Symtab); |
824 | |
825 | llvm::sort(C&: OrderedFuncData, Comp: [](const RecordType &A, const RecordType &B) { |
826 | return std::tie(args: A.first, args: A.second.first) < |
827 | std::tie(args: B.first, args: B.second.first); |
828 | }); |
829 | |
830 | for (const auto &record : OrderedFuncData) { |
831 | const StringRef &Name = record.first; |
832 | const FuncPair &Func = record.second; |
833 | writeRecordInText(Name, Hash: Func.first, Func: Func.second, Symtab, OS); |
834 | } |
835 | |
836 | for (const auto &record : OrderedFuncData) { |
837 | const FuncPair &Func = record.second; |
838 | if (Error E = validateRecord(Func: Func.second)) |
839 | return E; |
840 | } |
841 | |
842 | return Error::success(); |
843 | } |
844 | |
845 | void InstrProfWriter::writeTextTemporalProfTraceData(raw_fd_ostream &OS, |
846 | InstrProfSymtab &Symtab) { |
847 | OS << ":temporal_prof_traces\n" ; |
848 | OS << "# Num Temporal Profile Traces:\n" << TemporalProfTraces.size() << "\n" ; |
849 | OS << "# Temporal Profile Trace Stream Size:\n" |
850 | << TemporalProfTraceStreamSize << "\n" ; |
851 | for (auto &Trace : TemporalProfTraces) { |
852 | OS << "# Weight:\n" << Trace.Weight << "\n" ; |
853 | for (auto &NameRef : Trace.FunctionNameRefs) |
854 | OS << Symtab.getFuncOrVarName(MD5Hash: NameRef) << "," ; |
855 | OS << "\n" ; |
856 | } |
857 | OS << "\n" ; |
858 | } |
859 | |