diff --git a/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoSupport.h b/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoSupport.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/DebugInfoSupport.h @@ -0,0 +1,62 @@ +//===--- DebugInfoSupport.h ---- Utils for debug info support ---*- 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 +// +//===----------------------------------------------------------------------===// +// +// Utilities to preserve and parse debug info from LinkGraphs. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_DEBUGINFOSUPPORT_H +#define LLVM_EXECUTIONENGINE_ORC_DEBUGINFOSUPPORT_H + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" + +#include "llvm/DebugInfo/DWARF/DWARFContext.h" + +namespace llvm { + +namespace orc { + +Error preserveDebugSections(jitlink::LinkGraph &G); +// The backing stringmap is also returned, for memory liftime management. +Expected, + StringMap>>> +createDWARFContext(jitlink::LinkGraph &G); + +// Thin wrapper around preserveDebugSections to be used as a standalone plugin. +class DebugInfoPreservationPlugin : public ObjectLinkingLayer::Plugin { +public: + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &LG, + jitlink::PassConfiguration &PassConfig) override { + PassConfig.PrePrunePasses.push_back(preserveDebugSections); + } + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + // Do nothing. + return Error::success(); + } + Error notifyFailed(MaterializationResponsibility &MR) override { + // Do nothing. + return Error::success(); + } + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override { + // Do nothing. + } + + static Expected> Create() { + return std::make_unique(); + } +}; + +} // namespace orc + +} // namespace llvm + +#endif \ No newline at end of file diff --git a/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/PerfSupportPlugin.h @@ -0,0 +1,277 @@ +//===----- PerfSupportPlugin.h ----- Utils for perf support -----*- 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 +// +//===----------------------------------------------------------------------===// +// +// Handles support for registering code with perf +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_PERFSUPPORTPLUGIN_H +#define LLVM_EXECUTIONENGINE_ORC_PERFSUPPORTPLUGIN_H + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/SimplePackedSerialization.h" + +namespace llvm { +namespace orc { + +/// Log perf jitdump events for each object (see +/// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt). +/// Currently has support for dumping code load records and unwind info records. +class PerfSupportPlugin : public ObjectLinkingLayer::Plugin { +public: + PerfSupportPlugin(ExecutorProcessControl &EPC, + ExecutorAddr RegisterPerfStartAddr, + ExecutorAddr RegisterPerfEndAddr, + ExecutorAddr RegisterPerfImplAddr, bool EmitDebugInfo, + bool EmitUnwindInfo); + ~PerfSupportPlugin(); + + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) override; + + Error notifyFailed(MaterializationResponsibility &MR) override { + return Error::success(); + } + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + + static Expected> + Create(ExecutorProcessControl &EPC, JITDylib &JD, bool EmitDebugInfo, + bool EmitUnwindInfo); + +private: + ExecutorProcessControl &EPC; + ExecutorAddr RegisterPerfStartAddr; + ExecutorAddr RegisterPerfEndAddr; + ExecutorAddr RegisterPerfImplAddr; + std::atomic CodeIndex; + bool EmitDebugInfo; + bool EmitUnwindInfo; +}; + +// The following are POD struct definitions from the perf jit specification + +enum class PerfJITRecordType { + JIT_CODE_LOAD = 0, + JIT_CODE_MOVE = 1, // not emitted, code isn't moved + JIT_CODE_DEBUG_INFO = 2, + JIT_CODE_CLOSE = 3, // not emitted, unnecessary + JIT_CODE_UNWINDING_INFO = 4, // not emitted + + JIT_CODE_MAX +}; + +struct PerfJITRecordPrefix { + PerfJITRecordType Id; // record type identifier, uint32_t + uint32_t TotalSize; +}; +struct PerfJITCodeLoadRecord { + PerfJITRecordPrefix Prefix; + + uint32_t Pid; + uint32_t Tid; + uint64_t Vma; + uint64_t CodeAddr; + uint64_t CodeSize; + uint64_t CodeIndex; + std::string Name; +}; + +struct PerfJITDebugEntry { + uint64_t Addr; + uint32_t Lineno; // source line number starting at 1 + uint32_t Discrim; // column discriminator, 0 is default + std::string Name; +}; + +struct PerfJITDebugInfoRecord { + PerfJITRecordPrefix Prefix; + + uint64_t CodeAddr; + // this is technically redundant, but it serves as documentation of the actual + // record format + uint64_t NrEntry; + std::vector Entries; +}; + +struct PerfJITCodeUnwindingInfoRecord { + PerfJITRecordPrefix Prefix; + + uint64_t UnwindDataSize; + uint64_t EHFrameHdrSize; + uint64_t MappedSize; + // Union, one will always be 0/"", the other has data + uint64_t EHFrameHdrAddr; + std::string EHFrameHdr; + uint64_t EHFrameAddr; + // size is UnwindDataSize - EHFrameHdrSize +}; + +// Batch vehicle for minimizing RPC calls for perf jit records +struct PerfJITRecordBatch { + std::vector DebugInfoRecords; + std::vector CodeLoadRecords; + // only valid if record size > 0 + PerfJITCodeUnwindingInfoRecord UnwindingRecord; +}; + +// SPS traits for Records + +namespace shared { + +using SPSPerfJITRecordPrefix = SPSTuple; + +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITRecordPrefix &Val) { + return SPSPerfJITRecordPrefix::AsArgList::size( + static_cast(Val.Id), Val.TotalSize); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITRecordPrefix &Val) { + uint32_t Id; + if (!SPSPerfJITRecordPrefix::AsArgList::deserialize(IB, Id, Val.TotalSize)) + return false; + Val.Id = static_cast(Id); + return true; + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITRecordPrefix &Val) { + return SPSPerfJITRecordPrefix::AsArgList::serialize( + OB, static_cast(Val.Id), Val.TotalSize); + } +}; + +using SPSPerfJITCodeLoadRecord = + SPSTuple; + +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITCodeLoadRecord &Val) { + return SPSPerfJITCodeLoadRecord::AsArgList::size( + Val.Prefix, Val.Pid, Val.Tid, Val.Vma, Val.CodeAddr, Val.CodeSize, + Val.CodeIndex, Val.Name); + } + + static bool deserialize(SPSInputBuffer &IB, PerfJITCodeLoadRecord &Val) { + return SPSPerfJITCodeLoadRecord::AsArgList::deserialize( + IB, Val.Prefix, Val.Pid, Val.Tid, Val.Vma, Val.CodeAddr, Val.CodeSize, + Val.CodeIndex, Val.Name); + } + + static bool serialize(SPSOutputBuffer &OB, const PerfJITCodeLoadRecord &Val) { + return SPSPerfJITCodeLoadRecord::AsArgList::serialize( + OB, Val.Prefix, Val.Pid, Val.Tid, Val.Vma, Val.CodeAddr, Val.CodeSize, + Val.CodeIndex, Val.Name); + } +}; + +using SPSPerfJITDebugEntry = SPSTuple; + +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITDebugEntry &Val) { + return SPSPerfJITDebugEntry::AsArgList::size(Val.Addr, Val.Lineno, + Val.Discrim, Val.Name); + } + + static bool deserialize(SPSInputBuffer &IB, PerfJITDebugEntry &Val) { + return SPSPerfJITDebugEntry::AsArgList::deserialize( + IB, Val.Addr, Val.Lineno, Val.Discrim, Val.Name); + } + + static bool serialize(SPSOutputBuffer &OB, const PerfJITDebugEntry &Val) { + return SPSPerfJITDebugEntry::AsArgList::serialize(OB, Val.Addr, Val.Lineno, + Val.Discrim, Val.Name); + } +}; + +using SPSPerfJITDebugInfoRecord = + SPSTuple>; + +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITDebugInfoRecord &Val) { + return SPSPerfJITDebugInfoRecord::AsArgList::size(Val.Prefix, Val.CodeAddr, + Val.NrEntry, Val.Entries); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITDebugInfoRecord &Val) { + return SPSPerfJITDebugInfoRecord::AsArgList::deserialize( + IB, Val.Prefix, Val.CodeAddr, Val.NrEntry, Val.Entries); + } + static bool serialize(SPSOutputBuffer &OB, + const PerfJITDebugInfoRecord &Val) { + return SPSPerfJITDebugInfoRecord::AsArgList::serialize( + OB, Val.Prefix, Val.CodeAddr, Val.NrEntry, Val.Entries); + } +}; + +using SPSPerfJITCodeUnwindingInfoRecord = + SPSTuple; +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITCodeUnwindingInfoRecord &Val) { + return SPSPerfJITCodeUnwindingInfoRecord::AsArgList::size( + Val.Prefix, Val.UnwindDataSize, Val.EHFrameHdrSize, Val.MappedSize, + Val.EHFrameHdrAddr, Val.EHFrameHdr, Val.EHFrameAddr); + } + static bool deserialize(SPSInputBuffer &IB, + PerfJITCodeUnwindingInfoRecord &Val) { + return SPSPerfJITCodeUnwindingInfoRecord::AsArgList::deserialize( + IB, Val.Prefix, Val.UnwindDataSize, Val.EHFrameHdrSize, Val.MappedSize, + Val.EHFrameHdrAddr, Val.EHFrameHdr, Val.EHFrameAddr); + } + static bool serialize(SPSOutputBuffer &OB, + const PerfJITCodeUnwindingInfoRecord &Val) { + return SPSPerfJITCodeUnwindingInfoRecord::AsArgList::serialize( + OB, Val.Prefix, Val.UnwindDataSize, Val.EHFrameHdrSize, Val.MappedSize, + Val.EHFrameHdrAddr, Val.EHFrameHdr, Val.EHFrameAddr); + } +}; + +using SPSPerfJITRecordBatch = SPSTuple, + SPSSequence, + SPSPerfJITCodeUnwindingInfoRecord>; +template <> +class SPSSerializationTraits { +public: + static size_t size(const PerfJITRecordBatch &Val) { + return SPSPerfJITRecordBatch::AsArgList::size( + Val.CodeLoadRecords, Val.DebugInfoRecords, Val.UnwindingRecord); + } + static bool deserialize(SPSInputBuffer &IB, PerfJITRecordBatch &Val) { + return SPSPerfJITRecordBatch::AsArgList::deserialize( + IB, Val.CodeLoadRecords, Val.DebugInfoRecords, Val.UnwindingRecord); + } + static bool serialize(SPSOutputBuffer &OB, const PerfJITRecordBatch &Val) { + return SPSPerfJITRecordBatch::AsArgList::serialize( + OB, Val.CodeLoadRecords, Val.DebugInfoRecords, Val.UnwindingRecord); + } +}; + +} // namespace shared + +} // namespace orc +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_PERFSUPPORTPLUGIN_H \ No newline at end of file diff --git a/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h @@ -0,0 +1,28 @@ +//===------- JITLoaderPerf.h --- Register profiler objects ------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Register objects for access by profilers via the perf JIT interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H +#define LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H + +#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h" +#include + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfImpl(const char *Data, uint64_t Size); + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfStart(const char *Data, uint64_t Size); + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfEnd(const char *Data, uint64_t Size); + +#endif // LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERPERF_H \ No newline at end of file diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt @@ -10,6 +10,7 @@ Core.cpp DebugObjectManagerPlugin.cpp DebuggerSupportPlugin.cpp + DebugInfoSupport.cpp DebugUtils.cpp EPCDynamicLibrarySearchGenerator.cpp EPCDebugObjectRegistrar.cpp @@ -37,6 +38,7 @@ ObjectTransformLayer.cpp OrcABISupport.cpp OrcV2CBindings.cpp + PerfSupportPlugin.cpp RTDyldObjectLinkingLayer.cpp SimpleRemoteEPC.cpp Speculation.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/DebugInfoSupport.cpp b/llvm/lib/ExecutionEngine/Orc/DebugInfoSupport.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/DebugInfoSupport.cpp @@ -0,0 +1,124 @@ +//===--- DebugInfoSupport.cpp -- Utils for debug info support ---*- 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 +// +//===----------------------------------------------------------------------===// +// +// Utilities to preserve and parse debug info from LinkGraphs. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/Orc/DebugInfoSupport.h" + +#include "llvm/Support/SmallVectorMemoryBuffer.h" + +#define DEBUG_TYPE "orc" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +namespace { +static DenseSet DWARFSectionNames = { +#define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME, OPTION) \ + StringRef(ELF_NAME), +#include "llvm/BinaryFormat/Dwarf.def" +#undef HANDLE_DWARF_SECTION +}; + +// We might be able to drop relocations to symbols that do end up +// being pruned by the linker, but for now we just preserve all +static void preserveDWARFSection(LinkGraph &G, Section &Sec) { + DenseMap Preserved; + for (auto Sym : Sec.symbols()) { + if (Sym->isLive()) { + Preserved[&Sym->getBlock()] = Sym; + } else if (!Preserved.count(&Sym->getBlock())) { + Preserved[&Sym->getBlock()] = Sym; + } + } + for (auto Block : Sec.blocks()) { + auto &PSym = Preserved[Block]; + if (!PSym) { + PSym = &G.addAnonymousSymbol(*Block, 0, 0, false, true); + } else if (!PSym->isLive()) { + PSym->setLive(true); + } + } +} + +static SmallVector getSectionData(Section &Sec) { + SmallVector SecData; + SmallVector SecBlocks(Sec.blocks().begin(), Sec.blocks().end()); + std::sort(SecBlocks.begin(), SecBlocks.end(), [](Block *LHS, Block *RHS) { + return LHS->getAddress() < RHS->getAddress(); + }); + // Convert back to what object file would have, one blob of section content + // Assumes all zerofill + // TODO handle alignment? + // TODO handle alignment offset? + for (auto *Block : SecBlocks) { + if (Block->isZeroFill()) { + SecData.resize(SecData.size() + Block->getSize(), 0); + } else { + SecData.append(Block->getContent().begin(), Block->getContent().end()); + } + } + return SecData; +} + +static void dumpDWARFContext(DWARFContext &DC) { + auto options = llvm::DIDumpOptions(); + options.DumpType &= ~DIDT_UUID; + options.DumpType &= ~(1 << DIDT_ID_DebugFrame); + LLVM_DEBUG(DC.dump(dbgs(), options)); +} + +} // namespace + +Error llvm::orc::preserveDebugSections(LinkGraph &G) { + if (!G.getTargetTriple().isOSBinFormatELF()) { + return make_error( + "preserveDebugSections only supports ELF LinkGraphs!", + inconvertibleErrorCode()); + } + for (auto &Sec : G.sections()) { + if (DWARFSectionNames.count(Sec.getName())) { + LLVM_DEBUG(dbgs() << "Preserving DWARF section " << Sec.getName() + << "\n"); + preserveDWARFSection(G, Sec); + } + } + return Error::success(); +} + +Expected, + StringMap>>> +llvm::orc::createDWARFContext(LinkGraph &G) { + if (!G.getTargetTriple().isOSBinFormatELF()) { + return make_error( + "createDWARFContext only supports ELF LinkGraphs!", + inconvertibleErrorCode()); + } + StringMap> DWARFSectionData; + for (auto &Sec : G.sections()) { + if (DWARFSectionNames.count(Sec.getName())) { + auto SecData = getSectionData(Sec); + auto Name = Sec.getName(); + // DWARFContext expects the section name to not start with a dot + if (Name.startswith(".")) { + Name = Name.drop_front(); + } + LLVM_DEBUG(dbgs() << "Creating DWARFContext section " << Name + << " with size " << SecData.size() << "\n"); + DWARFSectionData[Name] = + std::make_unique(std::move(SecData)); + } + } + auto Ctx = DWARFContext::create(DWARFSectionData, G.getPointerSize(), + G.getEndianness() == support::little); + dumpDWARFContext(*Ctx); + return std::make_pair(std::move(Ctx), std::move(DWARFSectionData)); +} diff --git a/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/PerfSupportPlugin.cpp @@ -0,0 +1,312 @@ +//===----- PerfSupportPlugin.cpp --- Utils for perf support -----*- 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 +// +//===----------------------------------------------------------------------===// +// +// Handles support for registering code with perf +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" + +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" + +#include "llvm/ExecutionEngine/Orc/DebugInfoSupport.h" + +#include "llvm/ExecutionEngine/JITLink/x86_64.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/SimplePackedSerialization.h" + +#define DEBUG_TYPE "orc" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +namespace { + +// Creates an EH frame header prepared for a 32-bit relative relocation +// to the start of the .eh_frame section. Absolute injects a 64-bit absolute +// address space offset 4 bytes from the start instead of 4 bytes +std::string createX64EHFrameHeader(Section &EHFrame, + support::endianness endianness, + bool absolute) { + uint8_t Version = 1; + uint8_t EhFramePtrEnc = 0; + if (absolute) { + EhFramePtrEnc |= dwarf::DW_EH_PE_sdata8 | dwarf::DW_EH_PE_absptr; + } else { + EhFramePtrEnc |= dwarf::DW_EH_PE_sdata4 | dwarf::DW_EH_PE_datarel; + } + uint8_t FDECountEnc = dwarf::DW_EH_PE_omit; + uint8_t TableEnc = dwarf::DW_EH_PE_omit; + // X86_64_64 relocation to the start of the .eh_frame section + uint32_t EHFrameRelocation = 0; + // uint32_t FDECount = 0; + // Skip the FDE binary search table + // We'd have to reprocess the CIEs to get this information, + // which seems like more trouble than it's worth + // TODO consider implementing this. + // binary search table goes here + + size_t HeaderSize = (sizeof(Version) + sizeof(EhFramePtrEnc) + + sizeof(FDECountEnc) + sizeof(TableEnc) + absolute + ? sizeof(uint64_t) + : sizeof(EHFrameRelocation)); + std::string HeaderContent(HeaderSize, '\0'); + BinaryStreamWriter Writer( + MutableArrayRef( + reinterpret_cast(HeaderContent.data()), HeaderSize), + endianness); + Writer.writeInteger(Version); + Writer.writeInteger(EhFramePtrEnc); + Writer.writeInteger(FDECountEnc); + Writer.writeInteger(TableEnc); + if (absolute) { + uint64_t EHFrameAddr = support::endian::byte_swap( + SectionRange(EHFrame).getStart().getValue(), endianness); + Writer.writeInteger(EHFrameAddr); + } else { + Writer.writeInteger(EHFrameRelocation); + } + return HeaderContent; +} + +constexpr StringRef RegisterPerfStartSymbolName = + "llvm_orc_registerJITLoaderPerfStart"; +constexpr StringRef RegisterPerfEndSymbolName = + "llvm_orc_registerJITLoaderPerfEnd"; +constexpr StringRef RegisterPerfImplSymbolName = + "llvm_orc_registerJITLoaderPerfImpl"; + +static inline uint64_t timespec_to_ns(const struct timespec *ts) { + const uint64_t NanoSecPerSec = 1000000000; + return ((uint64_t)ts->tv_sec * NanoSecPerSec) + ts->tv_nsec; +} + +static inline uint64_t perf_get_timestamp() { +#ifdef __linux__ + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) + return 0; + + return timespec_to_ns(&ts); +#else + return 0; +#endif +} + +static PerfJITCodeLoadRecord +getCodeLoadRecord(const Symbol &Sym, std::atomic &CodeIndex) { + PerfJITCodeLoadRecord Record; + auto Name = Sym.getName(); + auto Addr = Sym.getAddress(); + auto Size = Sym.getSize(); + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_LOAD; + // Runtime sets PID + Record.Pid = 0; + // Runtime sets TID + Record.Tid = 0; + Record.Vma = Addr.getValue(); + Record.CodeAddr = Addr.getValue(); + Record.CodeSize = Size; + Record.CodeIndex = CodeIndex++; + Record.Name = Name.str(); + // Initialize last, once all the other fields are filled + Record.Prefix.TotalSize = + (2 * sizeof(uint32_t) // id, total_size + + sizeof(uint64_t) // timestamp + + 2 * sizeof(uint32_t) // pid, tid + + 4 * sizeof(uint64_t) // vma, code_addr, code_size, code_index + + Name.size() + 1 // symbol name + + Record.CodeSize // code + ); + return Record; +} + +static std::optional +getDebugInfoRecord(const Symbol &Sym, DWARFContext &DC) { + auto &Section = Sym.getBlock().getSection(); + auto Addr = Sym.getAddress(); + auto Size = Sym.getSize(); + auto SAddr = object::SectionedAddress{Addr.getValue(), Section.getOrdinal()}; + LLVM_DEBUG(dbgs() << "Getting debug info for symbol " << Sym.getName() + << " at address " << Addr.getValue() << " with size " + << Size << "\n" + << "Section ordinal: " << Section.getOrdinal() << "\n"); + auto LInfo = DC.getLineInfoForAddressRange( + SAddr, Size, DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath); + if (LInfo.empty()) { + // No line info available + LLVM_DEBUG(dbgs() << "No line info available\n"); + return std::nullopt; + } + PerfJITDebugInfoRecord Record; + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_DEBUG_INFO; + Record.CodeAddr = Addr.getValue(); + for (const auto &Entry : LInfo) { + auto Addr = Entry.first; + // The function re-created by perf is preceded by a elf + // header. Need to adjust for that, otherwise the results are + // wrong. + Addr += 0x40; + Record.Entries.push_back({Addr, Entry.second.Line, + Entry.second.Discriminator, + Entry.second.FileName}); + } + Record.NrEntry = Record.Entries.size(); + size_t EntriesBytes = (2 // record header + + 2 // record fields + ) * + sizeof(uint64_t); + for (const auto &Entry : Record.Entries) { + EntriesBytes += + sizeof(uint64_t) + 2 * sizeof(uint32_t); // Addr, Line/Discrim + EntriesBytes += Entry.Name.size() + 1; // Name + } + Record.Prefix.TotalSize = EntriesBytes; + LLVM_DEBUG(dbgs() << "Created debug info record\n" + << "Total size: " << Record.Prefix.TotalSize << "\n" + << "Nr entries: " << Record.NrEntry << "\n"); + return Record; +} + +static PerfJITCodeUnwindingInfoRecord getUnwindingRecord(LinkGraph &G) { + PerfJITCodeUnwindingInfoRecord Record; + Record.Prefix.Id = PerfJITRecordType::JIT_CODE_UNWINDING_INFO; + Record.Prefix.TotalSize = 0; + auto Eh_frame = G.findSectionByName(".eh_frame"); + if (!Eh_frame) { + LLVM_DEBUG(dbgs() << "No .eh_frame section found\n"); + return Record; + } + if (!G.getTargetTriple().isOSBinFormatELF()) { + LLVM_DEBUG(dbgs() << "Not an ELF file, will not emit unwinding info\n"); + return Record; + } + auto SR = SectionRange(*Eh_frame); + auto EHFrameSize = SR.getSize(); + auto Eh_frame_hdr = G.findSectionByName(".eh_frame_hdr"); + if (!Eh_frame_hdr) { + if (G.getTargetTriple().getArch() == Triple::x86_64) { + Record.EHFrameHdr = + createX64EHFrameHeader(*Eh_frame, G.getEndianness(), true); + } else { + LLVM_DEBUG(dbgs() << "No .eh_frame_hdr section found\n"); + return Record; + } + Record.EHFrameHdrAddr = 0; + Record.EHFrameHdrSize = Record.EHFrameHdr.size(); + Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; + Record.MappedSize = 0; // Because the EHFrame header was not mapped + } else { + auto SR = SectionRange(*Eh_frame_hdr); + Record.EHFrameHdrAddr = SR.getStart().getValue(); + Record.EHFrameHdrSize = SR.getSize(); + Record.UnwindDataSize = EHFrameSize + Record.EHFrameHdrSize; + Record.MappedSize = Record.UnwindDataSize; + } + Record.EHFrameAddr = SR.getStart().getValue(); + Record.Prefix.TotalSize = + (2 * sizeof(uint32_t) // id, total_size + + sizeof(uint64_t) // timestamp + + + 3 * sizeof(uint64_t) // unwind_data_size, eh_frame_hdr_size, mapped_size + + Record.UnwindDataSize // eh_frame_hdr, eh_frame + ); + LLVM_DEBUG(dbgs() << "Created unwind record\n" + << "Total size: " << Record.Prefix.TotalSize << "\n" + << "Unwind size: " << Record.UnwindDataSize << "\n" + << "EHFrame size: " << EHFrameSize << "\n" + << "EHFrameHdr size: " << Record.EHFrameHdrSize << "\n"); + return Record; +} + +static PerfJITRecordBatch getRecords(ExecutionSession &ES, LinkGraph &G, + std::atomic &CodeIndex, + bool EmitDebugInfo, bool EmitUnwindInfo) { + std::unique_ptr DC; + StringMap> DCBacking; + if (EmitDebugInfo) { + auto EDC = createDWARFContext(G); + if (!EDC) { + ES.reportError(EDC.takeError()); + EmitDebugInfo = false; + } else { + DC = std::move(EDC->first); + DCBacking = std::move(EDC->second); + } + } + PerfJITRecordBatch Batch; + for (auto Sym : G.defined_symbols()) { + if (!Sym->hasName() || !Sym->isCallable()) + continue; + if (EmitDebugInfo) { + auto DebugInfo = getDebugInfoRecord(*Sym, *DC); + if (DebugInfo) + Batch.DebugInfoRecords.push_back(std::move(*DebugInfo)); + } + Batch.CodeLoadRecords.push_back(getCodeLoadRecord(*Sym, CodeIndex)); + } + if (EmitUnwindInfo) { + Batch.UnwindingRecord = getUnwindingRecord(G); + } else { + Batch.UnwindingRecord.Prefix.TotalSize = 0; + } + return Batch; +} +} // namespace + +PerfSupportPlugin::PerfSupportPlugin(ExecutorProcessControl &EPC, + ExecutorAddr RegisterPerfStartAddr, + ExecutorAddr RegisterPerfEndAddr, + ExecutorAddr RegisterPerfImplAddr, + bool EmitDebugInfo, bool EmitUnwindInfo) + : EPC(EPC), RegisterPerfStartAddr(RegisterPerfStartAddr), + RegisterPerfEndAddr(RegisterPerfEndAddr), + RegisterPerfImplAddr(RegisterPerfImplAddr), CodeIndex(0), + EmitDebugInfo(EmitDebugInfo), EmitUnwindInfo(EmitUnwindInfo) { + cantFail(EPC.callSPSWrapper(RegisterPerfStartAddr)); +} +PerfSupportPlugin::~PerfSupportPlugin() { + cantFail(EPC.callSPSWrapper(RegisterPerfEndAddr)); +} + +void PerfSupportPlugin::modifyPassConfig(MaterializationResponsibility &MR, + LinkGraph &G, + PassConfiguration &Config) { + Config.PostFixupPasses.push_back([this, &MR](LinkGraph &G) { + auto Batch = getRecords(EPC.getExecutionSession(), G, CodeIndex, + EmitDebugInfo, EmitUnwindInfo); + G.allocActions().push_back( + {cantFail(shared::WrapperFunctionCall::Create< + shared::SPSArgList>( + RegisterPerfImplAddr, Batch)), + {}}); + return Error::success(); + }); +} + +Expected> +PerfSupportPlugin::Create(ExecutorProcessControl &EPC, JITDylib &JD, + bool EmitDebugInfo, bool EmitUnwindInfo) { + auto &ES = EPC.getExecutionSession(); + auto StartName = ES.intern(RegisterPerfStartSymbolName); + auto EndName = ES.intern(RegisterPerfEndSymbolName); + auto ImplName = ES.intern(RegisterPerfImplSymbolName); + SymbolLookupSet SLS({StartName, EndName, ImplName}); + auto Res = ES.lookup(makeJITDylibSearchOrder({&JD}), std::move(SLS)); + if (!Res) + return Res.takeError(); + auto StartAddr = ExecutorAddr(Res->find(StartName)->second.getAddress()); + auto EndAddr = ExecutorAddr(Res->find(EndName)->second.getAddress()); + auto ImplAddr = ExecutorAddr(Res->find(ImplName)->second.getAddress()); + return std::make_unique(EPC, StartAddr, EndAddr, ImplAddr, + EmitDebugInfo, EmitUnwindInfo); +} diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt @@ -5,6 +5,7 @@ add_llvm_component_library(LLVMOrcTargetProcess ExecutorSharedMemoryMapperService.cpp JITLoaderGDB.cpp + JITLoaderPerf.cpp OrcRTBootstrap.cpp RegisterEHFrames.cpp SimpleExecutorDylibManager.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.cpp @@ -0,0 +1,446 @@ +//===------- JITLoaderPerf.cpp - Register profiler objects ------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Register objects for access by profilers via the perf JIT interface. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" + +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Threading.h" + +#include +#include + +#ifdef __linux__ +#include // mmap() +#include // clock_gettime(), time(), localtime_r() */ +#include // for read(), close() +#endif + +#define DEBUG_TYPE "orc" + +// language identifier (XXX: should we generate something better from debug +// info?) +#define JIT_LANG "llvm-IR" +#define LLVM_PERF_JIT_MAGIC \ + ((uint32_t)'J' << 24 | (uint32_t)'i' << 16 | (uint32_t)'T' << 8 | \ + (uint32_t)'D') +#define LLVM_PERF_JIT_VERSION 1 + +using namespace llvm; +using namespace llvm::orc; + +struct PerfState { + // cache lookups + uint32_t Pid; + + // base directory for output data + std::string JitPath; + + // output data stream, closed via Dumpstream + int DumpFd = -1; + + // output data stream + std::unique_ptr Dumpstream; + + // perf mmap marker + void *MarkerAddr = NULL; +}; + +// prevent concurrent dumps from messing up the output file +static std::mutex Mutex; +static std::optional state; + +struct RecHeader { + uint32_t Id; + uint32_t TotalSize; + uint64_t Timestamp; +}; + +struct DIR { + RecHeader Prefix; + uint64_t CodeAddr; + uint64_t NrEntry; +}; + +struct DIE { + uint64_t CodeAddr; + uint32_t Line; + uint32_t Discrim; +}; + +struct CLR { + RecHeader Prefix; + uint32_t Pid; + uint32_t Tid; + uint64_t Vma; + uint64_t CodeAddr; + uint64_t CodeSize; + uint64_t CodeIndex; +}; + +struct UWR { + RecHeader Prefix; + uint64_t UnwindDataSize; + uint64_t EhFrameHeaderSize; + uint64_t MappedSize; +}; + +static inline uint64_t timespec_to_ns(const struct timespec *ts) { + const uint64_t NanoSecPerSec = 1000000000; + return ((uint64_t)ts->tv_sec * NanoSecPerSec) + ts->tv_nsec; +} + +static inline uint64_t perf_get_timestamp() { +#ifdef __linux__ + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) + return 0; + + return timespec_to_ns(&ts); +#else + return 0; +#endif +} + +static void writeDebugRecord(const PerfJITDebugInfoRecord &DebugRecord) { + assert(state && "PerfState not initialized"); + LLVM_DEBUG(dbgs() << "Writing debug record with " + << DebugRecord.Entries.size() << " entries\n"); + size_t Written = 0; + DIR dir{RecHeader{static_cast(DebugRecord.Prefix.Id), + DebugRecord.Prefix.TotalSize, perf_get_timestamp()}, + DebugRecord.CodeAddr, DebugRecord.NrEntry}; + state->Dumpstream->write(reinterpret_cast(&dir), sizeof(dir)); + Written += sizeof(dir); + for (auto &die : DebugRecord.Entries) { + DIE d{die.Addr, die.Lineno, die.Discrim}; + state->Dumpstream->write(reinterpret_cast(&d), sizeof(d)); + state->Dumpstream->write(die.Name.data(), die.Name.size() + 1); + Written += sizeof(d) + die.Name.size() + 1; + } + LLVM_DEBUG(dbgs() << "wrote " << Written << " bytes of debug info\n"); +} + +static void writeCodeRecord(const PerfJITCodeLoadRecord &CodeRecord) { + assert(state && "PerfState not initialized"); + uint32_t Tid = get_threadid(); + LLVM_DEBUG(dbgs() << "Writing code record with code size " + << CodeRecord.CodeSize << " and code index " + << CodeRecord.CodeIndex << "\n"); + CLR clr{RecHeader{static_cast(CodeRecord.Prefix.Id), + CodeRecord.Prefix.TotalSize, perf_get_timestamp()}, + state->Pid, + Tid, + CodeRecord.Vma, + CodeRecord.CodeAddr, + CodeRecord.CodeSize, + CodeRecord.CodeIndex}; + LLVM_DEBUG(dbgs() << "wrote " << sizeof(clr) << " bytes of CLR, " + << CodeRecord.Name.size() + 1 << " bytes of name, " + << CodeRecord.CodeSize << " bytes of code\n"); + state->Dumpstream->write(reinterpret_cast(&clr), sizeof(clr)); + state->Dumpstream->write(CodeRecord.Name.data(), CodeRecord.Name.size() + 1); + state->Dumpstream->write((const char *)CodeRecord.CodeAddr, + CodeRecord.CodeSize); +} + +static void +writeUnwindRecord(const PerfJITCodeUnwindingInfoRecord &UnwindRecord) { + assert(state && "PerfState not initialized"); + dbgs() << "Writing unwind record with unwind data size " + << UnwindRecord.UnwindDataSize << " and EH frame header size " + << UnwindRecord.EHFrameHdrSize << " and mapped size " + << UnwindRecord.MappedSize << "\n"; + UWR uwr{RecHeader{static_cast(UnwindRecord.Prefix.Id), + UnwindRecord.Prefix.TotalSize, perf_get_timestamp()}, + UnwindRecord.UnwindDataSize, UnwindRecord.EHFrameHdrSize, + UnwindRecord.MappedSize}; + LLVM_DEBUG(dbgs() << "wrote " << sizeof(uwr) << " bytes of UWR, " + << UnwindRecord.EHFrameHdrSize + << " bytes of EH frame header, " + << UnwindRecord.UnwindDataSize - UnwindRecord.EHFrameHdrSize + << " bytes of EH frame\n"); + state->Dumpstream->write(reinterpret_cast(&uwr), sizeof(uwr)); + if (UnwindRecord.EHFrameHdrAddr) { + state->Dumpstream->write((const char *)UnwindRecord.EHFrameHdrAddr, + UnwindRecord.EHFrameHdrSize); + } else { + state->Dumpstream->write(UnwindRecord.EHFrameHdr.data(), + UnwindRecord.EHFrameHdrSize); + } + state->Dumpstream->write((const char *)UnwindRecord.EHFrameAddr, + UnwindRecord.UnwindDataSize - + UnwindRecord.EHFrameHdrSize); +} + +static Error registerJITLoaderPerfImpl(const PerfJITRecordBatch &Batch) { + if (!state) { + return make_error("PerfState not initialized", + inconvertibleErrorCode()); + } + + // Serialize the batch + std::lock_guard Lock(Mutex); + if (Batch.UnwindingRecord.Prefix.TotalSize > 0) { + writeUnwindRecord(Batch.UnwindingRecord); + } + for (const auto &DebugInfo : Batch.DebugInfoRecords) { + writeDebugRecord(DebugInfo); + } + for (const auto &CodeLoad : Batch.CodeLoadRecords) { + writeCodeRecord(CodeLoad); + } + + state->Dumpstream->flush(); + + return Error::success(); +} + +struct Header { + uint32_t Magic; // characters "JiTD" + uint32_t Version; // header version + uint32_t TotalSize; // total size of header + uint32_t ElfMach; // elf mach target + uint32_t Pad1; // reserved + uint32_t Pid; + uint64_t Timestamp; // timestamp + uint64_t Flags; // flags +}; + +static Error OpenMarker(PerfState &state) { +#ifdef __linux__ + // We mmap the jitdump to create an MMAP RECORD in perf.data file. The mmap + // is captured either live (perf record running when we mmap) or in deferred + // mode, via /proc/PID/maps. The MMAP record is used as a marker of a jitdump + // file for more meta data info about the jitted code. Perf report/annotate + // detect this special filename and process the jitdump file. + // + // Mapping must be PROT_EXEC to ensure it is captured by perf record + // even when not using -d option. + state.MarkerAddr = + ::mmap(NULL, sys::Process::getPageSizeEstimate(), PROT_READ | PROT_EXEC, + MAP_PRIVATE, state.DumpFd, 0); + + if (state.MarkerAddr == MAP_FAILED) { + return make_error("could not mmap JIT marker", + inconvertibleErrorCode()); + } + return Error::success(); +#else + return make_error("jitdump not supported on this platform", + inconvertibleErrorCode()); +#endif +} + +void CloseMarker(PerfState &state) { +#ifdef __linux__ + if (!state.MarkerAddr) + return; + + munmap(state.MarkerAddr, sys::Process::getPageSizeEstimate()); + state.MarkerAddr = nullptr; +#endif +} + +static Expected
FillMachine(PerfState &state) { + Header hdr; + hdr.Magic = LLVM_PERF_JIT_MAGIC; + hdr.Version = LLVM_PERF_JIT_VERSION; + hdr.TotalSize = sizeof(hdr); + hdr.Pid = state.Pid; + hdr.Timestamp = perf_get_timestamp(); + + char id[16]; + struct { + uint16_t e_type; + uint16_t e_machine; + } info; + + size_t RequiredMemory = sizeof(id) + sizeof(info); + + ErrorOr> MB = + MemoryBuffer::getFileSlice("/proc/self/exe", RequiredMemory, 0); + + // This'll not guarantee that enough data was actually read from the + // underlying file. Instead the trailing part of the buffer would be + // zeroed. Given the ELF signature check below that seems ok though, + // it's unlikely that the file ends just after that, and the + // consequence would just be that perf wouldn't recognize the + // signature. + if (!MB) { + return make_error("could not open /proc/self/exe", + MB.getError()); + } + + memcpy(&id, (*MB)->getBufferStart(), sizeof(id)); + memcpy(&info, (*MB)->getBufferStart() + sizeof(id), sizeof(info)); + + // check ELF signature + if (id[0] != 0x7f || id[1] != 'E' || id[2] != 'L' || id[3] != 'F') { + return make_error("invalid ELF signature", + inconvertibleErrorCode()); + } + + hdr.ElfMach = info.e_machine; + + return hdr; +} + +static Error InitDebuggingDir(PerfState &state) { + time_t Time; + struct tm LocalTime; + char TimeBuffer[sizeof("YYYYMMDD")]; + SmallString<64> Path; + + // search for location to dump data to + if (const char *BaseDir = getenv("JITDUMPDIR")) + Path.append(BaseDir); + else if (!sys::path::home_directory(Path)) + Path = "."; + + // create debug directory + Path += "/.debug/jit/"; + if (auto EC = sys::fs::create_directories(Path)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not create jit cache directory " << Path << ": " + << EC.message() << "\n"; + return make_error(std::move(errstr), inconvertibleErrorCode()); + } + + // create unique directory for dump data related to this process +#ifdef __linux__ + time(&Time); + localtime_r(&Time, &LocalTime); + strftime(TimeBuffer, sizeof(TimeBuffer), "%Y%m%d", &LocalTime); +#endif + Path += JIT_LANG "-jit-"; + Path += TimeBuffer; + + SmallString<128> UniqueDebugDir; + + using sys::fs::createUniqueDirectory; + if (auto EC = createUniqueDirectory(Path, UniqueDebugDir)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not create unique jit cache directory " + << UniqueDebugDir << ": " << EC.message() << "\n"; + return make_error(std::move(errstr), inconvertibleErrorCode()); + } + + state.JitPath = std::string(UniqueDebugDir.str()); + + return Error::success(); +} + +static Error registerJITLoaderPerfStartImpl() { + PerfState tentative; + tentative.Pid = sys::Process::getProcessId(); + // check if clock-source is supported + if (!perf_get_timestamp()) { + return make_error("kernel does not support CLOCK_MONOTONIC", + inconvertibleErrorCode()); + } + + if (auto err = InitDebuggingDir(tentative)) { + return std::move(err); + } + + std::string Filename; + raw_string_ostream FilenameBuf(Filename); + FilenameBuf << tentative.JitPath << "/jit-" << tentative.Pid << ".dump"; + + // Need to open ourselves, because we need to hand the FD to OpenMarker() and + // raw_fd_ostream doesn't expose the FD. + using sys::fs::openFileForWrite; + if (auto EC = openFileForReadWrite(FilenameBuf.str(), tentative.DumpFd, + sys::fs::CD_CreateNew, sys::fs::OF_None)) { + std::string errstr; + raw_string_ostream errstream(errstr); + errstream << "could not open JIT dump file " << FilenameBuf.str() << ": " + << EC.message() << "\n"; + return make_error(std::move(errstr), inconvertibleErrorCode()); + } + + tentative.Dumpstream = + std::make_unique(tentative.DumpFd, true); + + auto header = FillMachine(tentative); + if (!header) { + return header.takeError(); + } + + // signal this process emits JIT information + if (auto err = OpenMarker(tentative)) { + return std::move(err); + } + + tentative.Dumpstream->write(reinterpret_cast(&header.get()), + sizeof(*header)); + + // Everything initialized, can do profiling now. + if (tentative.Dumpstream->has_error()) { + return make_error("could not write JIT dump header", + inconvertibleErrorCode()); + } + state = std::move(tentative); + return Error::success(); +} + +static Error registerJITLoaderPerfEndImpl() { + if (!state) { + return make_error("PerfState not initialized", + inconvertibleErrorCode()); + } + RecHeader close; + close.Id = static_cast(PerfJITRecordType::JIT_CODE_CLOSE); + close.TotalSize = sizeof(close); + close.Timestamp = perf_get_timestamp(); + state->Dumpstream->write(reinterpret_cast(&close), + sizeof(close)); + if (state->MarkerAddr) { + CloseMarker(*state); + } + state.reset(); + return Error::success(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfImpl(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle( + Data, Size, registerJITLoaderPerfImpl) + .release(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfStart(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle(Data, Size, + registerJITLoaderPerfStartImpl) + .release(); +} + +extern "C" llvm::orc::shared::CWrapperFunctionResult +llvm_orc_registerJITLoaderPerfEnd(const char *Data, uint64_t Size) { + using namespace orc::shared; + return WrapperFunction::handle(Data, Size, + registerJITLoaderPerfEndImpl) + .release(); +} diff --git a/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/ELF_x86-64_perf.s @@ -0,0 +1,202 @@ +# RUN: rm -rf %t && mkdir -p %t +# RUN: llvm-mc -triple=x86_64-unknown-linux -position-independent \ +# RUN: -filetype=obj -o %t/ELF_x86-64_perf.o %s +# RUN: JITDUMPDIR="%t" llvm-jitlink -perf-support \ +# RUN: %t/ELF_x86-64_perf.o +# RUN: test -f %t/.debug/jit/llvm-IR-jit-*/jit-*.dump + +# Test ELF perf support for code load records and unwind info + + .text + .file "example.c" + .section .text.source,"ax",@progbits + .globl source # -- Begin function source + .p2align 4, 0x90 + .type source,@function +source: # @source +.Lfunc_begin0: + .file 1 "/app" "example.c" + .loc 1 1 0 # example.c:1:0 + .cfi_startproc +# %bb.0: + .loc 1 2 5 prologue_end # example.c:2:5 + movl $1, %eax + retq +.Ltmp0: +.Lfunc_end0: + .size source, .Lfunc_end0-source + .cfi_endproc + # -- End function + .section .text.passthrough,"ax",@progbits + .globl passthrough # -- Begin function passthrough + .p2align 4, 0x90 + .type passthrough,@function +passthrough: # @passthrough +.Lfunc_begin1: + .loc 1 5 0 # example.c:5:0 + .cfi_startproc +# %bb.0: + .loc 1 6 5 prologue_end # example.c:6:5 + movl $1, %eax + retq +.Ltmp1: +.Lfunc_end1: + .size passthrough, .Lfunc_end1-passthrough + .cfi_endproc + # -- End function + .section .text.main,"ax",@progbits + .globl main # -- Begin function main + .p2align 4, 0x90 + .type main,@function +main: # @main +.Lfunc_begin2: + .loc 1 9 0 # example.c:9:0 + .cfi_startproc +# %bb.0: + .loc 1 10 5 prologue_end # example.c:10:5 + xorl %eax, %eax + retq +.Ltmp2: +.Lfunc_end2: + .size main, .Lfunc_end2-main + .cfi_endproc + # -- End function + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 37 # DW_AT_producer + .byte 14 # DW_FORM_strp + .byte 19 # DW_AT_language + .byte 5 # DW_FORM_data2 + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 27 # DW_AT_comp_dir + .byte 14 # DW_FORM_strp + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 85 # DW_AT_ranges + .byte 23 # DW_FORM_sec_offset + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 0 # DW_CHILDREN_no + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 64 # DW_AT_frame_base + .byte 24 # DW_FORM_exprloc + .ascii "\227B" # DW_AT_GNU_all_call_sites + .byte 25 # DW_FORM_flag_present + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 3 # Abbreviation Code + .byte 36 # DW_TAG_base_type + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 62 # DW_AT_encoding + .byte 11 # DW_FORM_data1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + .section .debug_info,"",@progbits +.Lcu_begin0: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 4 # DWARF version number + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 8 # Address Size (in bytes) + .byte 1 # Abbrev [1] 0xb:0x72 DW_TAG_compile_unit + .long .Linfo_string0 # DW_AT_producer + .short 12 # DW_AT_language + .long .Linfo_string1 # DW_AT_name + .long .Lline_table_start0 # DW_AT_stmt_list + .long .Linfo_string2 # DW_AT_comp_dir + .quad 0 # DW_AT_low_pc + .long .Ldebug_ranges0 # DW_AT_ranges + .byte 2 # Abbrev [2] 0x2a:0x19 DW_TAG_subprogram + .quad .Lfunc_begin0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string3 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 1 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 2 # Abbrev [2] 0x43:0x19 DW_TAG_subprogram + .quad .Lfunc_begin1 # DW_AT_low_pc + .long .Lfunc_end1-.Lfunc_begin1 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string5 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 5 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 2 # Abbrev [2] 0x5c:0x19 DW_TAG_subprogram + .quad .Lfunc_begin2 # DW_AT_low_pc + .long .Lfunc_end2-.Lfunc_begin2 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + # DW_AT_GNU_all_call_sites + .long .Linfo_string6 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 9 # DW_AT_decl_line + .long 117 # DW_AT_type + # DW_AT_external + .byte 3 # Abbrev [3] 0x75:0x7 DW_TAG_base_type + .long .Linfo_string4 # DW_AT_name + .byte 5 # DW_AT_encoding + .byte 4 # DW_AT_byte_size + .byte 0 # End Of Children Mark +.Ldebug_info_end0: + .section .debug_ranges,"",@progbits +.Ldebug_ranges0: + .quad .Lfunc_begin0 + .quad .Lfunc_end0 + .quad .Lfunc_begin1 + .quad .Lfunc_end1 + .quad .Lfunc_begin2 + .quad .Lfunc_end2 + .quad 0 + .quad 0 + .section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)" # string offset=0 +.Linfo_string1: + .asciz "/app/example.c" # string offset=105 +.Linfo_string2: + .asciz "/app" # string offset=120 +.Linfo_string3: + .asciz "source" # string offset=125 +.Linfo_string4: + .asciz "int" # string offset=132 +.Linfo_string5: + .asciz "passthrough" # string offset=136 +.Linfo_string6: + .asciz "main" # string offset=148 + .ident "clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)" + .section ".note.GNU-stack","",@progbits + .addrsig + .section .debug_line,"",@progbits +.Lline_table_start0: diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp --- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp @@ -17,6 +17,7 @@ #include "llvm/BinaryFormat/Magic.h" #include "llvm/ExecutionEngine/Orc/COFFPlatform.h" #include "llvm/ExecutionEngine/Orc/COFFVCRuntimeSupport.h" +#include "llvm/ExecutionEngine/Orc/DebugInfoSupport.h" #include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" #include "llvm/ExecutionEngine/Orc/DebuggerSupportPlugin.h" #include "llvm/ExecutionEngine/Orc/ELFNixPlatform.h" @@ -28,8 +29,10 @@ #include "llvm/ExecutionEngine/Orc/MachOPlatform.h" #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h" +#include "llvm/ExecutionEngine/Orc/PerfSupportPlugin.h" #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" @@ -140,6 +143,11 @@ cl::desc("Enable debugger suppport (default = !-noexec)"), cl::init(true), cl::Hidden, cl::cat(JITLinkCategory)); +static cl::opt + PerfSupport("perf-support", + cl::desc("Enable perf profiling suppport (default = !-noexec)"), + cl::init(true), cl::Hidden, cl::cat(JITLinkCategory)); + static cl::opt NoProcessSymbols("no-process-syms", cl::desc("Do not resolve to llvm-jitlink process symbols"), @@ -246,10 +254,14 @@ static ExitOnError ExitOnErr; static LLVM_ATTRIBUTE_USED void linkComponents() { - errs() << (void *)&llvm_orc_registerEHFrameSectionWrapper - << (void *)&llvm_orc_deregisterEHFrameSectionWrapper - << (void *)&llvm_orc_registerJITLoaderGDBWrapper - << (void *)&llvm_orc_registerJITLoaderGDBAllocAction; + errs() << "Linking in runtime functions\n" + << (void *)&llvm_orc_registerEHFrameSectionWrapper << '\n' + << (void *)&llvm_orc_deregisterEHFrameSectionWrapper << '\n' + << (void *)&llvm_orc_registerJITLoaderGDBWrapper << '\n' + << (void *)&llvm_orc_registerJITLoaderGDBAllocAction << '\n' + << (void *)&llvm_orc_registerJITLoaderPerfStart << '\n' + << (void *)&llvm_orc_registerJITLoaderPerfEnd << '\n' + << (void *)&llvm_orc_registerJITLoaderPerfImpl << '\n'; } static bool UseTestResultOverride = false; @@ -987,6 +999,12 @@ ObjLayer.addPlugin(ExitOnErr( GDBJITDebugInfoRegistrationPlugin::Create(this->ES, *MainJD, TT))); + if (PerfSupport && TT.isOSBinFormatELF()) { + ObjLayer.addPlugin(ExitOnErr(DebugInfoPreservationPlugin::Create())); + ObjLayer.addPlugin(ExitOnErr(PerfSupportPlugin::Create( + this->ES.getExecutorProcessControl(), *MainJD, true, true))); + } + // Set up the platform. if (TT.isOSBinFormatMachO() && !OrcRuntime.empty()) { if (auto P =