diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h index fe782d13421ec..2be2a78c89174 100644 --- a/core/meta/inc/TInterpreter.h +++ b/core/meta/inc/TInterpreter.h @@ -29,6 +29,7 @@ #include "TVirtualRWMutex.h" #include +#include #include #include #include @@ -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 > FwdDeclArgsToKeepCollection_t; @@ -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 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; diff --git a/core/metacling/src/CMakeLists.txt b/core/metacling/src/CMakeLists.txt index f7d91833b7e34..2d59287e9fe73 100644 --- a/core/metacling/src/CMakeLists.txt +++ b/core/metacling/src/CMakeLists.txt @@ -28,6 +28,7 @@ ROOT_OBJECT_LIBRARY(MetaCling TClingDataMemberInfo.cxx TClingDeclInfo.cxx TClingDiagnostics.cxx + TClingDisposable.cxx TClingMemberIter.cxx TClingMethodArgInfo.cxx TClingMethodInfo.cxx diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 022cc1ddba473..e8e8fe53ab27e 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -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" @@ -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()); + fDisposableArgs.assign(interpArgs.begin(), interpArgs.end()); + fLlvmResourceDir = llvmResourceDir.Data(); + fInterpreter = std::make_unique(interpArgs.size(), &(interpArgs[0]), llvmResourceDir, extensions, @@ -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 TCling::CreateDisposable() +{ + auto disposable = std::make_unique(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 diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h index 1c2be1bace445..8ecc65789aeab 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -130,6 +130,8 @@ class TCling final : public TInterpreter { Bool_t fCxxModulesEnabled;// True if C++ modules was enabled std::unique_ptr fInterpreter; // The interpreter. + std::vector fDisposableArgs; // The interpreter's arguments, for CreateDisposable() + std::string fLlvmResourceDir; // The interpreter's resource directory, for CreateDisposable() std::unique_ptr fMetaProcessor; // The metaprocessor. std::vector *fTemporaries; // Stack of temporaries @@ -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 CreateDisposable() final; Bool_t Declare(const char* code) final; void EndOfLineAction() final; TClass *GetClass(const std::type_info& typeinfo, Bool_t load) const final; diff --git a/core/metacling/src/TClingDisposable.cxx b/core/metacling/src/TClingDisposable.cxx new file mode 100644 index 0000000000000..877d285315ea6 --- /dev/null +++ b/core/metacling/src/TClingDisposable.cxx @@ -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 +#include + +//////////////////////////////////////////////////////////////////////////////// +/// 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 &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(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; +} diff --git a/core/metacling/src/TClingDisposable.h b/core/metacling/src/TClingDisposable.h new file mode 100644 index 0000000000000..a3208592d4680 --- /dev/null +++ b/core/metacling/src/TClingDisposable.h @@ -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 +#include +#include + +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 fArgs; ///< cling keeps pointers into the arguments + std::vector fArgv; ///< The arguments as passed to cling + std::unique_ptr fInterpreter; ///< Declared last, so destroyed first + +public: + TClingDisposable(const std::vector &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 diff --git a/core/metacling/test/TClingTests.cxx b/core/metacling/test/TClingTests.cxx index 08f846ee5f256..e70fe81af588a 100644 --- a/core/metacling/test/TClingTests.cxx +++ b/core/metacling/test/TClingTests.cxx @@ -3,6 +3,7 @@ #include "TClass.h" #include "TInterpreter.h" #include "TROOT.h" +#include "TString.h" #include "TSystem.h" #include "gmock/gmock.h" @@ -11,6 +12,10 @@ #include #include +#ifdef __GLIBC__ +#include +#endif + // FIXME: We should probably have such a facility in TCling. static void cleanup() { @@ -509,4 +514,38 @@ using func0_ret_t = typename ROOT::TypeTraits::CallableTraits:: auto res = gInterpreter->Declare(expression.c_str()); EXPECT_TRUE(res); } -#endif \ No newline at end of file +#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 diff --git a/graf3d/eve7/inc/ROOT/REveManager.hxx b/graf3d/eve7/inc/ROOT/REveManager.hxx index 82e6e547440fa..454b1ff1d6016 100644 --- a/graf3d/eve7/inc/ROOT/REveManager.hxx +++ b/graf3d/eve7/inc/ROOT/REveManager.hxx @@ -17,6 +17,7 @@ #include #include +#include "TInterpreter.h" #include "TSysEvtHandler.h" #include @@ -28,6 +29,7 @@ #include #include #include +#include class TMap; class TExMap; @@ -157,6 +159,12 @@ protected: ServerState fServerState; std::unordered_map > fMethCallMap; + // MIR execution in a disposable interpreter, see WebEve.MIRInterpreter + int fMIRsPerInterpreter{0}; ///< 0: MIRs go through TMethodCall + int fMIRInterpreterCalls{0}; + std::unique_ptr fMIRInterpreter; + std::unordered_set fMIRInterpreterClasses; ///< Classes declared in fMIRInterpreter + Logger fLogger; REveServerStatus fServerStatus; bool fIsRCore{false}; @@ -171,6 +179,7 @@ protected: void MIRExecThread(); void ExecuteMIR(std::shared_ptr mir); + void AssertMIRInterpreter(TClass *cls); void StreamSceneChangesToJson(); void SendMotionChanges(); diff --git a/graf3d/eve7/src/REveManager.cxx b/graf3d/eve7/src/REveManager.cxx index 913959309f8b3..93bbb7c5568a2 100644 --- a/graf3d/eve7/src/REveManager.cxx +++ b/graf3d/eve7/src/REveManager.cxx @@ -33,6 +33,7 @@ #include "TMap.h" #include "TExMap.h" #include "TEnv.h" +#include "TString.h" #include "TError.h" #include "TColor.h" #include "TPRegexp.h" @@ -48,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +116,8 @@ WebEve.DisableShow: 1 # do not start new web browser when REveManager::Show i WebEve.HTimeout: 200 # timeout in ms for elements highlight WebEve.DblClick: Off # mouse double click handling in GL viewer: Off or Reset WebEve.TableRowHeight: 33 # size of each row in pixels in the Table view, can be used to make design more compact +WebEve.MIRInterpreter: TMethodCall # how MIRs are executed: TMethodCall, or Disposable to compile them in a disposable interpreter +WebEve.MIRsPerInterpreter: 1000 # with Disposable, the number of MIRs after which the interpreter is replaced */ //////////////////////////////////////////////////////////////////////////////// @@ -214,6 +218,9 @@ REveManager::REveManager() if (strcmp(gl_viewer, "RCore") == 0) fIsRCore = true; + if (strcmp(gEnv->GetValue("WebEve.MIRInterpreter", "TMethodCall"), "Disposable") == 0) + fMIRsPerInterpreter = std::max(1, gEnv->GetValue("WebEve.MIRsPerInterpreter", 1000)); + // this is call-back, invoked when message received via websocket fWebWindow->SetCallBacks([this](unsigned connid) { WindowConnect(connid); }, [this](unsigned connid, const std::string &arg) { WindowData(connid, arg); }, @@ -1099,37 +1106,47 @@ void REveManager::ExecuteMIR(std::shared_ptr mir) tag = mir->fCtype + "::" + m.str(1); std::shared_ptr mc; - auto mmi = fMethCallMap.find(tag); - if (mmi != fMethCallMap.end()) + if (fMIRsPerInterpreter > 0) { - mc = mmi->second; + AssertMIRInterpreter(call_cls); } else { - const TMethod *meth = call_cls->GetMethodAllAny(m.str(1).c_str()); - if ( ! meth) - throw eh + "Can not find TMethod matching '" + m.str(1) + "'."; - mc = std::make_shared(meth); - fMethCallMap.insert(std::make_pair(tag, mc)); + auto mmi = fMethCallMap.find(tag); + if (mmi != fMethCallMap.end()) + { + mc = mmi->second; + } + else + { + const TMethod *meth = call_cls->GetMethodAllAny(m.str(1).c_str()); + if ( ! meth) + throw eh + "Can not find TMethod matching '" + m.str(1) + "'."; + mc = std::make_shared(meth); + fMethCallMap.insert(std::make_pair(tag, mc)); + } } REveElement::SetMirContext(el); - R__LOCKGUARD_CLING(gInterpreterMutex); - mc->Execute(el_casted, m.str(2).c_str()); + if (mc) + { + R__LOCKGUARD_CLING(gInterpreterMutex); + mc->Execute(el_casted, m.str(2).c_str()); + } + else + { + // Compiled code, so arguments of any type TMethodCall can not handle work, too. + std::string line = TString::Format("((%s *)%p)->%s(%s);", mir->fCtype.c_str(), el_casted, + m.str(1).c_str(), m.str(2).c_str()).Data(); + if ( ! fMIRInterpreter->ProcessLine(line.c_str())) + R__LOG_ERROR(REveLog()) << eh << "error compiling " << tag << ": '" << line << "'."; + } if (REveElement::stlMirError) { R__LOG_ERROR(REveLog()) << eh << "error executing " << tag << ":" << REveElement::stlMirErrorString << " (code " << REveElement::stlMirError << ")."; } REveElement::ClearMirContext(); - - // Alternative implementation through Cling. "Leaks" 200 kB per call. - // This might be needed for function calls that involve data-types TMethodCall - // can not handle. - // std::stringstream cmd; - // cmd << "((" << mir->fCtype << "*)" << std::hex << std::showbase << (size_t)el << ")->" << mir->fCmd << ";"; - // std::cout << cmd.str() << std::endl; - // gROOT->ProcessLine(cmd.str().c_str()); } catch (std::exception &e) { R__LOG_ERROR(REveLog()) << "caught exception executing " << tag << ": " << e.what(); } catch (...) { @@ -1137,6 +1154,32 @@ void REveManager::ExecuteMIR(std::shared_ptr mir) } } +//////////////////////////////////////////////////////////////////////////////// +/// Make sure the disposable interpreter for MIRs exists and has the declaration of +/// class `cls`. The interpreter is replaced every fMIRsPerInterpreter MIRs: destroying +/// it frees the memory of the code compiled for the MIRs it ran. + +void REveManager::AssertMIRInterpreter(TClass *cls) +{ + static const REveException eh(""); + + if (!fMIRInterpreter || fMIRInterpreterCalls >= fMIRsPerInterpreter) { + fMIRInterpreter.reset(); + fMIRInterpreterClasses.clear(); + fMIRInterpreterCalls = 0; + fMIRInterpreter = gInterpreter->CreateDisposable(); + if (!fMIRInterpreter) + throw eh + "Can not create a disposable interpreter for MIRs."; + } + if (fMIRInterpreterClasses.count(cls) == 0) { + const char *header = cls->GetDeclFileName(); + if (!header || !*header || !fMIRInterpreter->Declare(TString::Format("#include \"%s\"", header))) + throw eh + "Can not declare class '" + cls->GetName() + "' for MIRs."; + fMIRInterpreterClasses.insert(cls); + } + ++fMIRInterpreterCalls; +} + // Write scene change into scenes's internal json member void REveManager::StreamSceneChangesToJson() { diff --git a/interpreter/cling/include/cling/Interpreter/ClingOptions.td b/interpreter/cling/include/cling/Interpreter/ClingOptions.td index 69b34aba2dde8..21a1053232d6a 100644 --- a/interpreter/cling/include/cling/Interpreter/ClingOptions.td +++ b/interpreter/cling/include/cling/Interpreter/ClingOptions.td @@ -15,6 +15,8 @@ include "llvm/Option/OptParser.td" def _debugFlags_EQ : Joined<["--"], "debug-only=">; def _debugFlags : Flag<["--"], "debug-only">; #endif +def _disposable : Flag<["--"], "disposable">, + HelpText<"Free all memory of the interpreter, its compiled code included, when it is destroyed">; def _errorout : Flag<["--"], "errorout">, HelpText<"Do not recover from input errors">; // Re-implement to forward to our help def help : Flag<["-", "--"], "help">, HelpText<"Print this help text">; diff --git a/interpreter/cling/include/cling/Interpreter/InvocationOptions.h b/interpreter/cling/include/cling/Interpreter/InvocationOptions.h index e92c7aab5e1ad..226d876ab7139 100644 --- a/interpreter/cling/include/cling/Interpreter/InvocationOptions.h +++ b/interpreter/cling/include/cling/Interpreter/InvocationOptions.h @@ -97,6 +97,7 @@ namespace cling { unsigned ShowVersion : 1; unsigned Help : 1; unsigned NoRuntime : 1; + unsigned Disposable : 1; /// Free all memory on destruction unsigned PtrCheck : 1; /// Enable NullDerefProtectionTransformer bool Verbose() const { return CompilerOpts.Verbose; } diff --git a/interpreter/cling/lib/Interpreter/IncrementalExecutor.cpp b/interpreter/cling/lib/Interpreter/IncrementalExecutor.cpp index 8c184cb352d2b..4bd89ee0de09f 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalExecutor.cpp +++ b/interpreter/cling/lib/Interpreter/IncrementalExecutor.cpp @@ -36,7 +36,8 @@ namespace cling { IncrementalExecutor::IncrementalExecutor(clang::DiagnosticsEngine& /*diags*/, const clang::CompilerInstance& CI, - void *ExtraLibHandle, bool Verbose): + void *ExtraLibHandle, bool Verbose, + bool ReleaseMemory): m_Callbacks(nullptr) #if 0 : m_Diags(diags) @@ -50,7 +51,7 @@ IncrementalExecutor::IncrementalExecutor(clang::DiagnosticsEngine& /*diags*/, llvm::Error Err = llvm::Error::success(); auto EPC = llvm::cantFail(llvm::orc::SelfExecutorProcessControl::Create()); m_JIT.reset(new IncrementalJIT(*this, CI, std::move(EPC), Err, - ExtraLibHandle, Verbose)); + ExtraLibHandle, Verbose, ReleaseMemory)); if (Err) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Fatal: "); llvm_unreachable("Propagate this error and exit gracefully"); diff --git a/interpreter/cling/lib/Interpreter/IncrementalExecutor.h b/interpreter/cling/lib/Interpreter/IncrementalExecutor.h index eeb89f2eabdd1..942a3d44641a1 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalExecutor.h +++ b/interpreter/cling/lib/Interpreter/IncrementalExecutor.h @@ -143,7 +143,8 @@ namespace cling { IncrementalExecutor(clang::DiagnosticsEngine& diags, const clang::CompilerInstance& CI, void *ExtraLibHandle = nullptr, - bool Verbose = false); + bool Verbose = false, + bool ReleaseMemory = false); ~IncrementalExecutor(); diff --git a/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp b/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp index 2be6ea97c7d6e..33462eeb453fc 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp +++ b/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp @@ -114,7 +114,11 @@ namespace { } class ClingMMapper final : public SectionMemoryManager::MemoryMapper { + bool m_Release; + public: + explicit ClingMMapper(bool Release) : m_Release(Release) {} + sys::MemoryBlock allocateMappedMemory(SectionMemoryManager::AllocationPurpose Purpose, size_t NumBytes, @@ -129,14 +133,13 @@ namespace { } std::error_code releaseMappedMemory(sys::MemoryBlock& M) override { - // Disabled until CallFunc is informed about unloading, and can - // re-generate the wrapper (if the decl is still available). See + if (m_Release) + return sys::Memory::releaseMappedMemory(M); + // Disabled, except for disposable interpreters, until CallFunc is + // informed about unloading, and can re-generate the wrapper (if the decl + // is still available). See // https://github.com/root-project/root/issues/10898 -#if 0 - return sys::Memory::releaseMappedMemory(M); -#else return {}; -#endif } }; @@ -263,15 +266,25 @@ namespace { bool needsToReserveAllocationSpace() override { return true; } }; - /// A JITLinkMemoryManager for Cling that never frees its allocations. + /// A JITLinkMemoryManager for Cling that frees its allocations only for a + /// disposable interpreter. class ClingJITLinkMemoryManager : public InProcessMemoryManager { + bool m_Release; + public: - using InProcessMemoryManager::InProcessMemoryManager; + ClingJITLinkMemoryManager(uint64_t PageSize, bool Release) + : InProcessMemoryManager(PageSize), m_Release(Release) {} void deallocate(std::vector Allocs, OnDeallocatedFunction OnDeallocated) override { - // Disabled until CallFunc is informed about unloading, and can - // re-generate the wrapper (if the decl is still available). See + if (m_Release) { + InProcessMemoryManager::deallocate(std::move(Allocs), + std::move(OnDeallocated)); + return; + } + // Disabled, except for disposable interpreters, until CallFunc is + // informed about unloading, and can re-generate the wrapper (if the decl + // is still available). See // https://github.com/root-project/root/issues/10898 // We still have to release the allocations which resets their addresses @@ -557,7 +570,7 @@ IncrementalJIT::~IncrementalJIT() { IncrementalJIT::IncrementalJIT( IncrementalExecutor& Executor, const clang::CompilerInstance &CI, std::unique_ptr EPC, Error& Err, - void *ExtraLibHandle, bool Verbose) + void *ExtraLibHandle, bool Verbose, bool ReleaseMemory) : SkipHostProcessLookup(false), m_JITLink(UseJITLink(CI.getTarget().getTriple())), m_TM(CreateTargetMachine(CI, m_JITLink)), @@ -594,11 +607,12 @@ IncrementalJIT::IncrementalJIT( // together which is needed for exception handling support. unsigned PageSize = cantFail(sys::Process::getPageSize()); auto ObjLinkingLayer = std::make_unique( - ES, std::make_unique(PageSize)); + ES, + std::make_unique(PageSize, ReleaseMemory)); return ObjLinkingLayer; } - auto MMapper = std::make_unique(); + auto MMapper = std::make_unique(ReleaseMemory); auto GetMemMgr = [MMapper = std::move(MMapper)](const llvm::MemoryBuffer &) { return std::make_unique(*MMapper); }; diff --git a/interpreter/cling/lib/Interpreter/IncrementalJIT.h b/interpreter/cling/lib/Interpreter/IncrementalJIT.h index 6fefecb2809f9..c4ce4ee1a8e98 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalJIT.h +++ b/interpreter/cling/lib/Interpreter/IncrementalJIT.h @@ -60,7 +60,8 @@ class IncrementalJIT { IncrementalJIT(IncrementalExecutor& Executor, const clang::CompilerInstance &CI, std::unique_ptr EPC, - llvm::Error &Err, void *ExtraLibHandle, bool Verbose); + llvm::Error &Err, void *ExtraLibHandle, bool Verbose, + bool ReleaseMemory = false); ~IncrementalJIT(); diff --git a/interpreter/cling/lib/Interpreter/Interpreter.cpp b/interpreter/cling/lib/Interpreter/Interpreter.cpp index 2fc17556f4dc3..722395e293dd7 100644 --- a/interpreter/cling/lib/Interpreter/Interpreter.cpp +++ b/interpreter/cling/lib/Interpreter/Interpreter.cpp @@ -268,9 +268,15 @@ namespace cling { Sema& SemaRef = getSema(); + // A disposable interpreter frees its AST and its compiled code when it is + // destroyed. Other interpreters keep both until the process ends. + if (m_Opts.Disposable) + getCI()->getFrontendOpts().DisableFree = false; + if (!isInSyntaxOnlyMode() && !m_Opts.CompilerOpts.CUDADevice) { m_Executor.reset(new IncrementalExecutor(SemaRef.Diags, *getCI(), - extraLibHandle, m_Opts.Verbose())); + extraLibHandle, m_Opts.Verbose(), + m_Opts.Disposable)); if (!m_Executor) return; diff --git a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp index 0281592abdab3..52364d8f5851e 100644 --- a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp +++ b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp @@ -72,7 +72,7 @@ namespace cling { /// \brief wraps an ExternalASTSource in an ExternalSemaSource. No functional /// difference between the original source and this wrapper intended. class ExternalASTSourceWrapper : public ExternalSemaSource { - ExternalASTSource* m_Source; + llvm::IntrusiveRefCntPtr m_Source; public: ExternalASTSourceWrapper(ExternalASTSource* Source) : m_Source(Source) { @@ -284,11 +284,6 @@ namespace cling { // If we already have source, we need to create a multiplexer with the // existing source. if (ExistingSource) { - // Make sure the context is not deleting the existing source. - // FIXME: We should delete this, but looking at the other TODO's in - // the destructor we can't easily free our callbacks from here... - Ctx.ExternalSource.resetWithoutRelease(); - // Wrap the existing source in a wrapper so that it becomes an // external sema source. This way we can use the existing multiplexer // for this. diff --git a/interpreter/cling/lib/Interpreter/InvocationOptions.cpp b/interpreter/cling/lib/Interpreter/InvocationOptions.cpp index 2acf8a1b5e40b..650a2e1060529 100644 --- a/interpreter/cling/lib/Interpreter/InvocationOptions.cpp +++ b/interpreter/cling/lib/Interpreter/InvocationOptions.cpp @@ -96,6 +96,7 @@ static const char kNoStdInc[] = "-nostdinc"; Opts.ShowVersion = Args.hasArg(OPT_version); Opts.Help = Args.hasArg(OPT_help); Opts.NoRuntime = Args.hasArg(OPT_noruntime); + Opts.Disposable = Args.hasArg(OPT__disposable); Opts.PtrCheck = Args.hasArg(OPT__ptrcheck); if (Arg* MetaStringArg = Args.getLastArg(OPT__metastr, OPT__metastr_EQ)) { Opts.MetaString = MetaStringArg->getValue(); @@ -212,7 +213,7 @@ bool CompilerOptions::DefaultLanguage(const LangOptions* LangOpts) const { InvocationOptions::InvocationOptions(int argc, const char* const* argv) : MetaString("."), ErrorOut(false), NoLogo(false), ShowVersion(false), - Help(false), NoRuntime(false) { + Help(false), NoRuntime(false), Disposable(false) { ArrayRef ArgStrings(argv, argv + argc); unsigned MissingArgIndex, MissingArgCount;