1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-23 03:02:36 +01:00
llvm-mirror/tools/llvm-exegesis/lib/Analysis.cpp
Philipp Krones df7a8b162e [MC] Refactor MCObjectFileInfo initialization and allow targets to create MCObjectFileInfo
This makes it possible for targets to define their own MCObjectFileInfo.
This MCObjectFileInfo is then used to determine things like section alignment.

This is a follow up to D101462 and prepares for the RISCV backend defining the
text section alignment depending on the enabled extensions.

Reviewed By: MaskRay

Differential Revision: https://reviews.llvm.org/D101921
2021-05-23 14:15:23 -07:00

606 lines
20 KiB
C++

//===-- Analysis.cpp --------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Analysis.h"
#include "BenchmarkResult.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/Support/FormatVariadic.h"
#include <limits>
#include <unordered_set>
#include <vector>
namespace llvm {
namespace exegesis {
static const char kCsvSep = ',';
namespace {
enum EscapeTag { kEscapeCsv, kEscapeHtml, kEscapeHtmlString };
template <EscapeTag Tag> void writeEscaped(raw_ostream &OS, const StringRef S);
template <> void writeEscaped<kEscapeCsv>(raw_ostream &OS, const StringRef S) {
if (!llvm::is_contained(S, kCsvSep)) {
OS << S;
} else {
// Needs escaping.
OS << '"';
for (const char C : S) {
if (C == '"')
OS << "\"\"";
else
OS << C;
}
OS << '"';
}
}
template <> void writeEscaped<kEscapeHtml>(raw_ostream &OS, const StringRef S) {
for (const char C : S) {
if (C == '<')
OS << "&lt;";
else if (C == '>')
OS << "&gt;";
else if (C == '&')
OS << "&amp;";
else
OS << C;
}
}
template <>
void writeEscaped<kEscapeHtmlString>(raw_ostream &OS, const StringRef S) {
for (const char C : S) {
if (C == '"')
OS << "\\\"";
else
OS << C;
}
}
} // namespace
template <EscapeTag Tag>
static void
writeClusterId(raw_ostream &OS,
const InstructionBenchmarkClustering::ClusterId &CID) {
if (CID.isNoise())
writeEscaped<Tag>(OS, "[noise]");
else if (CID.isError())
writeEscaped<Tag>(OS, "[error]");
else
OS << CID.getId();
}
template <EscapeTag Tag>
static void writeMeasurementValue(raw_ostream &OS, const double Value) {
// Given Value, if we wanted to serialize it to a string,
// how many base-10 digits will we need to store, max?
static constexpr auto MaxDigitCount =
std::numeric_limits<decltype(Value)>::max_digits10;
// Also, we will need a decimal separator.
static constexpr auto DecimalSeparatorLen = 1; // '.' e.g.
// So how long of a string will the serialization produce, max?
static constexpr auto SerializationLen = MaxDigitCount + DecimalSeparatorLen;
// WARNING: when changing the format, also adjust the small-size estimate ^.
static constexpr StringLiteral SimpleFloatFormat = StringLiteral("{0:F}");
writeEscaped<Tag>(
OS, formatv(SimpleFloatFormat.data(), Value).sstr<SerializationLen>());
}
template <typename EscapeTag, EscapeTag Tag>
void Analysis::writeSnippet(raw_ostream &OS, ArrayRef<uint8_t> Bytes,
const char *Separator) const {
SmallVector<std::string, 3> Lines;
// Parse the asm snippet and print it.
while (!Bytes.empty()) {
MCInst MI;
uint64_t MISize = 0;
if (!Disasm_->getInstruction(MI, MISize, Bytes, 0, nulls())) {
writeEscaped<Tag>(OS, join(Lines, Separator));
writeEscaped<Tag>(OS, Separator);
writeEscaped<Tag>(OS, "[error decoding asm snippet]");
return;
}
SmallString<128> InstPrinterStr; // FIXME: magic number.
raw_svector_ostream OSS(InstPrinterStr);
InstPrinter_->printInst(&MI, 0, "", *SubtargetInfo_, OSS);
Bytes = Bytes.drop_front(MISize);
Lines.emplace_back(StringRef(InstPrinterStr).trim());
}
writeEscaped<Tag>(OS, join(Lines, Separator));
}
// Prints a row representing an instruction, along with scheduling info and
// point coordinates (measurements).
void Analysis::printInstructionRowCsv(const size_t PointId,
raw_ostream &OS) const {
const InstructionBenchmark &Point = Clustering_.getPoints()[PointId];
writeClusterId<kEscapeCsv>(OS, Clustering_.getClusterIdForPoint(PointId));
OS << kCsvSep;
writeSnippet<EscapeTag, kEscapeCsv>(OS, Point.AssembledSnippet, "; ");
OS << kCsvSep;
writeEscaped<kEscapeCsv>(OS, Point.Key.Config);
OS << kCsvSep;
assert(!Point.Key.Instructions.empty());
const MCInst &MCI = Point.keyInstruction();
unsigned SchedClassId;
std::tie(SchedClassId, std::ignore) = ResolvedSchedClass::resolveSchedClassId(
*SubtargetInfo_, *InstrInfo_, MCI);
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
const MCSchedClassDesc *const SCDesc =
SubtargetInfo_->getSchedModel().getSchedClassDesc(SchedClassId);
writeEscaped<kEscapeCsv>(OS, SCDesc->Name);
#else
OS << SchedClassId;
#endif
for (const auto &Measurement : Point.Measurements) {
OS << kCsvSep;
writeMeasurementValue<kEscapeCsv>(OS, Measurement.PerInstructionValue);
}
OS << "\n";
}
Analysis::Analysis(const Target &Target, std::unique_ptr<MCInstrInfo> InstrInfo,
const InstructionBenchmarkClustering &Clustering,
double AnalysisInconsistencyEpsilon,
bool AnalysisDisplayUnstableOpcodes,
const std::string &ForceCpuName)
: Clustering_(Clustering), InstrInfo_(std::move(InstrInfo)),
AnalysisInconsistencyEpsilonSquared_(AnalysisInconsistencyEpsilon *
AnalysisInconsistencyEpsilon),
AnalysisDisplayUnstableOpcodes_(AnalysisDisplayUnstableOpcodes) {
if (Clustering.getPoints().empty())
return;
const InstructionBenchmark &FirstPoint = Clustering.getPoints().front();
const std::string CpuName =
ForceCpuName.empty() ? FirstPoint.CpuName : ForceCpuName;
RegInfo_.reset(Target.createMCRegInfo(FirstPoint.LLVMTriple));
MCTargetOptions MCOptions;
AsmInfo_.reset(
Target.createMCAsmInfo(*RegInfo_, FirstPoint.LLVMTriple, MCOptions));
SubtargetInfo_.reset(
Target.createMCSubtargetInfo(FirstPoint.LLVMTriple, CpuName, ""));
InstPrinter_.reset(Target.createMCInstPrinter(
Triple(FirstPoint.LLVMTriple), 0 /*default variant*/, *AsmInfo_,
*InstrInfo_, *RegInfo_));
Context_ =
std::make_unique<MCContext>(Triple(FirstPoint.LLVMTriple), AsmInfo_.get(),
RegInfo_.get(), SubtargetInfo_.get());
Disasm_.reset(Target.createMCDisassembler(*SubtargetInfo_, *Context_));
assert(Disasm_ && "cannot create MCDisassembler. missing call to "
"InitializeXXXTargetDisassembler ?");
}
template <>
Error Analysis::run<Analysis::PrintClusters>(raw_ostream &OS) const {
if (Clustering_.getPoints().empty())
return Error::success();
// Write the header.
OS << "cluster_id" << kCsvSep << "opcode_name" << kCsvSep << "config"
<< kCsvSep << "sched_class";
for (const auto &Measurement : Clustering_.getPoints().front().Measurements) {
OS << kCsvSep;
writeEscaped<kEscapeCsv>(OS, Measurement.Key);
}
OS << "\n";
// Write the points.
for (const auto &ClusterIt : Clustering_.getValidClusters()) {
for (const size_t PointId : ClusterIt.PointIndices) {
printInstructionRowCsv(PointId, OS);
}
OS << "\n\n";
}
return Error::success();
}
Analysis::ResolvedSchedClassAndPoints::ResolvedSchedClassAndPoints(
ResolvedSchedClass &&RSC)
: RSC(std::move(RSC)) {}
std::vector<Analysis::ResolvedSchedClassAndPoints>
Analysis::makePointsPerSchedClass() const {
std::vector<ResolvedSchedClassAndPoints> Entries;
// Maps SchedClassIds to index in result.
std::unordered_map<unsigned, size_t> SchedClassIdToIndex;
const auto &Points = Clustering_.getPoints();
for (size_t PointId = 0, E = Points.size(); PointId < E; ++PointId) {
const InstructionBenchmark &Point = Points[PointId];
if (!Point.Error.empty())
continue;
assert(!Point.Key.Instructions.empty());
// FIXME: we should be using the tuple of classes for instructions in the
// snippet as key.
const MCInst &MCI = Point.keyInstruction();
unsigned SchedClassId;
bool WasVariant;
std::tie(SchedClassId, WasVariant) =
ResolvedSchedClass::resolveSchedClassId(*SubtargetInfo_, *InstrInfo_,
MCI);
const auto IndexIt = SchedClassIdToIndex.find(SchedClassId);
if (IndexIt == SchedClassIdToIndex.end()) {
// Create a new entry.
SchedClassIdToIndex.emplace(SchedClassId, Entries.size());
ResolvedSchedClassAndPoints Entry(
ResolvedSchedClass(*SubtargetInfo_, SchedClassId, WasVariant));
Entry.PointIds.push_back(PointId);
Entries.push_back(std::move(Entry));
} else {
// Append to the existing entry.
Entries[IndexIt->second].PointIds.push_back(PointId);
}
}
return Entries;
}
// Parallel benchmarks repeat the same opcode multiple times. Just show this
// opcode and show the whole snippet only on hover.
static void writeParallelSnippetHtml(raw_ostream &OS,
const std::vector<MCInst> &Instructions,
const MCInstrInfo &InstrInfo) {
if (Instructions.empty())
return;
writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instructions[0].getOpcode()));
if (Instructions.size() > 1)
OS << " (x" << Instructions.size() << ")";
}
// Latency tries to find a serial path. Just show the opcode path and show the
// whole snippet only on hover.
static void writeLatencySnippetHtml(raw_ostream &OS,
const std::vector<MCInst> &Instructions,
const MCInstrInfo &InstrInfo) {
bool First = true;
for (const MCInst &Instr : Instructions) {
if (First)
First = false;
else
OS << " &rarr; ";
writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instr.getOpcode()));
}
}
void Analysis::printPointHtml(const InstructionBenchmark &Point,
llvm::raw_ostream &OS) const {
OS << "<li><span class=\"mono\" title=\"";
writeSnippet<EscapeTag, kEscapeHtmlString>(OS, Point.AssembledSnippet, "\n");
OS << "\">";
switch (Point.Mode) {
case InstructionBenchmark::Latency:
writeLatencySnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
break;
case InstructionBenchmark::Uops:
case InstructionBenchmark::InverseThroughput:
writeParallelSnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
break;
default:
llvm_unreachable("invalid mode");
}
OS << "</span> <span class=\"mono\">";
writeEscaped<kEscapeHtml>(OS, Point.Key.Config);
OS << "</span></li>";
}
void Analysis::printSchedClassClustersHtml(
const std::vector<SchedClassCluster> &Clusters,
const ResolvedSchedClass &RSC, raw_ostream &OS) const {
const auto &Points = Clustering_.getPoints();
OS << "<table class=\"sched-class-clusters\">";
OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
assert(!Clusters.empty());
for (const auto &Measurement :
Points[Clusters[0].getPointIds()[0]].Measurements) {
OS << "<th>";
writeEscaped<kEscapeHtml>(OS, Measurement.Key);
OS << "</th>";
}
OS << "</tr>";
for (const SchedClassCluster &Cluster : Clusters) {
OS << "<tr class=\""
<< (Cluster.measurementsMatch(*SubtargetInfo_, RSC, Clustering_,
AnalysisInconsistencyEpsilonSquared_)
? "good-cluster"
: "bad-cluster")
<< "\"><td>";
writeClusterId<kEscapeHtml>(OS, Cluster.id());
OS << "</td><td><ul>";
for (const size_t PointId : Cluster.getPointIds()) {
printPointHtml(Points[PointId], OS);
}
OS << "</ul></td>";
for (const auto &Stats : Cluster.getCentroid().getStats()) {
OS << "<td class=\"measurement\">";
writeMeasurementValue<kEscapeHtml>(OS, Stats.avg());
OS << "<br><span class=\"minmax\">[";
writeMeasurementValue<kEscapeHtml>(OS, Stats.min());
OS << ";";
writeMeasurementValue<kEscapeHtml>(OS, Stats.max());
OS << "]</span></td>";
}
OS << "</tr>";
}
OS << "</table>";
}
void Analysis::SchedClassCluster::addPoint(
size_t PointId, const InstructionBenchmarkClustering &Clustering) {
PointIds.push_back(PointId);
const auto &Point = Clustering.getPoints()[PointId];
if (ClusterId.isUndef())
ClusterId = Clustering.getClusterIdForPoint(PointId);
assert(ClusterId == Clustering.getClusterIdForPoint(PointId));
Centroid.addPoint(Point.Measurements);
}
bool Analysis::SchedClassCluster::measurementsMatch(
const MCSubtargetInfo &STI, const ResolvedSchedClass &RSC,
const InstructionBenchmarkClustering &Clustering,
const double AnalysisInconsistencyEpsilonSquared_) const {
assert(!Clustering.getPoints().empty());
const InstructionBenchmark::ModeE Mode = Clustering.getPoints()[0].Mode;
if (!Centroid.validate(Mode))
return false;
const std::vector<BenchmarkMeasure> ClusterCenterPoint =
Centroid.getAsPoint();
const std::vector<BenchmarkMeasure> SchedClassPoint =
RSC.getAsPoint(Mode, STI, Centroid.getStats());
if (SchedClassPoint.empty())
return false; // In Uops mode validate() may not be enough.
assert(ClusterCenterPoint.size() == SchedClassPoint.size() &&
"Expected measured/sched data dimensions to match.");
return Clustering.isNeighbour(ClusterCenterPoint, SchedClassPoint,
AnalysisInconsistencyEpsilonSquared_);
}
void Analysis::printSchedClassDescHtml(const ResolvedSchedClass &RSC,
raw_ostream &OS) const {
OS << "<table class=\"sched-class-desc\">";
OS << "<tr><th>Valid</th><th>Variant</th><th>NumMicroOps</th><th>Latency</"
"th><th>RThroughput</th><th>WriteProcRes</th><th title=\"This is the "
"idealized unit resource (port) pressure assuming ideal "
"distribution\">Idealized Resource Pressure</th></tr>";
if (RSC.SCDesc->isValid()) {
const auto &SM = SubtargetInfo_->getSchedModel();
OS << "<tr><td>&#10004;</td>";
OS << "<td>" << (RSC.WasVariant ? "&#10004;" : "&#10005;") << "</td>";
OS << "<td>" << RSC.SCDesc->NumMicroOps << "</td>";
// Latencies.
OS << "<td><ul>";
for (int I = 0, E = RSC.SCDesc->NumWriteLatencyEntries; I < E; ++I) {
const auto *const Entry =
SubtargetInfo_->getWriteLatencyEntry(RSC.SCDesc, I);
OS << "<li>" << Entry->Cycles;
if (RSC.SCDesc->NumWriteLatencyEntries > 1) {
// Dismabiguate if more than 1 latency.
OS << " (WriteResourceID " << Entry->WriteResourceID << ")";
}
OS << "</li>";
}
OS << "</ul></td>";
// inverse throughput.
OS << "<td>";
writeMeasurementValue<kEscapeHtml>(
OS,
MCSchedModel::getReciprocalThroughput(*SubtargetInfo_, *RSC.SCDesc));
OS << "</td>";
// WriteProcRes.
OS << "<td><ul>";
for (const auto &WPR : RSC.NonRedundantWriteProcRes) {
OS << "<li><span class=\"mono\">";
writeEscaped<kEscapeHtml>(OS,
SM.getProcResource(WPR.ProcResourceIdx)->Name);
OS << "</span>: " << WPR.Cycles << "</li>";
}
OS << "</ul></td>";
// Idealized port pressure.
OS << "<td><ul>";
for (const auto &Pressure : RSC.IdealizedProcResPressure) {
OS << "<li><span class=\"mono\">";
writeEscaped<kEscapeHtml>(OS, SubtargetInfo_->getSchedModel()
.getProcResource(Pressure.first)
->Name);
OS << "</span>: ";
writeMeasurementValue<kEscapeHtml>(OS, Pressure.second);
OS << "</li>";
}
OS << "</ul></td>";
OS << "</tr>";
} else {
OS << "<tr><td>&#10005;</td><td></td><td></td></tr>";
}
OS << "</table>";
}
void Analysis::printClusterRawHtml(
const InstructionBenchmarkClustering::ClusterId &Id, StringRef display_name,
llvm::raw_ostream &OS) const {
const auto &Points = Clustering_.getPoints();
const auto &Cluster = Clustering_.getCluster(Id);
if (Cluster.PointIndices.empty())
return;
OS << "<div class=\"inconsistency\"><p>" << display_name << " Cluster ("
<< Cluster.PointIndices.size() << " points)</p>";
OS << "<table class=\"sched-class-clusters\">";
// Table Header.
OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
for (const auto &Measurement : Points[Cluster.PointIndices[0]].Measurements) {
OS << "<th>";
writeEscaped<kEscapeHtml>(OS, Measurement.Key);
OS << "</th>";
}
OS << "</tr>";
// Point data.
for (const auto &PointId : Cluster.PointIndices) {
OS << "<tr class=\"bad-cluster\"><td>" << display_name << "</td><td><ul>";
printPointHtml(Points[PointId], OS);
OS << "</ul></td>";
for (const auto &Measurement : Points[PointId].Measurements) {
OS << "<td class=\"measurement\">";
writeMeasurementValue<kEscapeHtml>(OS, Measurement.PerInstructionValue);
}
OS << "</tr>";
}
OS << "</table>";
OS << "</div>";
} // namespace exegesis
static constexpr const char kHtmlHead[] = R"(
<head>
<title>llvm-exegesis Analysis Results</title>
<style>
body {
font-family: sans-serif
}
span.sched-class-name {
font-weight: bold;
font-family: monospace;
}
span.opcode {
font-family: monospace;
}
span.config {
font-family: monospace;
}
div.inconsistency {
margin-top: 50px;
}
table {
margin-left: 50px;
border-collapse: collapse;
}
table, table tr,td,th {
border: 1px solid #444;
}
table ul {
padding-left: 0px;
margin: 0px;
list-style-type: none;
}
table.sched-class-clusters td {
padding-left: 10px;
padding-right: 10px;
padding-top: 10px;
padding-bottom: 10px;
}
table.sched-class-desc td {
padding-left: 10px;
padding-right: 10px;
padding-top: 2px;
padding-bottom: 2px;
}
span.mono {
font-family: monospace;
}
td.measurement {
text-align: center;
}
tr.good-cluster td.measurement {
color: #292
}
tr.bad-cluster td.measurement {
color: #922
}
tr.good-cluster td.measurement span.minmax {
color: #888;
}
tr.bad-cluster td.measurement span.minmax {
color: #888;
}
</style>
</head>
)";
template <>
Error Analysis::run<Analysis::PrintSchedClassInconsistencies>(
raw_ostream &OS) const {
const auto &FirstPoint = Clustering_.getPoints()[0];
// Print the header.
OS << "<!DOCTYPE html><html>" << kHtmlHead << "<body>";
OS << "<h1><span class=\"mono\">llvm-exegesis</span> Analysis Results</h1>";
OS << "<h3>Triple: <span class=\"mono\">";
writeEscaped<kEscapeHtml>(OS, FirstPoint.LLVMTriple);
OS << "</span></h3><h3>Cpu: <span class=\"mono\">";
writeEscaped<kEscapeHtml>(OS, FirstPoint.CpuName);
OS << "</span></h3>";
for (const auto &RSCAndPoints : makePointsPerSchedClass()) {
if (!RSCAndPoints.RSC.SCDesc)
continue;
// Bucket sched class points into sched class clusters.
std::vector<SchedClassCluster> SchedClassClusters;
for (const size_t PointId : RSCAndPoints.PointIds) {
const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId);
if (!ClusterId.isValid())
continue; // Ignore noise and errors. FIXME: take noise into account ?
if (ClusterId.isUnstable() ^ AnalysisDisplayUnstableOpcodes_)
continue; // Either display stable or unstable clusters only.
auto SchedClassClusterIt = llvm::find_if(
SchedClassClusters, [ClusterId](const SchedClassCluster &C) {
return C.id() == ClusterId;
});
if (SchedClassClusterIt == SchedClassClusters.end()) {
SchedClassClusters.emplace_back();
SchedClassClusterIt = std::prev(SchedClassClusters.end());
}
SchedClassClusterIt->addPoint(PointId, Clustering_);
}
// Print any scheduling class that has at least one cluster that does not
// match the checked-in data.
if (all_of(SchedClassClusters, [this,
&RSCAndPoints](const SchedClassCluster &C) {
return C.measurementsMatch(*SubtargetInfo_, RSCAndPoints.RSC,
Clustering_,
AnalysisInconsistencyEpsilonSquared_);
}))
continue; // Nothing weird.
OS << "<div class=\"inconsistency\"><p>Sched Class <span "
"class=\"sched-class-name\">";
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
writeEscaped<kEscapeHtml>(OS, RSCAndPoints.RSC.SCDesc->Name);
#else
OS << RSCAndPoints.RSC.SchedClassId;
#endif
OS << "</span> contains instructions whose performance characteristics do"
" not match that of LLVM:</p>";
printSchedClassClustersHtml(SchedClassClusters, RSCAndPoints.RSC, OS);
OS << "<p>llvm SchedModel data:</p>";
printSchedClassDescHtml(RSCAndPoints.RSC, OS);
OS << "</div>";
}
printClusterRawHtml(InstructionBenchmarkClustering::ClusterId::noise(),
"[noise]", OS);
OS << "</body></html>";
return Error::success();
}
} // namespace exegesis
} // namespace llvm