Skip to content

CppInterOp based cppyy migration#22709

Draft
guitargeek wants to merge 45 commits into
root-project:masterfrom
guitargeek:cppyy-interop-migration_jonas
Draft

CppInterOp based cppyy migration#22709
guitargeek wants to merge 45 commits into
root-project:masterfrom
guitargeek:cppyy-interop-migration_jonas

Conversation

@guitargeek

Copy link
Copy Markdown
Contributor

Mirroring #21261 so we can debug remaining failures in parallel.

aaronj0 and others added 30 commits June 25, 2026 20:09
…stream]

Adapts upstream CppInterOp for use with ROOT & Cling:

- Add SynthesizingCodeRAII guard for scope/class reflection
- Call buildLookup in lookup paths, to populate Cling's lazy lookup tables
- Drop class_name:: scope qualification in make_narg_call so virtual
  dispatch works for pure-virtual methods (TInterpreter::Declare)
- Export CppGetProcAddress from libCling's linker script for dispatch mechanism
- GetTypeAsString: revert the PrintingPolicy ctor to
  PrintingPolicy((LangOptions())) instead of deriving it from the
  ASTContext. Using default LangOptions restores the string form
  (std::basic_string<char>) that the CPyCppyy factory expects
Source: compres forks cppyy-backend/master 597bcf4

Drops ROOT's old clingwrapper in favour of the CppInterOp-based implementation from the compres fork.
Replaces gInterpreter/ROOT-meta TCling API calls with CppInterOp via the dispatch mechanism (cppinterop_dispatch.cxx to load API)
Adds recursive_mutex thread safety, switches execution to JitCall. To be followed with ROOT-specific patches.
…patch]

CPPINTEROP_DIR is the include-root that contains the CppInterOp/ subdir
with Dispatch.h and the tablegen-generated CppInterOpAPI.inc. Pass it
straight to Cpp::AddIncludePath instead of concatenating "/include",
matching where the build system actually places those headers.
Based on ROOT:

- Load libCling via gSystem->DynamicPathName instead of the fork's
  CPPINTEROP_DIR/lib path
- include ROOT core headers and call TClass::GetClass in GetScope
  for dictionary/module autoloading.
- Guard GetActualClass and GetBaseOffset with Cpp::IsComplete to
  handle incomplete types (e.g. TCling with no public header)
- Initialise ROOT globals and required dylibs in ApplicationStarter
- Move precommondefs.h include into cpp_cppyy.h; use
  Cpp::TCppFuncAddr_t for proper alignment
- Update CMakeLists for ROOT build-tree integration
Dispatch.h transitively includes the tablegen-generated CppInterOpAPI.inc,
which lives in the build tree, not the source tree. ROOT stages CppInterOp's
public headers + .inc files into ${BINARY_DIR}/etc/cppinterop/CppInterOp/
via the CppInterOpEtc custom target. Point cppyy_backend's include dir
and the runtime CPPINTEROP_DIR macro at that staging dir, and depend on
CppInterOpEtc so staging completes before this library compiles.
Source: compres forks cppyy/master 273ed88

- Calls gCling.EnableAutoLoading()
- Adds cppyy.evaluate / cppyy.macro() / cppyy.ll.as_memoryview
- Numba pointer/reference support
- Metaclass naming fix, isinstance() idiom cleanup
- Bug fixes in basic_string / span / npos pythonizations
- Fallback decoding paths for non-UTF-8 compiler output

To be followed with ROOT-specific patches.
… [upstream]

Same as in Pythonize.cxx basic_string name from GetQualifiedCompleteName
so the NPOS object is registered for the canonical class name shapes.
ROOT-specific patches on top of the compres fork cppyy/master baseline:

__init__.py:
- set gInterpereter to gbl.TInterpreter.Instance()
- load_library(): use gSystem.Load/FindDynamicLibrary, add Windows winmode=0 for search path
- add_library_path(): use gSystem.AddDynamicPath
- Drop the CPPYY_API_PATH apipath_extra dispatcher-headers (ROOT installs CPyCppyy API headers)

