Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Exegesis] Add supports to serialize/deserialize object files into benchmarks #121993

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions llvm/docs/CommandGuide/llvm-exegesis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ OPTIONS
* ``measure``: Same as ``assemble-measured-code``, but also runs the measurement.
* ``dry-run-measurement``: Same as measure, but does not actually execute the snippet.

.. option:: --serialize-benchmarks

Generate a fully serialized benchmarks file, including the assembled object
files. This is useful to resume the measurement later with ``--run-measurement``.

.. option:: --force-serialized-obj-compress-format=[zlib|zstd]

When serializing benchmarks with ``--serialize-benchmarks``, always use the
compression format designated by this flag.

.. option:: --run-measurement=<benchmarks file>

Given a fully serialized benchmarks file generated after the
``assembly-measured-code`` phase with ``--serialize-benchmarks``, resume the
measurement phase from it.

.. option:: --x86-lbr-sample-period=<nBranches/sample>

Specify the LBR sampling period - how many branches before we take a sample.
Expand Down
11 changes: 11 additions & 0 deletions llvm/include/llvm/Support/Compression.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/Support/YAMLTraits.h"

namespace llvm {
template <typename T> class SmallVectorImpl;
Expand Down Expand Up @@ -126,6 +127,16 @@ Error decompress(DebugCompressionType T, ArrayRef<uint8_t> Input,

} // End of namespace compression

namespace yaml {
// Related YAML traits.
template <> struct ScalarEnumerationTraits<compression::Format> {
static void enumeration(IO &Io, compression::Format &Format) {
Io.enumCase(Format, "zstd", compression::Format::Zstd);
Io.enumCase(Format, "zlib", compression::Format::Zlib);
}
};
} // namespace yaml

} // End of namespace llvm

#endif
41 changes: 41 additions & 0 deletions llvm/test/tools/llvm-exegesis/serialize-obj-file.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# RUN: llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --opcode-name=SH3ADD --serialize-benchmarks --benchmark-phase=assemble-measured-code \
# RUN: --mode=latency --benchmarks-file=%t.yaml
# RUN: FileCheck --input-file=%t.yaml %s --check-prefixes=CHECK,SERIALIZE
# RUN: llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --run-measurement=%t.yaml --mode=latency --benchmark-phase=dry-run-measurement --use-dummy-perf-counters \
# RUN: --dump-object-to-disk=%t.o | FileCheck %s --check-prefixes=CHECK,DESERIALIZE
# RUN: llvm-objdump -d %t.o | FileCheck %s --check-prefix=OBJDUMP

# We should not serialie benchmarks by default.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serialize*

# RUN: llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --opcode-name=SH3ADD --benchmark-phase=assemble-measured-code --mode=latency | \
# RUN: FileCheck %s --check-prefix=NO-SERIALIZE

# We currently don't support serialization for repetition modes that require more than one snippets.
# RUN: not llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --opcode-name=SH3ADD --mode=latency --serialize-benchmarks --benchmark-phase=assemble-measured-code \
# RUN: --repetition-mode=min 2>&1 | FileCheck %s --check-prefix=NOT-SUPPORTED
# RUN: not llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --opcode-name=SH3ADD --mode=latency --serialize-benchmarks --benchmark-phase=assemble-measured-code \
# RUN: --repetition-mode=middle-half-loop 2>&1 | FileCheck %s --check-prefix=NOT-SUPPORTED
# RUN: not llvm-exegesis -mtriple=riscv64 -mcpu=sifive-p470 --opcode-name=SH3ADD --mode=latency --serialize-benchmarks --benchmark-phase=assemble-measured-code \
# RUN: --repetition-mode=middle-half-duplicate 2>&1 | FileCheck %s --check-prefix=NOT-SUPPORTED

# REQUIRES: riscv-registered-target && native-registered-exegesis-target
# REQUIRES: zlib || zstd

