From 1fea5f11e0b7701c4806ac9f742229653d6b1770 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 28 Jul 2026 06:20:53 +0000 Subject: [PATCH] [core] Fix Declare failing to autoload symbols for static initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TInterpreter::Declare of a global whose static initializer needs symbols from a not-yet-loaded library (e.g. TVectorT from libMatrix) failed with "Failed to materialize symbols". It worked in 6.30 but regressed in 6.32. Declare wraps parsing in a SuspendAutoLoadingRAII to behave like a plain compiler; that is a parse-time, class/dictionary-autoloading concern. But commit e601bf80321 ("Reduce symbol search only to when autoloading is enabled", first released in 6.32) gated the AutoloadLibraryGenerator ORC definition generator on IsAutoLoadingEnabled() for performance, which inadvertently coupled the two: during Declare the global's static initializer runs, the JIT needs the constructor symbol from libMatrix, and the generator bailed out because class autoloading was suspended. The generator's gate cannot be relaxed for all Static lookups: a passive address query such as TGlobal::GetAddress() also issues a Static lookup, and it must keep returning "not loaded" while autoloading is suspended (see the gGeoManager check in TClingDataMemberInfo.Offset). The distinguishing factor is that the issue's suspension is Declare-internal, not a user request. Therefore let Declare opt back into JIT symbol autoloading for exactly the window where it emits and runs code, via a dedicated TClingCallbacks::IsAutoLoadingForJITSymbols() flag the generator also honors. Passive lookups outside Declare are unaffected, so the gGeoManager semantics are preserved. Closes #16601. 🤖 Done with the help of AI. --- core/metacling/src/TCling.cxx | 30 ++++++++++++++++++++++++++ core/metacling/src/TClingCallbacks.cxx | 9 +++++++- core/metacling/src/TClingCallbacks.h | 7 ++++++ core/metacling/test/TClingTests.cxx | 15 +++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 904b85242bc9d..998e29cec2d64 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -3191,6 +3191,30 @@ void TCling::ClearStack() // No-op for cling due to cling::Value. } +namespace { + // Re-enable JIT symbol library autoloading (independently of class/dictionary + // autoloading) for the lifetime of the object. See its use in TCling::Declare + // and #16601. + class EnableAutoLoadingForJITSymbolsRAII { + TClingCallbacks *fCallbacks; + bool fOldValue = false; + + public: + EnableAutoLoadingForJITSymbolsRAII(TClingCallbacks *callbacks) : fCallbacks(callbacks) + { + if (fCallbacks) { + fOldValue = fCallbacks->IsAutoLoadingForJITSymbols(); + fCallbacks->SetAutoLoadingForJITSymbols(true); + } + } + ~EnableAutoLoadingForJITSymbolsRAII() + { + if (fCallbacks) + fCallbacks->SetAutoLoadingForJITSymbols(fOldValue); + } + }; +} + //////////////////////////////////////////////////////////////////////////////// /// Declare code to the interpreter, without any of the interpreter actions /// that could trigger a re-interpretation of the code. I.e. make cling @@ -3206,6 +3230,12 @@ bool TCling::Declare(const char* code) SuspendAutoLoadingRAII autoLoadOff(this); SuspendAutoParsing autoParseRaii(this); + // The suspensions above make parsing behave like a plain compiler, but they also + // gate the JIT's library autoloading - and a declared global's static initializer + // may still need symbols from a not-yet-loaded library (e.g. TVectorT from + // libMatrix). Re-allow autoloading for such genuine JIT symbol resolution. #16601 + EnableAutoLoadingForJITSymbolsRAII autoLoadJITOn(fClingCallbacks); + bool oldDynLookup = fInterpreter->isDynamicLookupEnabled(); fInterpreter->enableDynamicLookup(false); bool oldRawInput = fInterpreter->isRawInputEnabled(); diff --git a/core/metacling/src/TClingCallbacks.cxx b/core/metacling/src/TClingCallbacks.cxx index 771f19fe9feb3..0b4ae9c873aa1 100644 --- a/core/metacling/src/TClingCallbacks.cxx +++ b/core/metacling/src/TClingCallbacks.cxx @@ -97,7 +97,14 @@ class AutoloadLibraryGenerator : public llvm::orc::DefinitionGenerator { llvm::orc::JITDylibLookupFlags JDLookupFlags, const llvm::orc::SymbolLookupSet &Symbols) override { - if (!fCallbacks.IsAutoLoadingEnabled()) + // The IsAutoLoadingEnabled() gate keeps speculative lookups (e.g. querying + // the address of a global that is not loaded) from triggering a costly scan + // of all libraries. But that gate is also flipped off by the class-autoloading + // suspension in TCling::Declare, which merely wants parsing to behave like a + // plain compiler - it must not stop us from autoloading a library whose + // symbols the emitted static-initializer code genuinely needs to run. Declare + // signals that case via IsAutoLoadingForJITSymbols(). See #16601. + if (!fCallbacks.IsAutoLoadingEnabled() && !fCallbacks.IsAutoLoadingForJITSymbols()) return llvm::Error::success(); // If we get here, the symbols have not been found in the current process, diff --git a/core/metacling/src/TClingCallbacks.h b/core/metacling/src/TClingCallbacks.h index f729b266ae4d4..5a81d817cfa76 100644 --- a/core/metacling/src/TClingCallbacks.h +++ b/core/metacling/src/TClingCallbacks.h @@ -46,6 +46,7 @@ class TClingCallbacks : public cling::InterpreterCallbacks { bool fIsAutoLoading = false; bool fIsAutoLoadingRecursively = false; bool fIsAutoParsingSuspended = false; + bool fIsAutoLoadingForJITSymbols = false; bool fIsCodeGening = false; bool fIsLoadingModule = false; llvm::DenseMap m_LoadedModuleFiles; @@ -63,6 +64,12 @@ class TClingCallbacks : public cling::InterpreterCallbacks { void SetAutoParsingSuspended(bool val = true) { fIsAutoParsingSuspended = val; } bool IsAutoParsingSuspended() { return fIsAutoParsingSuspended; } + // Whether the JIT is allowed to autoload a library to resolve a symbol it + // genuinely needs to link and run emitted code, even while class/dictionary + // autoloading is suspended (as TCling::Declare does while parsing). See #16601. + void SetAutoLoadingForJITSymbols(bool val = true) { fIsAutoLoadingForJITSymbols = val; } + bool IsAutoLoadingForJITSymbols() const { return fIsAutoLoadingForJITSymbols; } + bool LibraryLoadingFailed(const std::string &, const std::string &, bool, bool) override; void InclusionDirective(clang::SourceLocation /*HashLoc*/, const clang::Token & /*IncludeTok*/, diff --git a/core/metacling/test/TClingTests.cxx b/core/metacling/test/TClingTests.cxx index 0777ab12d6093..2a54d12864dd3 100644 --- a/core/metacling/test/TClingTests.cxx +++ b/core/metacling/test/TClingTests.cxx @@ -436,6 +436,21 @@ TEST_F(TClingTests, UndeclaredIdentifierCrash) gInterpreter->ProcessLine("for(i=0; i < 0;); // the second usage of `i` was enough to get a segfault"); } +// https://github.com/root-project/root/issues/16601 +// Declaring a global whose static initializer needs symbols from a library that +// is not loaded yet (here libMatrix, via TVectorT) must still autoload that +// library: TInterpreter::Declare suspends *class* autoloading while parsing, but +// this must not prevent the JIT from materializing symbols that the emitted +// static-initializer code genuinely requires to run. +TEST_F(TClingTests, DeclareAutoloadsSymbolsForStaticInit) +{ + ASSERT_FALSE(gInterpreter->IsLoaded("libMatrix")); + EXPECT_TRUE(gInterpreter->Declare("#include \n" + "const auto gROOT16601vec = TVectorT(3);")); + // The variable must exist and be usable, i.e. its constructor ran. + EXPECT_EQ(3L, gInterpreter->ProcessLine("gROOT16601vec.GetNrows();")); +} + // https://github.com/root-project/root/issues/15818 #if !defined(_MSC_VER) || defined(R__ENABLE_BROKEN_WIN_TESTS) TEST_F(TClingTests, VeryLongExpression)