_cpython_cppyy.py:
- Wrap `from cppyy_backend import loader` in try/except, fall back to
  c = None (ROOT does not ship cppyy_backend.so)
- Platform: `import libcppyy` vs `import cppyy.libcppyy`
  (standalone cppyy uses the former; ROOT exposes it as cppyy.libcppyy)
- load_reflection_info(): use gSystem.Load

ROOT/_facade.py:
- Store gInterpreter and gPad in __dict__
Source: compres forks CPyCppyy/master 482ccb7
Brings in fork-ahead developments missing from ROOT's old copy and compatibility with the CppInterOp based backend:

- CppInterOp-aligned type system (Cppyy.h)
- Type-based CreateConverter / CreateExecutor factory overloads
- Instance_FromVoidPtr(scope) overload
- PythonGILRAII for exception-safe GIL management
- PyError_t RAII rewrite (unique_ptr-based)
- GetTemplateArgsTypes for type-based template instantiation
- ReduceReturnType/LambdaClass handling
- Rvalue forwarding in Dispatcher
- GetFailureMsg in Converters
- STL string executor returns bound C++ object

ROOT-specific CPyCppyyModule.h and CPyCppyyPyModule.cxx are preserved from master.
…tream]

Source: ROOT master

- Move to using SetGlobalPolicy(ECallFlags, bool) + GlobalPolicyFlags()
- Drop the CallContext* argument from UseStrictOwnership()
- Guard PyMapping_GetOptionalItemString for Python>3.13 in Dispatcher.cxx
Neither on ROOT-master, nor compres-forks

These fields hold C++ type handles, so semantically we should enforce this type
Source: master

- Deprecate __mempolicy__ getter/setter with a clear error pointing
  users to the SetOwnership() pythonization
- Drop the per-method memory policy from mp_call (referenced the
  removed sMemoryPolicy static)
- Use GlobalPolicyFlags() for the Clone heuristic
- Match the method name prefix before '<' only, so template
  arguments don't affect Clone detection
…tream]

Source: ROOT-master 760b6cc

- ToMemory override that lets the array converter copy Python
  strings (not only buffers) into C++ const char* arrays
- ToArrayFromBuffer<> template helper that copies a buffer to an
  array-converter memory address, lifetime management via SetLifeLine
…NVERTER [upstream]

Source: master

Adds the 'override' keyword on HasState() in the macro so the
derived-class definition matches the base-class virtual declaration.
…eam]

Source: ROOT-master

- DEFINE_CALL_POLICY_TOGGLE macro generates SetHeuristicMemoryPolicy,
  SetImplicitSmartPointerConversion, and SetGlobalSignalPolicy used by
  CallContext::SetGlobalPolicy()
- Drop the obsolete kMemoryHeuristics/kMemoryStrict module-level
  labels (no longer referenced the removed CallContext::kUseStrict)
- Add missing guard for Py_INCREF(gThisModule)
… [upstream]

Source: ROOT-master

- VectorIAdd returns self after the insert. Python += reassigns the
  lhs to the returned value, so returning the inserted-iterator
  result silently breaks the idiomatic += pattern on std::vector. Disable by wrapping VectorArray in #if 0
  and removes its Utility::AddToClass call (to be fixed at a later stage)
- Drop Cppyy::gGlobalScope extern, every caller uses Cppyy::GetGlobalScope()
…d [upstream]

This std::span branch went only into the string based overload and not
the new type based one that the compres forks use.
… args [upstream]

GetQualifiedCompleteName returns the canonical class name with default
template args spelled out
…ure mismatches [upstream]

In PyFunction_AsCPointer, the CPPOverload and TemplateProxy branches previously
returned null when the requested signature does not match the candidates,
preventing the generic-Python-callable fall-through (JIT-ed wrapper path),
even though CPPOverload and TemplateProxy are valid PyCallable_Check inputs.
As a result, std::function/fn ptr parameters could not accept a cppyy free
function or template overload whose signature did not match the target.
…[ROOT-patch]

