Skip to content

[CGData] llvm-cgdata #89884

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

Merged
merged 6 commits into from
Jul 23, 2024
Merged
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
204 changes: 204 additions & 0 deletions llvm/include/llvm/CodeGenData/CodeGenData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//===- CodeGenData.h --------------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for codegen data that has stable summary which
// can be used to optimize the code in the subsequent codegen.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CODEGENDATA_CODEGENDATA_H
#define LLVM_CODEGENDATA_CODEGENDATA_H

#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/CodeGenData/OutlinedHashTree.h"
#include "llvm/CodeGenData/OutlinedHashTreeRecord.h"
#include "llvm/IR/Module.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/TargetParser/Triple.h"
#include <mutex>

namespace llvm {

enum CGDataSectKind {
#define CG_DATA_SECT_ENTRY(Kind, SectNameCommon, SectNameCoff, Prefix) Kind,
#include "llvm/CodeGenData/CodeGenData.inc"
};

std::string getCodeGenDataSectionName(CGDataSectKind CGSK,
Triple::ObjectFormatType OF,
bool AddSegmentInfo = true);

enum class CGDataKind {
Unknown = 0x0,
// A function outlining info.
FunctionOutlinedHashTree = 0x1,
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/FunctionOutlinedHashTree)
};

const std::error_category &cgdata_category();

enum class cgdata_error {
success = 0,
eof,
bad_magic,
bad_header,
empty_cgdata,
malformed,
unsupported_version,
};

inline std::error_code make_error_code(cgdata_error E) {
return std::error_code(static_cast<int>(E), cgdata_category());
}

class CGDataError : public ErrorInfo<CGDataError> {
public:
CGDataError(cgdata_error Err, const Twine &ErrStr = Twine())
: Err(Err), Msg(ErrStr.str()) {
assert(Err != cgdata_error::success && "Not an error");
}

std::string message() const override;

void log(raw_ostream &OS) const override { OS << message(); }

std::error_code convertToErrorCode() const override {
return make_error_code(Err);
}

cgdata_error get() const { return Err; }
const std::string &getMessage() const { return Msg; }

/// Consume an Error and return the raw enum value contained within it, and
/// the optional error message. The Error must either be a success value, or
/// contain a single CGDataError.
static std::pair<cgdata_error, std::string> take(Error E) {
auto Err = cgdata_error::success;
std::string Msg;
handleAllErrors(std::move(E), [&Err, &Msg](const CGDataError &IPE) {
assert(Err == cgdata_error::success && "Multiple errors encountered");
Err = IPE.get();
Msg = IPE.getMessage();
});
return {Err, Msg};
}

static char ID;

private:
cgdata_error Err;
std::string Msg;
};

enum CGDataMode {
None,
Read,
Write,
};

class CodeGenData {
/// Global outlined hash tree that has oulined hash sequences across modules.
std::unique_ptr<OutlinedHashTree> PublishedHashTree;

/// This flag is set when -fcodegen-data-generate is passed.
/// Or, it can be mutated with -fcodegen-data-thinlto-two-rounds.
bool EmitCGData;

/// This is a singleton instance which is thread-safe. Unlike profile data
/// which is largely function-based, codegen data describes the whole module.
/// Therefore, this can be initialized once, and can be used across modules
/// instead of constructing the same one for each codegen backend.
static std::unique_ptr<CodeGenData> Instance;
static std::once_flag OnceFlag;

CodeGenData() = default;

public:
~CodeGenData() = default;

static CodeGenData &getInstance();

/// Returns true if we have a valid outlined hash tree.
bool hasOutlinedHashTree() {
return PublishedHashTree && !PublishedHashTree->empty();
}

/// Returns the outlined hash tree. This can be globally used in a read-only
/// manner.
const OutlinedHashTree *getOutlinedHashTree() {
return PublishedHashTree.get();
}

/// Returns true if we should write codegen data.
bool emitCGData() { return EmitCGData; }

/// Publish the (globally) merged or read outlined hash tree.
void publishOutlinedHashTree(std::unique_ptr<OutlinedHashTree> HashTree) {
PublishedHashTree = std::move(HashTree);
// Ensure we disable emitCGData as we do not want to read and write both.
EmitCGData = false;
}
};

namespace cgdata {

inline bool hasOutlinedHashTree() {
return CodeGenData::getInstance().hasOutlinedHashTree();
}

inline const OutlinedHashTree *getOutlinedHashTree() {
return CodeGenData::getInstance().getOutlinedHashTree();
}

inline bool emitCGData() { return CodeGenData::getInstance().emitCGData(); }

inline void
publishOutlinedHashTree(std::unique_ptr<OutlinedHashTree> HashTree) {
CodeGenData::getInstance().publishOutlinedHashTree(std::move(HashTree));
}

void warn(Error E, StringRef Whence = "");
void warn(Twine Message, std::string Whence = "", std::string Hint = "");

} // end namespace cgdata

namespace IndexedCGData {

// A signature for data validation, representing "\xffcgdata\x81" in
// little-endian order
const uint64_t Magic = 0x81617461646763ff;

enum CGDataVersion {
// Version 1 is the first version. This version supports the outlined
// hash tree.
Version1 = 1,
CurrentVersion = CG_DATA_INDEX_VERSION
Comment on lines +181 to +182
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to also have a SubVersion - which is compatible within the same Version but data might be logically different (i.e. minor fixes, etc) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't anticipate many changes of this format in the future, and aim for simplicity. The code is largely modeled after the IRPGO profile -- https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ProfileData/InstrProf.h#L1071

};
const uint64_t Version = CGDataVersion::CurrentVersion;

struct Header {
uint64_t Magic;
uint32_t Version;
uint32_t DataKind;
uint64_t OutlinedHashTreeOffset;

// New fields should only be added at the end to ensure that the size
// computation is correct. The methods below need to be updated to ensure that
// the new field is read correctly.

// Reads a header struct from the buffer.
static Expected<Header> readFromBuffer(const unsigned char *Curr);
};

} // end namespace IndexedCGData

} // end namespace llvm

#endif // LLVM_CODEGEN_PREPARE_H
46 changes: 46 additions & 0 deletions llvm/include/llvm/CodeGenData/CodeGenData.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*===-- CodeGenData.inc ----------------------------------------*- 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
|*
\*===----------------------------------------------------------------------===*/
/*
* This is the main file that defines all the data structure, signature,
* constant literals that are shared across compiler, host tools (reader/writer)
* to support codegen data.
*
\*===----------------------------------------------------------------------===*/

/* Helper macros. */
#define CG_DATA_SIMPLE_QUOTE(x) #x
#define CG_DATA_QUOTE(x) CG_DATA_SIMPLE_QUOTE(x)

#ifdef CG_DATA_SECT_ENTRY
#define CG_DATA_DEFINED
CG_DATA_SECT_ENTRY(CG_outline, CG_DATA_QUOTE(CG_DATA_OUTLINE_COMMON),
CG_DATA_OUTLINE_COFF, "__DATA,")

#undef CG_DATA_SECT_ENTRY
#endif

/* section name strings common to all targets other
than WIN32 */
#define CG_DATA_OUTLINE_COMMON __llvm_outline
/* Since cg data sections are not allocated, we don't need to
* access them at runtime.
*/
#define CG_DATA_OUTLINE_COFF ".loutline"

#ifdef _WIN32
/* Runtime section names and name strings. */
#define CG_DATA_SECT_NAME CG_DATA_OUTLINE_COFF

#else
/* Runtime section names and name strings. */
#define CG_DATA_SECT_NAME CG_DATA_QUOTE(CG_DATA_OUTLINE_COMMON)

