1//===-- Analysis.cpp --------------------------------------------*- C++ -*-===//
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#include "Analysis.h"
10#include "BenchmarkResult.h"
11#include "llvm/ADT/STLExtras.h"
12#include "llvm/MC/MCAsmInfo.h"
13#include "llvm/MC/MCTargetOptions.h"
14#include "llvm/Support/FormatVariadic.h"
15#include <cmath>
16#include <limits>
17#include <vector>
18
19namespace llvm {
20namespace exegesis {
21
22static const char kCsvSep = ',';
23
24namespace {
25
26enum EscapeTag { kEscapeCsv, kEscapeHtml, kEscapeHtmlString };
27
28template <EscapeTag Tag> void writeEscaped(raw_ostream &OS, const StringRef S);
29
30template <> void writeEscaped<kEscapeCsv>(raw_ostream &OS, const StringRef S) {
31 if (!S.contains(C: kCsvSep)) {
32 OS << S;
33 } else {
34 // Needs escaping.
35 OS << '"';
36 for (const char C : S) {
37 if (C == '"')
38 OS << "\"\"";
39 else
40 OS << C;
41 }
42 OS << '"';
43 }
44}
45
46template <> void writeEscaped<kEscapeHtml>(raw_ostream &OS, const StringRef S) {
47 for (const char C : S) {
48 if (C == '<')
49 OS << "&lt;";
50 else if (C == '>')
51 OS << "&gt;";
52 else if (C == '&')
53 OS << "&amp;";
54 else
55 OS << C;
56 }
57}
58
59template <>
60void writeEscaped<kEscapeHtmlString>(raw_ostream &OS, const StringRef S) {
61 for (const char C : S) {
62 if (C == '"')
63 OS << "\\\"";
64 else
65 OS << C;
66 }
67}
68
69} // namespace
70
71template <EscapeTag Tag>
72static void
73writeClusterId(raw_ostream &OS,
74 const BenchmarkClustering::ClusterId &CID) {
75 if (CID.isNoise())
76 writeEscaped<Tag>(OS, "[noise]");
77 else if (CID.isError())
78 writeEscaped<Tag>(OS, "[error]");
79 else
80 OS << CID.getId();
81}
82
83template <EscapeTag Tag>
84static void writeMeasurementValue(raw_ostream &OS, const double Value) {
85 // Given Value, if we wanted to serialize it to a string,
86 // how many base-10 digits will we need to store, max?
87 static constexpr auto MaxDigitCount =
88 std::numeric_limits<decltype(Value)>::max_digits10;
89 // Also, we will need a decimal separator.
90 static constexpr auto DecimalSeparatorLen = 1; // '.' e.g.
91 // So how long of a string will the serialization produce, max?
92 static constexpr auto SerializationLen = MaxDigitCount + DecimalSeparatorLen;
93
94 // WARNING: when changing the format, also adjust the small-size estimate ^.
95 static constexpr StringLiteral SimpleFloatFormat = StringLiteral("{0:F}");
96
97 writeEscaped<Tag>(
98 OS, formatv(Fmt: SimpleFloatFormat.data(), Vals: Value).sstr<SerializationLen>());
99}
100
101template <typename EscapeTag, EscapeTag Tag>
102void Analysis::writeSnippet(raw_ostream &OS, ArrayRef<uint8_t> Bytes,
103 const char *Separator) const {
104 SmallVector<std::string, 3> Lines;
105 // Parse the asm snippet and print it.
106 while (!Bytes.empty()) {
107 MCInst MI;
108 uint64_t MISize = 0;
109 if (!DisasmHelper_->decodeInst(MI, MISize, Bytes)) {
110 writeEscaped<Tag>(OS, join(R&: Lines, Separator));
111 writeEscaped<Tag>(OS, Separator);
112 writeEscaped<Tag>(OS, "[error decoding asm snippet]");
113 return;
114 }
115 SmallString<128> InstPrinterStr; // FIXME: magic number.
116 raw_svector_ostream OSS(InstPrinterStr);
117 DisasmHelper_->printInst(MI: &MI, OS&: OSS);
118 Bytes = Bytes.drop_front(N: MISize);
119 Lines.emplace_back(Args: InstPrinterStr.str().trim());
120 }
121 writeEscaped<Tag>(OS, join(R&: Lines, Separator));
122}
123
124// Prints a row representing an instruction, along with scheduling info and
125// point coordinates (measurements).
126void Analysis::printInstructionRowCsv(const size_t PointId,
127 raw_ostream &OS) const {
128 const Benchmark &Point = Clustering_.getPoints()[PointId];
129 writeClusterId<kEscapeCsv>(OS, CID: Clustering_.getClusterIdForPoint(P: PointId));
130 OS << kCsvSep;
131 writeSnippet<EscapeTag, kEscapeCsv>(OS, Bytes: Point.AssembledSnippet, Separator: "; ");
132 OS << kCsvSep;
133 writeEscaped<kEscapeCsv>(OS, S: Point.Key.Config);
134 OS << kCsvSep;
135 assert(!Point.Key.Instructions.empty());
136 const MCInst &MCI = Point.keyInstruction();
137 unsigned SchedClassId;
138 std::tie(args&: SchedClassId, args: std::ignore) = ResolvedSchedClass::resolveSchedClassId(
139 SubtargetInfo: State_.getSubtargetInfo(), InstrInfo: State_.getInstrInfo(), MCI);
140#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
141 StringRef SCDescName =
142 State_.getSubtargetInfo().getSchedModel().getSchedClassName(SchedClassId);
143 writeEscaped<kEscapeCsv>(OS, SCDescName);
144#else
145 OS << SchedClassId;
146#endif
147 for (const auto &Measurement : Point.Measurements) {
148 OS << kCsvSep;
149 writeMeasurementValue<kEscapeCsv>(OS, Value: Measurement.PerInstructionValue);
150 }
151 OS << "\n";
152}
153
154Analysis::Analysis(const LLVMState &State,
155 const BenchmarkClustering &Clustering,
156 double AnalysisInconsistencyEpsilon,
157 bool AnalysisDisplayUnstableOpcodes)
158 : Clustering_(Clustering), State_(State),
159 AnalysisInconsistencyEpsilonSquared_(AnalysisInconsistencyEpsilon *
160 AnalysisInconsistencyEpsilon),
161 AnalysisDisplayUnstableOpcodes_(AnalysisDisplayUnstableOpcodes) {
162 if (Clustering.getPoints().empty())
163 return;
164
165 DisasmHelper_ = std::make_unique<DisassemblerHelper>(args: State);
166}
167
168template <>
169Error Analysis::run<Analysis::PrintClusters>(raw_ostream &OS) const {
170 if (Clustering_.getPoints().empty())
171 return Error::success();
172
173 // Write the header.
174 OS << "cluster_id" << kCsvSep << "opcode_name" << kCsvSep << "config"
175 << kCsvSep << "sched_class";
176 for (const auto &Measurement : Clustering_.getPoints().front().Measurements) {
177 OS << kCsvSep;
178 writeEscaped<kEscapeCsv>(OS, S: Measurement.Key);
179 }
180 OS << "\n";
181
182 // Write the points.
183 for (const auto &ClusterIt : Clustering_.getValidClusters()) {
184 for (const size_t PointId : ClusterIt.PointIndices) {
185 printInstructionRowCsv(PointId, OS);
186 }
187 OS << "\n\n";
188 }
189 return Error::success();
190}
191
192Analysis::ResolvedSchedClassAndPoints::ResolvedSchedClassAndPoints(
193 ResolvedSchedClass &&RSC)
194 : RSC(std::move(RSC)) {}
195
196std::vector<Analysis::ResolvedSchedClassAndPoints>
197Analysis::makePointsPerSchedClass() const {
198 std::vector<ResolvedSchedClassAndPoints> Entries;
199 // Maps SchedClassIds to index in result.
200 DenseMap<unsigned, size_t> SchedClassIdToIndex;
201 const auto &Points = Clustering_.getPoints();
202 for (size_t PointId = 0, E = Points.size(); PointId < E; ++PointId) {
203 const Benchmark &Point = Points[PointId];
204 if (!Point.Error.empty())
205 continue;
206 assert(!Point.Key.Instructions.empty());
207 // FIXME: we should be using the tuple of classes for instructions in the
208 // snippet as key.
209 const MCInst &MCI = Point.keyInstruction();
210 unsigned SchedClassId;
211 bool WasVariant;
212 std::tie(args&: SchedClassId, args&: WasVariant) =
213 ResolvedSchedClass::resolveSchedClassId(SubtargetInfo: State_.getSubtargetInfo(),
214 InstrInfo: State_.getInstrInfo(), MCI);
215 const auto IndexIt = SchedClassIdToIndex.find(Val: SchedClassId);
216 if (IndexIt == SchedClassIdToIndex.end()) {
217 // Create a new entry.
218 SchedClassIdToIndex.try_emplace(Key: SchedClassId, Args: Entries.size());
219 ResolvedSchedClassAndPoints Entry(ResolvedSchedClass(
220 State_.getSubtargetInfo(), SchedClassId, WasVariant));
221 Entry.PointIds.push_back(x: PointId);
222 Entries.push_back(x: std::move(Entry));
223 } else {
224 // Append to the existing entry.
225 Entries[IndexIt->second].PointIds.push_back(x: PointId);
226 }
227 }
228 return Entries;
229}
230
231// Parallel benchmarks repeat the same opcode multiple times. Just show this
232// opcode and show the whole snippet only on hover.
233static void writeParallelSnippetHtml(raw_ostream &OS,
234 const std::vector<MCInst> &Instructions,
235 const MCInstrInfo &InstrInfo) {
236 if (Instructions.empty())
237 return;
238 writeEscaped<kEscapeHtml>(OS, S: InstrInfo.getName(Opcode: Instructions[0].getOpcode()));
239 if (Instructions.size() > 1)
240 OS << " (x" << Instructions.size() << ")";
241}
242
243// Latency tries to find a serial path. Just show the opcode path and show the
244// whole snippet only on hover.
245static void writeLatencySnippetHtml(raw_ostream &OS,
246 const std::vector<MCInst> &Instructions,
247 const MCInstrInfo &InstrInfo) {
248 ListSeparator LS(" &rarr; ");
249 for (const MCInst &Instr : Instructions) {
250 OS << LS;
251 writeEscaped<kEscapeHtml>(OS, S: InstrInfo.getName(Opcode: Instr.getOpcode()));
252 }
253}
254
255void Analysis::printPointHtml(const Benchmark &Point, raw_ostream &OS) const {
256 OS << "<li><span class=\"mono\" title=\"";
257 writeSnippet<EscapeTag, kEscapeHtmlString>(OS, Bytes: Point.AssembledSnippet, Separator: "\n");
258 OS << "\">";
259 switch (Point.Mode) {
260 case Benchmark::Latency:
261 writeLatencySnippetHtml(OS, Instructions: Point.Key.Instructions, InstrInfo: State_.getInstrInfo());
262 break;
263 case Benchmark::Uops:
264 case Benchmark::InverseThroughput:
265 writeParallelSnippetHtml(OS, Instructions: Point.Key.Instructions, InstrInfo: State_.getInstrInfo());
266 break;
267 default:
268 llvm_unreachable("invalid mode");
269 }
270 OS << "</span> <span class=\"mono\">";
271 writeEscaped<kEscapeHtml>(OS, S: Point.Key.Config);
272 OS << "</span></li>";
273}
274
275void Analysis::printSchedClassClustersHtml(
276 const std::vector<SchedClassCluster> &Clusters,
277 const ResolvedSchedClass &RSC, raw_ostream &OS) const {
278 const auto &Points = Clustering_.getPoints();
279 OS << "<table class=\"sched-class-clusters\">";
280 OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
281 assert(!Clusters.empty());
282 for (const auto &Measurement :
283 Points[Clusters[0].getPointIds()[0]].Measurements) {
284 OS << "<th>";
285 writeEscaped<kEscapeHtml>(OS, S: Measurement.Key);
286 OS << "</th>";
287 }
288 OS << "</tr>";
289 for (const SchedClassCluster &Cluster : Clusters) {
290 OS << "<tr class=\""
291 << (Cluster.measurementsMatch(STI: State_.getSubtargetInfo(), SC: RSC,
292 Clustering: Clustering_,
293 AnalysisInconsistencyEpsilonSquared_)
294 ? "good-cluster"
295 : "bad-cluster")
296 << "\"><td>";
297 writeClusterId<kEscapeHtml>(OS, CID: Cluster.id());
298 OS << "</td><td><ul>";
299 for (const size_t PointId : Cluster.getPointIds()) {
300 printPointHtml(Point: Points[PointId], OS);
301 }
302 OS << "</ul></td>";
303 for (const auto &Stats : Cluster.getCentroid().getStats()) {
304 OS << "<td class=\"measurement\">";
305 writeMeasurementValue<kEscapeHtml>(OS, Value: Stats.avg());
306 OS << "<br><span class=\"minmax\">[";
307 writeMeasurementValue<kEscapeHtml>(OS, Value: Stats.min());
308 OS << ";";
309 writeMeasurementValue<kEscapeHtml>(OS, Value: Stats.max());
310 OS << "]</span></td>";
311 }
312 OS << "</tr>";
313 }
314 OS << "</table>";
315}
316
317void Analysis::SchedClassCluster::addPoint(
318 size_t PointId, const BenchmarkClustering &Clustering) {
319 PointIds.push_back(x: PointId);
320 const auto &Point = Clustering.getPoints()[PointId];
321 if (ClusterId.isUndef())
322 ClusterId = Clustering.getClusterIdForPoint(P: PointId);
323 assert(ClusterId == Clustering.getClusterIdForPoint(PointId));
324
325 Centroid.addPoint(Point: Point.Measurements);
326}
327
328bool Analysis::SchedClassCluster::measurementsMatch(
329 const MCSubtargetInfo &STI, const ResolvedSchedClass &RSC,
330 const BenchmarkClustering &Clustering,
331 const double AnalysisInconsistencyEpsilonSquared_) const {
332 assert(!Clustering.getPoints().empty());
333 const Benchmark::ModeE Mode = Clustering.getPoints()[0].Mode;
334
335 if (!Centroid.validate(Mode))
336 return false;
337
338 const std::vector<BenchmarkMeasure> ClusterCenterPoint =
339 Centroid.getAsPoint();
340
341 const std::vector<BenchmarkMeasure> SchedClassPoint =
342 RSC.getAsPoint(Mode, STI, Representative: Centroid.getStats());
343 if (SchedClassPoint.empty())
344 return false; // In Uops mode validate() may not be enough.
345
346 assert(ClusterCenterPoint.size() == SchedClassPoint.size() &&
347 "Expected measured/sched data dimensions to match.");
348
349 return Clustering.isNeighbour(P: ClusterCenterPoint, Q: SchedClassPoint,
350 EpsilonSquared_: AnalysisInconsistencyEpsilonSquared_);
351}
352
353void Analysis::printSchedClassDescHtml(const ResolvedSchedClass &RSC,
354 raw_ostream &OS) const {
355 OS << "<table class=\"sched-class-desc\">";
356 OS << "<tr><th>Valid</th><th>Variant</th><th>NumMicroOps</th><th>Latency</"
357 "th><th>RThroughput</th><th>WriteProcRes</th><th title=\"This is the "
358 "idealized unit resource (port) pressure assuming ideal "
359 "distribution\">Idealized Resource Pressure</th></tr>";
360 if (RSC.SCDesc->isValid()) {
361 const auto &SI = State_.getSubtargetInfo();
362 const auto &SM = SI.getSchedModel();
363 OS << "<tr><td>&#10004;</td>";
364 OS << "<td>" << (RSC.WasVariant ? "&#10004;" : "&#10005;") << "</td>";
365 OS << "<td>" << RSC.SCDesc->NumMicroOps << "</td>";
366 // Latencies.
367 OS << "<td><ul>";
368 for (int I = 0, E = RSC.SCDesc->NumWriteLatencyEntries; I < E; ++I) {
369 const auto *const Entry = SI.getWriteLatencyEntry(SC: RSC.SCDesc, DefIdx: I);
370 OS << "<li>" << Entry->Cycles;
371 if (RSC.SCDesc->NumWriteLatencyEntries > 1) {
372 // Dismabiguate if more than 1 latency.
373 OS << " (WriteResourceID " << Entry->WriteResourceID << ")";
374 }
375 OS << "</li>";
376 }
377 OS << "</ul></td>";
378 // inverse throughput.
379 OS << "<td>";
380 writeMeasurementValue<kEscapeHtml>(
381 OS, Value: MCSchedModel::getReciprocalThroughput(STI: SI, SCDesc: *RSC.SCDesc));
382 OS << "</td>";
383 // WriteProcRes.
384 OS << "<td><ul>";
385 for (const auto &WPR : RSC.NonRedundantWriteProcRes) {
386 OS << "<li><span class=\"mono\">";
387 writeEscaped<kEscapeHtml>(OS,
388 S: SM.getProcResource(ProcResourceIdx: WPR.ProcResourceIdx)->Name);
389 OS << "</span>: " << WPR.ReleaseAtCycle << "</li>";
390 }
391 OS << "</ul></td>";
392 // Idealized port pressure.
393 OS << "<td><ul>";
394 for (const auto &Pressure : RSC.IdealizedProcResPressure) {
395 OS << "<li><span class=\"mono\">";
396 writeEscaped<kEscapeHtml>(
397 OS, S: SI.getSchedModel().getProcResource(ProcResourceIdx: Pressure.first)->Name);
398 OS << "</span>: ";
399 writeMeasurementValue<kEscapeHtml>(OS, Value: Pressure.second);
400 OS << "</li>";
401 }
402 OS << "</ul></td>";
403 OS << "</tr>";
404 } else {
405 OS << "<tr><td>&#10005;</td><td></td><td></td></tr>";
406 }
407 OS << "</table>";
408}
409
410void Analysis::printClusterRawHtml(const BenchmarkClustering::ClusterId &Id,
411 StringRef display_name,
412 raw_ostream &OS) const {
413 const auto &Points = Clustering_.getPoints();
414 const auto &Cluster = Clustering_.getCluster(Id);
415 if (Cluster.PointIndices.empty())
416 return;
417
418 OS << "<div class=\"inconsistency\"><p>" << display_name << " Cluster ("
419 << Cluster.PointIndices.size() << " points)</p>";
420 OS << "<table class=\"sched-class-clusters\">";
421 // Table Header.
422 OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
423 for (const auto &Measurement : Points[Cluster.PointIndices[0]].Measurements) {
424 OS << "<th>";
425 writeEscaped<kEscapeHtml>(OS, S: Measurement.Key);
426 OS << "</th>";
427 }
428 OS << "</tr>";
429
430 // Point data.
431 for (const auto &PointId : Cluster.PointIndices) {
432 OS << "<tr class=\"bad-cluster\"><td>" << display_name << "</td><td><ul>";
433 printPointHtml(Point: Points[PointId], OS);
434 OS << "</ul></td>";
435 for (const auto &Measurement : Points[PointId].Measurements) {
436 OS << "<td class=\"measurement\">";
437 writeMeasurementValue<kEscapeHtml>(OS, Value: Measurement.PerInstructionValue);
438 }
439 OS << "</tr>";
440 }
441 OS << "</table>";
442
443 OS << "</div>";
444
445} // namespace exegesis
446
447static constexpr char kHtmlHead[] = R"(
448<head>
449<title>llvm-exegesis Analysis Results</title>
450<style>
451body {
452 font-family: sans-serif
453}
454span.sched-class-name {
455 font-weight: bold;
456 font-family: monospace;
457}
458span.opcode {
459 font-family: monospace;
460}
461span.config {
462 font-family: monospace;
463}
464div.inconsistency {
465 margin-top: 50px;
466}
467table {
468 margin-left: 50px;
469 border-collapse: collapse;
470}
471table, table tr,td,th {
472 border: 1px solid #444;
473}
474table ul {
475 padding-left: 0px;
476 margin: 0px;
477 list-style-type: none;
478}
479table.sched-class-clusters td {
480 padding-left: 10px;
481 padding-right: 10px;
482 padding-top: 10px;
483 padding-bottom: 10px;
484}
485table.sched-class-desc td {
486 padding-left: 10px;
487 padding-right: 10px;
488 padding-top: 2px;
489 padding-bottom: 2px;
490}
491span.mono {
492 font-family: monospace;
493}
494td.measurement {
495 text-align: center;
496}
497tr.good-cluster td.measurement {
498 color: #292
499}
500tr.bad-cluster td.measurement {
501 color: #922
502}
503tr.good-cluster td.measurement span.minmax {
504 color: #888;
505}
506tr.bad-cluster td.measurement span.minmax {
507 color: #888;
508}
509</style>
510</head>
511)";
512
513template <>
514Error Analysis::run<Analysis::PrintSchedClassInconsistencies>(
515 raw_ostream &OS) const {
516 const auto &FirstPoint = Clustering_.getPoints()[0];
517 // Print the header.
518 OS << "<!DOCTYPE html><html>" << kHtmlHead << "<body>";
519 OS << "<h1><span class=\"mono\">llvm-exegesis</span> Analysis Results</h1>";
520 OS << "<h3>Triple: <span class=\"mono\">";
521 writeEscaped<kEscapeHtml>(OS, S: FirstPoint.LLVMTriple);
522 OS << "</span></h3><h3>Cpu: <span class=\"mono\">";
523 writeEscaped<kEscapeHtml>(OS, S: FirstPoint.CpuName);
524 OS << "</span></h3>";
525 OS << "<h3>Epsilon: <span class=\"mono\">"
526 << format(Fmt: "%0.2f", Vals: std::sqrt(x: AnalysisInconsistencyEpsilonSquared_))
527 << "</span></h3>";
528
529 const auto &SI = State_.getSubtargetInfo();
530 for (const auto &RSCAndPoints : makePointsPerSchedClass()) {
531 if (!RSCAndPoints.RSC.SCDesc)
532 continue;
533 // Bucket sched class points into sched class clusters.
534 std::vector<SchedClassCluster> SchedClassClusters;
535 for (const size_t PointId : RSCAndPoints.PointIds) {
536 const auto &ClusterId = Clustering_.getClusterIdForPoint(P: PointId);
537 if (!ClusterId.isValid())
538 continue; // Ignore noise and errors. FIXME: take noise into account ?
539 if (ClusterId.isUnstable() ^ AnalysisDisplayUnstableOpcodes_)
540 continue; // Either display stable or unstable clusters only.
541 auto SchedClassClusterIt =
542 find_if(Range&: SchedClassClusters, P: [ClusterId](const SchedClassCluster &C) {
543 return C.id() == ClusterId;
544 });
545 if (SchedClassClusterIt == SchedClassClusters.end()) {
546 SchedClassClusters.emplace_back();
547 SchedClassClusterIt = std::prev(x: SchedClassClusters.end());
548 }
549 SchedClassClusterIt->addPoint(PointId, Clustering: Clustering_);
550 }
551
552 // Print any scheduling class that has at least one cluster that does not
553 // match the checked-in data.
554 if (all_of(Range&: SchedClassClusters, P: [this, &RSCAndPoints,
555 &SI](const SchedClassCluster &C) {
556 return C.measurementsMatch(STI: SI, RSC: RSCAndPoints.RSC, Clustering: Clustering_,
557 AnalysisInconsistencyEpsilonSquared_);
558 }))
559 continue; // Nothing weird.
560
561 OS << "<div class=\"inconsistency\"><p>Sched Class <span "
562 "class=\"sched-class-name\">";
563#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
564 writeEscaped<kEscapeHtml>(OS, SI.getSchedModel().getSchedClassName(
565 RSCAndPoints.RSC.SchedClassId));
566#else
567 OS << RSCAndPoints.RSC.SchedClassId;
568#endif
569 OS << "</span> contains instructions whose performance characteristics do"
570 " not match that of LLVM:</p>";
571 printSchedClassClustersHtml(Clusters: SchedClassClusters, RSC: RSCAndPoints.RSC, OS);
572 OS << "<p>llvm SchedModel data:</p>";
573 printSchedClassDescHtml(RSC: RSCAndPoints.RSC, OS);
574 OS << "</div>";
575 }
576
577 printClusterRawHtml(Id: BenchmarkClustering::ClusterId::noise(),
578 display_name: "[noise]", OS);
579
580 OS << "</body></html>";
581 return Error::success();
582}
583
584} // namespace exegesis
585} // namespace llvm
586