-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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
[CGData] llvm-cgdata #89884
Changes from all commits
94c2659
ea8714f
d8f8b23
59952c7
09fd9d4
3949783
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
}; | ||
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 |
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 |
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 |
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 |
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 |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Or does this not work because you need to use There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||
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); | ||||||||
kyulee-com marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
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) { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||
// 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 |
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ... There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
} |
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: [ ] | ||
... |
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. | ||
kyulee-com marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
kyulee-com marked this conversation as resolved.
Show resolved
Hide resolved
|
||
RUN: diff %t_header.cgdata %t_emptyheader.cgdata |
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 |
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" |
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" |
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" |
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" |
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: [ ] | ||
... |
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 | ||
) |
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; | ||
} |
There was a problem hiding this comment.
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) ?
There was a problem hiding this comment.
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