#endif

/* Indexed codegen data format version (start from 1). */
#define CG_DATA_INDEX_VERSION 1
154 changes: 154 additions & 0 deletions llvm/include/llvm/CodeGenData/CodeGenDataReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//===- CodeGenDataReader.h --------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for reading codegen data.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CODEGENDATA_CODEGENDATAREADER_H
#define LLVM_CODEGENDATA_CODEGENDATAREADER_H

#include "llvm/CodeGenData/CodeGenData.h"
#include "llvm/CodeGenData/OutlinedHashTreeRecord.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/VirtualFileSystem.h"

namespace llvm {

class CodeGenDataReader {
cgdata_error LastError = cgdata_error::success;
std::string LastErrorMsg;

public:
CodeGenDataReader() = default;
virtual ~CodeGenDataReader() = default;

/// Read the header. Required before reading first record.
virtual Error read() = 0;
/// Return the codegen data version.
virtual uint32_t getVersion() const = 0;
/// Return the codegen data kind.
virtual CGDataKind getDataKind() const = 0;
/// Return true if the data has an outlined hash tree.
virtual bool hasOutlinedHashTree() const = 0;
/// Return the outlined hash tree that is released from the reader.
std::unique_ptr<OutlinedHashTree> releaseOutlinedHashTree() {
return std::move(HashTreeRecord.HashTree);
}

/// Factory method to create an appropriately typed reader for the given
/// codegen data file path and file system.
static Expected<std::unique_ptr<CodeGenDataReader>>
create(const Twine &Path, vfs::FileSystem &FS);

/// Factory method to create an appropriately typed reader for the given
/// memory buffer.
static Expected<std::unique_ptr<CodeGenDataReader>>
create(std::unique_ptr<MemoryBuffer> Buffer);

/// Extract the cgdata embedded in sections from the given object file and
/// merge them into the GlobalOutlineRecord. This is a static helper that
/// is used by `llvm-cgdata merge` or ThinLTO's two-codegen rounds.
static Error mergeFromObjectFile(const object::ObjectFile *Obj,
OutlinedHashTreeRecord &GlobalOutlineRecord);

protected:
/// The outlined hash tree that has been read. When it's released by
/// releaseOutlinedHashTree(), it's no longer valid.
OutlinedHashTreeRecord HashTreeRecord;

/// Set the current error and return same.
Error error(cgdata_error Err, const std::string &ErrMsg = "") {
LastError = Err;
LastErrorMsg = ErrMsg;
if (Err == cgdata_error::success)
return Error::success();
return make_error<CGDataError>(Err, ErrMsg);
}

Error error(Error &&E) {
handleAllErrors(std::move(E), [&](const CGDataError &IPE) {
LastError = IPE.get();
LastErrorMsg = IPE.getMessage();
});
return make_error<CGDataError>(LastError, LastErrorMsg);
}

/// Clear the current error and return a successful one.
Error success() { return error(cgdata_error::success); }
};

class IndexedCodeGenDataReader : public CodeGenDataReader {
/// The codegen data file contents.
std::unique_ptr<MemoryBuffer> DataBuffer;
/// The header
IndexedCGData::Header Header;

public:
IndexedCodeGenDataReader(std::unique_ptr<MemoryBuffer> DataBuffer)
: DataBuffer(std::move(DataBuffer)) {}
IndexedCodeGenDataReader(const IndexedCodeGenDataReader &) = delete;
IndexedCodeGenDataReader &
operator=(const IndexedCodeGenDataReader &) = delete;

/// Return true if the given buffer is in binary codegen data format.
static bool hasFormat(const MemoryBuffer &Buffer);
/// Read the contents including the header.
Error read() override;
/// Return the codegen data version.
uint32_t getVersion() const override { return Header.Version; }
/// Return the codegen data kind.
CGDataKind getDataKind() const override {
return static_cast<CGDataKind>(Header.DataKind);
}
/// Return true if the header indicates the data has an outlined hash tree.
/// This does not mean that the data is still available.
bool hasOutlinedHashTree() const override {
return Header.DataKind &
static_cast<uint32_t>(CGDataKind::FunctionOutlinedHashTree);
}
};

/// This format is a simple text format that's suitable for test data.
/// The header is a custom format starting with `:` per line to indicate which
/// codegen data is recorded. `#` is used to indicate a comment.
/// The subsequent data is a YAML format per each codegen data in order.
/// Currently, it only has a function outlined hash tree.
class TextCodeGenDataReader : public CodeGenDataReader {
/// The codegen data file contents.
std::unique_ptr<MemoryBuffer> DataBuffer;
/// Iterator over the profile data.
line_iterator Line;
/// Describe the kind of the codegen data.
CGDataKind DataKind = CGDataKind::Unknown;

public:
TextCodeGenDataReader(std::unique_ptr<MemoryBuffer> DataBuffer_)
: DataBuffer(std::move(DataBuffer_)), Line(*DataBuffer, true, '#') {}
TextCodeGenDataReader(const TextCodeGenDataReader &) = delete;
TextCodeGenDataReader &operator=(const TextCodeGenDataReader &) = delete;

/// Return true if the given buffer is in text codegen data format.
static bool hasFormat(const MemoryBuffer &Buffer);
/// Read the contents including the header.
Error read() override;
/// Text format does not have version, so return 0.
uint32_t getVersion() const override { return 0; }
/// Return the codegen data kind.
CGDataKind getDataKind() const override { return DataKind; }
/// Return true if the header indicates the data has an outlined hash tree.
/// This does not mean that the data is still available.
bool hasOutlinedHashTree() const override {
return static_cast<uint32_t>(DataKind) &
static_cast<uint32_t>(CGDataKind::FunctionOutlinedHashTree);
}
};

} // end namespace llvm

#endif // LLVM_CODEGENDATA_CODEGENDATAREADER_H
68 changes: 68 additions & 0 deletions llvm/include/llvm/CodeGenData/CodeGenDataWriter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//===- CodeGenDataWriter.h --------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for writing codegen data.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CODEGENDATA_CODEGENDATAWRITER_H
#define LLVM_CODEGENDATA_CODEGENDATAWRITER_H

#include "llvm/CodeGenData/CodeGenData.h"
#include "llvm/CodeGenData/OutlinedHashTreeRecord.h"
#include "llvm/Support/Error.h"

namespace llvm {

class CGDataOStream;

class CodeGenDataWriter {
/// The outlined hash tree to be written.
OutlinedHashTreeRecord HashTreeRecord;

/// A bit mask describing the kind of the codegen data.
CGDataKind DataKind = CGDataKind::Unknown;

public:
CodeGenDataWriter() = default;
~CodeGenDataWriter() = default;

/// Add the outlined hash tree record. The input Record is released.
void addRecord(OutlinedHashTreeRecord &Record);

/// Write the codegen data to \c OS
Error write(raw_fd_ostream &OS);

/// Write the codegen data in text format to \c OS
Error writeText(raw_fd_ostream &OS);

/// Return the attributes of the current CGData.
CGDataKind getCGDataKind() const { return DataKind; }

/// Return true if the header indicates the data has an outlined hash tree.
bool hasOutlinedHashTree() const {
return static_cast<uint32_t>(DataKind) &
static_cast<uint32_t>(CGDataKind::FunctionOutlinedHashTree);
}

private:
/// The offset of the outlined hash tree in the file.
uint64_t OutlinedHashTreeOffset;

/// Write the codegen data header to \c COS
Error writeHeader(CGDataOStream &COS);

/// Write the codegen data header in text to \c OS
Error writeHeaderText(raw_fd_ostream &OS);

Error writeImpl(CGDataOStream &COS);
};

} // end namespace llvm