Source: ROOT

- SignalTryCatch.h: gException resolved to definition from libCore.so
- CPPInstance.h: replace CPYCPPYY_IMPORT with explicit extern / __declspec(dllimport) pair. Used by
  libROOTPythonizations which does not include CommonDefs.h for the definition of CPYCPPYY_IMPORT
aaronj0 and others added 14 commits June 25, 2026 20:25
…-patch]

Source: ROOT-master

ROOT runs CPyCppyy and libROOTPythonizations as two shared libs
that under the same libcppyy Python extension module. The real PyInit_libcppyy
lives in libROOTPythonizations and calls CPyCppyy::Init() to start the extension.
…s [ROOT-patch]

Source: ROOT-master

- Register a std::span pythonization that overrides begin() / end()
  with a JIT-compiled __cppyy_internal::ptr_iterator helper.
  libstdc++ (GCC >= 15) implements std::span::iterator via a private
  nested tag type, which CallFunc-generated wrappers cannot name
  without violating access rules — the pointer-based iterator
  sidesteps that
- Add more std::basic_string name variants to the STLWString pythonization
… [ROOT-patch]

Source: ROOT

- __template_args__ read-only property on TemplateProxy for
  introspection of a method's template arguments
- "ss:__overload__" branch in tpp_overload so callers can select an
  overload by both signature and template arguments

Added for ROOT's Numba-introspection support.
…ype iterators [ROOT-patch]

Pythonize.cxx tags begin()-returns as STL iterator types when
Cppyy::GetScope succeeds on the return-type string. CppInterOp's
GetScope returns the underlying class scopes, leading to raw-pointer
iterators (e.g. RVec<T>::iterator = T*) falsely classified as STL
iterator types. Restore the earlier behaviour of skipping pointer
return values by doing a IsPointerType check on the type-handle. ROOT
master's TCling-based GetScope returned null for pointer names and did
not hit this false positive.
5cfc2da changed std::string-returning methods to always produce Python str instead
of a CPPInstance proxy (gbl.std.string). Update test18_operator_plus_overloads
and test34_cstring_template_argument asserts expecting gbl.std.string
…OOT-patch]

_generic.pythonize_generic skips the pretty-printer for std::string
because ToString returns a quoted ""x"" form, and CPyCppyy already
pythonizes std::string with the unquoted shape. With CppInterOp,
klass.__cpp_name__ is the canonical fully-qualified template form,
so the typedef "std::string" no longer matched the exclude check
The `gPad and `gVirtualX` identifiers are injected into ROOT meta via
`TGlobalMappedFunction::MakeFunctor()`. However, in the ROOT Python
interface we don't want to rely on ROOT meta but use Cling or Clang-Repl
directly. Hence, we inject these identifiers manually into the facade.

The `TDirectoryPythonAdapter` previously used for `gDirectory` mirrored
the live-tracking semantics of the C++ macros: every attribute access
re-resolves to the current directory. The same semantics are needed
for `gPad` and `gVirtualX`, which on the C++ side are also preprocessor
macros that expand to a static accessor call. Therefore, a new
`LiveProxy` is introduced for this.
@guitargeek guitargeek self-assigned this Jun 25, 2026
When a derived class pulls a base method into its own overload set with
`using Base::method;`, the method is still declared in the base. If that
base is not the first one, its subobject sits at a non-zero offset in the
derived object.

`CPPMethod::Call` computed the this-pointer offset against `fScope` (the class
the method is bound on, i.e. the derived class) rather than the method's
declaring base. For a using-declared method these differ, yielding a zero
offset. The generated wrapper, however, casts 'this' to the declaring base,
so the call wrote through an unadjusted pointer, corrupting memory and
crashing on destruction.

Compute the offset against the method's declaring scope
(`Cppyy::GetParentScope`) instead. This is a no-op for ordinary inherited
methods, which are bound on their declaring base already.

Surfaced by `roottest-python-cpp-cpp` (`TGraphMultiErrors::SetLineColor`, where
`TAttLine` is a secondary base).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants