Skip to content

Commit

Permalink
Add support for profiling/debugging interpreted/JITted code
Browse files Browse the repository at this point in the history
This commit builds on previous work for getting GDB support for JITted
and interpreted code working in cling. It adds a JIT event listener as
well to create perf map files to allow profiling of interpreted/JITted
code with cling.

The functionality provided is disabled by default. The interface to
enable it has been chosen to be via environment variables to allow both
interpreted code in the prompt as well as linked code to be able to
optionally enable debugging/profiling. Using ld.so as example, where
LD_DEBUG and LD_PROFILE exist, we chose CLING_DEBUG and CLING_PROFILE
to enable these features. For the moment, setting these variables to
anything enables the feature. Later on, if support for oprofile is
added, for example, it may be better to enable specific JIT event
listeners depending on the value of the variables, for example with
CLING_PROFILE=perf or CLING_PROFILE=oprofile, respectively.
  • Loading branch information
amadio committed Jun 7, 2022
1 parent 6a636d5 commit 22b1606
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 15 deletions.
12 changes: 11 additions & 1 deletion interpreter/cling/lib/Interpreter/CIFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "llvm/Target/TargetOptions.h"

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <limits>
#include <memory>
Expand Down Expand Up @@ -1322,6 +1323,12 @@ namespace {
if(COpts.CUDAHost)
argvCompile.push_back("--cuda-host-only");

#ifdef __linux__
// Keep frame pointer to make JIT stack unwinding reliable for profiling
if (std::getenv("CLING_PROFILE"))
argvCompile.push_back("-fno-omit-frame-pointer");
#endif

// argv[0] already inserted, get the rest
argvCompile.insert(argvCompile.end(), argv+1, argv + argc);

Expand Down Expand Up @@ -1660,7 +1667,10 @@ namespace {
// adjusted per transaction in IncrementalParser::codeGenTransaction().
CGOpts.setInlining(CodeGenOptions::NormalInlining);

// CGOpts.setDebugInfo(clang::CodeGenOptions::FullDebugInfo);
// Add debugging info when debugging or profiling
if (std::getenv("CLING_DEBUG") || std::getenv("CLING_PROFILE"))
CGOpts.setDebugInfo(clang::codegenoptions::FullDebugInfo);

// CGOpts.EmitDeclMetadata = 1; // For unloading, for later
// aliasing the complete ctor to the base ctor causes the JIT to crash
CGOpts.CXXCtorDtorAliases = 0;
Expand Down
1 change: 1 addition & 0 deletions interpreter/cling/lib/Interpreter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ add_cling_library(clingInterpreter OBJECT
InvocationOptions.cpp
LookupHelper.cpp
NullDerefProtectionTransformer.cpp
PerfJITEventListener.cpp
RequiredSymbols.cpp
Transaction.cpp
TransactionUnloader.cpp
Expand Down
14 changes: 12 additions & 2 deletions interpreter/cling/lib/Interpreter/IncrementalJIT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class ClingMemoryManager: public SectionMemoryManager {

namespace cling {

///\brief Creates JIT event listener to allow profiling of JITted code with perf
llvm::JITEventListener* createPerfJITEventListener();

///\brief Memory manager for the OrcJIT layers to resolve symbols from the
/// common IncrementalJIT. I.e. the master of the Orcs.
/// Each ObjectLayer instance has one Azog object.
Expand Down Expand Up @@ -355,8 +358,15 @@ IncrementalJIT::IncrementalJIT(IncrementalExecutor& exe,
// Enable JIT symbol resolution from the binary.
llvm::sys::DynamicLibrary::LoadLibraryPermanently(0, 0);

// Make debug symbols available.
m_GDBListener = 0; // JITEventListener::createGDBRegistrationListener();
// Enable GDB JIT listener for debugging if CLING_DEBUG is set
if (std::getenv("CLING_DEBUG"))
m_Listeners.push_back(JITEventListener::createGDBRegistrationListener());

#ifdef __linux__
// Enable perf profiling of JITted code on Linux if CLING_PROFILE is set
if (std::getenv("CLING_PROFILE"))
m_Listeners.push_back(cling::createPerfJITEventListener());
#endif

// #if MCJIT
// llvm::EngineBuilder builder(std::move(m));
Expand Down
18 changes: 6 additions & 12 deletions interpreter/cling/lib/Interpreter/IncrementalJIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,23 @@ class IncrementalJIT {
private:
friend class Azog;

llvm::JITEventListener* m_GDBListener; // owned by llvm::ManagedStaticBase
// owned by llvm::ManagedStaticBase
std::vector<llvm::JITEventListener*> m_Listeners;

SymbolMapT m_SymbolMap;

class NotifyObjectLoadedT {
public:
NotifyObjectLoadedT(IncrementalJIT &jit) : m_JIT(jit) {}
void operator()(llvm::orc::VModuleKey K,
const llvm::object::ObjectFile &Object,
const llvm::LoadedObjectInfo &/*Info*/) const {
const llvm::object::ObjectFile& Object,
const llvm::RuntimeDyld::LoadedObjectInfo& Info) const {
m_JIT.m_UnfinalizedSections[K]
= std::move(m_JIT.m_SectionsAllocatedSinceLastLoad);
m_JIT.m_SectionsAllocatedSinceLastLoad = SectionAddrSet();

// FIXME: NotifyObjectEmitted requires a RuntimeDyld::LoadedObjectInfo
// object. In order to get it one should call
// RTDyld.loadObject(*ObjToLoad->getBinary()) according to r306058.
// Moreover this should be done in the finalizer. Currently we are
// disabling this since we have globally disabled this functionality in
// IncrementalJIT.cpp (m_GDBListener = 0).
//
// if (auto GDBListener = m_JIT.m_GDBListener)
// GDBListener->NotifyObjectEmitted(*Object->getBinary(), Info);
for (auto listener : m_JIT.m_Listeners)
listener->notifyObjectLoaded(K, Object, Info);

for (const auto &Symbol: Object.symbols()) {
auto Flags = Symbol.getFlags();
Expand Down
129 changes: 129 additions & 0 deletions interpreter/cling/lib/Interpreter/PerfJITEventListener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//--------------------------------------------------------------------*- C++ -*-
// CLING - the C++ LLVM-based InterpreterG :)
// author: Guilherme Amadio <[email protected]>
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------
//
// This file implements a JITEventListener object that tells perf about JITted
// symbols using perf map files (/tmp/perf-%d.map, where %d = pid of process).
//
// Documentation for this perf jit interface is available at:
// https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jit-interface.txt
//
//------------------------------------------------------------------------------

#ifdef __linux__

#include "llvm/Config/config.h"
#include "llvm/ExecutionEngine/JITEventListener.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Object/SymbolSize.h"
#include "llvm/Support/ManagedStatic.h"

#include <cstdint>
#include <cstdio>
#include <mutex>

#include <unistd.h>

using namespace llvm;
using namespace llvm::object;

namespace {

class PerfJITEventListener : public JITEventListener {
public:
PerfJITEventListener();
~PerfJITEventListener() {
if (m_Perfmap)
fclose(m_Perfmap);
}

void notifyObjectLoaded(ObjectKey K, const ObjectFile& Obj,
const RuntimeDyld::LoadedObjectInfo& L) override;
void notifyFreeingObject(ObjectKey K) override;

private:
std::mutex m_Mutex;
FILE* m_Perfmap;
};

PerfJITEventListener::PerfJITEventListener() {
char filename[64];
snprintf(filename, 64, "/tmp/perf-%d.map", getpid());
m_Perfmap = fopen(filename, "a");
}

void PerfJITEventListener::notifyObjectLoaded(
ObjectKey K, const ObjectFile& Obj,
const RuntimeDyld::LoadedObjectInfo& L) {

if (!m_Perfmap)
return;

OwningBinary<ObjectFile> DebugObjOwner = L.getObjectForDebug(Obj);
const ObjectFile& DebugObj = *DebugObjOwner.getBinary();

// For each symbol, we want to check its address and size
// if it's a function and write the information to the perf
// map file, otherwise we just ignore the symbol and any
// related errors. This implementation is adapted from LLVM:
// llvm/src/lib/ExecutionEngine/PerfJITEvents/PerfJITEventListener.cpp

for (const std::pair<SymbolRef, uint64_t>& P :
computeSymbolSizes(DebugObj)) {
SymbolRef Sym = P.first;

Expected<SymbolRef::Type> SymTypeOrErr = Sym.getType();
if (!SymTypeOrErr) {
consumeError(SymTypeOrErr.takeError());
continue;
}

SymbolRef::Type SymType = *SymTypeOrErr;
if (SymType != SymbolRef::ST_Function)
continue;

Expected<StringRef> Name = Sym.getName();
if (!Name) {
consumeError(Name.takeError());
continue;
}

Expected<uint64_t> AddrOrErr = Sym.getAddress();
if (!AddrOrErr) {
consumeError(AddrOrErr.takeError());
continue;
}

uint64_t address = *AddrOrErr;
uint64_t size = P.second;

if (size == 0)
continue;

std::lock_guard<std::mutex> lock(m_Mutex);
fprintf(m_Perfmap, "%" PRIx64 " %" PRIx64 " %s\n", address, size, Name->data());
}

fflush(m_Perfmap);
}

void PerfJITEventListener::notifyFreeingObject(ObjectKey K) {
// nothing to be done
}

llvm::ManagedStatic<PerfJITEventListener> PerfListener;

} // end anonymous namespace

namespace cling {

JITEventListener* createPerfJITEventListener() { return &*PerfListener; }

} // namespace cling

#endif

0 comments on commit 22b1606

Please sign in to comment.