#endif // LLVM_CODEGENDATA_CODEGENDATAWRITER_H
3 changes: 3 additions & 0 deletions llvm/lib/CodeGenData/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
add_llvm_component_library(LLVMCodeGenData
CodeGenData.cpp
CodeGenDataReader.cpp
CodeGenDataWriter.cpp
OutlinedHashTree.cpp
OutlinedHashTreeRecord.cpp

196 changes: 196 additions & 0 deletions llvm/lib/CodeGenData/CodeGenData.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
//===-- CodeGenData.cpp ---------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for codegen data that has stable summary which
// can be used to optimize the code in the subsequent codegen.
//
//===----------------------------------------------------------------------===//

#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGenData/CodeGenDataReader.h"
#include "llvm/CodeGenData/OutlinedHashTreeRecord.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/WithColor.h"

#define DEBUG_TYPE "cg-data"

using namespace llvm;
using namespace cgdata;

static std::string getCGDataErrString(cgdata_error Err,
const std::string &ErrMsg = "") {
std::string Msg;
raw_string_ostream OS(Msg);

switch (Err) {
case cgdata_error::success:
OS << "success";
break;
case cgdata_error::eof:
OS << "end of File";
break;
case cgdata_error::bad_magic:
OS << "invalid codegen data (bad magic)";
break;
case cgdata_error::bad_header:
OS << "invalid codegen data (file header is corrupt)";
break;
case cgdata_error::empty_cgdata:
OS << "empty codegen data";
break;
case cgdata_error::malformed:
OS << "malformed codegen data";
break;
case cgdata_error::unsupported_version:
OS << "unsupported codegen data version";
break;
}

// If optional error message is not empty, append it to the message.
if (!ErrMsg.empty())
OS << ": " << ErrMsg;

return OS.str();
}

namespace {

// FIXME: This class is only here to support the transition to llvm::Error. It
// will be removed once this transition is complete. Clients should prefer to
// deal with the Error value directly, rather than converting to error_code.
class CGDataErrorCategoryType : public std::error_category {
const char *name() const noexcept override { return "llvm.cgdata"; }

std::string message(int IE) const override {
return getCGDataErrString(static_cast<cgdata_error>(IE));
}
};

} // end anonymous namespace

const std::error_category &llvm::cgdata_category() {
static CGDataErrorCategoryType ErrorCategory;
return ErrorCategory;
}

std::string CGDataError::message() const {
return getCGDataErrString(Err, Msg);
}

char CGDataError::ID = 0;

namespace {

const char *CodeGenDataSectNameCommon[] = {
#define CG_DATA_SECT_ENTRY(Kind, SectNameCommon, SectNameCoff, Prefix) \
SectNameCommon,
#include "llvm/CodeGenData/CodeGenData.inc"
};

const char *CodeGenDataSectNameCoff[] = {
#define CG_DATA_SECT_ENTRY(Kind, SectNameCommon, SectNameCoff, Prefix) \
SectNameCoff,
#include "llvm/CodeGenData/CodeGenData.inc"
};

const char *CodeGenDataSectNamePrefix[] = {
#define CG_DATA_SECT_ENTRY(Kind, SectNameCommon, SectNameCoff, Prefix) Prefix,
#include "llvm/CodeGenData/CodeGenData.inc"
};

} // namespace

namespace llvm {

std::string getCodeGenDataSectionName(CGDataSectKind CGSK,
Triple::ObjectFormatType OF,
bool AddSegmentInfo) {
std::string SectName;

if (OF == Triple::MachO && AddSegmentInfo)
SectName = CodeGenDataSectNamePrefix[CGSK];

if (OF == Triple::COFF)
SectName += CodeGenDataSectNameCoff[CGSK];
else
SectName += CodeGenDataSectNameCommon[CGSK];

return SectName;
}

std::unique_ptr<CodeGenData> CodeGenData::Instance = nullptr;
std::once_flag CodeGenData::OnceFlag;

CodeGenData &CodeGenData::getInstance() {
std::call_once(CodeGenData::OnceFlag, []() {
Instance = std::unique_ptr<CodeGenData>(new CodeGenData());

// TODO: Initialize writer or reader mode for the client optimization.
});
return *(Instance.get());
}

namespace IndexedCGData {

Expected<Header> Header::readFromBuffer(const unsigned char *Curr) {
using namespace support;

static_assert(std::is_standard_layout_v<llvm::IndexedCGData::Header>,
"The header should be standard layout type since we use offset "
"of fields to read.");
Header H;
H.Magic = endian::readNext<uint64_t, endianness::little, unaligned>(Curr);
if (H.Magic != IndexedCGData::Magic)
return make_error<CGDataError>(cgdata_error::bad_magic);
H.Version = endian::readNext<uint32_t, endianness::little, unaligned>(Curr);
if (H.Version > IndexedCGData::CGDataVersion::CurrentVersion)
return make_error<CGDataError>(cgdata_error::unsupported_version);
H.DataKind = endian::readNext<uint32_t, endianness::little, unaligned>(Curr);

switch (H.Version) {
// When a new field is added to the header add a case statement here to
// compute the size as offset of the new field + size of the new field. This
// relies on the field being added to the end of the list.
static_assert(IndexedCGData::CGDataVersion::CurrentVersion == Version1,
"Please update the size computation below if a new field has "
"been added to the header, if not add a case statement to "
"fall through to the latest version.");
case 1ull:
H.OutlinedHashTreeOffset =
endian::readNext<uint64_t, endianness::little, unaligned>(Curr);
}

return H;
}

} // end namespace IndexedCGData

namespace cgdata {

void warn(Twine Message, std::string Whence, std::string Hint) {
WithColor::warning();
if (!Whence.empty())
errs() << Whence << ": ";
errs() << Message << "\n";
if (!Hint.empty())
WithColor::note() << Hint << "\n";
}

void warn(Error E, StringRef Whence) {
if (E.isA<CGDataError>()) {
handleAllErrors(std::move(E), [&](const CGDataError &IPE) {
warn(IPE.message(), Whence.str(), "");
});
}
}

} // end namespace cgdata

} // end namespace llvm
174 changes: 174 additions & 0 deletions llvm/lib/CodeGenData/CodeGenDataReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//===- CodeGenDataReader.cpp ----------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for reading codegen data.
//
//===----------------------------------------------------------------------===//

#include "llvm/CodeGenData/CodeGenDataReader.h"
#include "llvm/CodeGenData/OutlinedHashTreeRecord.h"
#include "llvm/Support/MemoryBuffer.h"

#define DEBUG_TYPE "cg-data-reader"

using namespace llvm;

namespace llvm {

static Expected<std::unique_ptr<MemoryBuffer>>
setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) {
auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN()
: FS.getBufferForFile(Filename);
Comment on lines +25 to +26
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN()
: FS.getBufferForFile(Filename);
auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(Filename)

Or does this not work because you need to use FS?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to the IRPGO case https://github.com/llvm/llvm-project/blob/main/llvm/lib/ProfileData/InstrProfReader.cpp#L73-L74, I want to keep FS in case a custom file system is needed.

if (std::error_code EC = BufferOrErr.getError())
return errorCodeToError(EC);
return std::move(BufferOrErr.get());
}

Error CodeGenDataReader::mergeFromObjectFile(
const object::ObjectFile *Obj,
OutlinedHashTreeRecord &GlobalOutlineRecord) {
Triple TT = Obj->makeTriple();
auto CGOutLineName =
getCodeGenDataSectionName(CG_outline, TT.getObjectFormat(), false);

for (auto &Section : Obj->sections()) {
Expected<StringRef> NameOrErr = Section.getName();
if (!NameOrErr)
return NameOrErr.takeError();
Expected<StringRef> ContentsOrErr = Section.getContents();
if (!ContentsOrErr)
return ContentsOrErr.takeError();
auto *Data = reinterpret_cast<const unsigned char *>(ContentsOrErr->data());
auto *EndData = Data + ContentsOrErr->size();

if (*NameOrErr == CGOutLineName) {
// In case dealing with an executable that has concatenated cgdata,
// we want to merge them into a single cgdata.
// Although it's not a typical workflow, we support this scenario.
while (Data != EndData) {
OutlinedHashTreeRecord LocalOutlineRecord;
LocalOutlineRecord.deserialize(Data);
GlobalOutlineRecord.merge(LocalOutlineRecord);
}
}
// TODO: Add support for other cgdata sections.
}

return Error::success();
}

Error IndexedCodeGenDataReader::read() {
using namespace support;

// The smallest header with the version 1 is 24 bytes
const unsigned MinHeaderSize = 24;
if (DataBuffer->getBufferSize() < MinHeaderSize)
return error(cgdata_error::bad_header);

auto *Start =
reinterpret_cast<const unsigned char *>(DataBuffer->getBufferStart());
auto *End =
reinterpret_cast<const unsigned char *>(DataBuffer->getBufferEnd());
if (auto E = IndexedCGData::Header::readFromBuffer(Start).moveInto(Header))
return std::move(E);

if (hasOutlinedHashTree()) {
const unsigned char *Ptr = Start + Header.OutlinedHashTreeOffset;
if (Ptr >= End)
return error(cgdata_error::eof);
HashTreeRecord.deserialize(Ptr);
}

return success();
}

Expected<std::unique_ptr<CodeGenDataReader>>
CodeGenDataReader::create(const Twine &Path, vfs::FileSystem &FS) {
// Set up the buffer to read.
auto BufferOrError = setupMemoryBuffer(Path, FS);
if (Error E = BufferOrError.takeError())
return std::move(E);
return CodeGenDataReader::create(std::move(BufferOrError.get()));
}

Expected<std::unique_ptr<CodeGenDataReader>>
CodeGenDataReader::create(std::unique_ptr<MemoryBuffer> Buffer) {
if (Buffer->getBufferSize() == 0)
return make_error<CGDataError>(cgdata_error::empty_cgdata);

std::unique_ptr<CodeGenDataReader> Reader;
// Create the reader.
if (IndexedCodeGenDataReader::hasFormat(*Buffer))
Reader = std::make_unique<IndexedCodeGenDataReader>(std::move(Buffer));
else if (TextCodeGenDataReader::hasFormat(*Buffer))
Reader = std::make_unique<TextCodeGenDataReader>(std::move(Buffer));
else
return make_error<CGDataError>(cgdata_error::malformed);

// Initialize the reader and return the result.
if (Error E = Reader->read())
return std::move(E);

return std::move(Reader);
}

bool IndexedCodeGenDataReader::hasFormat(const MemoryBuffer &DataBuffer) {
using namespace support;
if (DataBuffer.getBufferSize() < sizeof(IndexedCGData::Magic))
return false;

uint64_t Magic = endian::read<uint64_t, llvm::endianness::little, aligned>(
DataBuffer.getBufferStart());
// Verify that it's magical.
return Magic == IndexedCGData::Magic;
}

bool TextCodeGenDataReader::hasFormat(const MemoryBuffer &Buffer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this function is kind of confusing - or contradictory with description. Should it be something like hasAsciiFormat - maybe I'm misinterpreting the usage ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The description in the header is following, which is also modeled after the case of IRPGO -- https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ProfileData/InstrProfReader.h#L252-L253
I'd like to keep them synced, and also I think it's clearer without it as this is a static function and the class name TextCodeGenDataReader already implies we're interested in a text format here.

  /// Return true if the given buffer is in text codegen data format.
  static bool hasFormat(const MemoryBuffer &Buffer);

// Verify that this really looks like plain ASCII text by checking a
// 'reasonable' number of characters (up to the magic size).
StringRef Prefix = Buffer.getBuffer().take_front(sizeof(uint64_t));
return llvm::all_of(Prefix, [](char c) { return isPrint(c) || isSpace(c); });
}
Error TextCodeGenDataReader::read() {
using namespace support;

// Parse the custom header line by line.
for (; !Line.is_at_eof(); ++Line) {
// Skip empty or whitespace-only lines
if (Line->trim().empty())
continue;

if (!Line->starts_with(":"))
break;
StringRef Str = Line->drop_front().rtrim();
if (Str.equals_insensitive("outlined_hash_tree"))
DataKind |= CGDataKind::FunctionOutlinedHashTree;
else
return error(cgdata_error::bad_header);
}

// We treat an empty header (that is a comment # only) as a valid header.
if (Line.is_at_eof()) {
if (DataKind == CGDataKind::Unknown)
return Error::success();
return error(cgdata_error::bad_header);
}

// The YAML docs follow after the header.
const char *Pos = Line->data();
size_t Size = reinterpret_cast<size_t>(DataBuffer->getBufferEnd()) -
reinterpret_cast<size_t>(Pos);
yaml::Input YOS(StringRef(Pos, Size));
if (hasOutlinedHashTree())
HashTreeRecord.deserializeYAML(YOS);

// TODO: Add more yaml cgdata in order

return Error::success();
}
} // end namespace llvm
162 changes: 162 additions & 0 deletions llvm/lib/CodeGenData/CodeGenDataWriter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//===- CodeGenDataWriter.cpp ----------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains support for writing codegen data.
//
//===----------------------------------------------------------------------===//

#include "llvm/CodeGenData/CodeGenDataWriter.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/EndianStream.h"

#define DEBUG_TYPE "cg-data-writer"

using namespace llvm;

namespace llvm {

/// A struct to define how the data stream should be patched.
struct CGDataPatchItem {
uint64_t Pos; // Where to patch.
uint64_t *D; // Pointer to an array of source data.
int N; // Number of elements in \c D array.
};

// A wrapper class to abstract writer stream with support of bytes
// back patching.
class CGDataOStream {
public:
CGDataOStream(raw_fd_ostream &FD)
: IsFDOStream(true), OS(FD), LE(FD, llvm::endianness::little) {}
CGDataOStream(raw_string_ostream &STR)
: IsFDOStream(false), OS(STR), LE(STR, llvm::endianness::little) {}

uint64_t tell() { return OS.tell(); }
void write(uint64_t V) { LE.write<uint64_t>(V); }
void write32(uint32_t V) { LE.write<uint32_t>(V); }
void write8(uint8_t V) { LE.write<uint8_t>(V); }

// \c patch can only be called when all data is written and flushed.
// For raw_string_ostream, the patch is done on the target string
// directly and it won't be reflected in the stream's internal buffer.
void patch(ArrayRef<CGDataPatchItem> P) {
using namespace support;

if (IsFDOStream) {
raw_fd_ostream &FDOStream = static_cast<raw_fd_ostream &>(OS);
const uint64_t LastPos = FDOStream.tell();
for (const auto &K : P) {
FDOStream.seek(K.Pos);
for (int I = 0; I < K.N; I++)
write(K.D[I]);
}
// Reset the stream to the last position after patching so that users
// don't accidentally overwrite data. This makes it consistent with
// the string stream below which replaces the data directly.
FDOStream.seek(LastPos);
} else {
raw_string_ostream &SOStream = static_cast<raw_string_ostream &>(OS);
std::string &Data = SOStream.str(); // with flush
for (const auto &K : P) {
for (int I = 0; I < K.N; I++) {
uint64_t Bytes =
endian::byte_swap<uint64_t, llvm::endianness::little>(K.D[I]);
Data.replace(K.Pos + I * sizeof(uint64_t), sizeof(uint64_t),
(const char *)&Bytes, sizeof(uint64_t));
}
}
}
}

// If \c OS is an instance of \c raw_fd_ostream, this field will be
// true. Otherwise, \c OS will be an raw_string_ostream.
bool IsFDOStream;
raw_ostream &OS;
support::endian::Writer LE;
};

} // end namespace llvm