# A round-trip test for serialize/deserialize benchmarks.

# CHECK: mode: latency
# CHECK: instructions:
# CHECK-NEXT: - 'SH3ADD X{{.*}} X{{.*}} X{{.*}}'
# CHECK: cpu_name: sifive-p470
# CHECK-NEXT: llvm_triple: riscv64
# CHECK-NEXT: min_instructions: 10000
# CHECK-NEXT: measurements: []
# SERIALIZE: error: actual measurements skipped.
# DESERIALIZE: error: ''
# CHECK: info: Repeating a single explicitly serial instruction

# OBJDUMP: sh3add

# Negative tests.

# NOT-SUPPORTED: -serialize-benchmarks currently only supports -repetition-mode of loop and duplicate.
# NO-SERIALIZE-NOT: object_file:
89 changes: 87 additions & 2 deletions llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/bit.h"
#include "llvm/ObjectYAML/YAML.h"
#include "llvm/Support/Base64.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"

static constexpr const char kIntegerPrefix[] = "i_0x";
Expand All @@ -27,6 +30,13 @@ static constexpr const char kInvalidOperand[] = "INVALID";

namespace llvm {

static cl::opt<compression::Format> ForceObjectFileCompressionFormat(
"force-serialized-obj-compress-format", cl::Hidden,
cl::desc(
"Force to use this compression format for serialized object files."),
cl::values(clEnumValN(compression::Format::Zstd, "zstd", "Using Zstandard"),
clEnumValN(compression::Format::Zlib, "zlib", "Using LibZ")));

namespace {

// A mutable struct holding an LLVMState that can be passed through the
Expand Down Expand Up @@ -288,6 +298,33 @@ template <> struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> {
}
};

template <> struct MappingTraits<exegesis::Benchmark::ObjectFile> {
struct NormalizedBase64Binary {
std::string Base64Str;

NormalizedBase64Binary(IO &) {}
NormalizedBase64Binary(IO &, const std::vector<uint8_t> &Data)
: Base64Str(llvm::encodeBase64(Data)) {}

std::vector<uint8_t> denormalize(IO &) {
std::vector<char> Buffer;
if (Error E = llvm::decodeBase64(Base64Str, Buffer))
report_fatal_error(std::move(E));

StringRef Data(Buffer.data(), Buffer.size());
return std::vector<uint8_t>(Data.bytes_begin(), Data.bytes_end());
}
};

static void mapping(IO &Io, exegesis::Benchmark::ObjectFile &Obj) {
Io.mapRequired("compression", Obj.CompressionFormat);
Io.mapRequired("original_size", Obj.UncompressedSize);
MappingNormalization<NormalizedBase64Binary, std::vector<uint8_t>>
ObjFileString(Io, Obj.CompressedBytes);
Io.mapRequired("compressed_bytes", ObjFileString->Base64Str);
}
};

template <> struct MappingContextTraits<exegesis::Benchmark, YamlContext> {
struct NormalizedBinary {
NormalizedBinary(IO &io) {}
Expand Down Expand Up @@ -325,9 +362,11 @@ template <> struct MappingContextTraits<exegesis::Benchmark, YamlContext> {
Io.mapRequired("error", Obj.Error);
Io.mapOptional("info", Obj.Info);
// AssembledSnippet
MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
MappingNormalization<NormalizedBinary, std::vector<uint8_t>> SnippetString(
Io, Obj.AssembledSnippet);
Io.mapOptional("assembled_snippet", BinaryString->Binary);
Io.mapOptional("assembled_snippet", SnippetString->Binary);
// ObjectFile
Io.mapOptional("object_file", Obj.ObjFile);
}
};

Expand Down Expand Up @@ -364,6 +403,52 @@ Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) {
return Result;
}

Error Benchmark::setObjectFile(StringRef RawBytes) {
SmallVector<uint8_t> CompressedBytes;
llvm::compression::Format CompressionFormat;

auto isFormatAvailable = [](llvm::compression::Format F) -> bool {
switch (F) {
case compression::Format::Zstd:
return compression::zstd::isAvailable();
case compression::Format::Zlib:
return compression::zlib::isAvailable();
}
};
if (ForceObjectFileCompressionFormat.getNumOccurrences() > 0) {
CompressionFormat = ForceObjectFileCompressionFormat;
if (!isFormatAvailable(CompressionFormat))
return make_error<StringError>(
"The designated compression format is not available.",
inconvertibleErrorCode());
} else if (isFormatAvailable(compression::Format::Zstd)) {
// Try newer compression algorithm first.
CompressionFormat = compression::Format::Zstd;
} else if (isFormatAvailable(compression::Format::Zlib)) {
CompressionFormat = compression::Format::Zlib;
} else {
return make_error<StringError>(
"None of the compression methods is available.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is -> are

inconvertibleErrorCode());
}

switch (CompressionFormat) {
case compression::Format::Zstd:
compression::zstd::compress({RawBytes.bytes_begin(), RawBytes.bytes_end()},
CompressedBytes);
break;
case compression::Format::Zlib:
compression::zlib::compress({RawBytes.bytes_begin(), RawBytes.bytes_end()},
CompressedBytes);
break;
}

ObjFile = {CompressionFormat,
RawBytes.size(),
{CompressedBytes.begin(), CompressedBytes.end()}};
return Error::success();
}

Expected<Benchmark> Benchmark::readYaml(const LLVMState &State,
MemoryBufferRef Buffer) {
yaml::Input Yin(Buffer);
Expand Down
20 changes: 20 additions & 0 deletions llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstBuilder.h"
#include "llvm/Support/Compression.h"
#include "llvm/Support/YAMLTraits.h"
#include <limits>
#include <set>
Expand Down Expand Up @@ -77,6 +78,11 @@ struct BenchmarkKey {
uintptr_t SnippetAddress = 0;
// The register that should be used to hold the loop counter.
unsigned LoopRegister;

bool operator==(const BenchmarkKey &RHS) const {
return Config == RHS.Config &&
Instructions[0].getOpcode() == RHS.Instructions[0].getOpcode();
}
};

struct BenchmarkMeasure {
Expand Down Expand Up @@ -123,6 +129,16 @@ struct Benchmark {
std::string Error;
std::string Info;
std::vector<uint8_t> AssembledSnippet;

struct ObjectFile {
llvm::compression::Format CompressionFormat;
size_t UncompressedSize = 0;
std::vector<uint8_t> CompressedBytes;

bool isValid() const { return UncompressedSize && CompressedBytes.size(); }
};
std::optional<ObjectFile> ObjFile;

// How to aggregate measurements.
enum ResultAggregationModeE { Min, Max, Mean, MinVariance };

Expand All @@ -133,6 +149,10 @@ struct Benchmark {
Benchmark &operator=(const Benchmark &) = delete;
Benchmark &operator=(Benchmark &&) = delete;

// Compress raw object file bytes and assign the result and compression type
// to CompressedObjectFile and ObjFileCompression, respectively.
class Error setObjectFile(StringRef RawBytes);

// Read functions.
static Expected<Benchmark> readYaml(const LLVMState &State,
MemoryBufferRef Buffer);
Expand Down
55 changes: 55 additions & 0 deletions llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
Expand Down Expand Up @@ -51,6 +52,14 @@
#endif // __linux__

namespace llvm {

static cl::opt<bool>
SerializeBenchmarks("serialize-benchmarks",
cl::desc("Generate fully-serialized benchmarks "
"that can later be deserialized and "
"resuming the measurement."),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resuming -> resume

cl::init(false));

namespace exegesis {

BenchmarkRunner::BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode,
Expand Down Expand Up @@ -619,6 +628,7 @@ Expected<SmallString<0>> BenchmarkRunner::assembleSnippet(
Expected<BenchmarkRunner::RunnableConfiguration>
BenchmarkRunner::getRunnableConfiguration(
const BenchmarkCode &BC, unsigned MinInstructions, unsigned LoopBodySize,
Benchmark::RepetitionModeE RepetitionMode,
const SnippetRepetitor &Repetitor) const {
RunnableConfiguration RC;

Expand Down Expand Up @@ -663,12 +673,57 @@ BenchmarkRunner::getRunnableConfiguration(
LoopBodySize, GenerateMemoryInstructions);
if (Error E = Snippet.takeError())
return std::move(E);

// Generate fully-serialized benchmarks.
if (SerializeBenchmarks) {
if (RepetitionMode != Benchmark::Loop &&
RepetitionMode != Benchmark::Duplicate)
return make_error<Failure>(
"-serialize-benchmarks currently only supports -repetition-mode "
"of loop and duplicate.");

if (Error E = BenchmarkResult.setObjectFile(*Snippet))
return std::move(E);
}

RC.ObjectFile = getObjectFromBuffer(*Snippet);
}

return std::move(RC);
}

Expected<BenchmarkRunner::RunnableConfiguration>
BenchmarkRunner::getRunnableConfiguration(Benchmark &&B) const {
assert(B.ObjFile.has_value() && B.ObjFile->isValid() &&
"No serialized obejct file is attached?");
const Benchmark::ObjectFile &ObjFile = *B.ObjFile;
SmallVector<uint8_t> DecompressedObjFile;
switch (ObjFile.CompressionFormat) {
case compression::Format::Zstd:
if (!compression::zstd::isAvailable())
return make_error<StringError>("zstd is not available for decompression.",
inconvertibleErrorCode());
if (Error E = compression::zstd::decompress(ObjFile.CompressedBytes,
DecompressedObjFile,
ObjFile.UncompressedSize))
return std::move(E);
break;
case compression::Format::Zlib:
if (!compression::zlib::isAvailable())
return make_error<StringError>("zlib is not available for decompression.",
inconvertibleErrorCode());
if (Error E = compression::zlib::decompress(ObjFile.CompressedBytes,
DecompressedObjFile,
ObjFile.UncompressedSize))
return std::move(E);
break;
}

StringRef Buffer(reinterpret_cast<const char *>(DecompressedObjFile.begin()),
DecompressedObjFile.size());
return RunnableConfiguration{std::move(B), getObjectFromBuffer(Buffer)};
}

Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>>
BenchmarkRunner::createFunctionExecutor(
object::OwningBinary<object::ObjectFile> ObjectFile,
Expand Down
11 changes: 9 additions & 2 deletions llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,25 @@ class BenchmarkRunner {
RunnableConfiguration &operator=(RunnableConfiguration &&) = delete;
RunnableConfiguration &operator=(const RunnableConfiguration &) = delete;

Benchmark BenchmarkResult;
object::OwningBinary<object::ObjectFile> ObjectFile;

private:
RunnableConfiguration() = default;

Benchmark BenchmarkResult;
object::OwningBinary<object::ObjectFile> ObjectFile;
RunnableConfiguration(Benchmark &&B,
object::OwningBinary<object::ObjectFile> &&OF)
: BenchmarkResult(std::move(B)), ObjectFile(std::move(OF)) {}
};

Expected<RunnableConfiguration>
getRunnableConfiguration(const BenchmarkCode &Configuration,
unsigned MinInstructions, unsigned LoopUnrollFactor,
Benchmark::RepetitionModeE RepetitionMode,
const SnippetRepetitor &Repetitor) const;

Expected<RunnableConfiguration> getRunnableConfiguration(Benchmark &&B) const;

std::pair<Error, Benchmark>
runConfiguration(RunnableConfiguration &&RC,
const std::optional<StringRef> &DumpFile,
Expand Down
Loading
Loading