Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions core/meta/inc/TInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "TVirtualRWMutex.h"

#include <map>
#include <memory>
#include <typeinfo>
#include <vector>
#include <string>
Expand Down Expand Up @@ -129,6 +130,24 @@ class TInterpreter : public TNamed {
~SuspendAutoLoadingRAII() { fInterp->SetClassAutoLoading(fOldValue); }
};

/// An interpreter of its own, for code that is not needed after it has run, created by
/// CreateDisposable(). Destroying it frees the memory of everything it compiled, which
/// the interpreter never does for its own code.
///
/// It is created with the interpreter's arguments, so it finds the same headers and C++
/// modules. It does not see what was declared in the interpreter itself, and it does not
/// load libraries: the code it compiles can call into libraries that are already loaded.
/// Nothing may point into its code once it is destroyed, eg a std::function holding a
/// lambda it compiled. Its calls take gInterpreterMutex.
class Disposable {
public:
virtual ~Disposable() = default;
/// Declare code, eg #include directives. Return false on error.
virtual bool Declare(const char *code) = 0;
/// Compile and run a line. Return false on error.
virtual bool ProcessLine(const char *line) = 0;
};

typedef int (*AutoLoadCallBack_t)(const char*);
typedef std::vector<std::pair<std::string, int> > FwdDeclArgsToKeepCollection_t;

Expand All @@ -144,6 +163,7 @@ class TInterpreter : public TNamed {
virtual Int_t AutoParse(const char* cls) = 0;
virtual void ClearFileBusy() = 0;
virtual void ClearStack() = 0; // Delete existing temporary values
virtual std::unique_ptr<Disposable> CreateDisposable() { return nullptr; }
virtual Bool_t Declare(const char* code) = 0;
virtual void EndOfLineAction() = 0;
virtual TClass *GetClass(const std::type_info& typeinfo, Bool_t load) const = 0;
Expand Down
1 change: 1 addition & 0 deletions core/metacling/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ROOT_OBJECT_LIBRARY(MetaCling
TClingDataMemberInfo.cxx
TClingDeclInfo.cxx
TClingDiagnostics.cxx
TClingDisposable.cxx
TClingMemberIter.cxx
TClingMethodArgInfo.cxx
TClingMethodInfo.cxx
Expand Down
16 changes: 16 additions & 0 deletions core/metacling/src/TCling.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ clang/LLVM technology.

#include "TClingBaseClassInfo.h"
#include "TClingCallFunc.h"
#include "TClingDisposable.h"
#include "TClingClassInfo.h"
#include "TClingDataMemberInfo.h"
#include "TClingMethodArgInfo.h"
Expand Down Expand Up @@ -1587,6 +1588,9 @@ TCling::TCling(const char *name, const char *title, const char* const argv[], vo
if (!EnvOpt.has_value())
extensions.push_back(std::make_shared<TClingRdictModuleFileExtension>());

fDisposableArgs.assign(interpArgs.begin(), interpArgs.end());
fLlvmResourceDir = llvmResourceDir.Data();

fInterpreter = std::make_unique<cling::Interpreter>(interpArgs.size(),
&(interpArgs[0]),
llvmResourceDir, extensions,
Expand Down Expand Up @@ -3201,6 +3205,18 @@ void TCling::ClearStack()
// No-op for cling due to cling::Value.
}

////////////////////////////////////////////////////////////////////////////////
/// Create a disposable interpreter, with this interpreter's arguments. Return
/// nullptr if it could not be created. See TInterpreter::Disposable.

std::unique_ptr<TInterpreter::Disposable> TCling::CreateDisposable()
{
auto disposable = std::make_unique<TClingDisposable>(fDisposableArgs, fLlvmResourceDir);
if (!disposable->IsValid())
return nullptr;
return disposable;
}

namespace {
// Re-enable JIT symbol library autoloading (independently of class/dictionary
// autoloading) for the lifetime of the object. See its use in TCling::Declare
Expand Down
3 changes: 3 additions & 0 deletions core/metacling/src/TCling.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ class TCling final : public TInterpreter {
Bool_t fCxxModulesEnabled;// True if C++ modules was enabled

std::unique_ptr<cling::Interpreter> fInterpreter; // The interpreter.
std::vector<std::string> fDisposableArgs; // The interpreter's arguments, for CreateDisposable()
std::string fLlvmResourceDir; // The interpreter's resource directory, for CreateDisposable()
std::unique_ptr<cling::MetaProcessor> fMetaProcessor; // The metaprocessor.

std::vector<cling::Value> *fTemporaries; // Stack of temporaries
Expand Down Expand Up @@ -208,6 +210,7 @@ class TCling final : public TInterpreter {
Bool_t IsAutoLoadNamespaceCandidate(const clang::NamespaceDecl* nsDecl);
void ClearFileBusy() final;
void ClearStack() final; // Delete existing temporary values
std::unique_ptr<Disposable> CreateDisposable() final;
Bool_t Declare(const char* code) final;
void EndOfLineAction() final;
TClass *GetClass(const std::type_info& typeinfo, Bool_t load) const final;
Expand Down
82 changes: 82 additions & 0 deletions core/metacling/src/TClingDisposable.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @(#)root/core/metacling:$Id$
// Author: Matevz Tadel 13/10/2026

/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#include "TClingDisposable.h"

#include "TVirtualMutex.h"

#include "cling/Interpreter/Interpreter.h"

#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/PreprocessorOptions.h"

#include <algorithm>
#include <cstring>

////////////////////////////////////////////////////////////////////////////////
/// Create the interpreter with `args`, the arguments of gInterpreter, and cling's
/// `--disposable`. It runs without cling's runtime: it does not print values.
///
/// ROOT's LLVM is built without thread support, so only one interpreter may
/// compile at a time. Creation, every call and destruction take gInterpreterMutex.

TClingDisposable::TClingDisposable(const std::vector<std::string> &args, const std::string &llvmResourceDir)
: fArgs(args)
{
fArgs.push_back("--disposable");
for (const auto &arg : fArgs)
fArgv.push_back(arg.c_str());

R__LOCKGUARD(gInterpreterMutex);
fInterpreter = std::make_unique<cling::Interpreter>(fArgv.size(), fArgv.data(), llvmResourceDir.c_str(),
cling::Interpreter::ModuleFileExtensions{},
/*extraLibHandle=*/nullptr, /*noRuntime=*/true);
if (!fInterpreter->getCI()) {
fInterpreter.reset();
return;
}

// The same settings as TCling's: do not check whether the modules' files exist,
// no spell checking, and modules on or off as in the arguments.
clang::CompilerInstance &CI = *fInterpreter->getCI();
CI.getPreprocessorOpts().DisablePCHOrModuleValidation = clang::DisableValidationForModuleKind::All;
CI.getLangOpts().SpellChecking = false;
CI.getLangOpts().Modules =
std::any_of(fArgv.begin(), fArgv.end(), [](const char *arg) { return !strcmp(arg, "-fmodules"); });
}

////////////////////////////////////////////////////////////////////////////////
/// Destroy the interpreter, and with it all the code it compiled.

TClingDisposable::~TClingDisposable()
{
R__LOCKGUARD(gInterpreterMutex);
fInterpreter.reset();
}

////////////////////////////////////////////////////////////////////////////////
/// Declare code, eg #include directives. Return false on error.

bool TClingDisposable::Declare(const char *code)
{
R__LOCKGUARD(gInterpreterMutex);
return fInterpreter->declare(code) == cling::Interpreter::kSuccess;
}

////////////////////////////////////////////////////////////////////////////////
/// Compile and run a line. Return false on error.

bool TClingDisposable::ProcessLine(const char *line)
{
R__LOCKGUARD(gInterpreterMutex);
return fInterpreter->process(line, /*V=*/nullptr, /*T=*/nullptr, /*disableValuePrinting=*/true) ==
cling::Interpreter::kSuccess;
}
43 changes: 43 additions & 0 deletions core/metacling/src/TClingDisposable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @(#)root/core/metacling:$Id$
// Author: Matevz Tadel 13/10/2026

/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#ifndef ROOT_TClingDisposable
#define ROOT_TClingDisposable

#include "TInterpreter.h"

#include <memory>
#include <string>
#include <vector>

namespace cling {
class Interpreter;
}

/// A cling interpreter created with cling's `--disposable` option, which frees
/// all its memory, compiled code included, when it is destroyed. See
/// TInterpreter::Disposable.
class TClingDisposable final : public TInterpreter::Disposable {
std::vector<std::string> fArgs; ///< cling keeps pointers into the arguments
std::vector<const char *> fArgv; ///< The arguments as passed to cling
std::unique_ptr<cling::Interpreter> fInterpreter; ///< Declared last, so destroyed first

public:
TClingDisposable(const std::vector<std::string> &args, const std::string &llvmResourceDir);
~TClingDisposable() override;

bool IsValid() const { return fInterpreter != nullptr; }

bool Declare(const char *code) final;
bool ProcessLine(const char *line) final;
};

#endif
41 changes: 40 additions & 1 deletion core/metacling/test/TClingTests.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "TClass.h"
#include "TInterpreter.h"
#include "TROOT.h"
#include "TString.h"
#include "TSystem.h"

#include "gmock/gmock.h"
Expand All @@ -11,6 +12,10 @@
#include <string>
#include <vector>

#ifdef __GLIBC__
#include <malloc.h>
#endif

// FIXME: We should probably have such a facility in TCling.
static void cleanup()
{
Expand Down Expand Up @@ -509,4 +514,38 @@ using func0_ret_t = typename ROOT::TypeTraits::CallableTraits<decltype(func0)>::
auto res = gInterpreter->Declare(expression.c_str());
EXPECT_TRUE(res);
}
#endif
#endif
// A disposable interpreter finds ROOT's headers and runs code that calls into loaded libraries.
TEST_F(TClingTests, Disposable)
{
auto disposable = gInterpreter->CreateDisposable();
ASSERT_NE(nullptr, disposable);
EXPECT_TRUE(disposable->Declare("#include \"TNamed.h\""));
int result = 0;
EXPECT_TRUE(disposable->ProcessLine(
TString::Format("{ TNamed n(\"name\", \"title\"); *(int *)%p = n.GetTitle()[0]; }", (void *)&result)));
EXPECT_EQ('t', result);
EXPECT_FALSE(disposable->ProcessLine("TClingTests_undeclared();"));
}

// Destroying a disposable interpreter frees the memory of the code it compiled.
#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 33))
TEST_F(TClingTests, DisposableFreesMemory)
{
int sum = 0;
auto run = [&sum]() {
auto disposable = gInterpreter->CreateDisposable();
for (int i = 0; i < 200; ++i)
disposable->ProcessLine(TString::Format("{ int v%d = %d; *(int *)%p += v%d; }", i, i, (void *)&sum, i));
};
// The first one leaves behind what stays for the process, eg the mapped module files.
run();
const long before = mallinfo2().uordblks;
for (int i = 0; i < 3; ++i)
run();
const long after = mallinfo2().uordblks;
EXPECT_EQ(4 * 19900, sum);
// gInterpreter keeps 5.7 MB for these 600 lines (Linux, Debug build).
EXPECT_LT(after - before, 1024 * 1024);
}
#endif
9 changes: 9 additions & 0 deletions graf3d/eve7/inc/ROOT/REveManager.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <ROOT/RLogger.hxx>
#include <ROOT/RWebDisplayArgs.hxx>

#include "TInterpreter.h"
#include "TSysEvtHandler.h"

#include <thread>
Expand All @@ -28,6 +29,7 @@
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>

class TMap;
class TExMap;
Expand Down Expand Up @@ -157,6 +159,12 @@ protected:
ServerState fServerState;
std::unordered_map<std::string, std::shared_ptr<TMethodCall> > fMethCallMap;

// MIR execution in a disposable interpreter, see WebEve.MIRInterpreter
int fMIRsPerInterpreter{0}; ///< 0: MIRs go through TMethodCall
int fMIRInterpreterCalls{0};
std::unique_ptr<TInterpreter::Disposable> fMIRInterpreter;
std::unordered_set<TClass *> fMIRInterpreterClasses; ///< Classes declared in fMIRInterpreter

Logger fLogger;
REveServerStatus fServerStatus;
bool fIsRCore{false};
Expand All @@ -171,6 +179,7 @@ protected:

void MIRExecThread();
void ExecuteMIR(std::shared_ptr<MIR> mir);
void AssertMIRInterpreter(TClass *cls);

void StreamSceneChangesToJson();
void SendMotionChanges();
Expand Down
Loading