void CodeGenDataWriter::addRecord(OutlinedHashTreeRecord &Record) {
assert(Record.HashTree && "empty hash tree in the record");
HashTreeRecord.HashTree = std::move(Record.HashTree);

DataKind |= CGDataKind::FunctionOutlinedHashTree;
}

Error CodeGenDataWriter::write(raw_fd_ostream &OS) {
CGDataOStream COS(OS);
return writeImpl(COS);
}

Error CodeGenDataWriter::writeHeader(CGDataOStream &COS) {
using namespace support;
IndexedCGData::Header Header;
Header.Magic = IndexedCGData::Magic;
Header.Version = IndexedCGData::Version;

// Set the CGDataKind depending on the kind.
Header.DataKind = 0;
if (static_cast<bool>(DataKind & CGDataKind::FunctionOutlinedHashTree))
Header.DataKind |=
static_cast<uint32_t>(CGDataKind::FunctionOutlinedHashTree);

Header.OutlinedHashTreeOffset = 0;

// Only write up to the CGDataKind. We need to remember the offset of the
// remaining fields to allow back-patching later.
COS.write(Header.Magic);
COS.write32(Header.Version);
COS.write32(Header.DataKind);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to add the ability to write some client-defined string metadata for the file ? Ex: module name, source hash, build system info, etc ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like the IRPGO case -- https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ProfileData/InstrProf.h#L1117-L1124, I aim to maintain a simplified header while still capturing any discrepancies. Details such as the module name may change based on client usage and can be distinguished by using a different DataKind. I expect that these details can be encoded within their respective data blobs as needed.


// Save the location of Header.OutlinedHashTreeOffset field in \c COS.
OutlinedHashTreeOffset = COS.tell();

// Reserve the space for OutlinedHashTreeOffset field.
COS.write(0);

return Error::success();
}

Error CodeGenDataWriter::writeImpl(CGDataOStream &COS) {
if (Error E = writeHeader(COS))
return E;

uint64_t OutlinedHashTreeFieldStart = COS.tell();
if (hasOutlinedHashTree())
HashTreeRecord.serialize(COS.OS);

// Back patch the offsets.
CGDataPatchItem PatchItems[] = {
{OutlinedHashTreeOffset, &OutlinedHashTreeFieldStart, 1}};
COS.patch(PatchItems);

return Error::success();
}

Error CodeGenDataWriter::writeHeaderText(raw_fd_ostream &OS) {
if (hasOutlinedHashTree())
OS << "# Outlined stable hash tree\n:outlined_hash_tree\n";

// TODO: Add more data types in this header

return Error::success();
}

Error CodeGenDataWriter::writeText(raw_fd_ostream &OS) {
if (Error E = writeHeaderText(OS))
return E;

yaml::Output YOS(OS);
if (hasOutlinedHashTree())
HashTreeRecord.serializeYAML(YOS);

// TODO: Write more yaml cgdata in order

return Error::success();
}
1 change: 1 addition & 0 deletions llvm/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@ set(LLVM_TEST_DEPENDS
llvm-c-test
llvm-cat
llvm-cfi-verify
llvm-cgdata
llvm-config
llvm-cov
llvm-cvtres
1 change: 1 addition & 0 deletions llvm/test/lit.cfg.py
Original file line number Diff line number Diff line change
@@ -180,6 +180,7 @@ def get_asan_rtlib():
"llvm-addr2line",
"llvm-bcanalyzer",
"llvm-bitcode-strip",
"llvm-cgdata",
"llvm-config",
"llvm-cov",
"llvm-cxxdump",
32 changes: 32 additions & 0 deletions llvm/test/tools/llvm-cgdata/dump.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Test dump between the binary and text formats.

RUN: split-file %s %t

RUN: llvm-cgdata dump -binary %t/dump.cgtext -o %t/dump.cgdata
RUN: llvm-cgdata dump -text %t/dump.cgdata -o %t/dump-round.cgtext
RUN: llvm-cgdata dump -binary %t/dump-round.cgtext -o %t/dump-round.cgdata
RUN: llvm-cgdata dump -text %t/dump-round.cgtext -o %t/dump-round-round.cgtext
RUN: diff %t/dump.cgdata %t/dump-round.cgdata
RUN: diff %t/dump-round.cgtext %t/dump-round-round.cgtext

;--- dump.cgtext
# Outlined stable hash tree
:outlined_hash_tree
---
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2, 3 ]
2:
Hash: 0x3
Terminals: 5
SuccessorIds: [ ]
3:
Hash: 0x2
Terminals: 4
SuccessorIds: [ ]
...
35 changes: 35 additions & 0 deletions llvm/test/tools/llvm-cgdata/empty.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Test no input file
RUN: not llvm-cgdata dump -o - 2>&1 | FileCheck %s --check-prefix=NOFILE --ignore-case
NOFILE: error: No such file or directory

# Test for empty cgdata file, which is invalid.
RUN: touch %t_emptyfile.cgtext
RUN: not llvm-cgdata dump %t_emptyfile.cgtext -text 2>&1 | FileCheck %s --check-prefix=EMPTY
EMPTY: {{.}}emptyfile.cgtext: empty codegen data

# Test for empty header in the text format. It can be converted to a valid binary file.
RUN: printf '#' > %t_emptyheader.cgtext
RUN: llvm-cgdata dump %t_emptyheader.cgtext -binary -o %t_emptyheader.cgdata

# Without any cgdata other than the header, no data shows by default.
RUN: llvm-cgdata show %t_emptyheader.cgdata | count 0

# The version number appears when asked, as it's in the header
RUN: llvm-cgdata show --cgdata-version %t_emptyheader.cgdata | FileCheck %s --check-prefix=VERSION
VERSION: Version: 1

# When converting a binary file (w/ the header only) to a text file, it's an empty file as the text format does not have an explicit header.
RUN: llvm-cgdata dump %t_emptyheader.cgdata -text | count 0

# Synthesize a header only cgdata.
# struct Header {
# uint64_t Magic;
# uint32_t Version;
# uint32_t DataKind;
# uint64_t OutlinedHashTreeOffset;
# }
RUN: printf '\xffcgdata\x81' > %t_header.cgdata
RUN: printf '\x01\x00\x00\x00' >> %t_header.cgdata
RUN: printf '\x00\x00\x00\x00' >> %t_header.cgdata
RUN: printf '\x18\x00\x00\x00\x00\x00\x00\x00' >> %t_header.cgdata
RUN: diff %t_header.cgdata %t_emptyheader.cgdata
38 changes: 38 additions & 0 deletions llvm/test/tools/llvm-cgdata/error.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Test various error cases

# Synthesize a header only cgdata.
# struct Header {
# uint64_t Magic;
# uint32_t Version;
# uint32_t DataKind;
# uint64_t OutlinedHashTreeOffset;
# }
RUN: touch %t_empty.cgdata
RUN: not llvm-cgdata show %t_empty.cgdata 2>&1 | FileCheck %s --check-prefix=EMPTY
EMPTY: {{.}}cgdata: empty codegen data

# Not a magic.
RUN: printf '\xff' > %t_malformed.cgdata
RUN: not llvm-cgdata show %t_malformed.cgdata 2>&1 | FileCheck %s --check-prefix=MALFORMED
MALFORMED: {{.}}cgdata: malformed codegen data

# The minimum header size is 24.
RUN: printf '\xffcgdata\x81' > %t_corrupt.cgdata
RUN: not llvm-cgdata show %t_corrupt.cgdata 2>&1 | FileCheck %s --check-prefix=CORRUPT
CORRUPT: {{.}}cgdata: invalid codegen data (file header is corrupt)

# The current version 1 while the header says 2.
RUN: printf '\xffcgdata\x81' > %t_version.cgdata
RUN: printf '\x02\x00\x00\x00' >> %t_version.cgdata
RUN: printf '\x00\x00\x00\x00' >> %t_version.cgdata
RUN: printf '\x18\x00\x00\x00\x00\x00\x00\x00' >> %t_version.cgdata
RUN: not llvm-cgdata show %t_version.cgdata 2>&1 | FileCheck %s --check-prefix=BAD_VERSION
BAD_VERSION: {{.}}cgdata: unsupported codegen data version

# Header says an outlined hash tree, but the file ends after the header.
RUN: printf '\xffcgdata\x81' > %t_eof.cgdata
RUN: printf '\x01\x00\x00\x00' >> %t_eof.cgdata
RUN: printf '\x01\x00\x00\x00' >> %t_eof.cgdata
RUN: printf '\x18\x00\x00\x00\x00\x00\x00\x00' >> %t_eof.cgdata
RUN: not llvm-cgdata show %t_eof.cgdata 2>&1 | FileCheck %s --check-prefix=EOF
EOF: {{.}}cgdata: end of File
90 changes: 90 additions & 0 deletions llvm/test/tools/llvm-cgdata/merge-archive.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# REQUIRES: shell
# UNSUPPORTED: system-windows

# Merge an archive that has two object files having cgdata (__llvm_outline)

RUN: split-file %s %t

# Synthesize raw cgdata without the header (24 byte) from the indexed cgdata.
RUN: llvm-cgdata dump -binary %t/raw-1.cgtext -o %t/raw-1.cgdata
RUN: od -t x1 -j 24 -An %t/raw-1.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-1-bytes.txt
RUN: sed -ie "s/<RAW_1_BYTES>/$(cat %t/raw-1-bytes.txt)/g" %t/merge-1.ll
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-1.ll -o %t/merge-1.o

# Synthesize raw cgdata without the header (24 byte) from the indexed cgdata.
RUN: llvm-cgdata dump -binary %t/raw-2.cgtext -o %t/raw-2.cgdata
RUN: od -t x1 -j 24 -An %t/raw-2.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-2-bytes.txt
RUN: sed -ie "s/<RAW_2_BYTES>/$(cat %t/raw-2-bytes.txt)/g" %t/merge-2.ll
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-2.ll -o %t/merge-2.o

# Make an archive from two object files
RUN: llvm-ar rcs %t/merge-archive.a %t/merge-1.o %t/merge-2.o

# Merge the archive into the codegen data file.
RUN: llvm-cgdata merge %t/merge-archive.a -o %t/merge-archive.cgdata
RUN: llvm-cgdata show %t/merge-archive.cgdata | FileCheck %s
CHECK: Outlined hash tree:
CHECK-NEXT: Total Node Count: 4
CHECK-NEXT: Terminal Node Count: 2
CHECK-NEXT: Depth: 2

RUN: llvm-cgdata dump %t/merge-archive.cgdata | FileCheck %s --check-prefix=TREE
TREE: # Outlined stable hash tree
TREE-NEXT: :outlined_hash_tree
TREE-NEXT: ---
TREE-NEXT: 0:
TREE-NEXT: Hash: 0x0
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 1 ]
TREE-NEXT: 1:
TREE-NEXT: Hash: 0x1
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 2, 3 ]
TREE-NEXT: 2:
TREE-NEXT: Hash: 0x3
TREE-NEXT: Terminals: 5
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: 3:
TREE-NEXT: Hash: 0x2
TREE-NEXT: Terminals: 4
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: ...

;--- raw-1.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x2
Terminals: 4
SuccessorIds: [ ]
...

;--- merge-1.ll
@.data = private unnamed_addr constant [72 x i8] c"<RAW_1_BYTES>", section "__DATA,__llvm_outline"


;--- raw-2.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x3
Terminals: 5
SuccessorIds: [ ]
...

;--- merge-2.ll
@.data = private unnamed_addr constant [72 x i8] c"<RAW_2_BYTES>", section "__DATA,__llvm_outline"
83 changes: 83 additions & 0 deletions llvm/test/tools/llvm-cgdata/merge-concat.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# REQUIRES: shell
# UNSUPPORTED: system-windows

# Merge a binary file (e.g., a linked executable) having concatenated cgdata (__llvm_outline)

RUN: split-file %s %t

# Synthesize two sets of raw cgdata without the header (24 byte) from the indexed cgdata.
# Concatenate them in merge-concat.ll
RUN: llvm-cgdata dump -binary %t/raw-1.cgtext -o %t/raw-1.cgdata
RUN: od -t x1 -j 24 -An %t/raw-1.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-1-bytes.txt
RUN: sed -ie "s/<RAW_1_BYTES>/$(cat %t/raw-1-bytes.txt)/g" %t/merge-concat.ll
RUN: llvm-cgdata dump -binary %t/raw-2.cgtext -o %t/raw-2.cgdata
RUN: od -t x1 -j 24 -An %t/raw-2.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-2-bytes.txt
RUN: sed -ie "s/<RAW_2_BYTES>/$(cat %t/raw-2-bytes.txt)/g" %t/merge-concat.ll

RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-concat.ll -o %t/merge-concat.o
RUN: llvm-cgdata merge %t/merge-concat.o -o %t/merge-concat.cgdata
RUN: llvm-cgdata show %t/merge-concat.cgdata | FileCheck %s
CHECK: Outlined hash tree:
CHECK-NEXT: Total Node Count: 4
CHECK-NEXT: Terminal Node Count: 2
CHECK-NEXT: Depth: 2

RUN: llvm-cgdata dump %t/merge-concat.cgdata | FileCheck %s --check-prefix=TREE
TREE: # Outlined stable hash tree
TREE-NEXT: :outlined_hash_tree
TREE-NEXT: ---
TREE-NEXT: 0:
TREE-NEXT: Hash: 0x0
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 1 ]
TREE-NEXT: 1:
TREE-NEXT: Hash: 0x1
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 2, 3 ]
TREE-NEXT: 2:
TREE-NEXT: Hash: 0x3
TREE-NEXT: Terminals: 5
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: 3:
TREE-NEXT: Hash: 0x2
TREE-NEXT: Terminals: 4
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: ...

;--- raw-1.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x2
Terminals: 4
SuccessorIds: [ ]
...

;--- raw-2.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x3
Terminals: 5
SuccessorIds: [ ]
...

;--- merge-concat.ll

; In an linked executable (as opposed to an object file), cgdata in __llvm_outline might be concatenated. Although this is not a typical workflow, we simply support this case to parse cgdata that is concatenated. In other words, the following two trees are encoded back-to-back in a binary format.
@.data1 = private unnamed_addr constant [72 x i8] c"<RAW_1_BYTES>", section "__DATA,__llvm_outline"
@.data2 = private unnamed_addr constant [72 x i8] c"<RAW_2_BYTES>", section "__DATA,__llvm_outline"
87 changes: 87 additions & 0 deletions llvm/test/tools/llvm-cgdata/merge-double.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# REQUIRES: shell
# UNSUPPORTED: system-windows

# Merge two object files having cgdata (__llvm_outline)

RUN: split-file %s %t

# Synthesize raw cgdata without the header (24 byte) from the indexed cgdata.
RUN: llvm-cgdata dump -binary %t/raw-1.cgtext -o %t/raw-1.cgdata
RUN: od -t x1 -j 24 -An %t/raw-1.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-1-bytes.txt
RUN: sed -ie "s/<RAW_1_BYTES>/$(cat %t/raw-1-bytes.txt)/g" %t/merge-1.ll
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-1.ll -o %t/merge-1.o

# Synthesize raw cgdata without the header (24 byte) from the indexed cgdata.
RUN: llvm-cgdata dump -binary %t/raw-2.cgtext -o %t/raw-2.cgdata
RUN: od -t x1 -j 24 -An %t/raw-2.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-2-bytes.txt
RUN: sed -ie "s/<RAW_2_BYTES>/$(cat %t/raw-2-bytes.txt)/g" %t/merge-2.ll
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-2.ll -o %t/merge-2.o

# Merge two object files into the codegen data file.
RUN: llvm-cgdata merge %t/merge-1.o %t/merge-2.o -o %t/merge.cgdata

RUN: llvm-cgdata show %t/merge.cgdata | FileCheck %s
CHECK: Outlined hash tree:
CHECK-NEXT: Total Node Count: 4
CHECK-NEXT: Terminal Node Count: 2
CHECK-NEXT: Depth: 2

RUN: llvm-cgdata dump %t/merge.cgdata | FileCheck %s --check-prefix=TREE
TREE: # Outlined stable hash tree
TREE-NEXT: :outlined_hash_tree
TREE-NEXT: ---
TREE-NEXT: 0:
TREE-NEXT: Hash: 0x0
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 1 ]
TREE-NEXT: 1:
TREE-NEXT: Hash: 0x1
TREE-NEXT: Terminals: 0
TREE-NEXT: SuccessorIds: [ 2, 3 ]
TREE-NEXT: 2:
TREE-NEXT: Hash: 0x3
TREE-NEXT: Terminals: 5
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: 3:
TREE-NEXT: Hash: 0x2
TREE-NEXT: Terminals: 4
TREE-NEXT: SuccessorIds: [ ]
TREE-NEXT: ...

;--- raw-1.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x2
Terminals: 4
SuccessorIds: [ ]
...

;--- merge-1.ll
@.data = private unnamed_addr constant [72 x i8] c"<RAW_1_BYTES>", section "__DATA,__llvm_outline"

;--- raw-2.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x3
Terminals: 5
SuccessorIds: [ ]
...

;--- merge-2.ll
@.data = private unnamed_addr constant [72 x i8] c"<RAW_2_BYTES>", section "__DATA,__llvm_outline"
49 changes: 49 additions & 0 deletions llvm/test/tools/llvm-cgdata/merge-single.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# REQUIRES: shell
# UNSUPPORTED: system-windows

# Test merge a single object file into a cgdata

RUN: split-file %s %t

# Merge an object file that has no cgdata (__llvm_outline). It still produces a header only cgdata.
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-empty.ll -o %t/merge-empty.o
RUN: llvm-cgdata merge %t/merge-empty.o -o %t/merge-empty.cgdata
# No summary appear with the header only cgdata.
RUN: llvm-cgdata show %t/merge-empty.cgdata | count 0

# Synthesize raw cgdata without the header (24 byte) from the indexed cgdata.
RUN: llvm-cgdata dump -binary %t/raw-single.cgtext -o %t/raw-single.cgdata
RUN: od -t x1 -j 24 -An %t/raw-single.cgdata | tr -d '\n\r\t' | sed 's/[ ]*$//' | sed 's/[ ][ ]*/\\\\/g' > %t/raw-single-bytes.txt

RUN: sed -ie "s/<RAW_1_BYTES>/$(cat %t/raw-single-bytes.txt)/g" %t/merge-single.ll
RUN: llc -filetype=obj -mtriple arm64-apple-darwin %t/merge-single.ll -o %t/merge-single.o

# Merge an object file having cgdata (__llvm_outline)
RUN: llvm-cgdata merge %t/merge-single.o -o %t/merge-single.cgdata
RUN: llvm-cgdata show %t/merge-single.cgdata | FileCheck %s
CHECK: Outlined hash tree:
CHECK-NEXT: Total Node Count: 3
CHECK-NEXT: Terminal Node Count: 1
CHECK-NEXT: Depth: 2

;--- merge-empty.ll
@.data = private unnamed_addr constant [1 x i8] c"\01"

;--- raw-single.cgtext
:outlined_hash_tree
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x2
Terminals: 4
SuccessorIds: [ ]
...

;--- merge-single.ll
@.data = private unnamed_addr constant [72 x i8] c"<RAW_1_BYTES>", section "__DATA,__llvm_outline"
30 changes: 30 additions & 0 deletions llvm/test/tools/llvm-cgdata/show.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Test show

RUN: split-file %s %t
RUN: llvm-cgdata show %t/show.cgtext | FileCheck %s

CHECK: Outlined hash tree:
CHECK-NEXT: Total Node Count: 3
CHECK-NEXT: Terminal Node Count: 1
CHECK-NEXT: Depth: 2

# Convert the text file to the binary file
RUN: llvm-cgdata dump -binary %t/show.cgtext -o %t/show.cgdata
RUN: llvm-cgdata show %t/show.cgdata | FileCheck %s

;--- show.cgtext
:outlined_hash_tree
---
0:
Hash: 0x0
Terminals: 0
SuccessorIds: [ 1 ]
1:
Hash: 0x1
Terminals: 0
SuccessorIds: [ 2 ]
2:
Hash: 0x2
Terminals: 3
SuccessorIds: [ ]
...
15 changes: 15 additions & 0 deletions llvm/tools/llvm-cgdata/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
set(LLVM_LINK_COMPONENTS
CodeGen
CodeGenData
Core
Object
Support
)

add_llvm_tool(llvm-cgdata
llvm-cgdata.cpp

DEPENDS
intrinsics_gen
GENERATE_DRIVER
)
268 changes: 268 additions & 0 deletions llvm/tools/llvm-cgdata/llvm-cgdata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
//===-- llvm-cgdata.cpp - LLVM CodeGen Data Tool --------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// llvm-cgdata parses raw codegen data embedded in compiled binary files, and
// merges them into a single .cgdata file. It can also inspect and maninuplate
// a .cgdata file. This .cgdata can contain various codegen data like outlining
// information, and it can be used to optimize the code in the subsequent build.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringRef.h"
#include "llvm/CodeGenData/CodeGenDataReader.h"
#include "llvm/CodeGenData/CodeGenDataWriter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Object/Archive.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/LLVMDriver.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace llvm::object;

// TODO: https://llvm.org/docs/CommandGuide/llvm-cgdata.html has documentations
// on each subcommand.
cl::SubCommand DumpSubcommand(
"dump",
"Dump the (indexed) codegen data file in either text or binary format.");
cl::SubCommand MergeSubcommand(
"merge", "Takes binary files having raw codegen data in custom sections, "
"and merge them into an index codegen data file.");
cl::SubCommand
ShowSubcommand("show", "Show summary of the (indexed) codegen data file.");

enum CGDataFormat {
CD_None = 0,
CD_Text,
CD_Binary,
};

cl::opt<std::string> OutputFilename("output", cl::value_desc("output"),
cl::init("-"), cl::desc("Output file"),
cl::sub(DumpSubcommand),
cl::sub(MergeSubcommand));
cl::alias OutputFilenameA("o", cl::desc("Alias for --output"),
cl::aliasopt(OutputFilename));

cl::opt<std::string> Filename(cl::Positional, cl::desc("<cgdata-file>"),
cl::sub(DumpSubcommand), cl::sub(ShowSubcommand));
cl::list<std::string> InputFilenames(cl::Positional, cl::sub(MergeSubcommand),
cl::desc("<binary-files...>"));
cl::opt<CGDataFormat> OutputFormat(
cl::desc("Format of output data"), cl::sub(DumpSubcommand),
cl::init(CD_Text),
cl::values(clEnumValN(CD_Text, "text", "Text encoding"),
clEnumValN(CD_Binary, "binary", "Binary encoding")));

cl::opt<bool> ShowCGDataVersion("cgdata-version",
cl::desc("Show cgdata version."),
cl::sub(ShowSubcommand));

static void exitWithError(Twine Message, std::string Whence = "",
std::string Hint = "") {
WithColor::error();
if (!Whence.empty())
errs() << Whence << ": ";
errs() << Message << "\n";
if (!Hint.empty())
WithColor::note() << Hint << "\n";
::exit(1);
}

static void exitWithError(Error E, StringRef Whence = "") {
if (E.isA<CGDataError>()) {
handleAllErrors(std::move(E), [&](const CGDataError &IPE) {
exitWithError(IPE.message(), std::string(Whence));
});
return;
}

exitWithError(toString(std::move(E)), std::string(Whence));
}

static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") {
exitWithError(EC.message(), std::string(Whence));
}

static int dump_main(int argc, const char *argv[]) {
if (Filename == OutputFilename) {
errs() << sys::path::filename(argv[0]) << " " << argv[1]
<< ": Input file name cannot be the same as the output file name!\n";
return 1;
}

std::error_code EC;
raw_fd_ostream OS(OutputFilename.data(), EC,
OutputFormat == CD_Text ? sys::fs::OF_TextWithCRLF
: sys::fs::OF_None);
if (EC)
exitWithErrorCode(EC, OutputFilename);

auto FS = vfs::getRealFileSystem();
auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);
if (Error E = ReaderOrErr.takeError())
exitWithError(std::move(E), Filename);

CodeGenDataWriter Writer;
auto Reader = ReaderOrErr->get();
if (Reader->hasOutlinedHashTree()) {
OutlinedHashTreeRecord Record(Reader->releaseOutlinedHashTree());
Writer.addRecord(Record);
}

if (OutputFormat == CD_Text) {
if (Error E = Writer.writeText(OS))
exitWithError(std::move(E));
} else {
if (Error E = Writer.write(OS))
exitWithError(std::move(E));
}

return 0;
}

static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
OutlinedHashTreeRecord &GlobalOutlineRecord);

static bool handleArchive(StringRef Filename, Archive &Arch,
OutlinedHashTreeRecord &GlobalOutlineRecord) {
bool Result = true;
Error Err = Error::success();
for (const auto &Child : Arch.children(Err)) {
auto BuffOrErr = Child.getMemoryBufferRef();
if (Error E = BuffOrErr.takeError())
exitWithError(std::move(E), Filename);
auto NameOrErr = Child.getName();
if (Error E = NameOrErr.takeError())
exitWithError(std::move(E), Filename);
std::string Name = (Filename + "(" + NameOrErr.get() + ")").str();
Result &= handleBuffer(Name, BuffOrErr.get(), GlobalOutlineRecord);
}
if (Err)
exitWithError(std::move(Err), Filename);
return Result;
}

static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
OutlinedHashTreeRecord &GlobalOutlineRecord) {
Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Buffer);
if (Error E = BinOrErr.takeError())
exitWithError(std::move(E), Filename);

bool Result = true;
if (auto *Obj = dyn_cast<ObjectFile>(BinOrErr->get())) {
if (Error E =
CodeGenDataReader::mergeFromObjectFile(Obj, GlobalOutlineRecord))
exitWithError(std::move(E), Filename);
} else if (auto *Arch = dyn_cast<Archive>(BinOrErr->get())) {
Result &= handleArchive(Filename, *Arch, GlobalOutlineRecord);
} else {
// TODO: Support for the MachO universal binary format.
errs() << "Error: unsupported binary file: " << Filename << "\n";
Result = false;
}

return Result;
}

static bool handleFile(StringRef Filename,
OutlinedHashTreeRecord &GlobalOutlineRecord) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
MemoryBuffer::getFileOrSTDIN(Filename);
if (std::error_code EC = BuffOrErr.getError())
exitWithErrorCode(EC, Filename);
return handleBuffer(Filename, *BuffOrErr.get(), GlobalOutlineRecord);
}

static int merge_main(int argc, const char *argv[]) {
bool Result = true;
OutlinedHashTreeRecord GlobalOutlineRecord;
for (auto &Filename : InputFilenames)
Result &= handleFile(Filename, GlobalOutlineRecord);

if (!Result) {
errs() << "Error: failed to merge codegen data files.\n";
return 1;
}

CodeGenDataWriter Writer;
if (!GlobalOutlineRecord.empty())
Writer.addRecord(GlobalOutlineRecord);

std::error_code EC;
raw_fd_ostream Output(OutputFilename, EC, sys::fs::OF_None);
if (EC)
exitWithErrorCode(EC, OutputFilename);

if (auto E = Writer.write(Output))
exitWithError(std::move(E));

return 0;
}

static int show_main(int argc, const char *argv[]) {
if (Filename == OutputFilename) {
errs() << sys::path::filename(argv[0]) << " " << argv[1]
<< ": Input file name cannot be the same as the output file name!\n";
return 1;
}

std::error_code EC;
raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF);
if (EC)
exitWithErrorCode(EC, OutputFilename);

auto FS = vfs::getRealFileSystem();
auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);
if (Error E = ReaderOrErr.takeError())
exitWithError(std::move(E), Filename);

auto Reader = ReaderOrErr->get();
if (ShowCGDataVersion)
OS << "Version: " << Reader->getVersion() << "\n";

if (Reader->hasOutlinedHashTree()) {
auto Tree = Reader->releaseOutlinedHashTree();
OS << "Outlined hash tree:\n";
OS << " Total Node Count: " << Tree->size() << "\n";
OS << " Terminal Node Count: " << Tree->size(/*GetTerminalCountOnly=*/true)
<< "\n";
OS << " Depth: " << Tree->depth() << "\n";
}

return 0;
}

int llvm_cgdata_main(int argc, char **argvNonConst, const llvm::ToolContext &) {
const char **argv = const_cast<const char **>(argvNonConst);

StringRef ProgName(sys::path::filename(argv[0]));

if (argc < 2) {
errs() << ProgName
<< ": No subcommand specified! Run llvm-cgdata --help for usage.\n";
return 1;
}

cl::ParseCommandLineOptions(argc, argv, "LLVM codegen data\n");

if (DumpSubcommand)
return dump_main(argc, argv);

if (MergeSubcommand)
return merge_main(argc, argv);

if (ShowSubcommand)
return show_main(argc, argv);

errs() << ProgName
<< ": Unknown command. Run llvm-cgdata --help for usage.\n";
return 1;
}