diff --git a/bindings/pyroot/CMakeLists.txt b/bindings/pyroot/CMakeLists.txt index a3bf1eec786e8..1575158b2a054 100644 --- a/bindings/pyroot/CMakeLists.txt +++ b/bindings/pyroot/CMakeLists.txt @@ -4,5 +4,11 @@ # For the licensing terms see $ROOTSYS/LICENSE. # For the list of contributors see $ROOTSYS/README/CREDITS. -add_subdirectory(cppyy) +# The vendored cppjit tree builds through ROOT-owned CMake files inside it +# (src/, python/, test/); upstream's own top-level CMakeLists.txt is vendored +# but unreferenced. +add_subdirectory(cppjit/src) +add_subdirectory(cppjit/python) +ROOT_ADD_TEST_SUBDIRECTORY(cppjit/test) + add_subdirectory(pythonizations) diff --git a/bindings/pyroot/cppjit/.clang-format b/bindings/pyroot/cppjit/.clang-format new file mode 100644 index 0000000000000..7188d73fcc862 --- /dev/null +++ b/bindings/pyroot/cppjit/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: LLVM + +Language: Cpp +Standard: c++17 +PointerAlignment: Left + +IncludeCategories: + - Regex: '^"[^/]+\"' + Priority: 10 + - Regex: '^"clang/' + Priority: 30 + - Regex: '^"llvm/' + Priority: 40 + - Regex: '^<' + Priority: 50 diff --git a/bindings/pyroot/cppjit/.gitignore b/bindings/pyroot/cppjit/.gitignore new file mode 100644 index 0000000000000..65e307ced6b02 --- /dev/null +++ b/bindings/pyroot/cppjit/.gitignore @@ -0,0 +1,33 @@ +# Byte-compiled +__pycache__/ +*.py[cod] + +# Built test dictionaries and extension modules +*.so +*.so.*.tmp +*Dict.lock + +# Packaging +build/ +dist/ +*.egg-info/ +wheelhouse/ + +# Test and coverage artifacts +.pytest_cache/ +.coverage +.coverage.* +coverage.xml +htmlcov/ + +# Environments and local config +.venv/ +venv/ +.env +.envrc +.python-version + +# Editors / OS +.idea/ +.vscode/ +.DS_Store diff --git a/bindings/pyroot/cppjit/CMakeLists.txt b/bindings/pyroot/cppjit/CMakeLists.txt new file mode 100644 index 0000000000000..79828f6e307c8 --- /dev/null +++ b/bindings/pyroot/cppjit/CMakeLists.txt @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 3.20) +project(CppJIT LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(GNUInstallDirs) + +# This option won't make a lot of sense since we only ship the shared library in site-packages +# Perhaps this should permanently be OFF and users can build their own CppInterOp if they want to run the tests? +option(CPPJIT_ENABLE_CPPINTEROP_TESTS "enable CppInterOp tests" OFF) +set(CPPINTEROP_GIT_REPOSITORY "https://github.com/compiler-research/CppInterOp.git" CACHE STRING "") +set(CPPINTEROP_GIT_TAG "c8126249dd18363000b89333b34313ca2a83fb82" CACHE STRING "") +set(CPPINTEROP_SOURCE_DIR "" CACHE PATH + "Override default CppInterOp built by ExternalProject_Add, with a path to local CppInterOp source") + +# The full Development component requires libpython, which manylinux +# images do not ship and extension modules do not need. +set(Python_FIND_VIRTUALENV ONLY) +find_package(Python COMPONENTS Interpreter Development.Module) +if(NOT Python_FOUND) + set(Python_FIND_VIRTUALENV STANDARD) + find_package(Python COMPONENTS Interpreter Development.Module) +endif() +if(NOT Python_Development.Module_FOUND) + message(FATAL_ERROR "Python development headers not found") +endif() + +# The LLVM range the pinned CppInterOp supports; update together with the tag. +set(CPPJIT_LLVM_VERSION_MIN 20) +set(CPPJIT_LLVM_VERSION_MAX 22) + +# Runtime fallback anchor for the dispatch (see cppinterop_paths()): the +# installed package under scikit-build-core, the build tree otherwise -- +# a plain `cmake --build` yields a runnable tree under /python. +if(SKBUILD) + execute_process( + COMMAND ${Python_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_path('platlib'))" + OUTPUT_VARIABLE _python_platlib + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(CPPINTEROP_INSTALL_PREFIX "${_python_platlib}/cppjit") +else() + set(CPPINTEROP_INSTALL_PREFIX "${CMAKE_BINARY_DIR}") +endif() + +# CppInterOp stages into the build tree in the runtime layout; the wheel +# ships a subset of it. +set(CPPINTEROP_STAGE_DIR "${CMAKE_BINARY_DIR}/interop") + +# Acquire CppInterOp: an external prebuilt one (CppInterOp_DIR / +# CMAKE_PREFIX_PATH), else the pinned ExternalProject. Exports the +# CPPJIT_INTEROP_* coordinates consumed below. +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) +cppjit_add_cppinterop() + +# this libcppjit.so merges both cpyrt and the interop wrapper +file(GLOB CPYRT_SOURCES CONFIGURE_DEPENDS src/cpyrt/*.cxx) +set(INTEROP_SOURCES + src/interop/interop_wrapper.cxx + src/interop/cppinterop_dispatch.cxx +) + +add_library(cppjit SHARED ${CPYRT_SOURCES} ${INTEROP_SOURCES}) +if(TARGET CppInterOp) + add_dependencies(cppjit CppInterOp) +endif() + +# The wrapper anchors these coordinates at its own load location, falling +# back to the install prefix (see cppinterop_paths()); the clang major +# names the versioned compiler probed for the runtime resource dir. +target_compile_definitions(cppjit PRIVATE + CPPINTEROP_INSTALL_PREFIX="${CPPINTEROP_INSTALL_PREFIX}" + CPPINTEROP_LIBRARY="${CPPJIT_INTEROP_LIBRARY}" + CPPINTEROP_INCLUDE_DIR="${CPPJIT_INTEROP_RUNTIME_INCLUDES}" + CPPJIT_CLANG_MAJOR="${CPPJIT_INTEROP_CLANG_MAJOR}" + CPPJIT_CLANG_INCLUDE_DIR="${CPPJIT_INTEROP_CLANG_DIR}" + $<$:CPPJIT_USE_CLING> +) + +target_include_directories(cppjit PRIVATE + # src/ itself resolves the public "cpyrt/*.h" spellings against the + # flattened src/cpyrt/ directory + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpyrt + ${CMAKE_CURRENT_SOURCE_DIR}/src/interop + ${CPPJIT_INTEROP_COMPILE_INCLUDES} + ${Python_INCLUDE_DIRS} +) + +target_compile_options(cppjit PRIVATE + -Wall -Wno-strict-aliasing -Wno-register +) + +if(CMAKE_COMPILER_IS_GNUCXX) + target_link_options(cppjit PRIVATE -Wl,-Bsymbolic-functions) +endif() + +# macOS needs to allow undefined Python symbols +if(APPLE) + set_target_properties(cppjit PROPERTIES + LINK_FLAGS "-undefined dynamic_lookup" + SUFFIX ".so" + ) +endif() + +set_target_properties(cppjit PROPERTIES + OUTPUT_NAME "cppjit" + PREFIX "lib" +) + +# A plain build yields a runnable tree: the extension and the python +# sources assemble under /python, beside /interop. +if(NOT SKBUILD) + set_target_properties(cppjit PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python/cppjit" + ) + add_custom_target(cppjit-python-tree ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/python/cppjit" + "${CMAKE_BINARY_DIR}/python/cppjit" + ) +endif() + +# the extension lives inside the package (import cppjit.libcppjit) +install(TARGETS cppjit + LIBRARY DESTINATION cppjit +) + +# scikit-build-core ships the python sources itself (wheel.packages); a +# plain CMake install lays them out here so PYTHONPATH= works. +if(NOT SKBUILD) + install(DIRECTORY python/cppjit + DESTINATION . + PATTERN "__pycache__" EXCLUDE + ) +endif() + +# An external CppInterOp is consumed in place and nothing of it ships; +# the bundled one installs with the builtin headers of the build clang, +# laid out as a headers-only resource dir (only include/ ships). +if(TARGET CppInterOp) + install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/lib/" + DESTINATION cppjit/interop/lib + ) + install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/include/" + DESTINATION cppjit/interop/include + ) + install(DIRECTORY "${CPPJIT_INTEROP_CLANG_RESOURCE_DIR}/include/" + DESTINATION "cppjit/interop/lib/clang/${CPPJIT_INTEROP_CLANG_MAJOR}/include" + ) +endif() + +# the public cpyrt API headers keep their installed cpyrt/ prefix +install(FILES + src/cpyrt/API.h + src/cpyrt/CommonDefs.h + src/cpyrt/DispatchPtr.h + src/cpyrt/PyException.h + src/cpyrt/Reflex.h + DESTINATION cppjit/interop/include/cpyrt +) diff --git a/bindings/pyroot/cppjit/README.md b/bindings/pyroot/cppjit/README.md new file mode 100644 index 0000000000000..26bb9ba7b5b4d --- /dev/null +++ b/bindings/pyroot/cppjit/README.md @@ -0,0 +1,225 @@ +# cppjit: automatic Python-C++ interop and bindings + +[![CI](https://github.com/compiler-research/cppjit/actions/workflows/ci.yml/badge.svg)](https://github.com/compiler-research/cppjit/actions/workflows/ci.yml) +[![Nightlies](https://github.com/compiler-research/cppjit/actions/workflows/nightly.yml/badge.svg)](https://github.com/compiler-research/cppjit/actions/workflows/nightly.yml) +[![Wheels](https://github.com/compiler-research/cppjit/actions/workflows/wheels.yml/badge.svg)](https://github.com/compiler-research/cppjit/actions/workflows/wheels.yml) +[![PyPI](https://img.shields.io/pypi/v/cppjit)](https://pypi.org/project/cppjit/) +[![Python](https://img.shields.io/pypi/pyversions/cppjit)](https://pypi.org/project/cppjit/) +[![License](https://img.shields.io/badge/license-BSD--3--Clause--LBNL-green)](https://spdx.org/licenses/BSD-3-Clause-LBNL.html) + +cppjit embeds an interactive C++ JIT compiler in Python: write or import +C++ at run time and use its functions, classes, and templates as if they +were Python. In contrast to binding libraries such as pybind11 and +nanobind, there is no wrapper code to write and no CMake build to set +up: bindings materialize automatically on demand, derived from the C++ +declarations. Run-time binding generation enables: + +- **Detailed specialization** of each call at the point of use +- **Lazy loading** for reduced memory use in large-scale projects +- **Python-side cross-inheritance and callbacks** for working with C++ + frameworks +- **Run-time template instantiation**, so the binding surface never has to + be enumerated ahead of time +- **Automatic object downcasting** and **exception mapping** +- **Interactive exploration** of C++ libraries from the Python prompt + +cppjit supports user-developed C++ frameworks and third-party C++ libraries +from standard package managers, and lets you use them from a Python +application or build a Python-based DSL on top. cppjit is the successor +project of [cppyy](https://github.com/wlav/cppyy), rebuilt on +[CppInterOp](https://github.com/compiler-research/CppInterOp) and the +clang-repl C++ interpreter in LLVM. See the +[CHEP 2026](https://indico.cern.ch/event/1471803/contributions/6968247/attachments/3283391/5868828/CppInterOp_CHEP2026.pdf) +and the [EuroLLVM 2026](https://youtu.be/GuodsU3VO8Q) talks for more +details. + +- **Source code:** https://github.com/compiler-research/cppjit +- **Bug reports:** https://github.com/compiler-research/cppjit/issues +- **Changelog:** https://github.com/compiler-research/cppjit/releases + +### Examples + +A CPython extension builds Python proxies for all C++ entities: +functions, classes, templates, and variables. Constructors, operators, +and data members follow Python conventions. The embedded JIT lazily +compiles the C++ behind each proxy: + +```python +import cppjit + +cppjit.cppdef(""" +struct Vec2 { + double x, y; + Vec2 operator+(const Vec2& o) const { return {x + o.x, y + o.y}; } +};""") + +c = cppjit.gbl.Vec2(1, 2) + cppjit.gbl.Vec2(3, 4) +c.x, c.y # (4.0, 6.0) +``` + +Templates instantiate on demand, and STL containers behave like Python +containers: + +```python +cppjit.cppdef(""" +#include +template +T largest(const std::vector& xs) { return *std::max_element(xs.begin(), xs.end()); } +""") + +v = cppjit.gbl.std.vector['int']([3, 1, 4, 1, 5]) +cppjit.gbl.largest(v) # 5; largest is compiled at this call +len(v), list(v) # vectors support len(), iteration, indexing +``` + +Python callables pass into C++ as function pointers: + +```python +cppjit.cppdef(""" +template +R callme(R (*f)(U...), A &&...args) { + return f(args...); +}""") + +def callback(x: int, y: float) -> float: + return x + y + +cppjit.gbl.callme(callback, 123, 321.5) # 444.5 +``` + +NumPy arrays pass zero-copy; the C++ side works on the same buffer: + +```python +import numpy as np +a = np.arange(6, dtype=np.float64) + +cppjit.cppdef("void scale(double* xs, std::size_t n, double f) { while (n--) xs[n] *= f; }") +cppjit.gbl.scale(a, a.size, 10.0) +a # array([ 0., 10., 20., 30., 40., 50.]); same buffer, no copy +``` + +An installed library binds at run time, with no binding code written +for it: + +```python +import cppjit +cppjit.include('zlib.h') # bring in the declarations +cppjit.load_library('libz') # load the symbols +cppjit.gbl.zlibVersion() # '1.3'; call the library directly +``` + +CppInterOp drives Clang and provides the necessary run-time +reflection and JIT compilation API for cppjit. + +### Use cases + +- Numerics and data science: move performance-critical code into C++ + in the same session. +- Template-heavy APIs: STL, Eigen, and user templates instantiate + lazily at call sites. +- Existing C++ codebases: use them from Python without modification. +- Domain-specific languages: user-defined "pythonizations" adapt the + bindings into Pythonic libraries. + +### Requirements + +A package manager installation of cppjit (such as pip) requires +GCC >= 9 or Clang >= 15 (the JIT compiles C++ against the host's +standard library headers). + +Building from source requires: + +- LLVM/Clang development packages, version 21 or 22 +- Python 3.12+ with development headers +- CMake 3.20+ +- A C++20 compiler: g++ 13+, or a Clang matching the LLVM major + (an older Clang fails to compile newer LLVM headers) +- CppInterOp, cloned at build time or supplied from a local checkout + (see the development builds below) + +### Source installation (pip) + +
+Ubuntu 24.04 + +```bash +sudo apt-get update +sudo apt-get install -y git cmake make g++ python3-dev python3-venv python3-pip \ + wget lsb-release software-properties-common gnupg libzstd-dev libedit-dev +wget https://apt.llvm.org/llvm.sh && sudo bash llvm.sh 21 +sudo apt-get install -y llvm-21-dev libclang-21-dev clang-21 libpolly-21-dev + +python3 -m venv venv && source venv/bin/activate +git clone https://github.com/compiler-research/cppjit.git && cd cppjit +pip install -v . --config-settings=cmake.define.LLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm +``` + +
+ +
+macOS + +```bash +brew install llvm@21 cmake ninja +python3 -m venv venv && source venv/bin/activate +git clone https://github.com/compiler-research/cppjit.git && cd cppjit +pip install -v . --config-settings=cmake.define.LLVM_DIR="$(brew --prefix llvm@21)/lib/cmake/llvm" +``` + +
+ +### Development build (pip editable) + +With the toolchain from the source installation above, an editable +install with a persistent build directory gives incremental rebuilds: + +```bash +export LLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm # your LLVM's CMake directory +pip install scikit-build-core +pip install --no-build-isolation -ve . \ + --config-settings=build-dir=build \ + --config-settings=cmake.define.LLVM_DIR=$LLVM_DIR +``` + +To co-develop both CppInterOp and cppjit, clone CppInterOp next to +cppjit and rerun the install with a separate build directory and +`--config-settings=cmake.define.CPPINTEROP_SOURCE_DIR=$PWD/../CppInterOp`. +The local checkout overrides the pinned tag, so new CppInterOp API is +usable from cppjit immediately. + +### Development build (CMake) + +The CMake build compiles and stages CppInterOp inside the build tree +and assembles the Python package under `/python`; point +`PYTHONPATH` there instead of installing: + +```bash +export LLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm # your LLVM's CMake directory +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=$LLVM_DIR +cmake --build build -j +export PYTHONPATH=$PWD/build/python +``` + +A prebuilt CppInterOp (built shared with `-DBUILD_SHARED_LIBS=ON` and +installed to a prefix) is consumed in place through `CppInterOp_DIR` +instead of being rebuilt: + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=$LLVM_DIR \ + -DCppInterOp_DIR=$PWD/../CppInterOp/install/lib/cmake/CppInterOp +``` + +### Running the test suite locally + +```bash +pip install -r requirements.txt +cd test +make -j4 # builds the *Dict.so loaded for tests +python -m pytest -ra --tb=short +``` + +### Contributing + +Bug reports, feature requests, and questions go to the +[issue tracker](https://github.com/compiler-research/cppjit/issues). +Pull requests are welcome; run the test suite before submitting. diff --git a/bindings/pyroot/cppjit/cmake/AddCppInterOp.cmake b/bindings/pyroot/cppjit/cmake/AddCppInterOp.cmake new file mode 100644 index 0000000000000..59b397b770785 --- /dev/null +++ b/bindings/pyroot/cppjit/cmake/AddCppInterOp.cmake @@ -0,0 +1,252 @@ +# Acquires CppInterOp. A distribution or developer supplies a prebuilt one +# -- an install prefix or a build directory -- through CppInterOp_DIR or +# CMAKE_PREFIX_PATH, and it is consumed in place with nothing bundled. +# Otherwise the pinned tag (or CPPINTEROP_SOURCE_DIR) builds as an +# ExternalProject and stages for bundling; default backend is clang-repl, +# CPPJIT_USE_CLING builds against a provided cling. Coordinates exported to +# the parent scope, relative ones anchored at the runtime layout and +# absolute ones standing on their own (see cppinterop_paths()): +# CPPJIT_INTEROP_LIBRARY runtime library +# CPPJIT_INTEROP_RUNTIME_INCLUDES runtime include dirs, ':'-joined +# CPPJIT_INTEROP_COMPILE_INCLUDES include dirs for building the wrapper +# CPPJIT_INTEROP_CLANG_MAJOR clang major of the interpreter +# CPPJIT_INTEROP_CLANG_DIR resource-dir coordinate; empty = probe +# CPPJIT_INTEROP_CLANG_RESOURCE_DIR builtin headers to bundle (install only) +include_guard(GLOBAL) +include(ExternalProject) + +# Hint LLVM discovery at an active conda toolchain. +set(_llvm_hints "") +if(DEFINED ENV{CONDA_PREFIX}) + list(APPEND _llvm_hints "$ENV{CONDA_PREFIX}/lib/cmake/llvm") +endif() + +# Developer toggle: Cling from either ROOT or standalone supplies LLVM_DIR/Clang_DIR +option(CPPJIT_USE_CLING "Build CppInterOp against a prebuilt Cling C++ Interpreter (ROOT)" OFF) +mark_as_advanced(CPPJIT_USE_CLING) + +set(Cling_DIR "" CACHE PATH "Cling CMake config dir; derived from LLVM_DIR when empty (cling mode only)") +# Surface Cling_DIR in ccmake/GUI only when cling mode is on. +if(CPPJIT_USE_CLING) + mark_as_advanced(CLEAR Cling_DIR) +else() + mark_as_advanced(Cling_DIR) +endif() + +function(cppjit_add_cppinterop) + # An explicit CppInterOp_DIR is authoritative, like LLVM_DIR below. A + # failed find_package caches it as -NOTFOUND, which if() reads as unset. + if(CppInterOp_DIR) + find_package(CppInterOp CONFIG REQUIRED PATHS "${CppInterOp_DIR}" NO_DEFAULT_PATH) + else() + find_package(CppInterOp CONFIG QUIET) + endif() + + set(_clang_resource_dir "") + if(CppInterOp_FOUND) + if(CPPINTEROP_SOURCE_DIR) + message(FATAL_ERROR + "An external CppInterOp and CPPINTEROP_SOURCE_DIR are mutually exclusive") + endif() + # The developer owns compatibility: warn, do not fail. + if(CPPINTEROP_LLVM_VERSION_MAJOR LESS CPPJIT_LLVM_VERSION_MIN OR + CPPINTEROP_LLVM_VERSION_MAJOR GREATER CPPJIT_LLVM_VERSION_MAX) + message(WARNING + "External CppInterOp embeds LLVM ${CPPINTEROP_LLVM_VERSION}, outside " + "the tested ${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX}") + endif() + # Builtin headers from a matching host LLVM when one is findable; an + # empty coordinate leaves them to the wrapper's runtime probe. + find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) + set(_clang_dir "") + if(LLVM_FOUND AND LLVM_VERSION_MAJOR EQUAL CPPINTEROP_LLVM_VERSION_MAJOR + AND EXISTS "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}/include") + set(_clang_dir "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") + endif() + + set(_library "${CPPINTEROP_LIBRARIES}") + set(_compile_includes "${CPPINTEROP_INCLUDE_DIRS}") + list(JOIN CPPINTEROP_INCLUDE_DIRS ":" _runtime_includes) + set(_clang_major "${CPPINTEROP_LLVM_VERSION_MAJOR}") + message(STATUS + "CppInterOp: external ${CPPINTEROP_VERSION} (LLVM ${CPPINTEROP_LLVM_VERSION}) " + "at ${CPPINTEROP_LIBRARIES}") + else() + if(LLVM_DIR) + # An explicit LLVM_DIR is authoritative: fail instead of falling back to a + # different LLVM than the one requested. A failed find_package resets + # LLVM_DIR to -NOTFOUND, so keep the requested value for the message. + set(_llvm_dir_arg "${LLVM_DIR}") + find_package(LLVM CONFIG PATHS "${LLVM_DIR}" NO_DEFAULT_PATH) + if(NOT LLVM_FOUND) + message(FATAL_ERROR + "No LLVMConfig.cmake under LLVM_DIR (${_llvm_dir_arg}); expected " + "/lib/cmake/llvm") + endif() + else() + find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) + if(NOT LLVM_FOUND) + message(FATAL_ERROR + "No LLVM CMake package found. Install LLVM " + "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX} development packages " + "(apt: llvm-${CPPJIT_LLVM_VERSION_MAX}-dev libclang-${CPPJIT_LLVM_VERSION_MAX}-dev; " + "conda: llvmdev clangdev), or point cppjit at your own LLVM build with " + "-DLLVM_DIR=/lib/cmake/llvm " + "(pip: --config-settings=cmake.define.LLVM_DIR=...)") + endif() + endif() + + message(STATUS "Found LLVM ${LLVM_VERSION} at ${LLVM_DIR}") + if(LLVM_VERSION_MAJOR LESS CPPJIT_LLVM_VERSION_MIN OR + LLVM_VERSION_MAJOR GREATER CPPJIT_LLVM_VERSION_MAX) + message(FATAL_ERROR + "LLVM ${LLVM_VERSION} is unsupported: the currently supported " + "CppInterOp version (${CPPINTEROP_GIT_TAG}) only supports LLVM " + "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX}") + endif() + + if(Clang_DIR) + # An explicit Clang_DIR is authoritative, like LLVM_DIR above. + set(_clang_dir_arg "${Clang_DIR}") + find_package(Clang CONFIG PATHS "${Clang_DIR}" NO_DEFAULT_PATH) + if(NOT Clang_FOUND) + message(FATAL_ERROR + "No ClangConfig.cmake under Clang_DIR (${_clang_dir_arg}); expected " + "/lib/cmake/clang") + endif() + else() + # Clang's package sits beside LLVM's in every supported layout; search + # only there so an unrelated system clang cannot satisfy the lookup. + find_package(Clang CONFIG QUIET HINTS "${LLVM_DIR}/../clang" NO_DEFAULT_PATH) + endif() + if(Clang_FOUND) + message(STATUS "Found Clang at ${Clang_DIR}") + endif() + + set(_args + -DLLVM_DIR=${LLVM_DIR} + -DCPPINTEROP_ENABLE_TESTING=${CPPJIT_ENABLE_CPPINTEROP_TESTS} + -DBUILD_SHARED_LIBS=ON + # The wheel ships a single unversioned library file. + -DCPPINTEROP_SHARED_LIBRARY_VERSIONING=OFF + -DCMAKE_INSTALL_PREFIX=${CPPINTEROP_STAGE_DIR} + -DCMAKE_INSTALL_LIBDIR=lib + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_CXX_STANDARD=17 + ) + + if(CPPJIT_USE_CLING) + set(_cling_dir "${Cling_DIR}") + if(NOT _cling_dir) + # LLVM_DIR is /lib/cmake/llvm; cling config sits at the + # sibling /lib/cmake/cling (setup-llvm flavor:cling layout). + get_filename_component(_prefix "${LLVM_DIR}" DIRECTORY) # /lib/cmake + get_filename_component(_prefix "${_prefix}" DIRECTORY) # /lib + get_filename_component(_prefix "${_prefix}" DIRECTORY) # + set(_cling_dir "${_prefix}/lib/cmake/cling") + endif() + message(STATUS "CppInterOp backend: Cling (found at ${_cling_dir})") + list(APPEND _args + -DCPPINTEROP_USE_CLING=ON + -DCPPINTEROP_USE_REPL=OFF + -DCling_DIR=${_cling_dir} + ) + else() + list(APPEND _args + -DCPPINTEROP_USE_REPL=ON + -DCPPINTEROP_USE_CLING=OFF + ) + endif() + + if(Clang_DIR) + list(APPEND _args -DClang_DIR=${Clang_DIR}) + endif() + if(CMAKE_C_COMPILER) + list(APPEND _args -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}) + endif() + if(CMAKE_CXX_COMPILER) + list(APPEND _args -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) + endif() + + set(_source_args + GIT_REPOSITORY ${CPPINTEROP_GIT_REPOSITORY} + GIT_TAG ${CPPINTEROP_GIT_TAG} + ) + set(_log_args + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_OUTPUT_ON_FAILURE ON + ) + if(CPPINTEROP_SOURCE_DIR) + message(STATUS "CppInterOp: building from local source at ${CPPINTEROP_SOURCE_DIR} " + "over the currently supported version ${CPPINTEROP_GIT_TAG}") + # BUILD_ALWAYS recompiles uncommitted edits and refreshes the installed + # libclangCppInterOp on every build; the sub-build keeps this incremental. + set(_source_args + SOURCE_DIR "${CPPINTEROP_SOURCE_DIR}" + BUILD_ALWAYS ON + ) + # Stream sub-build output in the dev loop instead of hiding it in log files. + set(_log_args "") + endif() + + # Keep the symbol table in debug-info builds; .dynsym alone + # suffices for the dlsym-based dispatch everywhere else. + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + set(_install_lib_target install-clangCppInterOp) + if(NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG" AND + NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "RELWITHDEBINFO") + string(APPEND _install_lib_target "-stripped") + endif() + + # Install only the library and headers, not CppInterOp's full install tree. + ExternalProject_Add(CppInterOp + ${_source_args} + PREFIX "${CMAKE_BINARY_DIR}/CppInterOp" + CMAKE_ARGS ${_args} + INSTALL_COMMAND ${CMAKE_COMMAND} --build + --target ${_install_lib_target} install-cppinterop-headers + BUILD_BYPRODUCTS + "${CPPINTEROP_STAGE_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + ${_log_args} + ) + + # Verify that the user-provided CppInterOp source override is legitimate. + ExternalProject_Add_Step(CppInterOp verify_project + COMMAND ${CMAKE_COMMAND} + -DCPPINTEROP_BINARY_DIR= + -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/VerifyCppInterOp.cmake" + DEPENDEES configure + DEPENDERS build + COMMENT "Verifying the configured source tree is CppInterOp" + ) + + set(_clang_resource_dir "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") + if(NOT EXISTS "${_clang_resource_dir}/include") + message(FATAL_ERROR + "No builtin headers at ${_clang_resource_dir}/include; the LLVM at " + "${LLVM_DIR} carries no clang resource directory") + endif() + + set(_library "interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(_compile_includes "${CPPINTEROP_STAGE_DIR}/include") + set(_runtime_includes "interop/include") + set(_clang_major "${LLVM_VERSION_MAJOR}") + # The wheel reads its bundled copy; a raw build tree bundles nothing + # and uses the build LLVM's headers where they are. + if(SKBUILD) + set(_clang_dir "interop/lib/clang/${LLVM_VERSION_MAJOR}") + else() + set(_clang_dir "${_clang_resource_dir}") + endif() + endif() + + set(CPPJIT_INTEROP_LIBRARY "${_library}" PARENT_SCOPE) + set(CPPJIT_INTEROP_RUNTIME_INCLUDES "${_runtime_includes}" PARENT_SCOPE) + set(CPPJIT_INTEROP_COMPILE_INCLUDES "${_compile_includes}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_MAJOR "${_clang_major}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_DIR "${_clang_dir}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_RESOURCE_DIR "${_clang_resource_dir}" PARENT_SCOPE) +endfunction() diff --git a/bindings/pyroot/cppjit/cmake/VerifyCppInterOp.cmake b/bindings/pyroot/cppjit/cmake/VerifyCppInterOp.cmake new file mode 100644 index 0000000000000..771887837ff87 --- /dev/null +++ b/bindings/pyroot/cppjit/cmake/VerifyCppInterOp.cmake @@ -0,0 +1,14 @@ +# Runs between the CppInterOp ExternalProject's configure and build steps: +# the sub-configure records its project() name in its cache; reject any +# source tree that does not declare itself CppInterOp. +load_cache("${CPPINTEROP_BINARY_DIR}" READ_WITH_PREFIX _sub_ CMAKE_PROJECT_NAME) +if(NOT DEFINED _sub_CMAKE_PROJECT_NAME) + message(FATAL_ERROR + "No CMAKE_PROJECT_NAME in ${CPPINTEROP_BINARY_DIR}/CMakeCache.txt " + "— the CppInterOp sub-configure did not complete") +endif() +if(NOT _sub_CMAKE_PROJECT_NAME STREQUAL "CppInterOp") + message(FATAL_ERROR + "The provided source path override declares project '${_sub_CMAKE_PROJECT_NAME}', " + "not CppInterOp") +endif() diff --git a/bindings/pyroot/cppjit/cppjit-version.tag b/bindings/pyroot/cppjit/cppjit-version.tag new file mode 100644 index 0000000000000..73ba1b109d5a2 --- /dev/null +++ b/bindings/pyroot/cppjit/cppjit-version.tag @@ -0,0 +1 @@ +7dc6bf0ff47b56afe528a1b67dbd2724423e79d9 diff --git a/bindings/pyroot/cppjit/docs/ReleaseNotes.md b/bindings/pyroot/cppjit/docs/ReleaseNotes.md new file mode 100644 index 0000000000000..569547556c29a --- /dev/null +++ b/bindings/pyroot/cppjit/docs/ReleaseNotes.md @@ -0,0 +1,42 @@ +# cppjit 0.1.0b1 Release Notes + +This document contains the release notes for cppjit 0.1.0b1, an +automatic Python-C++ interoperability layer and bindings generator +built on CppInterOp and the LLVM compiler infrastructure. If you are +reading this file from a git checkout, it describes the *next* release, +not the current one. The notes of previously released versions are +archived on the +[releases page](https://github.com/compiler-research/cppjit/releases). + +This release supports Python 3.12-3.14 and LLVM 21-22. + +## Highlights + +- ... + +## New features + +- ... + +## Deprecated features + +- ... + +## Backwards incompatible changes + +- ... + +## Other changes + +- ... + +## Contributors + +Special thanks to everyone who contributed to this release: + +- ... + + diff --git a/bindings/pyroot/cppjit/docs/ReleaseProcess.md b/bindings/pyroot/cppjit/docs/ReleaseProcess.md new file mode 100644 index 0000000000000..a324f5ca84c81 --- /dev/null +++ b/bindings/pyroot/cppjit/docs/ReleaseProcess.md @@ -0,0 +1,78 @@ +# Release Process + +A cppjit release is a git tag on main. The tag push builds the wheels, +and the PyPI workflow uploads what that build produced. Future releases +will extend this to more distribution channels like conda. + +## Procedure + +1. Open the release PR: + - Set `__version__` in `python/cppjit/_version.py` to the release + version. + - Fill in `docs/ReleaseNotes.md`. + - Add the `build-wheels` label to build the complete set of wheels + on the PR. + +2. Squash-merge the PR and tag the resulting commit on main: + + ```bash + git tag -a vX.Y.Z -m "cppjit X.Y.Z" + git push origin vX.Y.Z + ``` + + The tag push runs the Wheels workflow with every supported Python + on every platform. + +3. When that run succeeds, the PyPI workflow uploads its wheels and + sdist. If the `pypi` environment has required reviewers, the + upload waits for their approval. A failed run uploads nothing: fix + the problem and re-tag, or, if only the upload failed, re-run it + through the manual fallback below. + +4. Create the GitHub release for the tag. The body is the finalized + `docs/ReleaseNotes.md`. The attached artifacts are the uploaded + wheels and sdist. + +5. Announce the release on the compiler-research website and the + relevant mailing lists. + +6. Open the post-release PR. It sets `_version.py` to the next + version with a `.dev0` suffix (e.g., `0.1.0a2.dev0` follows + `0.1.0a1`) and resets `docs/ReleaseNotes.md`. + +## Version and metadata + +`python/cppjit/_version.py` is the source of truth for the version. +scikit-build-core reads it at build time through the +`[[tool.dynamic-metadata]]` entry in `pyproject.toml`. PyPI uses the +information in `pyproject.toml` to build the project page. The +versioning scheme follows PEP 440, and between releases the version +carries a `.dev0` suffix. + +## Release notes + +`docs/ReleaseNotes.md` collects notes for the release under +development. The release PR completes them and the post-release PR +resets them, so a git checkout always describes the next release. +The notes of a released version live on in its GitHub release. + +PyPI shows no release notes. It renders the README that was built +into each version, and the `Changelog` URL in `[project.urls]` leads +from there to the GitHub releases page. + +## The PyPI workflow + +`.github/workflows/pypi.yml` runs when the Wheels run for a `v` tag +completes. On success it downloads that run's wheels and sdist and +uploads them to PyPI, so what lands there is exactly what CI built +and tested. The upload uses trusted publishing: the job authenticates +with a short-lived OIDC token, and the repository stores no +credential. GitHub runs the copy of `pypi.yml` on main, so a change +to the workflow takes effect once it merges. + +The manual fallback is a `workflow_dispatch` of the same workflow +with the run id of a successful full Wheels run. + +PyPI accepts each version once, and deleting an upload does not free +its number. A broken release is replaced by the next patch or +prerelease number. diff --git a/bindings/pyroot/cppjit/pyproject.toml b/bindings/pyroot/cppjit/pyproject.toml new file mode 100644 index 0000000000000..7bbb14aa2abdd --- /dev/null +++ b/bindings/pyroot/cppjit/pyproject.toml @@ -0,0 +1,122 @@ +[build-system] +requires = ["scikit-build-core>=1.0"] +build-backend = "scikit_build_core.build" + +[project] +name = "cppjit" +dynamic = ["version"] +description = "Automatic Python-C++ interoperability layer and bindings generator" +readme = "README.md" +license = "BSD-3-Clause-LBNL" +requires-python = ">=3.12" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Implementation :: CPython", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Compilers", + "Topic :: Software Development :: Interpreters", + "Topic :: Software Development :: Libraries :: Python Modules", +] +keywords = [ + "cplusplus", + "interoperability", + "bindings-generator", + "llvm-jit", + "scientific-computing", +] +authors = [ + {name = "Wim Lavrijsen"}, + {name = "Vassil Vassilev"}, + {name = "Aaron Jomy"}, + {name = "Vipul Cariappa"}, + {name = "Jonas Rembser"}, + {name = "Baidyanath Kundu"} +] +maintainers = [ + {name = "Aaron Jomy", email = "aaronjomyjoseph@gmail.com"}, + {name = "Jonas Rembser", email = "jonas.rembser@cern.ch"}, + {name = "Vassil Vassilev", email = "v.g.vassilev@gmail.com"}, + {name = "Vipul Cariappa", email = "vipulcariappa@gmail.com"} +] + +[project.urls] +Homepage = "https://github.com/compiler-research/cppjit" +Issues = "https://github.com/compiler-research/cppjit/issues" +Changelog = "https://github.com/compiler-research/cppjit/releases" + +[tool.scikit-build] +minimum-version = "build-system.requires" +wheel.install-dir = "." +wheel.packages = ["python/cppjit"] +cmake.build-type = "Release" +sdist.exclude = [".github", ".gitignore", ".clang-format"] + +[[tool.dynamic-metadata]] +provider = "scikit_build_core.metadata.regex" +field = "version" +input = "python/cppjit/_version.py" + +[tool.cibuildwheel] +# cp314t needs a free-threading audit first; cp315 joins at its release. +build = ["cp312-*", "cp313-*", "cp314-*"] +skip = ["*-musllinux*"] +# one native runner per matrix row; no emulation +archs = ["auto64"] +build-verbosity = 1 +audit-requires = ["twine"] +audit-command = "twine check {wheel}" +test-sources = ["test", "requirements.txt", ".github/wheel_smoke.py", ".github/wheel_test.sh"] +# smoke + full suite; the script is shared with the relocation and sdist jobs +test-command = "bash .github/wheel_test.sh" +# imports must resolve from the installed wheel, not the checkout +test-environment = { PYTHONSAFEPATH = "1" } + +[tool.cibuildwheel.linux] +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" +before-test = "python -m pip install -r {project}/requirements.txt" +# /opt/llvm is staged on the runner by wheels.yml. +container-engine = { name = "docker", create-args = ["--volume=/opt/llvm:/opt/llvm"], disable-host-mount = true } +environment = { CMAKE_ARGS = "-DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DClang_DIR=/opt/llvm/lib/cmake/clang" } + +[tool.cibuildwheel.macos] +before-test = "brew install eigen boost && python -m pip install -r {project}/requirements.txt" +environment = { CMAKE_ARGS = "-DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DClang_DIR=/opt/llvm/lib/cmake/clang", MACOSX_DEPLOYMENT_TARGET = "14.0" } + +[tool.pytest.ini_options] +testpaths = ["test"] +pythonpath = ["test"] +xfail_strict = true + +[tool.ruff] +show-fixes = true + +[tool.ruff.format] +# the README's code fences hand-align their annotation comments +exclude = ["*.md"] + +[tool.ruff.lint] +# pyflakes/pycodestyle errors plus import sorting; wider rule sets are a +# post-squash cleanup +select = ["E4", "E7", "E9", "F", "I"] +ignore = [ + "E722", # bare excepts deliberately swallow C++-originated exceptions +] + +[tool.ruff.lint.per-file-ignores] +# the star import is load-bearing: it pulls the merged extension's names into +# the package namespace, and everything below it depends on them +"python/cppjit/__init__.py" = ["F403", "F405"] +# binding-test idioms: == True/False and type(x) == y assert proxy comparison +# semantics, fixtures use short upstream names, and unused locals hold +# references to control C++ object lifetimes +"test/**" = ["E712", "E721", "E741", "E742", "F841"] diff --git a/bindings/pyroot/cppjit/python/CMakeLists.txt b/bindings/pyroot/cppjit/python/CMakeLists.txt new file mode 100644 index 0000000000000..1b21aa6350adb --- /dev/null +++ b/bindings/pyroot/cppjit/python/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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. + +ROOT_PYTHON_PACKAGE(cppjit + SOURCES + cppjit/_stdcpp_fix.py + cppjit/__init__.py + cppjit/_cpython_cppjit.py + cppjit/_pythonization.py + cppjit/_typemap.py + cppjit/_version.py + cppjit/ll.py + cppjit/numba_ext.py + cppjit/reflex.py + cppjit/types.py +) diff --git a/bindings/pyroot/cppjit/python/cppjit/__init__.py b/bindings/pyroot/cppjit/python/cppjit/__init__.py new file mode 100644 index 0000000000000..3d0282af458fa --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/__init__.py @@ -0,0 +1,498 @@ +"""Dynamic C++ bindings generator. + +This module provides dynamic bindings to C++ through Cling, the LLVM-based C++ +interpreter, allowing interactive mixing of Python and C++. Example: + + >>> import cppjit + >>> cppjit.cppdef(\"\"\" + ... class MyClass { + ... public: + ... MyClass(int i) : m_data(i) {} + ... int m_data; + ... };\"\"\") + True + >>> from cppjit.gbl import MyClass + >>> m = MyClass(42) + >>> cppjit.cppdef(\"\"\" + ... void say_hello(MyClass* m) { + ... std::cout << "Hello, the number is: " << m->m_data << std::endl; + ... }\"\"\") + True + >>> MyClass.say_hello = cppjit.gbl.say_hello + >>> m.say_hello() + Hello, the number is: 42 + >>> m.m_data = 13 + >>> m.say_hello() + Hello, the number is: 13 + >>> + +For full documentation, see: + https://cppyy.readthedocs.io/ + +""" + +__author__ = "Wim Lavrijsen " + +__all__ = [ + "cppdef", # declare C++ source to Cling + "cppexec", # execute a C++ statement + "macro", # attempt to evaluate a cpp macro + "include", # load and jit a header file + "c_include", # load and jit a C header file + "load_library", # load a shared library + "nullptr", # unique pointer representing NULL + "sizeof", # size of a C++ type + "typeid", # typeid of a C++ type + "multi", # helper for multiple inheritance + "add_include_path", # add a path to search for headers + "add_library_path", # add a path to search for libraries + "add_autoload_map", # explicitly include an autoload map + "set_debug", # enable/disable debug output +] + +import contextlib +import ctypes +import os +import sys +import sysconfig +import warnings + +try: + import __pypy__ # noqa: F401 + + _ispypy = True +except ImportError: + _ispypy = False + +from . import _typemap +from ._version import __version__ as __version__ + +# import separately instead of in the above try/except block for easier to +# understand tracebacks +if _ispypy: + raise ImportError("cppjit requires CPython; PyPy is not supported") +from ._cpython_cppjit import * + +# - allow importing from gbl -------------------------------------------------- +sys.modules["cppjit.gbl"] = gbl +sys.modules["cppjit.gbl.std"] = gbl.std + + +# - force creation of std.exception ------------------------------------------------------- +_e = gbl.std.exception + + +# - enable auto-loading ------------------------------------------------------- +try: + gbl.cling.runtime.gCling.EnableAutoLoading() +except: + pass + + +# - external typemap ---------------------------------------------------------- +_typemap.initialize(_backend) # also creates (u)int8_t mapper + +try: + gbl.std.int8_t = gbl.int8_t # ensures same _integer_ type + gbl.std.uint8_t = gbl.uint8_t +except (AttributeError, TypeError): + pass + + +# - pythonization factories --------------------------------------------------- +from . import _pythonization as py # noqa: E402 + +py._set_backend(_backend) + + +def _standard_pythonizations(pyclass, name): + # pythonization of tuple; TODO: placed here for convenience, but a custom case + # for tuples on each platform can be made much more performant ... + if name.find("tuple<", 0, 6) == 0: + import cppjit + + pyclass._tuple_len = cppjit.gbl.std.tuple_size(pyclass).value + + def tuple_len(self): + return self.__class__._tuple_len + + pyclass.__len__ = tuple_len + + def tuple_getitem(self, idx, get=cppjit.gbl.std.get): + if idx < self.__class__._tuple_len: + res = get[idx](self) + try: + res.__life_line = self + except Exception: + pass + return res + raise IndexError(idx) + + pyclass.__getitem__ = tuple_getitem + + # pythonization of std::basic_string; placed here because it's simpler to write the + # custom "npos" object (to allow easy result checking of find/rfind) in Python + elif pyclass.__cpp_name__ in ( + "std::basic_string", + "std::basic_string,std::allocator >", + "std::basic_string, std::allocator >", + ): + + class NPOS(int): + def __init__(self, npos): + self.__cpp_npos = npos + + def __eq__(self, other): + return other == -1 or other == self.__cpp_npos + + def __ne__(self, other): + return other != -1 and other != self.__cpp_npos + + if hasattr(pyclass.__class__, "npos"): + del pyclass.__class__.npos # drop b/c is const data + pyclass.npos = NPOS(pyclass.npos) + + return True + + +if not _ispypy: + py.add_pythonization(_standard_pythonizations, "std") +# TODO: PyPy still has the old-style pythonizations, which require the full +# class name (not possible for std::tuple ...) + + +# std::make_shared/unique create needless templates: rely on Python's introspection +# instead. This also allows Python derived classes to be handled correctly. +class py_make_smartptr(object): + __slots__ = ["cls", "ptrcls"] + + def __init__(self, cls, ptrcls): + self.cls = cls + self.ptrcls = ptrcls + + def __call__(self, *args): + if len(args) == 1 and type(args[0]) == self.cls: # noqa: E721 + obj = args[0] + else: + obj = self.cls(*args) + return self.ptrcls[self.cls](obj) # C++ takes ownership + + +def _install_smartptr_makers(): + class make_smartptr(object): + __slots__ = ["ptrcls", "maker"] + + def __init__(self, ptrcls, maker): + self.ptrcls = ptrcls + self.maker = maker + + def __call__(self, ptr): + return py_make_smartptr(type(ptr), self.ptrcls)(ptr) + + def __getitem__(self, cls): + try: + if not cls.__module__ == int.__module__: + return py_make_smartptr(cls, self.ptrcls) + except AttributeError: + pass + if isinstance(cls, str) and cls not in ("int", "float"): + return py_make_smartptr(getattr(gbl, cls), self.ptrcls) + return self.maker[cls] + + gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) + gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) + + +_install_smartptr_makers() + +gbl.gInterpreter = gbl.TInterpreter.Instance() + + +# --- interface to Cling ------------------------------------------------------ +class _stderr_capture(object): + def __init__(self): + self._capture = not gbl.Cpp.IsDebugOutputEnabled() + self.err = "" + + def __enter__(self): + if self._capture: + _begin_capture_stderr() + return self + + def __exit__(self, tp, val, trace): + if self._capture: + self.err = _end_capture_stderr() + + +@contextlib.contextmanager +def _interpreter_lock(): + """Hold the interpreter lock around Cpp calls made directly from Python. + + Method calls through cpyrt take it themselves; the Cpp.Declare & co. + calls below are plain functions and would otherwise run unlocked next to + TCling users on other threads.""" + _lock_interpreter() + try: + yield + finally: + _unlock_interpreter() + + +def cppdef(src, verbose=True): + """Declare C++ source to Cling.""" + with _stderr_capture() as err, _interpreter_lock(): + errcode = gbl.Cpp.Declare(src, not verbose) + if not errcode == 0 or err.err: + if "warning" in err.err.lower() and "error" not in err.err.lower(): + warnings.warn(err.err, SyntaxWarning) + return True + raise SyntaxError("Failed to parse the given C++ code%s" % err.err) + return True + + +def cppexec(stmt): + """Execute C++ statement in Cling's global scope.""" + if stmt and stmt[-1] != ";": + stmt += ";" + + # capture stderr, but note that Process could legitimately be writing to + # std::cerr, in which case the captured output needs to be printed as normal + with _stderr_capture() as err, _interpreter_lock(): + errcode = ctypes.c_int(0) + try: + errcode = gbl.Cpp.Process(stmt) + except Exception as e: + sys.stderr.write("%s\n\n" % str(e)) + if not errcode.value: + errcode.value = 1 + + # the return code of Process does not cover errors raised while executing a + # wrapped expression: with dynamic scopes, a failed lookup compiles fine and + # fails at runtime, reported through an exception from the JIT that on + # Windows cannot cross the JITed wrapper frame (so nothing is raised and the + # return code stays 0); check the captured diagnostics as well, like the + # reference cppyy implementation does + if not errcode == 0 or ("input_line" in err.err and "error" in err.err): + raise SyntaxError("Failed to parse the given C++ code%s" % err.err) + elif err.err and err.err[1:] != "\n": + sys.stderr.write(err.err[1:]) + + return True + + +def evaluate(input): + with _interpreter_lock(): + box = gbl.Cpp.Evaluate(input) + # Truthy sentinel: skips Box::convertTo's UB-on-K_Unspecified arm. + if box.getKind() == gbl.Cpp.Box.K_Unspecified: + return ~0 + return box.convertTo["long"]() + + +def macro(cppm): + """Attempt to evalute a C/C++ pre-processor macro as a constant""" + + try: + macro_val = getattr(getattr(gbl, "__cppjit_macros", None), cppm + "_", None) + if macro_val is None: + cppdef("namespace __cppjit_macros { auto %s_ = %s; }" % (cppm, cppm)) + return getattr(getattr(gbl, "__cppjit_macros"), cppm + "_") + except Exception: + pass + + raise ValueError("Failed to evaluate macro %s", cppm) + + +def load_library(name): + """Explicitly load a shared library.""" + with _stderr_capture() as err: + gSystem = gbl.gSystem + if name[:3] != "lib": + if not gSystem.FindDynamicLibrary( + gbl.TString(name), True + ) and gSystem.FindDynamicLibrary(gbl.TString("lib" + name), True): + name = "lib" + name + sc = gSystem.Load(name) + if sc == -1: + # special case for Windows as of python3.8: use winmode=0, otherwise + # the default will not consider regular search paths (such as $PATH) + if 0x3080000 <= sys.hexversion and "win32" in sys.platform and os.path.isabs(name): + return ctypes.CDLL(name, ctypes.RTLD_GLOBAL, winmode=0) # raises on error + raise RuntimeError('Unable to load library "%s"%s' % (name, err.err)) + + return True + + +def include(header): + """Load (and JIT) header file
into Cling.""" + with _stderr_capture() as err, _interpreter_lock(): + errcode = gbl.Cpp.Declare('#include "%s"' % header, False) + if not errcode == 0: + raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) + return True + + +def c_include(header): + """Load (and JIT) header file
into Cling.""" + with _stderr_capture() as err, _interpreter_lock(): + errcode = gbl.Cpp.Declare( + """extern "C" { + #include "%s" + }""" + % header, + False, + ) + if not errcode == 0: + raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) + return True + + +def add_include_path(path): + """Add a path to the include paths available to Cling.""" + if not os.path.isdir(path): + raise OSError("No such directory: %s" % path) + with _interpreter_lock(): + gbl.Cpp.AddIncludePath(path) + + +def add_library_path(path): + """Add a path to the library search paths available to Cling.""" + if not os.path.isdir(path): + raise OSError("No such directory: %s" % path) + gbl.gSystem.AddDynamicPath(path) + + +def _setup_include_paths(): + # add access to Python C-API headers + apipath = sysconfig.get_path( + "include", "posix_prefix" if os.name == "posix" else os.name + ) + if os.path.exists(apipath): + add_include_path(apipath) + elif _ispypy: + # possibly structured without 'pythonx.y' in path + apipath = os.path.dirname(apipath) + if os.path.exists(apipath) and os.path.exists( + os.path.join(apipath, "Python.h") + ): + add_include_path(apipath) + + if os.getenv("CONDA_PREFIX"): + # MacOS, Linux + include_path = os.path.join(os.getenv("CONDA_PREFIX"), "include") + if os.path.exists(include_path): + add_include_path(include_path) + + # Windows + include_path = os.path.join(os.getenv("CONDA_PREFIX"), "Library", "include") + if os.path.exists(include_path): + add_include_path(include_path) + + # assuming that we are in PREFIX/lib/python/site-packages/cppjit, add PREFIX/include to the search path + include_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), *(4 * [os.path.pardir] + ["include"])) + ) + if os.path.exists(include_path): + add_include_path(include_path) + + +_setup_include_paths() + + +def add_autoload_map(fname): + """Add the entries from a autoload (.rootmap) file to Cling.""" + if not os.path.isfile(fname): + raise OSError("no such file: %s" % fname) + gbl.cling.runtime.gCling.LoadLibraryMap(fname) + + +def set_debug(enable=True): + """Enable/disable debug output.""" + gbl.Cpp.EnableDebugOutput(enable) + + +def _get_name(tt): + if isinstance(tt, str): + return tt + try: + ttname = tt.__cpp_name__ + except AttributeError: + ttname = tt.__name__ + return ttname + + +_sizes = {} + + +def sizeof(tt): + """Returns the storage size (in chars) of C++ type .""" + if not isinstance(tt, type) and not isinstance(tt, str): + tt = type(tt) + try: + return _sizes[tt] + except KeyError: + try: + sz = ctypes.sizeof(tt) + except TypeError: + # Route through evaluate() so the Box-returning Cpp::Evaluate + # is unboxed in one place (see the shim above). Cpp::SizeOf + # would be faster but its sibling Cpp::GetNamed does not + # traverse `Foo::Bar`-style qualified names, so handing it a + # nested `tt.__cpp_name__` resolves to a null scope and the + # subsequent SizeOf hangs/aborts. Stick with the legacy + # interpreter round-trip until we add a qualified-name + # resolver (or an overload of SizeOf that takes a name). + sz = evaluate("sizeof(%s)" % (_get_name(tt),)) + # scope = gbl.Cpp.GetNamed(_get_name(tt)) + # sz = gbl.Cpp.SizeOf(scope) + _sizes[tt] = sz + return sz + + +_typeids = {} + + +def typeid(tt): + """Returns the C++ runtime type information for type .""" + if not isinstance(tt, type): + tt = type(tt) + try: + return _typeids[tt] + except KeyError: + tidname = "typeid_" + str(len(_typeids)) + cppexec( + "namespace _cppjit_internal { auto* %s = &typeid(%s); }" + % ( + tidname, + _get_name(tt), + ) + ) + tid = getattr(gbl._cppjit_internal, tidname) + _typeids[tt] = tid + return tid + + +def multi(*bases): # after six, see also _typemap.py + """Resolve metaclasses for multiple inheritance.""" + # contruct a "no conflict" meta class; the '_meta' is needed by convention + nc_meta = type.__new__( + type, "cppjit_nc_meta", tuple(type(b) for b in bases if type(b) is not type), {} + ) + + class faux_meta(type): + def __new__(mcs, name, this_bases, d): + return nc_meta(name, bases, d) + + return type.__new__(faux_meta, "faux_meta", (), {}) + + +# - workaround (TODO: may not be needed with Clang9) -------------------------- +if "win32" in sys.platform: + # Ill-formed if std::endl was already instantiated; then the + # instantiation exists and the workaround is unnecessary. + try: + cppdef("""template<> + std::basic_ostream>& __cdecl std::endl>( + std::basic_ostream>&);""") + except SyntaxError: + pass diff --git a/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py new file mode 100644 index 0000000000000..cb4e4ec52525f --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py @@ -0,0 +1,239 @@ +"""CPython-specific touch-ups""" + +import ctypes +import importlib.util +import os +import sys + +from . import _stdcpp_fix # noqa: F401 + +# The libcppjit extension lives inside the package, but its dependent DLLs +# (cpyrt, the ROOT libraries) live one level above, as for the ROOT package +# itself (see ROOT/__init__.py). +if "win32" in sys.platform: + os.add_dll_directory(os.path.dirname(os.path.dirname(__file__))) + +__all__ = [ + "gbl", + "load_reflection_info", + "addressof", + "bind_object", + "nullptr", + "default", + "_backend", + "_begin_capture_stderr", + "_end_capture_stderr", + "_lock_interpreter", + "_unlock_interpreter", +] + + +def _preload_backend_library(): + # preload the merged extension with ctypes and run LoadCppInterOp() first, + # so the interpreter is ready before the extension module initializes + spec = importlib.util.find_spec("cppjit.libcppjit") + if spec is None or not spec.origin: + raise ImportError("cannot locate the cppjit.libcppjit extension module") + lib = ctypes.CDLL(spec.origin, ctypes.RTLD_GLOBAL) + if not hasattr(lib, "LoadCppInterOp"): + # In ROOT's split wiring the loader lives in the cpyrt DLL and the + # thin extension module does not re-export it on Windows. + libdir = os.path.dirname(os.path.dirname(spec.origin)) + lib = ctypes.CDLL(os.path.join(libdir, "libcpyrt.dll")) + if not lib.LoadCppInterOp(): + raise RuntimeError("failed to load CppInterOp (LoadCppInterOp returned 0)") + return lib + + +_w = _preload_backend_library() + +from . import libcppjit as _backend # noqa: E402 + + +### template support --------------------------------------------------------- +class Template(object): # expected/used by ProxyWrappers.cxx in cpyrt + stl_sequence_types = ["std::vector", "std::list", "std::set", "std::deque"] + stl_unrolled_types = ["std::pair"] + stl_fixed_size_types = ["std::array"] + stl_mapping_types = ["std::map", "std::unordered_map"] + + def __init__(self, name, scope): + self.__name__ = name + self.__cpp_name__ = name + self._instantiations = dict() + self.__scope__ = scope + + def __repr__(self): + return "" % (self.__name__, hex(id(self))) + + def __getitem__(self, *args): + # multi-argument to [] becomes a single tuple argument + if args and isinstance(args[0], tuple): + args = args[0] + + # if already instantiated, return the existing class + try: + return self._instantiations[args] + except KeyError: + pass + + # construct the type name from the types or their string representation + newargs = [self.__scope__] + for arg in args: + if isinstance(arg, str): + arg = ",".join(map(lambda x: x.strip(), arg.split(","))) + newargs.append(arg) + pyclass = _backend.MakeCppTemplateClass(*newargs) + + # memoize the class to prevent spurious lookups/re-pythonizations + self._instantiations[args] = pyclass + + # special case pythonization (builtin_map is not available from the C-API) + if "push_back" in pyclass.__dict__ and "__iadd__" not in pyclass.__dict__: + if "reserve" in pyclass.__dict__: + + def iadd(self, ll): + self.reserve(len(ll)) + for x in ll: + self.push_back(x) + return self + else: + + def iadd(self, ll): + for x in ll: + self.push_back(x) + return self + + pyclass.__iadd__ = iadd + + # back-pointer for reflection + pyclass.__cpp_template__ = self + + return pyclass + + def __call__(self, *args): + # for C++17, we're required to derive the type when using initializer syntax + # (i.e. a tuple or list); not sure how to do that in general, but below the + # most common cases are covered + if args: + args0 = args[0] + if args0 and isinstance(args0, (tuple, list)): + t = type(args0[0]) + if t is float: + t = "double" + + if self.__name__ in self.stl_sequence_types: + return self[t](*args) + if self.__name__ in self.stl_fixed_size_types: + return self[t, len(args0)](*args) + if self.__name__ in self.stl_unrolled_types: + return self[tuple(type(a) for a in args0)](*args0) + + if args0 and isinstance(args0, dict): + if self.__name__ in self.stl_mapping_types: + try: + pair = args0.items().__iter__().__next__() + except AttributeError: + pair = args0.items()[0] + t1 = type(pair[0]) + if t1 is float: + t1 = "double" + t2 = type(pair[1]) + if t2 is float: + t2 = "double" + return self[t1, t2](*args) + + return self.__getitem__(*(type(a) for a in args0))(*args) + + # old 'metaclass-style' template instantiations + return self.__getitem__(*args) + + +_backend.Template = Template + + +# - :: and std:: namespaces --------------------------------------------------- +gbl = _backend.CreateScopeProxy("") +gbl.__class__.__repr__ = lambda cls: "" % id(cls) +gbl.std = _backend.CreateScopeProxy("std") +# for move, we want our "pythonized" one, not the C++ template +gbl.std.move = _backend.move +# CppInterOp proxy object to access its API +Cpp = gbl.Cpp + +# - add to the dynamic path as needed ----------------------------------------- +import os # noqa: E402 + + +def _add_default_paths(): + if os.getenv("CONDA_PREFIX"): + # MacOS, Linux + lib_path = os.path.join(os.getenv("CONDA_PREFIX"), "lib") + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + # Windows + lib_path = os.path.join(os.getenv("CONDA_PREFIX"), "Library", "lib") + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + # assuming that we are in PREFIX/lib/python/site-packages/cppjit, add PREFIX/lib to the search path + lib_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir + ) + ) + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + try: + with open("/etc/ld.so.conf") as ldconf: + for line in ldconf: + f = line.strip() + if os.path.exists(f): + Cpp.AddSearchPath(f, True, False) + except IOError: + pass + + +_add_default_paths() + + +# - exports ------------------------------------------------------------------- +addressof = _backend.addressof +bind_object = _backend.bind_object +nullptr = _backend.nullptr +default = _backend.default + + +def load_reflection_info(name): + sc = gbl.gSystem.Load(name) + if sc == -1: + raise RuntimeError("Unable to load reflection library " + name) + + return True + + +_lock_interpreter = _backend._lock_interpreter +_unlock_interpreter = _backend._unlock_interpreter + + +def _begin_capture_stderr(): + _backend._begin_capture_stderr() + + +def _end_capture_stderr(): + err = _backend._end_capture_stderr() + if err: + try: + return "\n%s" % err + except UnicodeDecodeError as e: + original_error = e + try: + return "\n%s" % err.decode("gbk") # not guaranteed, but common + except UnicodeDecodeError: + pass + return "C++ issued an error message that could not be decoded (%s)" % str( + original_error + ) + return "" diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py b/bindings/pyroot/cppjit/python/cppjit/_pythonization.py similarity index 84% rename from bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py rename to bindings/pyroot/cppjit/python/cppjit/_pythonization.py index f8b2a4d0f870f..a3d1e74a24dd8 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py +++ b/bindings/pyroot/cppjit/python/cppjit/_pythonization.py @@ -1,14 +1,14 @@ -""" Pythonization API. -""" +"""Pythonization API.""" import re __all__ = [ - 'add_pythonization', - 'remove_pythonization', - 'pin_type', - 'add_type_reducer', - ] + "add_pythonization", + "remove_pythonization", + "pin_type", + "add_type_reducer", +] + def _set_backend(backend): global _backend @@ -16,16 +16,16 @@ def _set_backend(backend): # user-provided, general pythonizations -def add_pythonization(pythonizor, scope = ''): +def add_pythonization(pythonizor, scope=""): """ should be a callable taking two arguments: a class proxy, and its C++ name. It is called each time a named class from (the global one by default, but a relevant C++ namespace is recommended) is bound. """ return _backend.add_pythonization(pythonizor, scope) -def remove_pythonization(pythonizor, scope = ''): - """Remove previously registered from . - """ + +def remove_pythonization(pythonizor, scope=""): + """Remove previously registered from .""" return _backend.remove_pythonization(pythonizor, scope) @@ -36,8 +36,7 @@ def pin_type(klass): # mapper to reduce template expression trees def add_type_reducer(reducable, reduced): - """Reduce to type on returns from function calls. - """ + """Reduce to type on returns from function calls.""" return _backend._add_type_reducer(reducable, reduced) @@ -46,16 +45,19 @@ def add_exception_mapping(cpp_exception, py_exception): _backend.UserExceptions[cpp_exception] = py_exception +# --- Pythonization factories -------------------------------------------- -#--- Pythonization factories -------------------------------------------- def set_gil_policy(match_class, match_method, release_gil=True): - return set_method_property(match_class, match_method, '__release_gil__', int(release_gil)) + return set_method_property( + match_class, match_method, "__release_gil__", int(release_gil) + ) def set_ownership_policy(match_class, match_method, python_owns_result): - return set_method_property(match_class, match_method, - '__creates__', int(python_owns_result)) + return set_method_property( + match_class, match_method, "__creates__", int(python_owns_result) + ) # NB: Ideally, we'd use the version commented out below, but for now, we @@ -65,18 +67,21 @@ class attribute_pythonizor(object): class getter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj): return getattr(obj, self.attr) class setter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj, value): return setattr(obj, self.attr, value) class deleter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj): return delattr(obj, self.attr) @@ -89,13 +94,15 @@ def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: if self.match_attr.match(k): tmp = property(self.getter(k), self.setter(k), self.deleter(k)) setattr(obj, self.new_attr, tmp) - #if not self.keep_orig: delattr(obj, k) + # if not self.keep_orig: delattr(obj, k) + return attribute_pythonizor(match_class, orig_attribute, new_attribute, keep_orig) + # def rename_attribute(match_class, orig_attribute, new_attribute, keep_orig=False): # class method_pythonizor: # def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): @@ -122,6 +129,7 @@ def __call__(self, obj, name): # Shared with PyPy: + def add_overload(match_class, match_method, overload): class method_pythonizor(object): def __init__(self, match_class, match_method, overload): @@ -132,15 +140,17 @@ def __init__(self, match_class, match_method, overload): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: try: tmp = getattr(obj, k) - except AttributeError: + except: continue if self.match_method.match(k): try: tmp.__add_overload__(overload) - except AttributeError: pass + except AttributeError: + pass + return method_pythonizor(match_class, match_method, overload) @@ -160,14 +170,18 @@ def __call__(self, obj, name): continue try: f = getattr(obj, k) - except AttributeError: + except: continue + def make_fun(f, g): def h(self, *args, **kwargs): return g(self, f(self, *args, **kwargs)) + return h + h = make_fun(f, g) setattr(obj, k, h) + return composition_pythonizor(match_class, match_method, g) @@ -182,17 +196,20 @@ def __init__(self, match_class, match_method, prop, value): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: try: tmp = getattr(obj, k) - except AttributeError: + except: continue if self.match_method.match(k): setattr(tmp, self.prop, self.value) + return method_pythonizor(match_class, match_method, prop, value) -def make_property(match_class, match_get, match_set=None, match_del=None, prop_name=None): +def make_property( + match_class, match_get, match_set=None, match_del=None, prop_name=None +): class property_pythonizor(object): def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_class = re.compile(match_class) @@ -204,7 +221,7 @@ def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_set = re.compile(match_set) match_many_setters = self.match_set.groups == 1 if match_many_getters ^ match_many_setters: - raise ValueError('Must match getters and setters equally') + raise ValueError("Must match getters and setters equally") else: self.match_set = None @@ -212,20 +229,20 @@ def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_del = re.compile(match_del) match_many_deleters = self.match_del.groups == 1 if match_many_getters ^ match_many_deleters: - raise ValueError('Must match getters and deleters equally') + raise ValueError("Must match getters and deleters equally") else: self.match_del = None self.match_many = match_many_getters if not (self.match_many or prop_name): raise ValueError( - "If not matching properties by regex, " - "need a property name with exactly one substitution field") + "If not matching properties by regex, need a property name with exactly one substitution field" + ) if self.match_many and prop_name: - if prop_name.format(').!:(') == prop_name: + if prop_name.format(").!:(") == prop_name: raise ValueError( - "If matching properties by regex and providing a property name, " - "the name needs exactly one substitution field") + "If matching properties by regex and providing a property name, the name needs exactly one substitution field" + ) self.prop_name = prop_name @@ -236,6 +253,7 @@ def __init__(self, getter): def __call__(self, obj): return getattr(obj, self.getter)() + return proxy(getter) def make_set_proxy(self, setter): @@ -245,6 +263,7 @@ def __init__(self, setter): def __call__(self, obj, arg): return getattr(obj, self.setter)(arg) + return proxy(setter) def __call__(self, obj, name): @@ -259,13 +278,13 @@ def __call__(self, obj, name): if not self.match_many: fget, fset, fdel = None, None, None - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_get.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_getters[name] = k @@ -274,13 +293,13 @@ def __call__(self, obj, name): break if self.match_set: - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_set.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_setters[name] = k @@ -289,13 +308,13 @@ def __call__(self, obj, name): break if self.match_del: - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_del.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_deleters[name] = k @@ -313,6 +332,7 @@ def __call__(self, obj, name): names += list(named_deleters.keys()) names = set(names) + properties = [] # noqa: F841 for name in names: if name in named_getters: fget = self.make_get_del_proxy(named_getters[name]) @@ -338,4 +358,3 @@ def __call__(self, obj, name): setattr(obj, prop_name, new_prop) return property_pythonizor(match_class, match_get, match_set, match_del, prop_name) - diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py b/bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py similarity index 63% rename from bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py rename to bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py index 0004c87803b72..7b66014ea3261 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py +++ b/bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py @@ -1,13 +1,14 @@ import sys -# It may be that the interpreter (whether python or pypy-c) was not linked +# It may be that the interpreter (wether python or pypy-c) was not linked # with C++; force its loading before doing anything else (note that not # linking with C++ spells trouble anyway for any C++ libraries ...) -if 'linux' in sys.platform and 'GCC' in sys.version: +if "linux" in sys.platform and "GCC" in sys.version: # TODO: check executable to see whether linking indeed didn't happen import ctypes + try: - stdcpp = ctypes.CDLL('libstdc++.so', ctypes.RTLD_GLOBAL) + stdcpp = ctypes.CDLL("libstdc++.so", ctypes.RTLD_GLOBAL) except Exception: pass # TODO: what if Linux/clang and what if Mac? diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py b/bindings/pyroot/cppjit/python/cppjit/_typemap.py similarity index 71% rename from bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py rename to bindings/pyroot/cppjit/python/cppjit/_typemap.py index fd09510c4af8f..4a182bfaba977 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py +++ b/bindings/pyroot/cppjit/python/cppjit/_typemap.py @@ -1,25 +1,28 @@ -""" Externally provided types: get looked up if all else fails, e.g. - for typedef-ed C++ builtin types. +"""Externally provided types: get looked up if all else fails, e.g. +for typedef-ed C++ builtin types. """ import ctypes import sys import types + def _create_mapper(cls, extra_dct=None): def mapper(name, scope): if scope: - cppname = scope+'::'+name - modname = 'cppyy.gbl.'+scope + cppname = scope + "::" + name + modname = "cppjit.gbl." + scope else: cppname = name - modname = 'cppyy.gbl' - dct = {'__cpp_name__' : cppname, '__module__' : modname} + modname = "cppjit.gbl" + dct = {"__cpp_name__": cppname, "__module__": modname} if extra_dct: dct.update(extra_dct) return type(name, (cls,), dct) + return mapper + # from six.py --- # Copyright (c) 2010-2017 Benjamin Peterson # @@ -41,20 +44,21 @@ def mapper(name, scope): # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. + def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(type): - def __new__(cls, name, this_bases, d): if sys.version_info[:2] >= (3, 7): # This version introduced PEP 560 that requires a bit # of extra care (we mimic what is done by __build_class__). resolved_bases = types.resolve_bases(bases) if resolved_bases is not bases: - d['__orig_bases__'] = bases + d["__orig_bases__"] = bases else: resolved_bases = bases return meta(name, resolved_bases, d) @@ -62,57 +66,74 @@ def __new__(cls, name, this_bases, d): @classmethod def __prepare__(cls, name, this_bases): return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) + + return type.__new__(metaclass, "temporary_class", (), {}) + + # --- end from six.py + class _BoolMeta(type): - def __call__(cls, val = bool()): + def __call__(self, val=bool()): if val: return True - return False + else: + return False + class _Bool(with_metaclass(_BoolMeta, object)): pass def initialize(backend): - if not hasattr(backend, 'type_map'): + if not hasattr(backend, "type_map"): return tm = backend.type_map # boolean type (builtin type bool can nog be subclassed) - tm['bool'] = _create_mapper(_Bool) + tm["bool"] = _create_mapper(_Bool) # char types str_tm = _create_mapper(str) - for tp in ['char', 'unsigned char', 'signed char']: + for tp in ["char", "unsigned char", "signed char"]: tm[tp] = str_tm if sys.hexversion < 0x3000000: - tm['wchar_t'] = _create_mapper(unicode) + tm["wchar_t"] = _create_mapper(unicode) # noqa: F821 else: - tm['wchar_t'] = str_tm + tm["wchar_t"] = str_tm # integer types int_tm = _create_mapper(int) - for tp in ['int8_t', 'uint8_t', 'short', 'unsigned short', 'int']: + for tp in ["int8_t", "uint8_t", "short", "unsigned short", "int"]: tm[tp] = int_tm if sys.hexversion < 0x3000000: - long_tm = _create_mapper(long) + long_tm = _create_mapper(long) # noqa: F821 else: - long_tm = tm['int'] - for tp in ['unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long']: + long_tm = tm["int"] + for tp in [ + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + ]: tm[tp] = long_tm # floating point types float_tm = _create_mapper(float) - for tp in ['float', 'double', 'long double']: + for tp in ["float", "double", "long double"]: tm[tp] = float_tm # void* def voidp_init(self, arg=0): - import cppyy, ctypes - if arg == cppyy.nullptr: arg = 0 + import ctypes + + import cppjit + + if arg == cppjit.nullptr: + arg = 0 ctypes.c_void_p.__init__(self, arg) - tm['void*'] = _create_mapper(ctypes.c_void_p, {'__init__' : voidp_init}) + + tm["void *"] = _create_mapper(ctypes.c_void_p, {"__init__": voidp_init}) diff --git a/bindings/pyroot/cppjit/python/cppjit/_version.py b/bindings/pyroot/cppjit/python/cppjit/_version.py new file mode 100644 index 0000000000000..4ca1377f19634 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_version.py @@ -0,0 +1 @@ +__version__ = "0.1.0b1.dev0" diff --git a/bindings/pyroot/cppjit/python/cppjit/ll.py b/bindings/pyroot/cppjit/python/cppjit/ll.py new file mode 100644 index 0000000000000..9621e9f20ddc5 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/ll.py @@ -0,0 +1,174 @@ +"""Low-level utilities, to be used for "emergencies only".""" + +import ctypes +import sys +import warnings + +import cppjit + +try: + import __pypy__ + + del __pypy__ + ispypy = True +except ImportError: + ispypy = False + +__all__ = [ + "argv", + "argc", + "cast", + "static_cast", + "reinterpret_cast", + "dynamic_cast", + "malloc", + "free", + "array_new", + "array_delete", + "signals_as_exception", + "set_signals_as_exception", + "FatalError", + "BusError", + "SegmentationViolation", + "IllegalInstruction", + "AbortSignal", +] + + +# convenience functions to create C-style argv/argc +def argv(): + argc = len(sys.argv) # noqa: F841 + cargsv = (ctypes.c_char_p * len(sys.argv))(*(x.encode() for x in sys.argv)) + return ctypes.POINTER(ctypes.c_char_p)(cargsv) + + +def argc(): + return len(sys.argv) + + +# import low-level python converters +for _name in ["addressof", "as_cobject", "as_capsule", "as_ctypes", "as_memoryview"]: + try: + exec("%s = cppjit._backend.%s" % (_name, _name)) + __all__.append(_name) + except AttributeError: + pass +del _name + +# create low-level helpers once +if not hasattr(cppjit.gbl, "__cppjit_internal") or not hasattr( + cppjit.gbl.__cppjit_internal, "cppjit_cast" +): + cppjit.cppdef("""namespace __cppjit_internal { + // type casting + template + T cppjit_cast(U val) { return (T)val; } + + template + T cppjit_static_cast(U val) { return static_cast(val); } + + template + T cppjit_reinterpret_cast(U val) { return reinterpret_cast(val); } + + template + T* cppjit_dynamic_cast(S* obj) { return dynamic_cast(obj); } + + // memory allocation/free-ing + template + T* cppjit_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } + + template + T* cppjit_array_new(size_t count) { return new T[count]; } + + template + void cppjit_array_delete(T* ptr) { delete[] ptr; } + }""") + + +# helper for sizing arrays +class ArraySizer(object): + def __init__(self, func): + self.func = func + + def __getitem__(self, t): + self.array_type = t + return self + + def __call__(self, size, managed=False): + res = self.func[self.array_type](size) + try: + res.reshape((size,) + res.shape[1:]) + if managed: + res.__python_owns__ = True + except AttributeError: + res.__reshape__((size,)) + if managed: + warnings.warn("managed low-level arrays of instances not supported") + return res + + +class CArraySizer(ArraySizer): + def __call__(self, size, managed=False): + res = ArraySizer.__call__(self, size, managed) + res.__cpp_array__ = False + return res + + +# import casting helpers +cast = cppjit.gbl.__cppjit_internal.cppjit_cast +static_cast = cppjit.gbl.__cppjit_internal.cppjit_static_cast +reinterpret_cast = cppjit.gbl.__cppjit_internal.cppjit_reinterpret_cast +dynamic_cast = cppjit.gbl.__cppjit_internal.cppjit_dynamic_cast + +# import memory allocation/free-ing helpers +malloc = CArraySizer(cppjit.gbl.__cppjit_internal.cppjit_malloc) +free = cppjit.gbl.free # for symmetry +array_new = ArraySizer(cppjit.gbl.__cppjit_internal.cppjit_array_new) +array_delete = cppjit.gbl.__cppjit_internal.cppjit_array_delete + +# signals as exceptions +if not ispypy: + FatalError = cppjit._backend.FatalError + BusError = cppjit._backend.BusError + SegmentationViolation = cppjit._backend.SegmentationViolation + IllegalInstruction = cppjit._backend.IllegalInstruction + AbortSignal = cppjit._backend.AbortSignal + + class signals_as_exception: + def __enter__(self): + cppjit._backend.SetGlobalSignalPolicy(1) + + def __exit__(self, type, value, traceback): + cppjit._backend.SetGlobalSignalPolicy(0) + + set_signals_as_exception = cppjit._backend.SetGlobalSignalPolicy + +else: + + class FatalError(Exception): + pass + + class BusError(FatalError): + pass + + class SegmentationViolation(FatalError): + pass + + class IllegalInstruction(FatalError): + pass + + class AbortSignal(FatalError): + pass + + class signals_as_exception: + def __enter__(self): + pass # not yet implemented + + def __exit__(self, type, value, traceback): + pass # not yet implemented + + def set_signals_as_exception(seton): + return False + + +del ispypy diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py b/bindings/pyroot/cppjit/python/cppjit/numba_ext.py similarity index 67% rename from bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py rename to bindings/pyroot/cppjit/python/cppjit/numba_ext.py index a83985b909094..0f4a556098191 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py +++ b/bindings/pyroot/cppjit/python/cppjit/numba_ext.py @@ -1,23 +1,22 @@ -""" cppyy extensions for numba -""" +"""cppjit extensions for numba""" -import cppyy -import cppyy.types as cpp_types -import cppyy.reflex as cpp_refl +import re import numba -import numba.extending as nb_ext import numba.core.cgutils as nb_cgu import numba.core.datamodel as nb_dm import numba.core.imputils as nb_iutils import numba.core.registry as nb_reg -import numba.core.typing.templates as nb_tmpl import numba.core.types as nb_types import numba.core.typing as nb_typing - +import numba.core.typing.templates as nb_tmpl +import numba.extending as nb_ext from llvmlite import ir -from numba.extending import make_attribute_wrapper -import re + +import cppjit +import cppjit.reflex as cpp_refl +import cppjit.types as cpp_types + # setuptools entry point for Numba def _init_extension(): @@ -25,49 +24,53 @@ def _init_extension(): class Qualified: - default = 0 - value = 1 - instance = 2 + default = 0 + value = 1 + instance = 2 + -ir_byte = ir.IntType(8) -ir_voidptr = ir.PointerType(ir_byte) # by convention -ir_byteptr = ir_voidptr # for clarity -ir_intptr_t = ir.IntType(cppyy.sizeof('void*')*8) +ir_byte = ir.IntType(8) +ir_voidptr = ir.PointerType(ir_byte) # by convention +ir_byteptr = ir_voidptr # for clarity +ir_intptr_t = ir.IntType(cppjit.sizeof("void*") * 8) # special case access to unboxing/boxing APIs -cppyy_as_voidptr = cppyy.addressof('Instance_AsVoidPtr') -cppyy_from_voidptr = cppyy.addressof('Instance_FromVoidPtr') +cppjit_as_voidptr = cppjit.addressof("Instance_AsVoidPtr") +cppjit_from_voidptr = cppjit.addressof("Instance_FromVoidPtr") _cpp2numba = { - 'void' : nb_types.void, - 'void*' : nb_types.voidptr, - 'int8_t' : nb_types.int8, - 'uint8_t' : nb_types.uint8, - 'short' : nb_types.short, - 'unsigned short' : nb_types.ushort, - 'int' : nb_types.intc, - 'unsigned int' : nb_types.uintc, - 'int32_t' : nb_types.int32, - 'uint32_t' : nb_types.uint32, - 'int64_t' : nb_types.int64, - 'uint64_t' : nb_types.uint64, - 'long' : nb_types.long_, - 'unsigned long' : nb_types.ulong, - 'long long' : nb_types.longlong, - 'unsigned long long' : nb_types.ulonglong, - 'float' : nb_types.float32, - 'double' : nb_types.float64, - 'char' : nb_types.char, - 'unsigned char' : nb_types.uchar, - 'char*' : nb_types.unicode_type + "void": nb_types.void, + "void*": nb_types.voidptr, + "int8_t": nb_types.int8, + "uint8_t": nb_types.uint8, + "short": nb_types.short, + "unsigned short": nb_types.ushort, + "int": nb_types.intc, + "unsigned int": nb_types.uintc, + "int32_t": nb_types.int32, + "uint32_t": nb_types.uint32, + "int64_t": nb_types.int64, + "uint64_t": nb_types.uint64, + "long": nb_types.long_, + "unsigned long": nb_types.ulong, + "long long": nb_types.longlong, + "unsigned long long": nb_types.ulonglong, + "float": nb_types.float32, + "double": nb_types.float64, + "char": nb_types.char, + "unsigned char": nb_types.uchar, + "char*": nb_types.unicode_type, } + def resolve_std_vector(val): - return re.match(r'std::vector<(.+?)>', val).group(1) + return re.match(r"std::vector<(.+?)>", val).group(1) + def resolve_const_types(val): - return re.match(r'const\s+(.+)\s*\*', val).group(1) + return re.match(r"const\s+(.+)\s*\*", val).group(1) + def cpp2numba(val): if not isinstance(val, str): @@ -78,24 +81,26 @@ def cpp2numba(val): elif val.startswith("std::vector"): type_arr = getattr(numba, str(cpp2numba(resolve_std_vector(val))))[:] return type_arr - elif val[-1] == '*' or val[-1] == '&': - if val.startswith('const'): + elif val[-1] == "*" or val[-1] == "&": + if val.startswith("const"): return nb_types.CPointer(cpp2numba(resolve_const_types(val))) - return nb_types.CPointer(_cpp2numba[val[:-1]]) + return nb_types.CPointer(_cpp2numba[val[:-2]]) return _cpp2numba[val] + _numba2cpp = dict() for key, value in _cpp2numba.items(): _numba2cpp[value] = key # prefer "int" in the case of intc over "int32_t" -_numba2cpp[nb_types.intc] = 'int' +_numba2cpp[nb_types.intc] = "int" + def numba2cpp(val): - if hasattr(val, 'literal_type'): + if hasattr(val, "literal_type"): val = val.literal_type - if val == nb_types.int64: # Python int + if val == nb_types.int64: # Python int # TODO: this is only necessary until "best matching" is in place - val = nb_types.intc # more likely match candidate + val = nb_types.intc # more likely match candidate elif isinstance(val, numba.types.CPointer): return _numba2cpp[val.dtype] elif isinstance(val, numba.types.RawPointer): @@ -110,6 +115,7 @@ def numba2cpp(val): except: raise RuntimeError("Type mapping failed from Numba to C++ for ", val) + def numba_arg_convertor(args): args_cpp = [] for arg in list(args): @@ -124,34 +130,37 @@ def numba_arg_convertor(args): args_cpp.append(numba2cpp(arg)) return tuple(args_cpp) + def to_ref(type_list): ref_list = [] - for l in type_list: - ref_list.append(l + '&') + for l in type_list: # noqa: E741 + ref_list.append(l + " &") return ref_list + # TODO: looks like Numba treats unsigned types as signed when lowering, # which seems to work as they're just reinterpret_casts _cpp2ir = { - 'char*' : ir_byteptr, - 'int8_t' : ir.IntType(8), - 'uint8_t' : ir.IntType(8), - 'short' : ir.IntType(nb_types.short.bitwidth), - 'unsigned short' : ir.IntType(nb_types.ushort.bitwidth), - 'int' : ir.IntType(nb_types.intc.bitwidth), - 'unsigned int' : ir.IntType(nb_types.uintc.bitwidth), - 'int32_t' : ir.IntType(32), - 'uint32_t' : ir.IntType(32), - 'int64_t' : ir.IntType(64), - 'uint64_t' : ir.IntType(64), - 'long' : ir.IntType(nb_types.long_.bitwidth), - 'unsigned long' : ir.IntType(nb_types.ulong.bitwidth), - 'long long' : ir.IntType(nb_types.longlong.bitwidth), - 'unsigned long long' : ir.IntType(nb_types.ulonglong.bitwidth), - 'float' : ir.FloatType(), - 'double' : ir.DoubleType(), + "char *": ir_byteptr, + "int8_t": ir.IntType(8), + "uint8_t": ir.IntType(8), + "short": ir.IntType(nb_types.short.bitwidth), + "unsigned short": ir.IntType(nb_types.ushort.bitwidth), + "int": ir.IntType(nb_types.intc.bitwidth), + "unsigned int": ir.IntType(nb_types.uintc.bitwidth), + "int32_t": ir.IntType(32), + "uint32_t": ir.IntType(32), + "int64_t": ir.IntType(64), + "uint64_t": ir.IntType(64), + "long": ir.IntType(nb_types.long_.bitwidth), + "unsigned long": ir.IntType(nb_types.ulong.bitwidth), + "long long": ir.IntType(nb_types.longlong.bitwidth), + "unsigned long long": ir.IntType(nb_types.ulonglong.bitwidth), + "float": ir.FloatType(), + "double": ir.DoubleType(), } + def cpp2ir(val): try: return _cpp2ir[val] @@ -160,12 +169,13 @@ def cpp2ir(val): ## TODO should be possible to obtain the vector length from the CPPDataMember val type_arr = ir.VectorType(cpp2ir(resolve_std_vector(val)), 3) return type_arr - elif val != "char*" and val[-1] == "*": - if val.startswith('const'): + elif val != "char *" and val[-1] == "*": + if val.startswith("const"): return ir.PointerType(cpp2ir(resolve_const_types(val))) - type_2 = _cpp2ir[val[:-1]] + type_2 = _cpp2ir[val[:-2]] return ir.PointerType(type_2) + # # C++ function pointer -> Numba # @@ -174,7 +184,7 @@ class CppFunctionNumbaType(nb_types.Callable): requires_gil = False def __init__(self, func, is_method=False): - super(CppFunctionNumbaType, self).__init__('CppFunction(%s)' % str(func)) + super(CppFunctionNumbaType, self).__init__("CppFunction(%s)" % str(func)) self.sig = None self._func = func @@ -186,7 +196,7 @@ def __init__(self, func, is_method=False): self.ret_type = None def is_precise(self): - return True # by definition + return True # by definition def get_call_type(self, context, args, kwds): try: @@ -195,7 +205,8 @@ def get_call_type(self, context, args, kwds): pass ol = CppFunctionNumbaType( - self._func.__overload__(numba_arg_convertor(args)), self._is_method) + self._func.__overload__(numba_arg_convertor(args)), self._is_method + ) thistype = None if self._is_method: @@ -203,28 +214,34 @@ def get_call_type(self, context, args, kwds): self.ret_type = cpp2numba(ol._func.__cpp_reflex__(cpp_refl.RETURN_TYPE)) ol.sig = nb_typing.Signature( - return_type=self.ret_type, - args=args, - recvr=thistype) + return_type=self.ret_type, args=args, recvr=thistype + ) extsig = ol.sig if self._is_method: self.ret_type = ol.sig.return_type args = (nb_types.voidptr, *args) extsig = nb_typing.Signature( - return_type=ol.sig.return_type, args=args, recvr=None) + return_type=ol.sig.return_type, args=args, recvr=None + ) self._impl_keys[args] = ol self._arg_set_matched = numba_arg_convertor(args) - @nb_iutils.lower_builtin(ol, *args) - def lower_external_call(context, builder, sig, args, - ty=nb_types.ExternalFunctionPointer(extsig, ol.get_pointer), - pyval=self._func, is_method=self._is_method): + def lower_external_call( + context, + builder, + sig, + args, + ty=nb_types.ExternalFunctionPointer(extsig, ol.get_pointer), + pyval=self._func, + is_method=self._is_method, + ): ptrty = context.get_function_pointer_type(ty) ptrval = context.add_dynamic_addr( - builder, ty.get_pointer(pyval), info=str(pyval)) + builder, ty.get_pointer(pyval), info=str(pyval) + ) fptr = builder.bitcast(ptrval, ptrty) return context.call_function_pointer(builder, fptr, args) @@ -244,7 +261,7 @@ def get_pointer(self, func): ol = func.__overload__(numba_arg_convertor(self.sig.args)) - address = cppyy.addressof(ol) + address = cppjit.addressof(ol) if not address: raise RuntimeError("unresolved address for %s" % str(ol)) return address @@ -258,20 +275,23 @@ def key(self): def typeof_function(val, c): return CppFunctionNumbaType(val) + @nb_ext.typeof_impl.register(cpp_types.Template) def typeof_template(val, c): - if hasattr(val, '__overload__'): + if hasattr(val, "__overload__"): return CppFunctionNumbaType(val) raise RuntimeError("only function templates supported") + @nb_ext.register_model(CppFunctionNumbaType) class CppFunctionModel(nb_dm.models.PrimitiveModel): def __init__(self, dmm, fe_type): - # the function pointer of this overload can not be exactly typed, but - # only the storage size is relevant, so simply use a void* + # the function pointer of this overload can not be exactly typed, but + # only the storage size is relevant, so simply use a void* be_type = ir.PointerType(dmm.lookup(nb_types.void).get_value_type()) super(CppFunctionModel, self).__init__(dmm, fe_type, be_type) + @nb_iutils.lower_constant(CppFunctionNumbaType) def constant_function_pointer(context, builder, ty, pyval): # TODO: needs to exist for the proper flow, but why? The lowering of the @@ -283,10 +303,10 @@ def constant_function_pointer(context, builder, ty, pyval): # C++ method / data member -> Numba # class CppDataMemberInfo(object): - __slots__ = ['f_name', 'f_offset', 'f_nbtype', 'f_irtype'] + __slots__ = ["f_name", "f_offset", "f_nbtype", "f_irtype"] def __init__(self, name, offset, cpptype): - self.f_name = name + self.f_name = name self.f_offset = offset self.f_nbtype = cpp2numba(cpptype) self.f_irtype = cpp2ir(cpptype) @@ -300,13 +320,13 @@ def __init__(self, scope, qualifier): addr = None cppinstance_val = None if qualifier == Qualified.instance: - addr = cppyy.addressof(scope) + addr = cppjit.addressof(scope) cppinstance_val = scope scope = type(scope) qualifier = Qualified.default super(CppClassNumbaType, self).__init__(scope.__init__) - self.name = 'CppClass(%s)' % scope.__cpp_name__ # overrides value in Type - self._scope = scope + self.name = "CppClass(%s)" % scope.__cpp_name__ # overrides value in Type + self._scope = scope self._qualifier = qualifier self._cppinstanceval = cppinstance_val self._addr = addr @@ -359,12 +379,15 @@ def generic_resolve(self, typ, attr): return ft + @nb_iutils.lower_getattr_generic(CppClassNumbaType) def cppclass_getattr_impl(context, builder, typ, val, attr): # TODO: the following relies on the fact that numba will first lower the # field access, then immediately lower the call; and that the `val` loads # the struct representing the C++ object. Neither need be stable. - if attr in typ._scope.__dict__ and isinstance(typ._scope.__dict__[attr], cpp_types.DataMember): + if attr in typ._scope.__dict__ and isinstance( + typ._scope.__dict__[attr], cpp_types.DataMember + ): dm = typ._scope.__dict__[attr] ct = dm.__cpp_reflex__(cpp_refl.TYPE) offset = dm.__cpp_reflex__(cpp_refl.OFFSET) @@ -389,7 +412,7 @@ def cppclass_getattr_impl(context, builder, typ, val, attr): pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) return builder.load(pf) - # assume this is a method + # assume this is a method q = typ.get_qualifier() if q == Qualified.default: return builder.bitcast(val, ir_voidptr) @@ -408,8 +431,8 @@ def get(self, builder, val, pos): if isinstance(pos, str): pos = self.get_field_position(pos) - # Use the offsets for direct addressing, rather than getting the elements - # from the struct type. + # Use the offsets for direct addressing, rather than getting the elements + # from the struct type. dmi = self._data_members[pos] stack = nb_cgu.alloca_once(builder, self.get_data_type()) @@ -421,34 +444,37 @@ def get(self, builder, val, pos): return builder.load(pf) + class ImplClassValueModel(ImplAggregateValueModel): - # TODO : Should the address have to be passed here and stored in meminfo - # value: representation inside function body. Maybe stored in stack. - # The representation here are flexible. + # TODO : Should the address have to be passed here and stored in meminfo + # value: representation inside function body. Maybe stored in stack. + # The representation here are flexible. def get_value_type(self): return self.get_data_type() - # data: representation used when storing into containers (e.g. arrays). + # data: representation used when storing into containers (e.g. arrays). def get_data_type(self): - # The struct model relies on data being a POD, but for C++ objects, there - # can be hidden data (e.g. vtable, thunks, or simply private members), and - # the alignment of Cling and Numba also need not be the same. Therefore, the - # struct is split in a series of byte members to get the total size right - # and to allow addressing at the correct offsets. + # The struct model relies on data being a POD, but for C++ objects, there + # can be hidden data (e.g. vtable, thunks, or simply private members), and + # the alignment of Cling and Numba also need not be the same. Therefore, the + # struct is split in a series of byte members to get the total size right + # and to allow addressing at the correct offsets. if self._data_type is None: - self._data_type = \ - ir.LiteralStructType([ir_byte for i in range(self._sizeof)], packed=True) + self._data_type = ir.LiteralStructType( + [ir_byte for i in range(self._sizeof)], packed=True + ) return self._data_type - # return: representation used for return argument. + # return: representation used for return argument. def get_return_type(self): return self.get_data_type() scope_numbatypes = (dict(), dict()) + @nb_ext.typeof_impl.register(cpp_types.Scope) -def typeof_scope(val, c, q = Qualified.default): +def typeof_scope(val, c, q=Qualified.default): is_instance = False cppinstance_val = None if q == Qualified.instance: @@ -479,35 +505,40 @@ class ImplClassType(CppClassNumbaType): scope_numbatypes[q][val] = cnt - # declare data members to Numba + # declare data members to Numba data_members = list() member_methods = dict() for name, field in val.__dict__.items(): if isinstance(field, cpp_types.DataMember): - data_members.append(CppDataMemberInfo( - name, field.__cpp_reflex__(cpp_refl.OFFSET), field.__cpp_reflex__(cpp_refl.TYPE)) + data_members.append( + CppDataMemberInfo( + name, + field.__cpp_reflex__(cpp_refl.OFFSET), + field.__cpp_reflex__(cpp_refl.TYPE), + ) ) elif isinstance(field, cpp_types.Function): member_methods[name] = field.__cpp_reflex__(cpp_refl.RETURN_TYPE) - # TODO: this refresh is needed b/c the scope type is registered as a - # callable after the tracing started; no idea of the side-effects ... + # TODO: this refresh is needed b/c the scope type is registered as a + # callable after the tracing started; no idea of the side-effects ... nb_reg.cpu_target.typing_context.refresh() - # create a model description for Numba + # create a model description for Numba if q == Qualified.default: + @nb_ext.register_model(ImplClassType) class ImplClassModel(nb_dm.models.StructModel): def __init__(self, dmm, fe_type): self._data_members = data_members self._member_methods = member_methods - # TODO: eventually we need not derive from StructModel + # TODO: eventually we need not derive from StructModel members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) - # proxies are always accessed by pointer, which are not composites + # proxies are always accessed by pointer, which are not composites def traverse(self, builder): return [] @@ -515,25 +546,25 @@ def traverse_models(self): return [] def traverse_types(self): - return [self._fe_type] # from StructModel + return [self._fe_type] # from StructModel - # data: representation used when storing into containers (e.g. arrays). + # data: representation used when storing into containers (e.g. arrays). # TODO ... - # value: representation inside function body. Maybe stored in stack. - # The representation here are flexible. + # value: representation inside function body. Maybe stored in stack. + # The representation here are flexible. def get_value_type(self): - # the C++ object, b/c through a proxy, is always accessed by pointer; it is - # represented as a pointer to POD to allow indexing by Numba for data member - # type checking, but the address offsetting for loading data member values is - # independent (see get(), below), so the exact layout need not match a POD + # the C++ object, b/c through a proxy, is always accessed by pointer; it is + # represented as a pointer to POD to allow indexing by Numba for data member + # type checking, but the address offsetting for loading data member values is + # independent (see get(), below), so the exact layout need not match a POD - # TODO: this doesn't work for real PODs, b/c those are unpacked into their elements - # and passed through registers + # TODO: this doesn't work for real PODs, b/c those are unpacked into their elements + # and passed through registers return ir.PointerType(super(ImplClassModel, self).get_value_type()) - # argument: representation used for function argument. Needs to be builtin type, - # but unlike other Numba composites, C++ proxies are not flattened. + # argument: representation used for function argument. Needs to be builtin type, + # but unlike other Numba composites, C++ proxies are not flattened. def get_argument_type(self): return self.get_value_type() @@ -543,10 +574,10 @@ def as_argument(self, builder, value): def from_argument(self, builder, value): return value - # return: representation used for return argument. + # return: representation used for return argument. # TODO ... - # access to public data members + # access to public data members def get(self, builder, val, pos): """Get a field at the given position/field name""" @@ -563,21 +594,23 @@ def get(self, builder, val, pos): elif q == Qualified.value: if val.__cpp_reflex__(cpp_refl.IS_AGGREGATE): + @nb_ext.register_model(ImplClassType) class ImplClassModel(ImplAggregateValueModel): pass else: + @nb_ext.register_model(ImplClassType) class ImplClassModel(ImplClassValueModel): pass - def init(self, dmm, fe_type, sz = cppyy.sizeof(val)): + def init(self, dmm, fe_type, sz=cppjit.sizeof(val)): self._data_members = data_members self._member_methods = member_methods self._sizeof = sz - # TODO: this code exists purely to be able to use the indexing and hierarchy - # of the base class StructModel, which isn't much of a reason + # TODO: this code exists purely to be able to use the indexing and hierarchy + # of the base class StructModel, which isn't much of a reason members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) @@ -586,13 +619,15 @@ def init(self, dmm, fe_type, sz = cppyy.sizeof(val)): else: assert not "unknown qualified type" - # Python proxy unwrapping for arguments into the Numba trace + # Python proxy unwrapping for arguments into the Numba trace @nb_ext.unbox(ImplClassType) def unbox_instance(typ, obj, c): - global cppyy_as_voidptr + global cppjit_as_voidptr ptrty = ir.PointerType(ir.FunctionType(ir_voidptr, [ir_voidptr])) - ptrval = c.context.add_dynamic_addr(c.builder, cppyy_as_voidptr, info='Instance_AsVoidPtr') + ptrval = c.context.add_dynamic_addr( + c.builder, cppjit_as_voidptr, info="Instance_AsVoidPtr" + ) fp = c.builder.bitcast(ptrval, ptrty) vptr = c.context.call_function_pointer(c.builder, fp, [obj]) @@ -604,33 +639,33 @@ def unbox_instance(typ, obj, c): def make_implclass(context, builder, typ, **kwargs): return nb_cgu.create_struct_proxy(typ)(context, builder, **kwargs) - # C++ object to Python proxy wrapping for returns from Numba trace + # C++ object to Python proxy wrapping for returns from Numba trace @nb_ext.box(ImplClassType) def box_instance(typ, val, c): assert not "requires object model and passing of intact object, not memberwise copy" - global cppyy_from_voidptr + global cppjit_from_voidptr if isinstance(val, ir.Constant): if val.constant == ir.Undefined: assert not "Value passed to instance boxing is undefined" - return NULL + return NULL # noqa: F821 implclass = make_implclass(c.context, c.builder, typ) classobj = c.pyapi.unserialize(c.pyapi.serialize_object(cpp_types.Instance)) box_list = [] - model = implclass._datamodel + model = implclass._datamodel # noqa: F841 cfr = CppClassFieldResolver(c.context) for i in typ._scope.__dict__: if isinstance(cfr.generic_resolve(typ, i), nb_types.Type): - box_list.append(c.box(cfr.generic_resolve(typ, i), getattr(implclass, i))) + box_list.append( + c.box(cfr.generic_resolve(typ, i), getattr(implclass, i)) + ) - box_res = c.pyapi.call_function_objargs( - classobj, tuple(box_list) - ) + box_res = c.pyapi.call_function_objargs(classobj, tuple(box_list)) # Required for nopython mode, numba nrt requres each member box call to decref # since it steals the reference for i in box_list: @@ -640,6 +675,7 @@ def box_instance(typ, val, c): return cnt + # # C++ instance -> Numba # diff --git a/bindings/pyroot/cppjit/python/cppjit/reflex.py b/bindings/pyroot/cppjit/python/cppjit/reflex.py new file mode 100644 index 0000000000000..70be356a47a73 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/reflex.py @@ -0,0 +1,26 @@ +"""cppjit reflection""" + +import cppjit + +try: + import __pypy__ # noqa: F401 + + __all__ = [] + +except ImportError: + __all__ = [ + "RETURN_TYPE", + ] + + cppjit.include("cpyrt/Reflex.h") + + IS_NAMESPACE = cppjit.gbl.cppjit.interop.Reflex.IS_NAMESPACE + IS_AGGREGATE = cppjit.gbl.cppjit.interop.Reflex.IS_AGGREGATE + + OFFSET = cppjit.gbl.cppjit.interop.Reflex.OFFSET + RETURN_TYPE = cppjit.gbl.cppjit.interop.Reflex.RETURN_TYPE + TYPE = cppjit.gbl.cppjit.interop.Reflex.TYPE + + OPTIMAL = cppjit.gbl.cppjit.interop.Reflex.OPTIMAL + AS_TYPE = cppjit.gbl.cppjit.interop.Reflex.AS_TYPE + AS_STRING = cppjit.gbl.cppjit.interop.Reflex.AS_STRING diff --git a/bindings/pyroot/cppjit/python/cppjit/types.py b/bindings/pyroot/cppjit/python/cppjit/types.py new file mode 100644 index 0000000000000..418ea1b127989 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/types.py @@ -0,0 +1,32 @@ +"""C++ proxy types.""" + +import cppjit + +bck = cppjit._backend +Instance = bck.CPPInstance + +try: + import __pypy__ # noqa: F401 + + __all__ = ["Instance"] + +except ImportError: + __all__ = [ + "DataMember", + "Instance", + "Function", + "MethodScope", # noqa: F822 + "InstanceArray", + "LowLevelView", + "Template", + ] + + DataMember = bck.CPPDataMember + Function = bck.CPPOverload + Method = bck.CPPOverload + Scope = bck.CPPScope + InstanceArray = bck.InstanceArray + LowLevelView = bck.LowLevelView + Template = bck.TemplateProxy + +del bck diff --git a/bindings/pyroot/cppjit/requirements.txt b/bindings/pyroot/cppjit/requirements.txt new file mode 100644 index 0000000000000..7b3c9bf569975 --- /dev/null +++ b/bindings/pyroot/cppjit/requirements.txt @@ -0,0 +1,10 @@ +pytest +pytest-xdist + +# Numba tests (numba dropped macOS x86_64 wheels after 0.61) +numba==0.67.0; sys_platform != "darwin" or platform_machine != "x86_64" +numba==0.61.2; sys_platform == "darwin" and platform_machine == "x86_64" and python_version < "3.14" +numpy + +# Leak check tests +psutil diff --git a/bindings/pyroot/cppjit/src/CMakeLists.txt b/bindings/pyroot/cppjit/src/CMakeLists.txt new file mode 100644 index 0000000000000..b63ff156f05c1 --- /dev/null +++ b/bindings/pyroot/cppjit/src/CMakeLists.txt @@ -0,0 +1,196 @@ +# 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. + +# ROOT build of the vendored cppjit tree. Upstream's scikit-build-core +# CMake (../CMakeLists.txt fetches CppInterOp as an external project) is +# vendored but unreferenced: CppInterOp comes from interpreter/CppInterOp, +# compiled into libCling, with its headers staged under etc/cppinterop. + +# --- interop layer (the cppyy-backend equivalent) ---------------------------- + +add_library(cppjit_backend STATIC + interop/interop_wrapper.cxx + interop/cppinterop_dispatch.cxx) + +target_include_directories(cppjit_backend PRIVATE + ${CMAKE_BINARY_DIR}/etc/cppinterop + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/cpyrt + ${CMAKE_CURRENT_SOURCE_DIR}/interop +) + +target_compile_definitions(cppjit_backend PRIVATE + CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" + # The interpreter is cling, which provides the cling::printValue pretty + # printer op_str looks up. + CPPJIT_USE_CLING + # The CppInterOp JitCall debug assertions (AreArgumentsValid, + # ReportInvokeStart) use Clang AST internals and cannot be resolved + # through the dispatch mechanism. Disable them in this translation unit. + NDEBUG + # libcpyrt owns the dispatch table (cppinterop_dispatch.cxx); consumers in + # other DLLs (ROOTPythonizations, tpython) import it. On Windows the data + # symbols need explicit dllexport - WINDOWS_EXPORT_ALL_SYMBOLS covers + # functions only. + CPPINTEROP_DISPATCH_EXPORTS +) + +target_include_directories(cppjit_backend SYSTEM PRIVATE ${Python3_INCLUDE_DIRS}) + +target_link_libraries(cppjit_backend Core) +# Ensure CppInterOpEtc responsible for staging the tablegen-generated .inc +# files is built before cppjit_backend. +add_dependencies(cppjit_backend CppInterOpEtc) + +if(NOT MSVC) + target_compile_options(cppjit_backend PRIVATE -fPIC) +endif() + +# --- cpyrt (the CPyCppyy equivalent) ------------------------------------------ + +set(headers + cpyrt/API.h + cpyrt/Reflex.h + cpyrt/CommonDefs.h + cpyrt/PyException.h + cpyrt/DispatchPtr.h +) + +set(sources + cpyrt/API.cxx + cpyrt/CPPClassMethod.cxx + cpyrt/CPPConstructor.cxx + cpyrt/CPPDataMember.cxx + cpyrt/CPPEnum.cxx + cpyrt/CPPExcInstance.cxx + cpyrt/CPPFunction.cxx + cpyrt/CPPGetSetItem.cxx + cpyrt/CPPInstance.cxx + cpyrt/CPPMethod.cxx + cpyrt/CPPOperator.cxx + cpyrt/CPPOverload.cxx + cpyrt/CPPScope.cxx + cpyrt/cpyrtModule.cxx + cpyrt/CallContext.cxx + cpyrt/Converters.cxx + cpyrt/CustomPyTypes.cxx + cpyrt/DispatchPtr.cxx + cpyrt/Dispatcher.cxx + cpyrt/Executors.cxx + cpyrt/LowLevelViews.cxx + cpyrt/MemoryRegulator.cxx + cpyrt/ProxyWrappers.cxx + cpyrt/PyException.cxx + cpyrt/PyStrings.cxx + cpyrt/Pythonize.cxx + cpyrt/TemplateProxy.cxx + cpyrt/TupleOfInstances.cxx + cpyrt/TypeManip.cxx + cpyrt/Utility.cxx +) + +add_library(cpyrt SHARED ${headers} ${sources}) +# Set the suffix to '.so' and the prefix to 'lib' +set_target_properties(cpyrt PROPERTIES ${ROOT_LIBRARY_PROPERTIES}) +if(MSVC) + target_link_libraries(cpyrt PRIVATE cppjit_backend) + target_link_libraries(cpyrt PUBLIC Python3::Python) + set_target_properties(cpyrt PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + target_link_options(cpyrt PRIVATE "-ignore:4197") +elseif(APPLE) + target_link_libraries(cpyrt PRIVATE -Wl,-bind_at_load -Wl,-w -Wl,-undefined -Wl,dynamic_lookup cppjit_backend) +else() + target_link_libraries(cpyrt PRIVATE -Wl,--unresolved-symbols=ignore-all cppjit_backend) +endif() + +if(NOT MSVC) + target_compile_options(cpyrt PRIVATE -Wno-strict-aliasing) +endif() +if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND NOT MSVC) + target_compile_options(cpyrt PRIVATE + -Wno-unused-but-set-parameter) +endif() + +add_library(cppjit SHARED cpyrt/cpyrtPyModule.cxx) + +# Set the suffix to '.so' and the prefix to 'lib' +set_target_properties(cppjit PROPERTIES ${ROOT_LIBRARY_PROPERTIES_NO_VERSION} + LIBRARY_OUTPUT_DIRECTORY ${localruntimedir}/cppjit) +if(MSVC) + target_link_libraries(cppjit PRIVATE cpyrt) + set_target_properties(cppjit PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + set_target_properties(cppjit PROPERTIES PREFIX "lib") + set_target_properties(cppjit PROPERTIES SUFFIX ".pyd") + # On Windows the DLL of a SHARED target follows the RUNTIME output + # directory; import cppjit.libcppjit needs it inside the package dir. + set_target_properties(cppjit PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${localruntimedir}/cppjit + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${localruntimedir}/cppjit + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${localruntimedir}/cppjit + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${localruntimedir}/cppjit) +elseif(APPLE) + target_link_libraries(cppjit PRIVATE -Wl,-bind_at_load -Wl,-w -Wl,-undefined -Wl,dynamic_lookup cpyrt) +else() + target_link_libraries(cppjit PRIVATE -Wl,--unresolved-symbols=ignore-all cpyrt) +endif() + +# Avoid warnings due to invalid function casts from C++ functions in cpyrt +# to CPython API function typedefs (e.g. PyCFunction). This is a common pattern +# in CPython extension implementations, explicitly encouraged by the official +# CPython docs for C/C++ extensions. see +# https://docs.python.org/3/extending/extending.html#keyword-parameters-for-extension-functions +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(cpyrt PRIVATE -Wno-cast-function-type) +endif() + +# CPPINTEROP_DISPATCH_EXPORTS: cpyrt TUs live in the same DLL as the dispatch +# table absorbed from cppjit_backend, so they mark it dllexport too. +target_compile_definitions(cpyrt PRIVATE NO_CPPJIT_LEGACY_NAMESPACE + CPPINTEROP_DISPATCH_EXPORTS + CPPJIT_USE_CLING) + +target_include_directories(cpyrt SYSTEM PUBLIC ${Python3_INCLUDE_DIRS}) + +# The source dir resolves the public "cpyrt/*.h" spellings against the +# flattened cpyrt directory; the interop dir provides cppjit_interop.h, +# which uses CppInterOp types directly, so consumers also need the staged +# CppInterOp headers. +target_include_directories(cpyrt + PUBLIC + $ + $ + $ + $ +) +add_dependencies(cpyrt CppInterOpEtc) + +set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS cpyrt) +set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS cppjit) + +if(NOT MSVC) + # Make sure that relative RUNPATH to main ROOT libraries is always correct. + ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(cppjit ${CMAKE_INSTALL_PYTHONDIR}/cppjit) + ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(cpyrt ${CMAKE_INSTALL_LIBDIR}) +endif() +# The loader imports cppjit.libcppjit from inside the package on all platforms. +set(CPPJIT_INSTALL_DIR ${CMAKE_INSTALL_PYTHONDIR}/cppjit) + +# Install library +install(TARGETS cpyrt EXPORT ${CMAKE_PROJECT_NAME}Exports + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries) + +install(TARGETS cppjit EXPORT ${CMAKE_PROJECT_NAME}Exports + RUNTIME DESTINATION ${CPPJIT_INSTALL_DIR} COMPONENT libraries # Windows + LIBRARY DESTINATION ${CPPJIT_INSTALL_DIR} COMPONENT libraries + ARCHIVE DESTINATION ${CPPJIT_INSTALL_DIR} COMPONENT libraries) + +file(COPY ${headers} DESTINATION ${CMAKE_BINARY_DIR}/include/cpyrt) + +install(FILES ${headers} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cpyrt + COMPONENT headers) diff --git a/bindings/pyroot/cppjit/src/cpyrt/API.cxx b/bindings/pyroot/cppjit/src/cpyrt/API.cxx new file mode 100644 index 0000000000000..b0da815ec12b4 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/API.cxx @@ -0,0 +1,477 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "cppjit_interop.h" +#define CPYRT_INTERNAL 1 +#include "cpyrt/API.h" +#undef CPYRT_INTERNAL + +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include +#include + +//______________________________________________________________________________ +// cpyrt API: Interpreter and Proxy Access +// ========================================== +// +// Access to cppjit Python objects from Cling and C++: allows conversion for +// instances and type checking for scopes, instances, etc. +// Adds a few convenience functions to call Python from Cling and expose Python +// classes to Cling for use in inheritance etc. + +//- data --------------------------------------------------------------------- +static PyObject* gMainDict = nullptr; + +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +} + +//- private helpers ---------------------------------------------------------- +namespace { + +static bool Initialize() { + // Private initialization method: setup the python interpreter and load the + // cppjit module. + static bool isInitialized = false; + if (isInitialized) + return true; + + if (!Py_IsInitialized()) { + // this happens if Cling comes in first + PyConfig config; + PyConfig_InitPythonConfig(&config); + PyConfig_SetString(&config, &config.program_name, L"cppjit"); + Py_InitializeFromConfig(&config); + + // try again to see if the interpreter is initialized + if (!Py_IsInitialized()) { + // give up ... + std::cerr << "Error: python has not been initialized; returning." + << std::endl; + return false; + } + } + + // Importing the extension module is what runs the cpyrt initialization + // that sets gThisModule. + if (!cpyrt::gThisModule) { + PyObject* cppjitmod = PyImport_ImportModule("cppjit"); + if (!cppjitmod) + return false; + Py_DECREF(cppjitmod); + } + + if (!gMainDict) { + cpyrt::PythonGILRAII python_gil_raii; + // retrieve the main dictionary + gMainDict = + PyModule_GetDict(PyImport_AddModule(const_cast("__main__"))); + // The gMainDict is borrowed, i.e. we are not calling Py_INCREF(gMainDict). + // Like this, we avoid unexpectedly affecting how long __main__ is kept + // alive. The gMainDict is only used in Exec(), ExecScript(), and Eval(), + // which should not be called after __main__ is garbage collected anyway. + } + + // declare success ... + isInitialized = true; + return true; +} + +} // unnamed namespace + +//- C++ access to cppjit objects --------------------------------------------- +std::string cpyrt::Instance_GetScopedFinalName(PyObject* pyobject) { + if (!Instance_Check(pyobject)) { + PyErr_SetString( + PyExc_TypeError, + "Instance_GetScopedFinalName : object is not a C++ instance"); + return ""; + } + + interop::TCppScope_t pyobjectClass = ((CPPInstance*)pyobject)->ObjectIsA(); + return interop::GetScopedFinalName(pyobjectClass); +} + +//----------------------------------------------------------------------------- +void* cpyrt::Instance_AsVoidPtr(PyObject* pyobject) { + // Extract the object pointer held by the CPPInstance pyobject. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return nullptr; + + // get held object (may be null) + return ((CPPInstance*)pyobject)->GetObject(); +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::Instance_FromVoidPtr(void* addr, const std::string& classname, + bool python_owns) { + // Bind the addr to a python object of class defined by classname. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // perform cast (the call will check TClass and addr, and set python errors) + PyObject* pyobject = + BindCppObjectNoCast(addr, interop::GetScope(classname), false); + + // give ownership, for ref-counting, to the python side, if so requested + if (python_owns && CPPInstance_Check(pyobject)) + ((CPPInstance*)pyobject)->PythonOwns(); + + return pyobject; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::Instance_FromVoidPtr(void* addr, + interop::TCppScope_t klass_scope, + bool python_owns) { + // Bind the addr to a python object of class defined by classname. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // perform cast (the call will check TClass and addr, and set python errors) + PyObject* pyobject = BindCppObjectNoCast(addr, klass_scope, false); + + // give ownership, for ref-counting, to the python side, if so requested + if (python_owns && CPPInstance_Check(pyobject)) + ((CPPInstance*)pyobject)->PythonOwns(); + + return pyobject; +} +namespace cppjit::cpyrt { +// version with C type arguments only for use with Numba +PyObject* Instance_FromVoidPtr(void* addr, const char* classname, + int python_owns) { + return Instance_FromVoidPtr(addr, std::string(classname), (bool)python_owns); +} +} // namespace cppjit::cpyrt + +//----------------------------------------------------------------------------- +bool cpyrt::Scope_Check(PyObject* pyobject) { + // Test if the given object is of a CPPScope derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + return CPPScope_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Scope_CheckExact(PyObject* pyobject) { + // Test if the given object is of a CPPScope type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + return CPPScope_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Instance_Check(PyObject* pyobject) { + // Test if the given pyobject is of CPPInstance derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // detailed walk through inheritance hierarchy + return CPPInstance_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Instance_CheckExact(PyObject* pyobject) { + // Test if the given pyobject is of CPPInstance type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // direct pointer comparison of type member + return CPPInstance_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetPythonOwns(PyObject* pyobject) { + if (!Initialize()) + return; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return; + + ((CPPInstance*)pyobject)->PythonOwns(); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetCppOwns(PyObject* pyobject) { + if (!Initialize()) + return; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return; + + ((CPPInstance*)pyobject)->CppOwns(); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Sequence_Check(PyObject* pyobject) { + PythonGILRAII python_gil_raii; + // Extends on PySequence_Check() to determine whether an object can be + // iterated over (technically, all objects can b/c of C++ pointer arithmetic, + // hence this check isn't 100% accurate, but neither is PySequence_Check()). + + // Note: simply having the iterator protocol does not constitute a sequence, + // bc PySequence_GetItem() would fail. + + // default to PySequence_Check() if called with a non-C++ object + if (!CPPInstance_Check(pyobject)) + return (bool)PySequence_Check(pyobject); + + // all C++ objects should have sq_item defined, but a user-derived class may + // have deleted it, in which case this is not a sequence + PyTypeObject* t = Py_TYPE(pyobject); + if (!t->tp_as_sequence || !t->tp_as_sequence->sq_item) + return false; + + // if this is the default getitem, it is only a sequence if it's an array type + if (t->tp_as_sequence->sq_item == CPPInstance_Type.tp_as_sequence->sq_item) { + if (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsArray) + return true; + return false; + } + + // TODO: could additionally verify whether __len__ is supported and/or whether + // operator()[] takes an int argument type + + return true; +} + +//----------------------------------------------------------------------------- +bool cppjit::cpyrt::Instance_IsLively(PyObject* pyobject) { + PythonGILRAII python_gil_raii; + // Test whether the given instance can safely return to C++ + if (!CPPInstance_Check(pyobject)) + return true; // simply don't know + + // the instance fails the lively test if it owns the C++ object while having a + // reference count of 1 (meaning: it could delete the C++ instance any moment) + if (Py_REFCNT(pyobject) <= 1 && + (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::Overload_Check(PyObject* pyobject) { + // Test if the given pyobject is of CPPOverload derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // detailed walk through inheritance hierarchy + return CPPOverload_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Overload_CheckExact(PyObject* pyobject) { + // Test if the given pyobject is of CPPOverload type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // direct pointer comparison of type member + return CPPOverload_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetReduceMethod(PyCFunction reduceMethod) { + CPPInstance::ReduceMethod() = reduceMethod; +} + +//- access to the python interpreter ---------------------------------------- +bool cpyrt::Import(const std::string& mod_name) { + // Import the named python module and create Cling equivalents for its + // classes. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + + PyObject* mod = PyImport_ImportModule(mod_name.c_str()); + if (!mod) { + PyErr_Print(); + return false; + } + + // allow finding to prevent creation of a python proxy for the C++ proxy + Py_INCREF(mod); + PyModule_AddObject(gThisModule, mod_name.c_str(), mod); + + // force creation of the module as a namespace + // TODO: the following is broken (and should live in cppjit_interop.cxx) + // TClass::GetClass(mod_name, true); + + PyObject* dct = PyModule_GetDict(mod); + + // create Cling classes for all new python classes + PyObject* values = PyDict_Values(dct); + for (int i = 0; i < PyList_GET_SIZE(values); ++i) { + PyObject* value = PyList_GET_ITEM(values, i); + Py_INCREF(value); + + // collect classes + if (PyClass_Check(value) || PyObject_HasAttr(value, PyStrings::gBases)) { + // get full class name (including module) + PyObject* pyClName = PyObject_GetAttr(value, PyStrings::gName); + if (PyErr_Occurred()) + PyErr_Clear(); + + // build full, qualified name + std::string fullname = mod_name; + fullname += "."; + fullname += cpyrt_PyText_AsString(pyClName); + + Py_XDECREF(pyClName); + } + + Py_DECREF(value); + } + + Py_DECREF(values); + + // TODO: mod "leaks" here + if (PyErr_Occurred()) + return false; + return true; +} + +//----------------------------------------------------------------------------- +void cpyrt::ExecScript(const std::string& name, + const std::vector& args) { + // Execute a python stand-alone script, with argv CLI arguments. + // + // example of use: + // cpyrt::ExecScript("test.py", {"1", "2", "3"}); + + if (!Initialize()) + return; + + PythonGILRAII python_gil_raii; + + // verify arguments + if (name.empty()) { + std::cerr << "Error: no file name specified." << std::endl; + return; + } + + FILE* fp = fopen(name.c_str(), "r"); + if (!fp) { + std::cerr << "Error: could not open file \"" << name << "\"." << std::endl; + return; + } + + // store a copy of the old cli for restoration + PyObject* oldargv = PySys_GetObject("argv"); // borrowed + if (oldargv) { + PyObject* copy = PyList_GetSlice(oldargv, 0, PyList_Size(oldargv)); + oldargv = copy; // now owned + } else { + PyErr_Clear(); + } + + // build new argv + const int argc = (int)args.size() + 1; + std::vector wargv(argc); + wargv[0] = Py_DecodeLocale(name.c_str(), nullptr); + + for (int i = 1; i < argc; ++i) { + wargv[i] = Py_DecodeLocale(args[i - 1].c_str(), nullptr); + } + + // set sys.argv + PyObject* sysmod = PyImport_ImportModule("sys"); // new reference + if (sysmod) { + PyObject* argv_obj = PyList_New(argc); + for (int i = 0; i < argc; ++i) { + PyList_SET_ITEM(argv_obj, i, PyUnicode_FromWideChar(wargv[i], -1)); + } + PyObject_SetAttrString(sysmod, "argv", argv_obj); + Py_DECREF(argv_obj); + Py_DECREF(sysmod); + } else { + PyErr_Print(); + } + + // actual script execution + PyObject* gbl = PyDict_Copy(gMainDict); + PyObject* result = // PyRun_FileEx closes fp (b/c of last argument "1") + PyRun_FileEx(fp, const_cast(name.c_str()), Py_file_input, gbl, gbl, + 1); + + if (!result) + PyErr_Print(); + + Py_XDECREF(result); + Py_DECREF(gbl); + + // restore original command line + if (oldargv) { + PySys_SetObject("argv", oldargv); + Py_DECREF(oldargv); + } + + // free memory from Py_DecodeLocale + for (auto ptr : wargv) + PyMem_RawFree(ptr); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Exec(const std::string& cmd) { + // Execute a python statement (e.g. "import noddy"). + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // execute the command + PyObject* result = PyRun_String(const_cast(cmd.c_str()), Py_file_input, + gMainDict, gMainDict); + + // test for error + if (result) { + Py_DECREF(result); + return true; + } + + PyErr_Print(); + return false; +} + +//----------------------------------------------------------------------------- +void cpyrt::Prompt() { + // Enter an interactive python session (exit with ^D). State is preserved + // between successive calls. + if (!Initialize()) + return; + + PythonGILRAII python_gil_raii; + // enter i/o interactive mode + PyRun_InteractiveLoop(stdin, const_cast("\0")); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/API.h b/bindings/pyroot/cppjit/src/cpyrt/API.h new file mode 100644 index 0000000000000..627132a2e265b --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/API.h @@ -0,0 +1,252 @@ +#ifndef CPYRT_API_H +#define CPYRT_API_H + +// +// Access to the python interpreter and API onto cpyrt. +// + +// Python +#ifdef _WIN32 +#pragma warning(disable : 4275) +#pragma warning(disable : 4251) +#pragma warning(disable : 4800) +#endif +#if defined(linux) +#include +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif +#endif +#include "Python.h" + +#define CPYRT_VERSION_HEX 0x011200 + +// cppjit_interop types +#ifndef CPYRT_INTERNAL + +namespace Cpp { +struct DeclRef; +struct TypeRef; +struct FuncRef; +struct ObjectRef; +} // namespace Cpp + +namespace cppjit::interop { +typedef Cpp::DeclRef TCppScope_t; +typedef Cpp::TypeRef TCppType_t; +typedef Cpp::ObjectRef TCppObject_t; +typedef Cpp::FuncRef TCppMethod_t; +typedef size_t TCppIndex_t; +typedef void* TCppFuncAddr_t; +} // namespace cppjit::interop +#endif + +// Bindings +#include "cpyrt/CommonDefs.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +//- type conversion --------------------------------------------------------- + +#ifndef CPYRT_PARAMETER +#define CPYRT_PARAMETER +// generic function argument type +struct Parameter { + union Value { + bool fBool; + int8_t fInt8; + int16_t fInt16; + int32_t fInt32; + uint8_t fUInt8; + uint16_t fUInt16; + uint32_t fUInt32; + short fShort; + unsigned short fUShort; + int fInt; + unsigned int fUInt; + long fLong; + intptr_t fIntPtr; + unsigned long fULong; + long long fLLong; + unsigned long long fULLong; + int64_t fInt64; + uint64_t fUInt64; + float fFloat; + double fDouble; + long double fLDouble; + void* fVoidp; + } fValue; + void* fRef; + char fTypeCode; +}; +#endif // CPYRT_PARAMETER + +// CallContext is not currently exposed +struct CallContext; + +// Dimensions class not currently exposed +#ifndef CPYRT_DIMENSIONS_H +#define CPYRT_DIMENSIONS_H +typedef Py_ssize_t dim_t; + +class Dimensions { // Windows note: NOT exported/imported + dim_t* fDims; + +public: + Dimensions(dim_t /*ndim*/ = 0, dim_t* /*dims*/ = nullptr) : fDims(nullptr) {} + ~Dimensions() { delete[] fDims; } + +public: + operator bool() const { return (bool)fDims; } +}; + +typedef Dimensions dims_t; +typedef const dims_t& cdims_t; +#endif // !CPYRT_DIMENSIONS_H + +// type converter base class +class CPYRT_CLASS_EXTERN Converter { +public: + virtual ~Converter(); + + // convert the python object and add store it on the parameter + virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; + + // convert a C++ object from memory to a Python object + virtual PyObject* FromMemory(void* address); + + // convert a Python object to a C++ object and store it on address + virtual bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr); + + // if a converter has state, it will be unique per function, shared otherwise + virtual bool HasState() { return false; } +}; + +// create a converter based on its full type name and dimensions +CPYRT_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0); +CPYRT_EXTERN Converter* CreateConverter(interop::TCppType_t type, cdims_t = 0); + +// delete a previously created converter +CPYRT_EXTERN void DestroyConverter(Converter* p); + +// register a custom converter +typedef Converter* (*ConverterFactory_t)(cdims_t); +CPYRT_EXTERN bool RegisterConverter(const std::string& name, + ConverterFactory_t); + +// register a custom converter that is a reference to an existing converter +CPYRT_EXTERN bool RegisterConverterAlias(const std::string& name, + const std::string& target); + +// remove a custom converter +CPYRT_EXTERN bool UnregisterConverter(const std::string& name); + +// function executor base class +class CPYRT_CLASS_EXTERN Executor { +public: + virtual ~Executor(); + + // callback when executing a function from Python + virtual PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) = 0; + + // if an executor has state, it will be unique per function, shared otherwise + virtual bool HasState() { return false; } +}; + +// create an executor based on its full type name +CPYRT_EXTERN Executor* CreateExecutor(const std::string& name, cdims_t = 0); +CPYRT_EXTERN Executor* CreateExecutor(interop::TCppType_t type, cdims_t = 0); + +// delete a previously created executor +CPYRT_EXTERN void DestroyConverter(Converter* p); + +// register a custom executor +typedef Executor* (*ExecutorFactory_t)(cdims_t); +CPYRT_EXTERN bool RegisterExecutor(const std::string& name, ExecutorFactory_t); + +// register a custom executor that is a reference to an existing converter +CPYRT_EXTERN bool RegisterExecutorAlias(const std::string& name, + const std::string& target); + +// remove a custom executor +CPYRT_EXTERN bool UnregisterExecutor(const std::string& name); + +// helper for calling into C++ from a custom executor +CPYRT_EXTERN void* CallVoidP(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*); + +//- C++ access to cppjit objects --------------------------------------------- + +// Get C++ Instance (python object proxy) name. +// Sets a TypeError and returns an empty string if the pyobject is not a +// CPPInstance. +CPYRT_EXTERN std::string Instance_GetScopedFinalName(PyObject* pyobject); + +// C++ Instance (python object proxy) to void* conversion +CPYRT_EXTERN void* Instance_AsVoidPtr(PyObject* pyobject); + +// void* to C++ Instance (python object proxy) conversion, returns a new +// reference +CPYRT_EXTERN PyObject* Instance_FromVoidPtr(void* addr, + const std::string& classname, + bool python_owns = false); +CPYRT_EXTERN PyObject* Instance_FromVoidPtr(void* addr, + interop::TCppScope_t klass_scope, + bool python_owns = false); +// type verifiers for C++ Scope +CPYRT_EXTERN bool Scope_Check(PyObject* pyobject); +CPYRT_EXTERN bool Scope_CheckExact(PyObject* pyobject); + +// type verifiers for C++ Instance +CPYRT_EXTERN bool Instance_Check(PyObject* pyobject); +CPYRT_EXTERN bool Instance_CheckExact(PyObject* pyobject); + +// memory management: ownership of the underlying C++ object +CPYRT_EXTERN void Instance_SetPythonOwns(PyObject* pyobject); +CPYRT_EXTERN void Instance_SetCppOwns(PyObject* pyobject); + +// type verifier for sequences +CPYRT_EXTERN bool Sequence_Check(PyObject* pyobject); + +// helper to verify expected safety of moving an instance into C++ +CPYRT_EXTERN bool Instance_IsLively(PyObject* pyobject); + +// type verifiers for C++ Overload +CPYRT_EXTERN bool Overload_Check(PyObject* pyobject); +CPYRT_EXTERN bool Overload_CheckExact(PyObject* pyobject); + +// Sets the __reduce__ method for the CPPInstance class, which is by default not +// implemented by cppjit but might make sense to implement by frameworks that +// support IO of arbitrary C++ objects, like ROOT. +CPYRT_EXTERN void Instance_SetReduceMethod(PyCFunction reduceMethod); + +//- access to the python interpreter ---------------------------------------- + +// import a python module, making its classes available to Cling +CPYRT_EXTERN bool Import(const std::string& name); + +// execute a python statement (e.g. "import sys") +CPYRT_EXTERN bool Exec(const std::string& cmd); + +// execute a python stand-alone script, with argv CLI arguments +CPYRT_EXTERN void ExecScript(const std::string& name, + const std::vector& args); + +// enter an interactive python session (exit with ^D) +CPYRT_EXTERN void Prompt(); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_API_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.cxx new file mode 100644 index 0000000000000..d6893a8bd7bbf --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.cxx @@ -0,0 +1,60 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPInstance.h" + +//- public members +//-------------------------------------------------------------- +PyObject* cpyrt::CPPClassMethod::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // preliminary check in case keywords are accidently used (they are ignored + // otherwise) + if (kwds && ((PyDict_Check(kwds) && PyDict_Size(kwds)) || + (PyTuple_CheckExact(kwds) && PyTuple_GET_SIZE(kwds)))) { + PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported"); + return nullptr; + } + + // setup as necessary + if (!this->Initialize(ctxt)) + return nullptr; + + // translate the arguments + // TODO: The following is not robust and should be revisited e.g. by making + // CPPOverloads that have only CPPClassMethods be true Python classmethods? + // Note that the original implementation wasn't 100% correct either (e.g. + // static size() mapped to len()). + // + // As-is, if no suitable `self` is given (normal case), but the type of the + // first argument matches the enclosing scope of the class method and it isn't + // needed for the call, then assume that the method was (incorrectly) bound + // and so drop that instance from args. + int nargs = (int)cpyrt_PyArgs_GET_SIZE(args, nargsf); + if ((!self || (PyObject*)self == Py_None) && nargs) { + PyObject* arg0 = cpyrt_PyArgs_GET_ITEM(args, 0); + if (CPPInstance_Check(arg0) && fArgsRequired <= nargs - 1 && + interop::IsSubclass(reinterpret_cast(arg0)->ObjectIsA(), + GetScope())) { + args += 1; // drops first argument + nargsf -= 1; + } + } + + if (!this->ConvertAndSetArgs(args, nargsf, ctxt)) + return nullptr; + + // execute function + return this->Execute(nullptr, 0, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPClassMethod::GetTypeName() { + PyObject* cppname = + cpyrt_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.h b/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.h new file mode 100644 index 0000000000000..4ffe761ccc413 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.h @@ -0,0 +1,24 @@ +#ifndef CPYRT_CPPCLASSMETHOD_H +#define CPYRT_CPPCLASSMETHOD_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPClassMethod : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetTypeName() override; + +public: + PyCallable* Clone() override { return new CPPClassMethod(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPCLASSMETHOD_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.cxx new file mode 100644 index 0000000000000..657f875557dd5 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.cxx @@ -0,0 +1,392 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPConstructor.h" +#include "CPPInstance.h" +#include "Executors.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" + +// Standard +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gNullPtrObject; +void* Instance_AsVoidPtr(PyObject* pyobject); +PyObject* Instance_FromVoidPtr(void* addr, interop::TCppScope_t klass_scope, + bool python_owns); +} // namespace cppjit::cpyrt + +//- protected members -------------------------------------------------------- +bool cpyrt::CPPConstructor::InitExecutor_(Executor*& executor, CallContext*) { + // pick up special case new object executor + executor = CreateExecutor("__init__"); + return true; +} + +//- public members ----------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::GetDocString() { + // GetMethod() may return an empty function if this is just a special case + // place holder + const std::string& clName = interop::GetFinalName(this->GetScope()); + return cpyrt_PyText_FromFormat( + "%s::%s%s", clName.c_str(), clName.c_str(), + this->GetMethod() ? this->GetSignatureString().c_str() : "()"); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t format) { + // C++ reflection tooling for constructors. + + if (request == interop::Reflex::RETURN_TYPE) { + std::string fn = interop::GetScopedFinalName(this->GetScope()); + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_TYPE) + return CreateScopeProxy(this->GetScope()); + else if (format == interop::Reflex::AS_STRING) + return cpyrt_PyText_FromString(fn.c_str()); + } + + return PyCallable::Reflex(request, format); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; // important: 0, not Py_None + + // fetch self, verify, and put the arguments in usable order + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // verify existence of self (i.e. tp_new called) + if (!self) { + PyErr_SetString(PyExc_ReferenceError, "no python object allocated"); + return nullptr; + } + + if (self->GetObject()) { + PyErr_SetString( + PyExc_ReferenceError, + "object already constructed; use __assign__ instead of __init__"); + return nullptr; + } + + const auto cppScopeFlags = ((CPPScope*)Py_TYPE(self))->fFlags; + + // Do nothing if the constructor is explicit and we are in an implicit + // conversion context. See also the ConvertImplicit() helper in + // Converters.cxx. + if ((cppScopeFlags & CPPScope::kActiveImplicitCall) && + interop::IsExplicit(GetMethod())) { + // FIXME: Cases with explicit marked std::complex constructors where we + // expect implicit conversionss + if (interop::GetMethodSignature(GetMethod(), true).find("std::complex") == + std::string::npos) + return nullptr; + } + + // self provides the python context for lifelines + if (!ctxt->fPyContext) + ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership + + // perform the call, nullptr 'this' makes the other side allocate the memory + interop::TCppScope_t disp = self->ObjectIsA(false /* check_smart */); + intptr_t address = 0; + if (GetScope() != disp) { + // happens for Python derived types (which have a dispatcher inserted that + // is not otherwise user-visible: call it instead) and C++ derived classes + // without public constructors + + // first, check whether we at least had a proper meta class, or whether that + // was also replaced user-side + if (!GetScope() || !disp) { + PyErr_SetString(PyExc_TypeError, + "can not construct incomplete C++ class"); + return nullptr; + } + + // get the dispatcher class and verify + PyObject* dispproxy = cpyrt::GetScopeProxy(disp); + if (!dispproxy) { + PyErr_SetString(PyExc_TypeError, "dispatcher proxy was never created"); + return nullptr; + } + + if (!(((CPPClass*)dispproxy)->fFlags & CPPScope::kIsPython)) { + PyErr_SetString(PyExc_TypeError, + const_cast(("constructor for " + + interop::GetScopedFinalName(disp) + + " is not a dispatcher") + .c_str())); + return nullptr; + } + + PyObject* pyobj = + cpyrt_PyObject_Call(dispproxy, cargs.fArgs, cargs.fNArgsf, kwds); + if (!pyobj) + return nullptr; + + // retrieve the actual pointer, take over control, and set set + // _internal_self + address = (intptr_t)((CPPInstance*)pyobj)->GetObject(); + if (address) { + ((CPPInstance*)pyobj) + ->CppOwns(); // b/c self will control the object on address + PyObject* res = PyObject_CallMethodObjArgs( + dispproxy, PyStrings::gDispInit, pyobj, (PyObject*)self, nullptr); + Py_XDECREF(res); + } + Py_DECREF(dispproxy); + + } else { + // translate the arguments + if (cppScopeFlags & CPPScope::kActiveImplicitCall) + ctxt->fFlags |= CallContext::kNoImplicit; + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + address = (intptr_t)this->Execute(nullptr, 0, ctxt); + } + + // return object if successful, lament if not + if (address) { + Py_INCREF(self); + + // note: constructors are no longer set to take ownership by default; + // instead that is decided by the method proxy (which carries a creator + // flag) upon return + self->Set((void*)address); + + // mark as actual to prevent needless auto-casting and register on its class + self->fFlags |= CPPInstance::kIsActual; + if (!(((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart)) + MemoryRegulator::RegisterPyObject(self, + interop::TCppObject_t((void*)address)); + + // handling smart types this way is deeply fugly, but if CPPInstance sets + // the proper types in op_new first, then the wrong init is called + if (((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart) { + PyObject* pyclass = + CreateScopeProxy(((CPPSmartClass*)Py_TYPE(self))->fUnderlyingType); + if (pyclass) { + self->SetSmart((PyObject*)Py_TYPE(self)); + Py_DECREF((PyObject*)Py_TYPE(self)); + Py_SET_TYPE(self, (PyTypeObject*)pyclass); + } + } + + // done with self + Py_DECREF(self); + + Py_RETURN_NONE; // by definition + } + + if (!PyErr_Occurred()) // should be set, otherwise write a generic error msg + PyErr_SetString(PyExc_TypeError, + const_cast((interop::GetScopedFinalName(GetScope()) + + " constructor failed") + .c_str())); + + // do not throw an exception, nullptr might trigger the overload handler to + // choose a different constructor, which if all fails will throw an exception + return nullptr; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor::CPPMultiConstructor(interop::TCppScope_t scope, + interop::TCppMethod_t method) + : CPPConstructor(scope, method) { + fNumBases = interop::GetNumBases(scope); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor::CPPMultiConstructor(const CPPMultiConstructor& s) + : CPPConstructor(s), fNumBases(s.fNumBases) {} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor& +cpyrt::CPPMultiConstructor::operator=(const CPPMultiConstructor& s) { + if (this != &s) { + CPPConstructor::operator=(s); + fNumBases = s.fNumBases; + } + return *this; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMultiConstructor::Call(CPPInstance*& self, + cpyrt_PyArgs_t argsin, size_t nargsf, + PyObject* kwds, CallContext* ctxt) { + // By convention, initialization parameters of multiple base classes are + // grouped by target base class. Here, we disambiguate and put in "sentinel" + // parameters that allow the dispatcher to propagate them. + + // Three options supported: + // 0. empty args: default constructor call + // 1. fNumBases tuples, each handed to individual constructors + // 2. less than fNumBases, assuming (void) for the missing base constructors + // 3. normal arguments, going to the first base only + + // TODO: this way of forwarding is expensive as the loop is external to this + // call; it would be more efficient to have the argument handling happen + // beforehand + + // fetch self, verify, and put the arguments in usable order (if self is not + // handled first, arguments can not be reordered with sentinels in place) + PyCallArgs cargs{self, argsin, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // to re-use the argument handling, simply change the argument array into a + // tuple (the benefits of not allocating the tuple are relatively minor in + // this case) + Py_ssize_t nargs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + PyObject* args = PyTuple_New(nargs); + for (Py_ssize_t i = 0; i < nargs; ++i) { + Py_INCREF(cargs.fArgs[i]); + PyTuple_SET_ITEM(args, i, cargs.fArgs[i]); + } + + // copy out self as it may have been updated + self = cargs.fSelf; + + if (PyTuple_CheckExact(args) && + PyTuple_GET_SIZE(args)) { // case 0. falls through + Py_ssize_t nArgs = PyTuple_GET_SIZE(args); + + bool isAllTuples = true; + Py_ssize_t nArgsTot = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* argi = PyTuple_GET_ITEM(args, i); + if (!PyTuple_CheckExact(argi)) { + isAllTuples = false; + break; + } + nArgsTot += PyTuple_GET_SIZE(argi); + } + + if (isAllTuples) { + // copy over the arguments, while filling in the sentinels (case 1. & 2.), + // with just sentinels for the remaining (void) calls (case 2.) + PyObject* newArgs = PyTuple_New(nArgsTot + fNumBases - 1); + Py_ssize_t idx = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + if (i != 0) { + // add sentinel + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); + idx += 1; + } + + PyObject* argi = PyTuple_GET_ITEM(args, i); + for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(argi); ++j) { + PyObject* item = PyTuple_GET_ITEM(argi, j); + Py_INCREF(item); + PyTuple_SET_ITEM(newArgs, idx, item); + idx += 1; + } + } + + // add final sentinels as needed + while (idx < (nArgsTot + fNumBases - 1)) { + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); + idx += 1; + } + + Py_DECREF(args); + args = newArgs; + } else { // case 3. add sentinels + // copy arguments as-is, then add sentinels at the end + PyObject* newArgs = PyTuple_New(PyTuple_GET_SIZE(args) + fNumBases - 1); + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* item = PyTuple_GET_ITEM(args, i); + Py_INCREF(item); + PyTuple_SET_ITEM(newArgs, i, item); + } + for (Py_ssize_t i = 0; i < fNumBases - 1; ++i) { + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, i + nArgs, gNullPtrObject); + } + Py_DECREF(args); + args = newArgs; + } + } + + nargs = PyTuple_GET_SIZE(args); + + // now unroll the new args tuple into a vector of objects + auto argsu = std::unique_ptr{new PyObject*[nargs]}; + for (Py_ssize_t i = 0; i < nargs; ++i) + argsu[i] = PyTuple_GET_ITEM(args, i); + cpyrt_PyArgs_t _args = argsu.get(); + + PyObject* result = CPPConstructor::Call(self, _args, nargs, kwds, ctxt); + Py_DECREF(args); + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPAbstractClassConstructor::Call(CPPInstance*& self, + cpyrt_PyArgs_t args, + size_t nargsf, + PyObject* kwds, + CallContext* ctxt) { + // do not allow instantiation of abstract classes + if ((self && GetScope() != self->ObjectIsA()) || + (!self && !(ctxt->fFlags & CallContext::kFromDescr) && + cpyrt_PyArgs_GET_SIZE(args, nargsf) && CPPInstance_Check(args[0]) && + GetScope() != ((CPPInstance*)args[0])->ObjectIsA())) { + // happens if a dispatcher is inserted; allow constructor call + return CPPConstructor::Call(self, args, nargsf, kwds, ctxt); + } + + PyErr_Format(PyExc_TypeError, + "cannot instantiate abstract class \'%s\'" + " (from derived classes, use super() instead)", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPNamespaceConstructor::Call(CPPInstance*&, cpyrt_PyArgs_t, + size_t, PyObject*, + CallContext*) { + // do not allow instantiation of namespaces + PyErr_Format(PyExc_TypeError, "cannot instantiate namespace \'%s\'", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPIncompleteClassConstructor::Call(CPPInstance*&, + cpyrt_PyArgs_t, size_t, + PyObject*, CallContext*) { + // do not allow instantiation of incomplete (forward declared) classes) + PyErr_Format(PyExc_TypeError, "cannot instantiate incomplete class \'%s\'", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPAllPrivateClassConstructor::Call(CPPInstance*&, + cpyrt_PyArgs_t, size_t, + PyObject*, CallContext*) { + // do not allow instantiation of classes with only private constructors + PyErr_Format( + PyExc_TypeError, + "cannot instantiate class \'%s\' that has no public constructors", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.h b/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.h new file mode 100644 index 0000000000000..d0bc7b22458fe --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.h @@ -0,0 +1,93 @@ +#ifndef CPYRT_CPPCONSTRUCTOR_H +#define CPYRT_CPPCONSTRUCTOR_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPConstructor : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetDocString() override; + PyObject* + Reflex(interop::Reflex::RequestId_t, + interop::Reflex::FormatId_t = interop::Reflex::OPTIMAL) override; + + PyCallable* Clone() override { return new CPPConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; + bool ResultIsPyObject() const override { return false; } +}; + +// specialization for multiple inheritance disambiguation +class CPPMultiConstructor : public CPPConstructor { +public: + CPPMultiConstructor(interop::TCppScope_t scope, interop::TCppMethod_t method); + CPPMultiConstructor(const CPPMultiConstructor&); + CPPMultiConstructor& operator=(const CPPMultiConstructor&); + +public: + PyCallable* Clone() override { return new CPPMultiConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +private: + Py_ssize_t fNumBases; +}; + +// specializations of prohibiting constructors +class CPPAbstractClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPAbstractClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPNamespaceConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { return new CPPNamespaceConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPIncompleteClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPIncompleteClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPAllPrivateClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPAllPrivateClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPCONSTRUCTOR_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.cxx new file mode 100644 index 0000000000000..b7a5787f6d3df --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.cxx @@ -0,0 +1,450 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPEnum.h" +#include "CPPInstance.h" +#include "Dimensions.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" +#include "cpyrt/Reflex.h" + +// Standard +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +enum ETypeDetails { + kNone = 0x0000, + kIsStaticData = 0x0001, + kIsConstData = 0x0002, + kIsArrayType = 0x0004, + kIsEnumPrep = 0x0008, + kIsEnumType = 0x0010, + kIsCachable = 0x0020 +}; + +//= cpyrt data member as Python property behavior ========================= +static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, + PyObject* /* kls */) { + // cache lookup for low level views + if (pyobj && dm->fFlags & kIsCachable) { + cpyrt::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) { + if (it->first == dm->fOffset) { + if (it->second) { + Py_INCREF(it->second); + return it->second; + } else + cache.erase(it); + break; + } + } + } + + if (dm->fFlags & (kIsEnumPrep | kIsEnumType)) { + if (dm->fFlags & kIsEnumPrep) { + // still need to do lookup; only ever try this once, then fallback on + // converter + dm->fFlags &= ~kIsEnumPrep; + + // fDescription contains the full name of the actual enum value object + const interop::TCppScope_t enum_type = + interop::GetParentScope(dm->fScope); + const interop::TCppScope_t enum_scope = + interop::GetParentScope(enum_type); + + PyObject* pyscope = CreateScopeProxy(enum_scope); + if (pyscope) { + PyObject* pyEnumType = PyObject_GetAttrString( + pyscope, interop::GetFinalName(enum_type).c_str()); + if (pyEnumType) { + PyObject* pyval = PyObject_GetAttrString( + pyEnumType, interop::GetFinalName(dm->fScope).c_str()); + Py_DECREF(pyEnumType); + if (pyval) { + Py_DECREF(dm->fDescription); + dm->fDescription = pyval; + dm->fFlags |= kIsEnumType; + } + } + Py_DECREF(pyscope); + } + if (!(dm->fFlags & kIsEnumType)) + PyErr_Clear(); + } + + if (dm->fFlags & kIsEnumType) { + Py_INCREF(dm->fDescription); + return dm->fDescription; + } + + if (interop::IsEnumConstant(dm->fScope)) { + // anonymous enum; cache the value in fDescription like the named case + // above: once kIsEnumPrep is cleared this block is not reached again + PyObject* pyval = pyval_from_enum(interop::ResolveEnum(dm->fScope), + nullptr, nullptr, dm->fScope); + if (pyval) { + Py_DECREF(dm->fDescription); + dm->fDescription = pyval; + dm->fFlags |= kIsEnumType; + Py_INCREF(pyval); + return pyval; + } + } + } + // non-initialized or public data accesses through class (e.g. by help()) + void* address = dm->GetAddress(pyobj); + if (!address || (intptr_t)address == -1 /* Cling error */) + return nullptr; + + if (dm->fConverter != 0) { + PyObject* result = dm->fConverter->FromMemory( + (dm->fFlags & kIsArrayType) ? &address : address); + if (!result) + return result; + + // low level views are expensive to create, so cache them on the object + // instead + bool isLLView = LowLevelView_CheckExact(result); + if (isLLView && CPPInstance_Check(pyobj)) { + Py_INCREF(result); + pyobj->GetDatamemberCache().push_back( + std::make_pair(dm->fOffset, result)); + dm->fFlags |= kIsCachable; + } + + // ensure that the encapsulating class does not go away for the duration + // of the data member's lifetime, if it is a bound type (it doesn't matter + // for builtin types, b/c those are copied over into python types and thus + // end up being "stand-alone") + // TODO: should be done for LLViews as well + else if (pyobj && !(dm->fFlags & kIsStaticData) && + CPPInstance_Check(result)) { + if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pyobj) == + -1) + PyErr_Clear(); // ignored + } + + return result; + } + + PyErr_Format(PyExc_NotImplementedError, "no converter available for \"%s\"", + dm->GetName().c_str()); + return nullptr; +} + +//----------------------------------------------------------------------------- +static int dm_set(CPPDataMember* dm, CPPInstance* pyobj, PyObject* value) { + // Set the value of the C++ datum held. + const int errret = -1; + + if (!value) { + // we're being deleted; fine for namespaces (redo lookup next time), but + // makes no sense for classes/structs + if (!interop::IsNamespace(dm->fEnclosingScope)) { + PyErr_SetString(PyExc_TypeError, "data member deletion is not supported"); + return errret; + } + + // deletion removes the proxy, with the idea that a fresh lookup can be + // made, to support Cling's shadowing of declarations (TODO: the use case + // here is redeclared variables, for which fDescription is indeed th ename; + // it might fail for enums). + return PyObject_DelAttr((PyObject*)Py_TYPE(pyobj), dm->fDescription); + } + + // filter const objects to prevent changing their values + if (dm->fFlags & kIsConstData) { + PyErr_SetString(PyExc_TypeError, "assignment to const data not allowed"); + return errret; + } + + // remove cached low level view, if any (will be restored upon reaeding) + if (dm->fFlags & kIsCachable) { + cpyrt::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) { + if (it->first == dm->fOffset) { + Py_XDECREF(it->second); + cache.erase(it); + break; + } + } + } + + intptr_t address = (intptr_t)dm->GetAddress(pyobj); + if (!address || address == -1 /* Cling error */) + return errret; + + // for fixed size arrays + void* ptr = (void*)address; + if (dm->fFlags & kIsArrayType) + ptr = &address; + + // actual conversion; return on success + if (dm->fConverter && dm->fConverter->ToMemory(value, ptr, (PyObject*)pyobj)) + return 0; + + // set a python error, if not already done + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_RuntimeError, + "property type mismatch or assignment not allowed"); + + // failure ... + return errret; +} + +//= cpyrt data member construction/destruction =========================== +static CPPDataMember* dm_new(PyTypeObject* pytype, PyObject*, PyObject*) { + // Create and initialize a new property descriptor. + CPPDataMember* dm = (CPPDataMember*)pytype->tp_alloc(pytype, 0); + + dm->fOffset = 0; + dm->fFlags = 0; + dm->fConverter = nullptr; + dm->fEnclosingScope = nullptr; + dm->fDescription = nullptr; + dm->fDoc = nullptr; + + new (&dm->fFullType) std::string{}; + + return dm; +} + +//---------------------------------------------------------------------------- +static void dm_dealloc(CPPDataMember* dm) { + // Deallocate memory held by this descriptor. + using namespace std; + if (dm->fConverter && dm->fConverter->HasState()) + delete dm->fConverter; + Py_XDECREF(dm->fDescription); // never exposed so no GC necessary + Py_XDECREF(dm->fDoc); + + dm->fFullType.~string(); + + Py_TYPE(dm)->tp_free((PyObject*)dm); +} + +static PyMemberDef dm_members[] = { + {(char*)"__doc__", T_OBJECT, offsetof(CPPDataMember, fDoc), 0, + (char*)"writable documentation"}, + {NULL, 0, 0, 0, nullptr} /* Sentinel */ +}; + +//= cpyrt datamember proxy access to internals ============================ +static PyObject* dm_reflex(CPPDataMember* dm, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + if (request == interop::Reflex::TYPE) { + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_STRING) + return cpyrt_PyText_FromString(dm->fFullType.c_str()); + } else if (request == interop::Reflex::OFFSET) { + if (format == interop::Reflex::OPTIMAL) + return PyLong_FromLong(dm->fOffset); + } + + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyMethodDef dm_methods[] = { + {(char*)"__cpp_reflex__", (PyCFunction)dm_reflex, METH_VARARGS, + (char*)"C++ datamember reflection information"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt data member type ================================================ +PyTypeObject CPPDataMember_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPDataMember", // tp_name + sizeof(CPPDataMember), // tp_basicsize + 0, // tp_itemsize + (destructor)dm_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT, // tp_flags + (char*)"cppjit data member (internal)", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + dm_methods, // tp_methods + dm_members, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)dm_get, // tp_descr_get + (descrsetfunc)dm_set, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)dm_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +//- public members ----------------------------------------------------------- +void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope, + interop::TCppScope_t data) { + if (interop::IsLambdaClass(interop::GetDatamemberType(data))) { + fScope = interop::WrapLambdaFromVariable(data); + } else { + fScope = data; + } + + fEnclosingScope = scope; + fOffset = interop::GetDatamemberOffset( + fScope, fScope == data + ? scope + : interop::GetScope( + "__cppjit_internal_wrap_g")); // XXX: Check back here // + // TODO: make lazy + fFlags = interop::IsStaticDatamember(fScope) ? kIsStaticData : 0; + + const std::string name = interop::GetFinalName(fScope); + interop::TCppType_t type; + + if (interop::IsEnumConstant(fScope)) { + type = interop::GetEnumConstantType(fScope); + fFullType = interop::GetTypeAsString(type); + if (fFullType.find("(anonymous)") == std::string::npos && + fFullType.find("(unnamed)") == std::string::npos) { + // repurpose fDescription for lazy lookup of the enum later + fDescription = cpyrt_PyText_FromString((fFullType + "::" + name).c_str()); + fFlags |= kIsEnumPrep; + } + type = interop::ResolveType(type); + fFlags |= kIsConstData; + } else { + type = interop::GetDatamemberType(fScope); + fFullType = interop::GetTypeAsString(type); + + // Get the integer type if it's an enum + if (interop::IsEnumType(type)) + type = interop::ResolveType(type); + + if (interop::IsConstVar(fScope)) + fFlags |= kIsConstData; + } + + auto ldims = interop::GetDimensions(type); + std::vector dims(ldims.begin(), ldims.end()); + + if (!dims.empty()) + fFlags |= kIsArrayType; + + if (dims.empty()) + fConverter = CreateConverter(type, 0); + else + fConverter = CreateConverter(type, {(dim_t)dims.size(), dims.data()}); + + if (!(fFlags & kIsEnumPrep)) + fDescription = cpyrt_PyText_FromString(name.c_str()); +} + +//----------------------------------------------------------------------------- +void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope, + const std::string& name, void* address) { + fEnclosingScope = scope; + fDescription = cpyrt_PyText_FromString(name.c_str()); + fOffset = (intptr_t)address; + fFlags = kIsStaticData | kIsConstData; + fConverter = CreateConverter("internal_enum_type_t"); + fFullType = "unsigned int"; +} + +//----------------------------------------------------------------------------- +void* cpyrt::CPPDataMember::GetAddress(CPPInstance* pyobj) { + // class attributes, global properties + if (fFlags & kIsStaticData) + return (void*)fOffset; + + // special case: non-static lookup through class + if (!pyobj) { + PyErr_SetString(PyExc_AttributeError, + "attribute access requires an instance"); + return nullptr; + } + + // instance attributes; requires valid object for full address + if (!CPPInstance_Check(pyobj)) { + PyErr_Format(PyExc_TypeError, + "object instance required for access to property \"%s\"", + GetName().c_str()); + return nullptr; + } + + void* obj = pyobj->GetObject(); + if (!obj) { + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + return nullptr; + } + + // the proxy's internal offset is calculated from the enclosing class + ptrdiff_t offset = 0; + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (oisa != fEnclosingScope) + offset = + interop::GetBaseOffset(oisa, fEnclosingScope, obj, 1 /* up-cast */); + + return (void*)((intptr_t)obj + offset + fOffset); +} + +//----------------------------------------------------------------------------- +std::string cpyrt::CPPDataMember::GetName() { + if (fFlags & kIsEnumType) { + PyObject* repr = PyObject_Repr(fDescription); + if (repr) { + std::string res = cpyrt_PyText_AsString(repr); + Py_DECREF(repr); + return res; + } + PyErr_Clear(); + return ""; + } else if (fFlags & kIsEnumPrep) { + std::string fullName = cpyrt_PyText_AsString(fDescription); + return fullName.substr(fullName.rfind("::") + 2, std::string::npos); + } + + return cpyrt_PyText_AsString(fDescription); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.h b/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.h new file mode 100644 index 0000000000000..d3b0d49ac7f61 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.h @@ -0,0 +1,72 @@ +#ifndef CPYRT_CPPDATAMEMBER_H +#define CPYRT_CPPDATAMEMBER_H + +// Bindings +#include "Converters.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPInstance; + +class CPPDataMember { +public: + void Set(interop::TCppScope_t scope, interop::TCppScope_t var); + void Set(interop::TCppScope_t scope, const std::string& name, void* address); + + std::string GetName(); + void* GetAddress(CPPInstance* pyobj /* owner */); + +public: // public, as the python C-API works with C structs + PyObject_HEAD intptr_t fOffset; + long fFlags; + Converter* fConverter; + interop::TCppScope_t fScope; + interop::TCppScope_t fEnclosingScope; + PyObject* fDescription; + PyObject* fDoc; + + // TODO: data members should have a unique identifier, just like methods, + // so that reflection information can be recovered post-initialization + std::string fFullType; + +private: // private, as the python C-API will handle creation + CPPDataMember() = delete; +}; + +//- property proxy for C++ data members, type and type verification ---------- +extern PyTypeObject CPPDataMember_Type; + +template inline bool CPPDataMember_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPDataMember_Type); +} + +template inline bool CPPDataMember_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPDataMember_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPDataMember* CPPDataMember_New(interop::TCppScope_t scope, + interop::TCppScope_t var) { + // Create an initialize a new property descriptor, given the C++ datum. + CPPDataMember* pyprop = (CPPDataMember*)CPPDataMember_Type.tp_new( + &CPPDataMember_Type, nullptr, nullptr); + pyprop->Set(scope, var); + return pyprop; +} + +inline CPPDataMember* CPPDataMember_NewConstant(interop::TCppScope_t scope, + const std::string& name, + void* address) { + // Create an initialize a new property descriptor, given the C++ datum. + CPPDataMember* pyprop = (CPPDataMember*)CPPDataMember_Type.tp_new( + &CPPDataMember_Type, nullptr, nullptr); + pyprop->Set(scope, name, address); + return pyprop; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPDATAMEMBER_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.cxx new file mode 100644 index 0000000000000..65852655d1228 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.cxx @@ -0,0 +1,263 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPEnum.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include + +//- private helpers ---------------------------------------------------------- +static PyObject* pytype_from_enum_type(const std::string& enum_type) { + if (enum_type == "char") + return (PyObject*)&cpyrt_PyText_Type; + else if (enum_type == "bool") + return (PyObject*)&PyInt_Type; // can't use PyBool_Type as base + else if (strstr("long", enum_type.c_str())) + return (PyObject*)&PyLong_Type; + return (PyObject*)&PyInt_Type; // covers most cases +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, + interop::TCppScope_t enum_constant) { + long long llval = interop::GetEnumDataValue(enum_constant); + + if (enum_type == "bool" && !(pytype && btype)) { + // no enum class to instantiate; the singletons can not carry attributes + PyObject* result = (bool)llval ? Py_True : Py_False; + Py_INCREF(result); + return result; + } + + PyObject* bval; + if (enum_type == "char") { + char val = (char)llval; + bval = PyUnicode_FromOrdinal((int)val); + } else if (enum_type == "int" || enum_type == "unsigned int") + bval = PyInt_FromLong((long)llval); + else + bval = PyLong_FromLongLong(llval); + + if (!bval) + return nullptr; // e.g. when out of range for small integers + + if (pytype && btype) { + PyObject* args = PyTuple_New(1); + PyTuple_SET_ITEM(args, 0, bval); + bval = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); + Py_DECREF(args); + } + return bval; +} + +//- enum methods ------------------------------------------------------------- +static int enum_setattro(PyObject* /* pyclass */, PyObject* /* pyname */, + PyObject* /* pyval */) { + // Helper to make enums read-only. + PyErr_SetString(PyExc_TypeError, "enum values are read-only"); + return -1; +} + +//---------------------------------------------------------------------------- +static PyObject* enum_repr(PyObject* self) { + using namespace cppjit::cpyrt; + + PyObject* kls_scope = + PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gThisModule); + if (!kls_scope) + PyErr_Clear(); + PyObject* kls_cppname = + PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gCppName); + if (!kls_cppname) + PyErr_Clear(); + PyObject* obj_cppname = PyObject_GetAttr(self, PyStrings::gCppName); + if (!obj_cppname) + PyErr_Clear(); + PyObject* obj_str = Py_TYPE(self)->tp_str(self); + + PyObject* repr = nullptr; + if (kls_cppname && obj_cppname && obj_str) { + const std::string resolved = + interop::ResolveEnum(PyLong_AsVoidPtr(kls_scope)); + repr = cpyrt_PyText_FromFormat( + "(%s::%s) : (%s) %s", cpyrt_PyText_AsString(kls_cppname), + cpyrt_PyText_AsString(obj_cppname), resolved.c_str(), + cpyrt_PyText_AsString(obj_str)); + } + Py_XDECREF(obj_cppname); + Py_XDECREF(kls_cppname); + + if (repr) { + Py_DECREF(obj_str); + return repr; + } + + return obj_str; +} + +//---------------------------------------------------------------------------- +// TODO: factor the following lookup with similar codes in Convertes and +// TemplateProxy.cxx + +static std::unordered_map gCTypesNames = { + {"bool", "c_bool"}, + {"char", "c_char"}, + {"wchar_t", "c_wchar"}, + {"std::byte", "c_byte"}, + {"int8_t", "c_byte"}, + {"uint8_t", "c_ubyte"}, + {"short", "c_short"}, + {"int16_t", "c_int16"}, + {"unsigned short", "c_ushort"}, + {"uint16_t", "c_uint16"}, + {"int", "c_int"}, + {"unsigned int", "c_uint"}, + {"long", "c_long"}, + {"unsigned long", "c_ulong"}, + {"long long", "c_longlong"}, + {"unsigned long long", "c_ulonglong"}}; + +// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself +// caching the types (thus also making them unique), so no ref-count is needed. +// Further, by keeping a ref-count on the module, it won't be off-loaded until +// the 2nd cleanup cycle. +static PyTypeObject* GetCTypesType(const std::string& cppname) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) + return nullptr; + + auto nn = gCTypesNames.find(cppname); + if (nn == gCTypesNames.end()) { + PyErr_Format(PyExc_TypeError, "Can not find ctypes type for \"%s\"", + cppname.c_str()); + return nullptr; + } + + return (PyTypeObject*)PyObject_GetAttrString(ctmod, nn->second.c_str()); +} + +static PyObject* enum_ctype(PyObject* cls, PyObject* args, PyObject* kwds) { + PyObject* pyres = PyObject_GetAttr(cls, cpyrt::PyStrings::gUnderlying); + if (!pyres) + PyErr_Clear(); + + std::string underlying = pyres ? cpyrt_PyText_AsString(pyres) : "int"; + PyTypeObject* ct = GetCTypesType(underlying); + if (!ct) + return nullptr; + + return PyType_Type.tp_call((PyObject*)ct, args, kwds); +} + +//- creation ----------------------------------------------------------------- +cpyrt::CPPEnum* cpyrt::CPPEnum_New(const std::string& name, + interop::TCppScope_t scope) { + // Create a new enum type based on the actual C++ type. Enum values are added + // to the type but may also live in the enclosing scope. + + CPPEnum* pyenum = nullptr; + + interop::TCppScope_t etype = scope; + const std::string& ename = interop::GetScopedFinalName(scope); + if (etype) { + // create new enum type with labeled values in place, with a meta-class + // to make sure the enum values are read-only + const std::string& resolved = interop::ResolveEnum(etype); + PyObject* pyside_type = pytype_from_enum_type(resolved); + PyObject* pymetabases = PyTuple_New(1); + PyObject* btype = (PyObject*)Py_TYPE(pyside_type); + Py_INCREF(btype); + PyTuple_SET_ITEM(pymetabases, 0, btype); + + PyObject* args = + Py_BuildValue((char*)"sO{}", (name + "_meta").c_str(), pymetabases); + Py_DECREF(pymetabases); + PyObject* pymeta = PyType_Type.tp_new(Py_TYPE(pyside_type), args, nullptr); + Py_DECREF(args); + + // prepare the base class + PyObject* pybases = PyTuple_New(1); + Py_INCREF(pyside_type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)pyside_type); + + // create the __cpp_name__ for templates + PyObject* dct = PyDict_New(); + PyObject* pycppname = cpyrt_PyText_FromString(ename.c_str()); + PyObject* pycppscope = PyLong_FromVoidPtr(etype.data); + PyDict_SetItem(dct, PyStrings::gCppName, pycppname); + PyDict_SetItem(dct, PyStrings::gThisModule, pycppscope); + Py_DECREF(pycppname); + PyObject* pyresolved = cpyrt_PyText_FromString(resolved.c_str()); + PyDict_SetItem(dct, PyStrings::gUnderlying, pyresolved); + Py_DECREF(pyresolved); + + // add the __module__ to allow pickling + std::string modname = TypeManip::extract_namespace(ename); + TypeManip::cppscope_to_pyscope(modname); // :: -> . + if (!modname.empty()) + modname = "." + modname; + PyObject* pymodname = + cpyrt_PyText_FromString(("cppjit.gbl" + modname).c_str()); + PyDict_SetItem(dct, PyStrings::gModule, pymodname); + Py_DECREF(pymodname); + + // create the actual enum class + args = Py_BuildValue((char*)"sOO", name.c_str(), pybases, dct); + Py_DECREF(pybases); + Py_DECREF(dct); + pyenum = + ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); + + // add pythonizations + Utility::AddToClass((PyObject*)Py_TYPE(pyenum), "__ctype__", + (PyCFunction)enum_ctype, METH_VARARGS | METH_KEYWORDS); + ((PyTypeObject*)pyenum)->tp_repr = enum_repr; + ((PyTypeObject*)pyenum)->tp_str = ((PyTypeObject*)pyside_type)->tp_repr; + + // collect the enum values + std::vector econstants = + interop::GetEnumConstants(etype); + bool values_ok = true; + for (auto econstant : econstants) { + PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, econstant); + if (!val) { + values_ok = false; + break; + } + const std::string& dname = interop::GetFinalName(econstant); + PyObject* pydname = cpyrt_PyText_FromString(dname.c_str()); + PyObject_SetAttr(pyenum, pydname, val); + Py_DECREF(pydname); + PyObject* pydcppname = cpyrt_PyText_FromString( + (ename.empty() ? dname : (ename + "::" + dname)).c_str()); + PyObject_SetAttr(val, PyStrings::gCppName, pydcppname); + Py_DECREF(pydcppname); + Py_DECREF(val); + } + + // disable writing onto enum values + ((PyTypeObject*)pymeta)->tp_setattro = enum_setattro; + + // final cleanup + Py_DECREF(args); + Py_DECREF(pymeta); + + if (!values_ok) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, + "could not set some of the enum values"); + Py_DECREF(pyenum); + return nullptr; + } + + } else { + // presumably not a class enum; simply pretend int + Py_INCREF(&PyInt_Type); + pyenum = (PyObject*)&PyInt_Type; + } + + return pyenum; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.h b/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.h new file mode 100644 index 0000000000000..146279188419a --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPEnum.h @@ -0,0 +1,17 @@ +#ifndef CPYRT_CPPENUM_H +#define CPYRT_CPPENUM_H + +namespace cppjit::cpyrt { + +// CPPEnum does not carry any additional C-side data for now, but can be of +// several types, based on the declared or compile-dependent types chosen. +typedef PyObject CPPEnum; + +//- creation ----------------------------------------------------------------- +CPPEnum* CPPEnum_New(const std::string& name, interop::TCppScope_t scope); + +PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, interop::TCppScope_t enum_constant); +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPENUM_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.cxx new file mode 100644 index 0000000000000..6a668f0c29503 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.cxx @@ -0,0 +1,253 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "PyStrings.h" + +//______________________________________________________________________________ +// Python-side exception proxy objects +// =================================== +// +// Python exceptions need to derive from PyException as it expects a set of +// specific data members. This class exists so that general CPPInstances need +// not carry that overhead. In use, it forwards to the embedded CPPInstance. + +namespace cppjit::cpyrt { + +//= cpyrt exception object proxy construction/destruction ================= +static PyObject* ep_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) { + // Create a new exception object proxy (holder only). + PyObject* pyobj = + ((PyTypeObject*)PyExc_Exception)->tp_new(subtype, nullptr, nullptr); + if (!pyobj) + return nullptr; + + CPPExcInstance* excobj = (CPPExcInstance*)pyobj; + excobj->fTopMessage = nullptr; + if (args) { + PyObject* ulc = + PyObject_GetAttr((PyObject*)subtype, PyStrings::gUnderlying); + excobj->fCppInstance = PyType_Type.tp_call(ulc, args, kwds); + if (!excobj->fCppInstance) { + // if this fails, then the construction may have been attempted from a + // string (e.g. from PyErr_Format); if so, drop the proxy and use + // fTopMessage instead + PyErr_Clear(); + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* msg = PyTuple_GET_ITEM(args, 0); + if (cpyrt_PyText_Check(msg)) { + Py_INCREF(msg); + excobj->fTopMessage = msg; + } + } + } + Py_DECREF(ulc); + } else + excobj->fCppInstance = nullptr; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static int ep_traverse(CPPExcInstance* pyobj, visitproc visit, void* args) { + // Garbage collector traverse of held python member objects. + ((PyTypeObject*)PyExc_Exception)->tp_traverse((PyObject*)pyobj, visit, args); + + if (pyobj->fCppInstance) + visit(pyobj->fCppInstance, args); + + if (pyobj->fTopMessage) + visit(pyobj->fTopMessage, args); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* ep_str(CPPExcInstance* self) { + if (self->fCppInstance) { + PyObject* what = + PyObject_CallMethod((PyObject*)self, (char*)"what", nullptr); + if (what) { + if (self->fTopMessage) { + Py_INCREF(self->fTopMessage); + PyObject* top = self->fTopMessage; + cpyrt_PyText_Append(&top, what); + Py_DECREF(what); + what = top; + } + return what; + } + PyErr_Clear(); + return PyObject_Str(self->fCppInstance); + } + + if (self->fTopMessage) { + Py_INCREF(self->fTopMessage); + return self->fTopMessage; + } + + return PyType_Type.tp_str((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static PyObject* ep_repr(CPPExcInstance* self) { + if (self->fCppInstance) + return PyObject_Repr(self->fCppInstance); + return PyType_Type.tp_repr((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static void ep_dealloc(CPPExcInstance* pyobj) { + PyObject* tmp1 = pyobj->fCppInstance; + PyObject* tmp2 = pyobj->fTopMessage; + + ((PyTypeObject*)PyExc_Exception)->tp_dealloc((PyObject*)pyobj); + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); +} + +//---------------------------------------------------------------------------- +static int ep_clear(CPPExcInstance* pyobj) { + // Garbage collector clear of held python member objects. + ((PyTypeObject*)PyExc_Exception)->tp_clear((PyObject*)pyobj); + + Py_CLEAR(pyobj->fCppInstance); + Py_CLEAR(pyobj->fTopMessage); + + return 0; +} + +//= forwarding methods ======================================================= +static PyObject* ep_getattro(CPPExcInstance* self, PyObject* attr) { + if (self->fCppInstance) { + PyObject* res = PyObject_GetAttr(self->fCppInstance, attr); + if (res) + return res; + PyErr_Clear(); + } + + return ((PyTypeObject*)PyExc_Exception)->tp_getattro((PyObject*)self, attr); +} + +//---------------------------------------------------------------------------- +static int ep_setattro(CPPExcInstance* self, PyObject* attr, PyObject* value) { + if (self->fCppInstance) { + int res = PyObject_SetAttr(self->fCppInstance, attr, value); + if (!res) + return res; + PyErr_Clear(); + } + + return ((PyTypeObject*)PyExc_Exception) + ->tp_setattro((PyObject*)self, attr, value); +} + +//---------------------------------------------------------------------------- +static PyObject* ep_richcompare(CPPExcInstance* self, PyObject* other, int op) { + return PyObject_RichCompare(self->fCppInstance, other, op); +} + +//---------------------------------------------------------------------------- +static int ep_nonzero(CPPExcInstance* self) { + if (self->fCppInstance) + return PyObject_IsTrue(self->fCppInstance); + + return 0; +} + +//----------------------------------------------------------------------------- +static PyNumberMethods ep_as_number = { + 0, // nb_add + 0, // nb_subtract + 0, // nb_multiply + 0, // nb_remainder + 0, // nb_divmod + 0, // nb_power + 0, // nb_negative + 0, // nb_positive + 0, // nb_absolute + (inquiry)ep_nonzero, // nb_bool (nb_nonzero in p2) + 0, // nb_invert + 0, // nb_lshift + 0, // nb_rshift + 0, // nb_and + 0, // nb_xor + 0, // nb_or + 0, // nb_int + 0, // nb_long (nb_reserved in p3) + 0, // nb_float + 0, // nb_inplace_add + 0, // nb_inplace_subtract + 0, // nb_inplace_multiply + 0, // nb_inplace_remainder + 0, // nb_inplace_power + 0, // nb_inplace_lshift + 0, // nb_inplace_rshift + 0, // nb_inplace_and + 0, // nb_inplace_xor + 0, // nb_inplace_or + 0, // nb_floor_divide + 0, // nb_true_divide + 0, // nb_inplace_floor_divide + 0, // nb_inplace_true_divide + 0, // nb_index + 0, // nb_matrix_multiply + 0 // nb_inplace_matrix_multiply +}; + +//= cpyrt exception object proxy type ====================================== +PyTypeObject CPPExcInstance_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPExcInstance", // tp_name + sizeof(CPPExcInstance), // tp_basicsize + 0, // tp_itemsize + (destructor)ep_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)ep_repr, // tp_repr + &ep_as_number, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + (reprfunc)ep_str, // tp_str + (getattrofunc)ep_getattro, // tp_getattro + (setattrofunc)ep_setattro, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_BASE_EXC_SUBCLASS | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, // tp_flags + (char*)"cppjit exception object proxy (internal)", // tp_doc + (traverseproc)ep_traverse, // tp_traverse + (inquiry)ep_clear, // tp_clear + (richcmpfunc)ep_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + 0, // tp_methods + 0, // tp_members + 0, // tp_getset + (PyTypeObject*)PyExc_Exception, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)ep_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.h b/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.h new file mode 100644 index 0000000000000..35e4c9fb624d7 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_CPPEXCINSTANCE_H +#define CPYRT_CPPEXCINSTANCE_H + +////////////////////////////////////////////////////////////////////////////// +// // +// cppjit::cpyrt::CPPExceptionInstance // +// // +// Python-side proxy, encapsulaties a C++ exception object. // +// // +////////////////////////////////////////////////////////////////////////////// + +namespace cppjit::cpyrt { + +class CPPExcInstance { +public: + PyBaseExceptionObject fBase; + PyObject* fCppInstance; + PyObject* fTopMessage; +}; + +//- object proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPExcInstance_Type; + +template inline bool CPPExcInstance_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPExcInstance_Type); +} + +template inline bool CPPExcInstance_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPExcInstance_Type; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPEXCINSTANCE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.cxx new file mode 100644 index 0000000000000..cc6e8522a6152 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.cxx @@ -0,0 +1,138 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" + +// Standard +#include + +//- CFunction helpers +//----------------------------------------------------------- +bool cpyrt::AdjustSelf(PyCallArgs& cargs) { + if (cargs.fNArgsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? + std::swap(((PyObject**)cargs.fArgs - 1)[0], (PyObject*&)cargs.fSelf); + cargs.fFlags |= PyCallArgs::kSelfSwap; + cargs.fArgs -= 1; + cargs.fNArgsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + cargs.fNArgsf += 1; + } else { + Py_ssize_t nkwargs = cargs.fKwds ? PyTuple_GET_SIZE(cargs.fKwds) : 0; + Py_ssize_t totalargs = PyVectorcall_NARGS(cargs.fNArgsf) + nkwargs; + PyObject** newArgs = + (PyObject**)PyMem_Malloc((totalargs + 1) * sizeof(PyObject*)); + if (!newArgs) + return false; + + newArgs[0] = (PyObject*)cargs.fSelf; + if (0 < totalargs) + memcpy((void*)&newArgs[1], cargs.fArgs, totalargs * sizeof(PyObject*)); + cargs.fArgs = newArgs; + cargs.fFlags |= PyCallArgs::kDoFree; + cargs.fNArgsf += 1; + } + return true; +} + +bool cpyrt::CPPFunction::ProcessArgs(PyCallArgs& cargs) { + // add self as part of the function arguments (means bound member) + if (cargs.fKwds) + return this->ProcessKwds((PyObject*)cargs.fSelf, cargs); + return AdjustSelf(cargs); +} + +//- CPPFunction public members +//-------------------------------------------------- +PyObject* cpyrt::CPPFunction::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; + + // if function was attached to a class, self will be non-zero and should be + // the first function argument, so reorder + PyCallArgs cargs{self, args, nargsf, kwds}; + if (self || kwds) { + if (!this->ProcessArgs(cargs)) + return nullptr; + } + + // special case, if this method was inserted as a constructor, then self is + // nullptr and it will be the first argument and needs to be used as Python + // context + if (IsConstructor(ctxt->fFlags) && !ctxt->fPyContext && + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf)) { + ctxt->fPyContext = cargs.fArgs[0]; + } + + // translate the arguments as normal + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + // execute function + PyObject* result = this->Execute(nullptr, 0, ctxt); + + // special case, if this method was inserted as a constructor, then if no self + // was provided, it will be the first argument and may have been updated + if (IsConstructor(ctxt->fFlags) && result && !cargs.fSelf && + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) && + CPPInstance_Check(cargs.fArgs[0])) { + self = (CPPInstance*)cargs.fArgs[0]; + Py_INCREF(self); + } + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPFunction::GetTypeName() { + PyObject* cppname = + cpyrt_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} + +//- CPPReverseBinary private helper +//--------------------------------------------- +bool cpyrt::CPPReverseBinary::ProcessArgs(PyCallArgs& cargs) { + if (cargs.fSelf || cargs.fKwds) { + // add self as part of the function arguments (means bound member) + if (!this->CPPFunction::ProcessArgs(cargs)) + return false; + } + + // swap the arguments + std::swap(((PyObject**)cargs.fArgs)[0], ((PyObject**)cargs.fArgs)[1]); + cargs.fFlags |= PyCallArgs::kArgsSwap; + + return true; +} + +//- CPPReverseBinary public members +//--------------------------------------------- +PyObject* cpyrt::CPPReverseBinary::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // This Call() function is very similar to the one of CPPFunction: only + // difference is that ProcessArgs() is always called. + + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; + + // if function was attached to a class, self will be non-zero and should be + // the first function argument, further, the arguments needs swapping + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // translate the arguments as normal + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + // execute function + return this->Execute(nullptr, 0, ctxt); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.h b/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.h new file mode 100644 index 0000000000000..9e356c86f0eac --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPFunction.h @@ -0,0 +1,44 @@ +#ifndef CPYRT_CPPFUNCTION_H +#define CPYRT_CPPFUNCTION_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +// Wrapper for global free/static C++ functions +class CPPFunction : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetTypeName() override; + +public: + PyCallable* Clone() override { return new CPPFunction(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +// Wrapper for global binary operators that swap arguments +class CPPReverseBinary : public CPPFunction { +public: + using CPPFunction::CPPFunction; + + PyCallable* Clone() override { return new CPPFunction(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +// Helper to add self to the arguments tuple if rebound +bool AdjustSelf(PyCallArgs& cargs); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPFUNCTION_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.cxx new file mode 100644 index 0000000000000..bb132b5136e1c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.cxx @@ -0,0 +1,103 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPGetSetItem.h" +#include "Executors.h" + +//- private helpers ----------------------------------------------------------- +static inline void unroll(cpyrt_PyArgs_t packed_args, cpyrt_PyArgs_t unrolled, + Py_ssize_t nArgs) { + // Unroll up to nArgs arguments from packed_args into unrolled. + for (int i = 0, iur = 0; i < nArgs; ++i, ++iur) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(packed_args, i); + if (PyTuple_Check(item)) { + for (int j = 0; j < PyTuple_GET_SIZE(item); ++j, ++iur) { + PyObject* subitem = PyTuple_GET_ITEM(item, j); + Py_INCREF(subitem); + cpyrt_PyArgs_SET_ITEM(unrolled, iur, subitem); + } + } else { + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(unrolled, iur, item); + } + } +} + +//- protected members --------------------------------------------------------- +bool cpyrt::CPPSetItem::InitExecutor_(Executor*& executor, CallContext*) { + // basic call will do + if (!CPPMethod::InitExecutor_(executor)) + return false; + + // check to make sure we're dealing with a RefExecutor + if (!dynamic_cast(executor)) { + PyErr_Format(PyExc_NotImplementedError, + "no __setitem__ handler for return type (%s)", + this->GetReturnTypeName().c_str()); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CPPSetItem::ProcessArgs(PyCallArgs& cargs) { + // Prepare executor with a buffer for the return value. + Py_ssize_t nArgs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + if (nArgs <= 1) { + PyErr_SetString(PyExc_TypeError, "insufficient arguments to __setitem__"); + return false; + } + + // use the last element of args for assignment upon return, then slice it from + // the (unrolled) actual arguments + ((RefExecutor*)this->GetExecutor()) + ->SetAssignable(cpyrt_PyArgs_GET_ITEM(cargs.fArgs, nArgs - 1)); + + // see whether any of the arguments is a tuple to be unrolled + Py_ssize_t realsize = 0; + for (Py_ssize_t i = 0; i < nArgs - 1; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; + } + + // unroll any tuples, if present in the arguments + if (realsize != nArgs - 1) { + cpyrt_PyArgs_t unrolled = + (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); + unroll(cargs.fArgs, unrolled, nArgs - 1); + cargs.fArgs = unrolled; + cargs.fFlags |= PyCallArgs::kDoFree; + } + cargs.fNArgsf = realsize; + + // continue normal method processing + return CPPMethod::ProcessArgs(cargs); +} + +//----------------------------------------------------------------------------- +bool cpyrt::CPPGetItem::ProcessArgs(PyCallArgs& cargs) { + // Unroll tuples for call, otherwise just like regular CPPMethod of + // __getitem__. + Py_ssize_t nArgs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + + // see whether any of the arguments is a tuple to be unrolled + Py_ssize_t realsize = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; + } + + // unroll any tuples, if present in the arguments + if (realsize != nArgs) { + cpyrt_PyArgs_t packed_args = cargs.fArgs; + cargs.fArgs = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); + cargs.fFlags |= PyCallArgs::kDoFree; + cargs.fNArgsf = realsize; + unroll(packed_args, cargs.fArgs, nArgs); + } + + // continue normal method processing + return CPPMethod::ProcessArgs(cargs); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.h b/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.h new file mode 100644 index 0000000000000..aad043e3a66e0 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_CPPGETSETITEM_H +#define CPYRT_CPPGETSETITEM_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPSetItem : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyCallable* Clone() override { return new CPPSetItem(*this); } + +protected: + bool ProcessArgs(PyCallArgs& args) override; + bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; +}; + +class CPPGetItem : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyCallable* Clone() override { return new CPPGetItem(*this); } + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPGETSETITEM_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx new file mode 100644 index 0000000000000..05d0bc987d913 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx @@ -0,0 +1,1187 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "Compatibility.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gNullPtrObject; +} + +//______________________________________________________________________________ +// Python-side proxy objects +// ========================= +// +// C++ objects are represented in Python by CPPInstances, which encapsulate +// them using either a pointer (normal), pointer-to-pointer (kIsReference set), +// or as an owned value (kIsValue set). Objects held as reference are never +// owned, otherwise the object is owned if kIsOwner is set. +// +// In addition to encapsulation, CPPInstance offers rudimentary comparison +// operators (based on pointer value and class comparisons); stubs (with lazy +// lookups) for numeric operators; and a representation that prints the C++ +// pointer values, rather than the PyObject* ones as is the default. +// +// Smart pointers have the underlying type as the Python type, but store the +// pointer to the smart pointer. They carry a pointer to the Python-sode smart +// class for dereferencing to get to the actual instance pointer. + +//- private helpers ---------------------------------------------------------- +namespace { + +// Several specific use cases require extra data in a CPPInstance, but can not +// be a new type. E.g. cross-inheritance derived types are by definition added +// a posterio, and caching of datamembers is up to the datamember, not the +// instance type. To not have normal use of CPPInstance take extra memory, this +// extended data can slot in place of fObject for those use cases. + +struct ExtendedData { + ExtendedData() + : fObject(nullptr), fSmartClass(nullptr), fDispatchPtr(nullptr), + fArraySize(0) {} + ~ExtendedData() { + for (auto& pc : fDatamemberCache) + Py_XDECREF(pc.second); + fDatamemberCache.clear(); + } + + // the original object reference it replaces (Note: has to be first data + // member, see usage in GetObjectRaw(), e.g. for ptr-ptr passing) + void* fObject; + + // for caching expensive-to-create data member representations + cpyrt::CI_DatamemberCache_t fDatamemberCache; + + // for smart pointer types + cpyrt::CPPSmartClass* fSmartClass; + + // for back-referencing from Python-derived instances + cpyrt::DispatchPtr* fDispatchPtr; + + // for representing T* as a low-level array + Py_ssize_t fArraySize; +}; + +} // unnamed namespace + +#define EXT_OBJECT(pyobj) ((ExtendedData*)((pyobj)->fObject))->fObject +#define DATA_CACHE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDatamemberCache +#define SMART_CLS(pyobj) ((ExtendedData*)((pyobj)->fObject))->fSmartClass +#define SMART_TYPE(pyobj) SMART_CLS(pyobj)->fCppType +#define DISPATCHPTR(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDispatchPtr +#define ARRAY_SIZE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fArraySize + +inline void cpyrt::CPPInstance::CreateExtension() { + if (fFlags & kIsExtended) + return; + void* obj = fObject; + fObject = (void*)new ExtendedData{}; + EXT_OBJECT(this) = obj; + fFlags |= kIsExtended; +} + +void* cpyrt::CPPInstance::GetExtendedObject() { + if (IsSmart()) { + // We get the raw pointer from the smart pointer each time, in case it has + // changed or has been freed. + return interop::CallR(SMART_CLS(this)->fDereferencer, EXT_OBJECT(this), 0, + nullptr); + } + return EXT_OBJECT(this); +} + +//- public methods ----------------------------------------------------------- +cpyrt::CPPInstance* cpyrt::CPPInstance::Copy(void* cppinst, + PyTypeObject* target) { + // create a fresh instance; args and kwds are not used by op_new (see below) + PyObject* self = (PyObject*)this; + if (!target) + target = Py_TYPE(self); + PyObject* newinst = target->tp_new(target, nullptr, nullptr); + + // set the C++ instance as given + ((CPPInstance*)newinst)->fObject = cppinst; + + // look for user-provided __cpp_copy__ (not reusing __copy__ b/c of + // differences in semantics: need to pass in the new instance) ... + PyObject* cpy = PyObject_GetAttrString(self, (char*)"__cpp_copy__"); + if (cpy && PyCallable_Check(cpy)) { + PyObject* args = PyTuple_New(1); + Py_INCREF(newinst); + PyTuple_SET_ITEM(args, 0, newinst); + PyObject* res = PyObject_CallObject(cpy, args); + Py_DECREF(args); + Py_DECREF(cpy); + if (res) { + Py_DECREF(res); + return (CPPInstance*)newinst; + } + + // error already set, but need to return nullptr + Py_DECREF(newinst); + return nullptr; + } else if (cpy) + Py_DECREF(cpy); + else + PyErr_Clear(); + + // ... otherwise, shallow copy any Python-side dictionary items + PyObject* selfdct = PyObject_GetAttr(self, PyStrings::gDict); + PyObject* newdct = PyObject_GetAttr(newinst, PyStrings::gDict); + bool bMergeOk = PyDict_Merge(newdct, selfdct, 1) == 0; + Py_DECREF(newdct); + Py_DECREF(selfdct); + + if (!bMergeOk) { + // presume error already set + Py_DECREF(newinst); + return nullptr; + } + + MemoryRegulator::RegisterPyObject((CPPInstance*)newinst, cppinst); + return (CPPInstance*)newinst; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::PythonOwns() { + fFlags |= kIsOwner; + if ((fFlags & kIsExtended) && DISPATCHPTR(this)) + DISPATCHPTR(this)->PythonOwns(); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::CppOwns() { + fFlags &= ~kIsOwner; + if ((fFlags & kIsExtended) && DISPATCHPTR(this)) + DISPATCHPTR(this)->CppOwns(); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::SetSmart(PyObject* smart_type) { + CreateExtension(); + Py_INCREF(smart_type); + SMART_CLS(this) = (CPPSmartClass*)smart_type; + fFlags |= kIsSmartPtr; +} + +//---------------------------------------------------------------------------- +interop::TCppScope_t cpyrt::CPPInstance::GetSmartIsA() const { + if (!IsSmart()) + return interop::TCppScope_t{}; + return SMART_TYPE(this); +} + +//---------------------------------------------------------------------------- +interop::TCppScope_t cpyrt::CPPInstance::GetSmartUnderlyingType() const { + // Declared underlying type of the embedded smart pointer ('Base' for a + // std::unique_ptr), independent of any downcast of the dereferenced + // object. + if (!IsSmart()) + return interop::TCppScope_t{}; + return SMART_CLS(this)->fUnderlyingType; +} + +//---------------------------------------------------------------------------- +cpyrt::CI_DatamemberCache_t& cpyrt::CPPInstance::GetDatamemberCache() { + // Return the cache for expensive data objects (and make extended as + // necessary) + CreateExtension(); + return DATA_CACHE(this); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::SetDispatchPtr(void* ptr) { + // Set up the dispatch pointer for memory management + CreateExtension(); + DISPATCHPTR(this) = (DispatchPtr*)ptr; +} + +//---------------------------------------------------------------------------- +void cpyrt::op_dealloc_nofree(CPPInstance* pyobj) { + // Destroy the held C++ object, if owned; does not deallocate the proxy. + + interop::TCppScope_t klass = pyobj->ObjectIsA(false /* check_smart */); + void*& cppobj = pyobj->GetObjectRaw(); + + if (pyobj->fFlags & CPPInstance::kIsRegulated) + MemoryRegulator::UnregisterPyObject(pyobj, + (PyObject*)Py_TYPE((PyObject*)pyobj)); + + if (cppobj && (pyobj->fFlags & CPPInstance::kIsOwner)) { + if (pyobj->fFlags & CPPInstance::kIsValue) { + interop::CallDestructor(klass, cppobj); + interop::Deallocate(klass, cppobj); + } else + interop::Destruct(klass, cppobj); + } + cppobj = nullptr; + + if (pyobj->IsExtended()) + delete (ExtendedData*)pyobj->fObject; + pyobj->fFlags = CPPInstance::kNoWrapConv; +} + +namespace cppjit::cpyrt { + +//---------------------------------------------------------------------------- +static int op_traverse(CPPInstance* /*pyobj*/, visitproc /*visit*/, + void* /*arg*/) { + return 0; +} + +//= cpyrt object proxy null-ness checking ================================= +static int op_nonzero(CPPInstance* self) { + // Null of the proxy is determined by null-ness of the held C++ object. + if (!self->GetObject()) + return 0; + + // If the object is valid, then the normal Python behavior is to allow __len__ + // to determine truth. However, that function is defined in typeobject.c and + // only installed if tp_as_number exists w/o the nb_nonzero/nb_bool slot + // filled in, so it can not be called directly. Instead, since we're only ever + // dealing with CPPInstance derived objects, ignore length from sequence or + // mapping and call the __len__ method, if any, directly. + + PyObject* pylen = + PyObject_CallMethodObjArgs((PyObject*)self, PyStrings::gLen, NULL); + if (!pylen) { + PyErr_Clear(); + return 1; // since it's still a valid object + } + + int result = PyObject_IsTrue(pylen); + Py_DECREF(pylen); + return result; +} + +//= cpyrt object explicit destruction ===================================== +static PyObject* op_destruct(CPPInstance* self) { + // User access to force deletion of the object. Needed in case of a true + // garbage collector (like in PyPy), to allow the user control over when + // the C++ destructor is called. This method requires that the C++ object + // is owned (no-op otherwise). + op_dealloc_nofree(self); + Py_RETURN_NONE; +} + +//= cpyrt object dispatch support ========================================= +static PyObject* op_dispatch(PyObject* self, PyObject* args, + PyObject* /* kwds */) { + // User-side __dispatch__ method to allow selection of a specific overloaded + // method. The actual selection is in the __overload__() method of + // CPPOverload. + PyObject *mname = nullptr, *sigarg = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!O!:__dispatch__"), + &cpyrt_PyText_Type, &mname, &cpyrt_PyText_Type, + &sigarg)) + return nullptr; + + // get the named overload + PyObject* pymeth = PyObject_GetAttr(self, mname); + if (!pymeth) + return nullptr; + + // get the '__overload__' method to allow overload selection + PyObject* pydisp = + PyObject_GetAttrString(pymeth, const_cast("__overload__")); + if (!pydisp) { + Py_DECREF(pymeth); + return nullptr; + } + + // finally, call dispatch to get the specific overload + PyObject* oload = PyObject_CallFunctionObjArgs(pydisp, sigarg, nullptr); + Py_DECREF(pydisp); + Py_DECREF(pymeth); + return oload; +} + +//= cpyrt smart pointer support =========================================== +static PyObject* op_get_smartptr(CPPInstance* self) { + if (!self->IsSmart()) { + // TODO: more likely should raise + Py_RETURN_NONE; + } + + return cpyrt::BindCppObjectNoCast(self->GetSmartObject(), SMART_TYPE(self), + CPPInstance::kNoWrapConv); +} + +//= pointer-as-array support for legacy C code =============================== +void cpyrt::CPPInstance::CastToArray(Py_ssize_t sz) { + CreateExtension(); + fFlags |= kIsArray; + ARRAY_SIZE(this) = sz; +} + +Py_ssize_t cpyrt::CPPInstance::ArrayLength() { + if (!(fFlags & kIsArray)) + return -1; + return (Py_ssize_t)ARRAY_SIZE(this); +} + +static PyObject* op_reshape(CPPInstance* self, PyObject* shape) { + // Allow the user to fix up the actual (type-strided) size of the buffer. + if (!PyTuple_Check(shape) || PyTuple_GET_SIZE(shape) != 1) { + PyErr_SetString(PyExc_TypeError, "tuple object of size 1 expected"); + return nullptr; + } + + long sz = PyLong_AsLong(PyTuple_GET_ITEM(shape, 0)); + if (sz <= 0) { + PyErr_SetString(PyExc_ValueError, "array length must be positive"); + return nullptr; + } + + self->CastToArray(sz); + + Py_RETURN_NONE; +} + +static PyObject* op_item(CPPInstance* self, Py_ssize_t idx) { + // In C, it is common to represent an array of structs as a pointer to the + // first object in the array. If the caller indexes a pointer to an object + // that does not define indexing, then highly likely such C-style indexing is + // the goal. Just like C, this is potentially unsafe, so caveat emptor. + + if (!(self->fFlags & (CPPInstance::kIsReference | CPPInstance::kIsArray))) { + PyErr_Format(PyExc_TypeError, "%s object does not support indexing", + Py_TYPE(self)->tp_name); + return nullptr; + } + + if (idx < 0) { + // this is debatable, and probably should not care, but the use case is + // pretty circumscribed anyway, so might as well keep the functionality + // simple + PyErr_SetString(PyExc_IndexError, + "negative indices not supported for array of structs"); + return nullptr; + } + + if (self->fFlags & CPPInstance::kIsArray) { + Py_ssize_t maxidx = ARRAY_SIZE(self); + if (0 <= maxidx && maxidx <= idx) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + } + + unsigned flags = 0; + size_t sz = sizeof(void*); + if (self->fFlags & CPPInstance::kIsPtrPtr) { + flags = CPPInstance::kIsReference; + } else { + sz = interop::SizeOf(((CPPClass*)Py_TYPE(self))->fCppType); + } + + uintptr_t address = + (uintptr_t)(flags ? self->GetObjectRaw() : self->GetObject()); + void* indexed_obj = (void*)(address + (uintptr_t)(idx * sz)); + + return BindCppObjectNoCast(indexed_obj, ((CPPClass*)Py_TYPE(self))->fCppType, + flags); +} + +//- sequence methods -------------------------------------------------------- +static PySequenceMethods op_as_sequence = { + 0, // sq_length + 0, // sq_concat + 0, // sq_repeat + (ssizeargfunc)op_item, // sq_item + 0, // sq_slice + 0, // sq_ass_item + 0, // sq_ass_slice + 0, // sq_contains + 0, // sq_inplace_concat + 0, // sq_inplace_repeat +}; + +PyCFunction& CPPInstance::ReduceMethod() { + static PyCFunction reducer = nullptr; + return reducer; +} + +PyObject* op_reduce(PyObject* self, PyObject* args) { + auto& reducer = CPPInstance::ReduceMethod(); + if (!reducer) { + PyErr_SetString(PyExc_NotImplementedError, ""); + return nullptr; + } + return reducer(self, args); +} + +//---------------------------------------------------------------------------- +static PyMethodDef op_methods[] = { + {(char*)"__destruct__", (PyCFunction)op_destruct, METH_NOARGS, + (char*)"call the C++ destructor"}, + {(char*)"__dispatch__", (PyCFunction)op_dispatch, METH_VARARGS, + (char*)"dispatch to selected overload"}, + {(char*)"__smartptr__", (PyCFunction)op_get_smartptr, METH_NOARGS, + (char*)"get associated smart pointer, if any"}, + {(char*)"__reduce__", (PyCFunction)op_reduce, METH_NOARGS, + (char*)"reduce method for serialization"}, + {(char*)"__reshape__", (PyCFunction)op_reshape, METH_O, + (char*)"cast pointer to 1D array type"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt object proxy construction/destruction =========================== +static CPPInstance* op_new(PyTypeObject* subtype, PyObject*, PyObject*) { + // Create a new object proxy (holder only). + CPPInstance* pyobj = (CPPInstance*)subtype->tp_alloc(subtype, 0); + pyobj->fObject = nullptr; + pyobj->fFlags = CPPInstance::kNoWrapConv; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static void op_dealloc(CPPInstance* pyobj) { + // Remove (Python-side) memory held by the object proxy. + PyObject_GC_UnTrack((PyObject*)pyobj); + op_dealloc_nofree(pyobj); + PyObject_GC_Del((PyObject*)pyobj); +} + +//---------------------------------------------------------------------------- +static int op_clear(CPPInstance* pyobj) { + // Garbage collector clear of held python member objects; this is a good time + // to safely remove this object from the memory regulator. + if (pyobj->fFlags & CPPInstance::kIsRegulated) + MemoryRegulator::UnregisterPyObject(pyobj, + (PyObject*)Py_TYPE((PyObject*)pyobj)); + + return 0; +} + +//---------------------------------------------------------------------------- +static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self, + PyObject* obj, int op) { + using namespace Utility; + + // special case for C++11 style nullptr + if (obj == gNullPtrObject) { + void* rawcpp = ((CPPInstance*)self)->GetObjectRaw(); + switch (op) { + case Py_EQ: + if (rawcpp == nullptr) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + case Py_NE: + if (rawcpp != nullptr) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + default: + return nullptr; // not implemented + } + } + + if (!klass->fOperators) + klass->fOperators = new PyOperators{}; + + bool flipit = false; + PyObject* binop = + op == Py_EQ ? klass->fOperators->fEq : klass->fOperators->fNe; + if (!binop) { + binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; + if (binop) + flipit = true; + } + if (!binop) { + // an operator== defined on a base class is proxied into the base's dict + // only and cached on the base at its pythonization; consult the mro before + // doing a (much more expensive, and possibly failing) lazy lookup: the base + // class method is what C++ overload resolution would select anyway (e.g. + // MSVC's STL iterators define operator== on the const_iterator base class + // only) + PyObject* mro = ((PyTypeObject*)klass)->tp_mro; + for (Py_ssize_t i = 1; mro && i < PyTuple_GET_SIZE(mro); ++i) { + PyObject* base = PyTuple_GET_ITEM(mro, i); + // static types such as CPPInstance_Type pass CPPScope_Check (their + // metatype is CPPScope_Type), but are plain PyTypeObjects without the + // CPPScope layout; only heap types created through the metatype carry + // fOperators, so reading it from anything else is out of bounds + if (!CPPScope_Check(base) || + !PyType_HasFeature((PyTypeObject*)base, Py_TPFLAGS_HEAPTYPE) || + !((CPPClass*)base)->fOperators) + continue; + PyOperators* bops = ((CPPClass*)base)->fOperators; + PyObject* bop = op == Py_EQ ? bops->fEq : bops->fNe; + if (bop && bop != Py_None) { + binop = bop; + break; + } + bop = op == Py_EQ ? bops->fNe : bops->fEq; + if (bop && bop != Py_None) { + binop = bop; + flipit = true; + break; + } + } + if (binop) { + // cache on this class so the mro walk happens only once; a flipped + // operator goes into the slot of the op it actually implements, which + // is what the slot-based flip logic above expects on the next call + Py_INCREF(binop); + bool asEq = (op == Py_EQ) != flipit; + if (asEq) + klass->fOperators->fEq = binop; + else + klass->fOperators->fNe = binop; + } + } + if (!binop) { + const char* cppop = op == Py_EQ ? "==" : "!="; + PyCallable* pyfunc = FindBinaryOperator(self, obj, cppop); + if (pyfunc) + binop = (PyObject*)CPPOverload_New(cppop, pyfunc); + else { + Py_INCREF(Py_None); + binop = Py_None; + } + // sets the operator to Py_None if not found, indicating that search was + // done + if (op == Py_EQ) + klass->fOperators->fEq = binop; + else + klass->fOperators->fNe = binop; + } + + if (!binop || binop == Py_None) + return nullptr; + + PyObject* args = PyTuple_New(1); + Py_INCREF(obj); + PyTuple_SET_ITEM(args, 0, obj); + // since this overload is "ours", don't have to worry about rebinding + ((CPPOverload*)binop)->fSelf = (CPPInstance*)self; + PyObject* result = CPPOverload_Type.tp_call(binop, args, nullptr); + ((CPPOverload*)binop)->fSelf = nullptr; + Py_DECREF(args); + + if (!result) { + PyErr_Clear(); + return nullptr; + } + + // successful result, but may need to reverse the outcome + if (!flipit) + return result; + + int istrue = PyObject_IsTrue(result); + Py_DECREF(result); + if (istrue) { + Py_RETURN_FALSE; + } + Py_RETURN_TRUE; +} + +static inline void* cast_actual(void* obj) { + void* address = ((CPPInstance*)obj)->GetObject(); + if (((CPPInstance*)obj)->fFlags & CPPInstance::kIsActual) + return address; + + interop::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; + interop::TCppScope_t clActual = interop::GetActualClass(klass, address); + if (clActual && clActual != klass) { + intptr_t offset = interop::GetBaseOffset( + clActual, klass, address, -1 /* down-cast */, true /* report errors */); + if (offset != -1) + address = (void*)((intptr_t)address + offset); + } + + return address; +} + +#define CPYRT_ORDERED_OPERATOR_STUB(op, ometh, label) \ + if (!ometh) { \ + PyCallable* pyfunc = \ + Utility::FindBinaryOperator((PyObject*)self, other, #op); \ + if (pyfunc) \ + ometh = (PyObject*)CPPOverload_New(#label, pyfunc); \ + } \ + meth = ometh; + +static PyObject* op_richcompare(CPPInstance* self, PyObject* other, int op) { + // Rich set of comparison objects; currently supported: + // == : Py_EQ + // != : Py_NE + // + // < : Py_LT + // <= : Py_LE + // > : Py_GT + // >= : Py_GE + // + + // associative comparison operators + if (op == Py_EQ || op == Py_NE) { + // special case for None to compare True to a null-pointer + if ((PyObject*)other == Py_None && !self->fObject) { + const char* msg = + "\nComparison of C++ nullptr objects with `None` is no longer " + "supported." + "\n\nPreviously, `None` was treated as equivalent to a null C++ " + "pointer, " + "but this led to confusing behavior where `x == None` could be True " + "even though `x is None` was False." + "\n\nTo test whether a C++ object is null or not, check its truth " + "value instead:" + "\n if not x: ..." + "\nor use `x is None` to explicitly check for Python None." + "\n"; + + PyErr_SetString(PyExc_TypeError, msg); + return NULL; // stop execution, raise TypeError + } + + // use C++-side operators if available + PyObject* result = + eqneq_binop((CPPClass*)Py_TYPE(self), (PyObject*)self, other, op); + if (!result && CPPInstance_Check(other)) + result = + eqneq_binop((CPPClass*)Py_TYPE(other), other, (PyObject*)self, op); + if (result) + return result; + + // default behavior: type + held pointer value defines identity; if both are + // CPPInstance objects, perform an additional autocast if need be + bool bIsEq = false; + + if ((Py_TYPE(self) == Py_TYPE(other) && + self->GetObject() == ((CPPInstance*)other)->GetObject())) { + // direct match + bIsEq = true; + } else if (CPPInstance_Check(other)) { + // try auto-cast match + void* addr1 = cast_actual(self); + void* addr2 = cast_actual(other); + bIsEq = addr1 && addr2 && (addr1 == addr2); + } + + if ((op == Py_EQ && bIsEq) || (op == Py_NE && !bIsEq)) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; + } + + // ordered comparison operators + else if (op == Py_LT || op == Py_LE || op == Py_GT || op == Py_GE) { + CPPClass* klass = (CPPClass*)Py_TYPE(self); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators{}; + PyObject* meth = nullptr; + + switch (op) { + case Py_LT: + CPYRT_ORDERED_OPERATOR_STUB(<, klass->fOperators->fLt, __lt__) + break; + case Py_LE: + CPYRT_ORDERED_OPERATOR_STUB(<=, klass->fOperators->fLe, __le__) + break; + case Py_GT: + CPYRT_ORDERED_OPERATOR_STUB(>, klass->fOperators->fGt, __gt__) + break; + case Py_GE: + CPYRT_ORDERED_OPERATOR_STUB(>=, klass->fOperators->fGe, __ge__) + break; + } + + if (!meth) { + PyErr_SetString(PyExc_NotImplementedError, ""); + return nullptr; + } + + return PyObject_CallFunctionObjArgs(meth, (PyObject*)self, other, nullptr); + } + + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +//---------------------------------------------------------------------------- +static PyObject* op_repr(CPPInstance* self) { + // Build a representation string of the object proxy that shows the address + // of the C++ object that is held, as well as its type. + PyObject* pyclass = (PyObject*)Py_TYPE(self); + if (CPPScope_Check(pyclass) && + (((CPPScope*)pyclass)->fFlags & CPPScope::kIsPython)) + return PyBaseObject_Type.tp_repr((PyObject*)self); + PyObject* modname = PyObject_GetAttr(pyclass, PyStrings::gModule); + + interop::TCppScope_t klass = self->ObjectIsA(); + std::string clName = klass ? interop::GetFinalName(klass) : ""; + if (self->fFlags & CPPInstance::kIsPtrPtr) + clName.append("**"); + else if (self->fFlags & CPPInstance::kIsReference) + clName.append("*"); + + PyObject* repr = nullptr; + if (self->IsSmart()) { + std::string smartPtrName = interop::GetScopedFinalName(SMART_TYPE(self)); + repr = cpyrt_PyText_FromFormat( + const_cast("<%s.%s object at %p held by %s at %p>"), + cpyrt_PyText_AsString(modname), clName.c_str(), self->GetObject(), + smartPtrName.c_str(), self->GetObjectRaw()); + } else { + repr = cpyrt_PyText_FromFormat(const_cast("<%s.%s object at %p>"), + cpyrt_PyText_AsString(modname), + clName.c_str(), self->GetObject()); + } + + Py_DECREF(modname); + return repr; +} + +//---------------------------------------------------------------------------- +static inline Py_hash_t cpyrt_PyLong_AsHash_t(PyObject* obj) { + // Cannot use PyLong_AsSize_t here, as it cuts of at PY_SSIZE_T_MAX, which is + // only half of the max of std::size_t returned by the hash. + if (sizeof(unsigned long) >= sizeof(size_t)) + return (Py_hash_t)PyLong_AsUnsignedLong(obj); + return (Py_hash_t)PyLong_AsUnsignedLongLong(obj); +} + +static Py_hash_t op_hash(CPPInstance* self) { + // Try to locate an std::hash for this type and use that if it exists + CPPClass* klass = (CPPClass*)Py_TYPE(self); + if (klass->fOperators && klass->fOperators->fHash) { + Py_hash_t h = 0; + PyObject* hashval = PyObject_CallFunctionObjArgs(klass->fOperators->fHash, + (PyObject*)self, nullptr); + if (hashval) { + h = cpyrt_PyLong_AsHash_t(hashval); + Py_DECREF(hashval); + } + return h; + } + + interop::TCppScope_t stdhash = interop::GetFullScope( + "std::hash<" + interop::GetScopedFinalName(self->ObjectIsA()) + ">"); + if (stdhash) { + PyObject* hashcls = CreateScopeProxy(stdhash); + PyObject* dct = PyObject_GetAttr(hashcls, PyStrings::gDict); + bool isValid = PyMapping_HasKeyString(dct, (char*)"__call__"); + Py_DECREF(dct); + if (isValid) { + PyObject* hashobj = PyObject_CallObject(hashcls, nullptr); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators{}; + klass->fOperators->fHash = hashobj; + Py_DECREF(hashcls); + + Py_hash_t h = 0; + PyObject* hashval = + PyObject_CallFunctionObjArgs(hashobj, (PyObject*)self, nullptr); + if (hashval) { + h = cpyrt_PyLong_AsHash_t(hashval); + Py_DECREF(hashval); + } + return h; + } + Py_DECREF(hashcls); + } + + // if not valid, simply reset the hash function so as to not kill performance + ((PyTypeObject*)Py_TYPE(self))->tp_hash = PyBaseObject_Type.tp_hash; + return PyBaseObject_Type.tp_hash((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static PyObject* op_str_internal(PyObject* pyobj, PyObject* lshift, + bool isBound) { + static interop::TCppScope_t sOStringStreamID = + interop::GetFullScope("std::ostringstream"); + std::ostringstream s; + PyObject* pys = BindCppObjectNoCast(&s, sOStringStreamID); + Py_INCREF(pys); + // for py3 and later, a ref-count of 2 is okay to consider the object + // temporary, but in this case, we can't lose our existing ostrinstring + // (otherwise, we'd have to peel it out of the return value, if moves are used + Py_INCREF(pys); + + PyObject* res; + if (isBound) + res = PyObject_CallFunctionObjArgs(lshift, pys, NULL); + else + res = PyObject_CallFunctionObjArgs(lshift, pys, pyobj, NULL); + + Py_DECREF(pys); + Py_DECREF(pys); + + if (res) { + Py_DECREF(res); + return cpyrt_PyText_FromString(s.str().c_str()); + } + + return nullptr; +} + +static inline bool ScopeFlagCheck(CPPInstance* self, CPPScope::EFlags flag) { + return ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags & flag; +} + +static inline void ScopeFlagSet(CPPInstance* self, CPPScope::EFlags flag) { + ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags |= flag; +} + +static PyObject* op_str(CPPInstance* self) { + // There are three possible options here: + // 1. Available operator<< to convert through an ostringstream + // 2. Cling's pretty printing + // 3. Generic printing as done in op_repr + // + // Additionally, there may be a mapped __str__ from the C++ type defining + // `operator char*` or `operator const char*`. Results are memoized for + // performance reasons. + + // 0. Protect against trying to print a typed nullptr object through an + // insertion operator + if (!self->GetObject()) + return op_repr(self); + + // 1. Available operator<< to convert through an ostringstream + if (!ScopeFlagCheck(self, CPPScope::kNoOSInsertion)) { + for (PyObject* pyname : {PyStrings::gLShift, PyStrings::gLShiftC, + (PyObject*)0x01, (PyObject*)0x02}) { + if (pyname == PyStrings::gLShift && + ScopeFlagCheck(self, CPPScope::kGblOSInsertion)) + continue; + + else if (pyname == (PyObject*)0x01) { + // normal lookup failed; attempt lazy install of global + // operator<<(ostream&, type&) + std::string rcname = Utility::ClassName((PyObject*)self); + interop::TCppScope_t rnsID = + interop::GetScope(TypeManip::extract_namespace(rcname)); + PyCallable* pyfunc = + Utility::FindBinaryOperator("std::ostream", rcname, "<<", rnsID); + if (!pyfunc) + continue; + + Utility::AddToClass((PyObject*)Py_TYPE((PyObject*)self), "__lshiftc__", + pyfunc); + + pyname = PyStrings::gLShiftC; + ScopeFlagSet(self, CPPScope::kGblOSInsertion); + + } else if (pyname == (PyObject*)0x02) { + // TODO: the only reason this still exists, is b/c friend functions are + // otherwise not found + // TODO: ToString() still leaks ... + const std::string& pretty = + compat::ObjToString(self->ObjectIsA(), self->GetObject()); + if (!pretty.empty()) + return cpyrt_PyText_FromString(pretty.c_str()); + continue; + } + + PyObject* lshift = PyObject_GetAttr( + pyname == PyStrings::gLShift ? (PyObject*)self + : (PyObject*)Py_TYPE((PyObject*)self), + pyname); + + if (lshift) { + PyObject* result = op_str_internal((PyObject*)self, lshift, + pyname == PyStrings::gLShift); + Py_DECREF(lshift); + if (result) + return result; + } + + PyErr_Clear(); + } + + // failed ostream printing; don't try again + ScopeFlagSet(self, CPPScope::kNoOSInsertion); + } + + // 2. Cling's pretty printing (not done through backend for performance + // reasons) + if (!ScopeFlagCheck(self, CPPScope::kNoPrettyPrint)) { + PyObject* printValue = compat::GetClingPrintValue(); + + if (printValue) { + // as printValue only works well for templates taking pointer arguments, + // we'll have to force the issue by working with a by-ptr object + interop::TCppObject_t cppobj = self->GetObjectRaw(); + PyObject* byref = (PyObject*)self; + if (!(self->fFlags & CPPInstance::kIsReference)) { + byref = BindCppObjectNoCast( + (interop::TCppObject_t)&cppobj, self->ObjectIsA(), + CPPInstance::kIsReference | CPPInstance::kNoMemReg); + } else { + Py_INCREF(byref); + } + + // explicit template lookup + PyObject* clName = + cpyrt_PyText_FromString(Utility::ClassName((PyObject*)self).c_str()); + PyObject* OL = PyObject_GetItem(printValue, clName); + Py_DECREF(clName); + + PyObject* pretty = + OL ? PyObject_CallFunctionObjArgs(OL, byref, nullptr) : nullptr; + Py_XDECREF(OL); + Py_DECREF(byref); + + PyObject* result = nullptr; + if (pretty) { + // the std::string executor may return a Python string + std::string pv; + if (cpyrt_PyText_Check(pretty)) { + if (const char* s = cpyrt_PyText_AsString(pretty)) + pv = s; + else + PyErr_Clear(); + } else if (CPPInstance_Check(pretty) && + ((CPPInstance*)pretty)->GetObject()) { + pv = *(std::string*)((CPPInstance*)pretty)->GetObject(); + } + // an address or the generic fallback means no usable pretty printer + if (!pv.empty() && pv.find("@0x") == std::string::npos && + pv != "{not representable}") + result = cpyrt_PyText_FromString(pv.c_str()); + Py_DECREF(pretty); + if (result) + return result; + } + + PyErr_Clear(); + } + + // if not available/specialized, don't try again + ScopeFlagSet(self, CPPScope::kNoPrettyPrint); + } + + // 3. Generic printing as done in op_repr + return op_repr(self); +} + +//----------------------------------------------------------------------------- +static PyObject* op_getownership(CPPInstance* pyobj, void*) { + return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsOwner)); +} + +//----------------------------------------------------------------------------- +static int op_setownership(CPPInstance* pyobj, PyObject* value, void*) { + // Set the ownership (True is python-owns) for the given object. + long shouldown = PyLong_AsLong(value); + if (shouldown == -1 && PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "__python_owns__ should be either True or False"); + return -1; + } + + (bool)shouldown ? pyobj->PythonOwns() : pyobj->CppOwns(); + + return 0; +} + +//----------------------------------------------------------------------------- +static PyGetSetDef op_getset[] = { + {(char*)"__python_owns__", (getter)op_getownership, (setter)op_setownership, + (char*)"If true, python manages the life time of this object", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt type number stubs to allow dynamic overrides ===================== +#define CPYRT_STUB_BODY(name, op) \ + bool previously_resolved_overload = (bool)meth; \ + if (!meth) { \ + PyErr_Clear(); \ + PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ + if (pyfunc) \ + meth = (PyObject*)CPPOverload_New(#name, pyfunc); \ + else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + } \ + PyObject* res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr); \ + if (!res && previously_resolved_overload) { \ + /* try again, in case (left, right) are different types than before */ \ + PyErr_Clear(); \ + PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ + if (pyfunc) \ + ((CPPOverload*&)meth)->AdoptMethod(pyfunc); \ + else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + /* use same overload with newly added function */ \ + res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr); \ + } \ + return res; + +#define CPYRT_OPERATOR_STUB(name, op, ometh) \ + static PyObject* op_##name##_stub(PyObject* left, PyObject* right) { \ + /* placeholder to lazily install and forward to 'ometh' if available */ \ + CPPClass* klass = (CPPClass*)Py_TYPE(left); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + PyObject*& meth = ometh; \ + PyObject *cppobj = left, *other = right; \ + CPYRT_STUB_BODY(name, op) \ + } + +#define CPYRT_ASSOCIATIVE_OPERATOR_STUB(name, op, lmeth, rmeth) \ + static PyObject* op_##name##_stub(PyObject* left, PyObject* right) { \ + /* placeholder to lazily install and forward do '(l/r)meth' if available \ + */ \ + CPPClass* klass; \ + PyObject** pmeth; \ + PyObject *cppobj, *other; \ + if (CPPInstance_Check(left)) { \ + klass = (CPPClass*)Py_TYPE(left); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + pmeth = &lmeth; \ + cppobj = left; \ + other = right; \ + } else if (CPPInstance_Check(right)) { \ + klass = (CPPClass*)Py_TYPE(right); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + pmeth = &rmeth; \ + cppobj = right; \ + other = left; \ + } else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + PyObject*& meth = *pmeth; \ + CPYRT_STUB_BODY(name, op) \ + } + +#define CPYRT_UNARY_OPERATOR(name, op, label) \ + static PyObject* op_##name##_stub(PyObject* pyobj) { \ + /* placeholder to lazily install unary operators */ \ + PyCallable* pyfunc = \ + Utility::FindUnaryOperator((PyObject*)Py_TYPE(pyobj), #op); \ + if (pyfunc && \ + Utility::AddToClass((PyObject*)Py_TYPE(pyobj), #label, pyfunc)) \ + return PyObject_CallMethod(pyobj, (char*)#label, nullptr); \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } + +// clang-format off +CPYRT_ASSOCIATIVE_OPERATOR_STUB(add, +, klass->fOperators->fLAdd, klass->fOperators->fRAdd) +CPYRT_OPERATOR_STUB( sub, -, klass->fOperators->fSub) +CPYRT_ASSOCIATIVE_OPERATOR_STUB(mul, *, klass->fOperators->fLMul, klass->fOperators->fRMul) +CPYRT_OPERATOR_STUB( div, /, klass->fOperators->fDiv) +CPYRT_UNARY_OPERATOR(neg, -, __neg__) +CPYRT_UNARY_OPERATOR(pos, +, __pos__) +CPYRT_UNARY_OPERATOR(invert, ~, __invert__) +// clang-format on + +//----------------------------------------------------------------------------- +static PyNumberMethods op_as_number = { + (binaryfunc)op_add_stub, // nb_add + (binaryfunc)op_sub_stub, // nb_subtract + (binaryfunc)op_mul_stub, // nb_multiply + 0, // nb_remainder + 0, // nb_divmod + 0, // nb_power + (unaryfunc)op_neg_stub, // nb_negative + (unaryfunc)op_pos_stub, // nb_positive + 0, // nb_absolute + (inquiry)op_nonzero, // nb_bool (nb_nonzero in p2) + (unaryfunc)op_invert_stub, // nb_invert + 0, // nb_lshift + 0, // nb_rshift + 0, // nb_and + 0, // nb_xor + 0, // nb_or + 0, // nb_int + 0, // nb_long (nb_reserved in p3) + 0, // nb_float + 0, // nb_inplace_add + 0, // nb_inplace_subtract + 0, // nb_inplace_multiply + 0, // nb_inplace_remainder + 0, // nb_inplace_power + 0, // nb_inplace_lshift + 0, // nb_inplace_rshift + 0, // nb_inplace_and + 0, // nb_inplace_xor + 0, // nb_inplace_or + 0, // nb_floor_divide + (binaryfunc)op_div_stub, // nb_true_divide + 0, // nb_inplace_floor_divide + 0, // nb_inplace_true_divide + 0, // nb_index + 0, // nb_matrix_multiply + 0 // nb_inplace_matrix_multiply +}; + +//= cpyrt object proxy type =============================================== +PyTypeObject CPPInstance_Type = { + PyVarObject_HEAD_INIT(&CPPScope_Type, + 0)(char*) "cppjit.CPPInstance", // tp_name + sizeof(CPPInstance), // tp_basicsize + 0, // tp_itemsize + (destructor)op_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)op_repr, // tp_repr + &op_as_number, // tp_as_number + &op_as_sequence, // tp_as_sequence + 0, // tp_as_mapping + (hashfunc)op_hash, // tp_hash + 0, // tp_call + (reprfunc)op_str, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_HAVE_GC, // tp_flags + (char*)"cppjit object proxy (internal)", // tp_doc + (traverseproc)op_traverse, // tp_traverse + (inquiry)op_clear, // tp_clear + (richcmpfunc)op_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + op_methods, // tp_methods + 0, // tp_members + op_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)op_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.h b/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.h new file mode 100644 index 0000000000000..c94ecfa16c220 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.h @@ -0,0 +1,149 @@ +#ifndef CPYRT_CPPINSTANCE_H +#define CPYRT_CPPINSTANCE_H + +////////////////////////////////////////////////////////////////////////////// +// // +// cppjit::cpyrt::CPPInstance // +// // +// Python-side proxy, encapsulaties a C++ object. // +// // +////////////////////////////////////////////////////////////////////////////// + +// Bindings +#ifndef Py_LIMITED_API +#include "CPPScope.h" +#endif +#include "CallContext.h" // for Parameter +#include "CommonDefs.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +typedef std::vector> CI_DatamemberCache_t; + +class CPPInstance { +public: + enum EFlags { + kDefault = 0x0000, + kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) + kIsOwner = 0x0002, // Python instance owns C++ object/memory + kIsExtended = 0x0004, // has extended data + kIsValue = 0x0008, // was created from a by-value return + kIsReference = 0x0010, // represents one indirection + kIsArray = 0x0020, // represents an array of objects + kIsSmartPtr = 0x0040, // is or embeds a smart pointer + kIsPtrPtr = 0x0080, // represents two indirections + kIsRValue = 0x0100, // can be used as an r-value + kIsLValue = 0x0200, // can be used as an l-value + kNoMemReg = 0x0400, // do not register with memory regulator + kIsRegulated = 0x0800, // is registered with memory regulator + kIsActual = 0x1000, // has been downcasted to actual type + kHasLifeLine = 0x2000, // has a life line set + }; + +public: // public, as the python C-API works with C structs + PyObject_HEAD void* fObject; + uint32_t fFlags; + +public: + // construction (never done directly) + CPPInstance() = delete; + + void Set(void* address, EFlags flags = kDefault); + CPPInstance* Copy(void* cppinst, PyTypeObject* target = nullptr); + + // state checking + bool IsExtended() const { return fFlags & kIsExtended; } + bool IsSmart() const { return fFlags & kIsSmartPtr; } + + // access to C++ pointer and type + void* GetObject(); + void*& GetObjectRaw() { return IsExtended() ? *(void**)fObject : fObject; } + interop::TCppScope_t ObjectIsA(bool check_smart = true) const; + + // memory management: ownership of the underlying C++ object + void PythonOwns(); + void CppOwns(); + + // data member cache + CI_DatamemberCache_t& GetDatamemberCache(); + + // smart pointer management + void SetSmart(PyObject* smart_type); + void* GetSmartObject() { return GetObjectRaw(); } + interop::TCppScope_t GetSmartIsA() const; + interop::TCppScope_t GetSmartUnderlyingType() const; + + // cross-inheritance dispatch + void SetDispatchPtr(void*); + + // redefine pointer to object as fixed-size array + void CastToArray(Py_ssize_t sz); + Py_ssize_t ArrayLength(); + + // implementation of the __reduce__ method: doesn't wrap any function by + // default but can be re-assigned by libraries that add C++ object + // serialization support, like ROOT + static PyCFunction& ReduceMethod(); + +private: + void CreateExtension(); + void* GetExtendedObject(); +}; + +//- public methods ----------------------------------------------------------- +inline void CPPInstance::Set(void* address, EFlags flags) { + // Initialize the proxy with the pointer value 'address.' + if (flags != kDefault) + fFlags = flags; + GetObjectRaw() = address; +} + +//---------------------------------------------------------------------------- +inline void* CPPInstance::GetObject() { + // Retrieve a pointer to the held C++ object. + if (!IsExtended()) { + if (fObject && (fFlags & kIsReference)) + return *(reinterpret_cast(fObject)); + else + return fObject; // may be null + } else + return GetExtendedObject(); +} + +#ifndef Py_LIMITED_API +//---------------------------------------------------------------------------- +inline interop::TCppScope_t CPPInstance::ObjectIsA(bool check_smart) const { + // Retrieve the C++ type identifier (or raw type if smart). + if (check_smart || !IsSmart()) + return ((CPPClass*)Py_TYPE(this))->fCppType; + return GetSmartIsA(); +} +#endif + +//- object proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPInstance_Type; + +#ifndef Py_LIMITED_API +template inline bool CPPInstance_Check(T* object) { + // Short-circuit the type check by checking tp_new which all generated + // subclasses of CPPInstance inherit. + return object && (Py_TYPE(object)->tp_new == CPPInstance_Type.tp_new || + PyObject_TypeCheck(object, &CPPInstance_Type)); +} +#endif + +template inline bool CPPInstance_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPInstance_Type; +} + +//- helper for memory regulation (no PyTypeObject equiv. member in p2.2) ----- +void op_dealloc_nofree(CPPInstance*); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPINSTANCE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.cxx new file mode 100644 index 0000000000000..36d652dc7caf9 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.cxx @@ -0,0 +1,1122 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPMethod.h" +#include "Converters.h" +#include "Executors.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +#include "cpyrt/PyException.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +//- data and local helpers --------------------------------------------------- +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +extern PyObject* gBusException; +extern PyObject* gSegvException; +extern PyObject* gIllException; +extern PyObject* gAbrtException; +} // namespace cppjit::cpyrt + +//- public helper ------------------------------------------------------------ +cpyrt::PyCallArgs::~PyCallArgs() { + if (fFlags & kSelfSwap) // if self swap, fArgs has been offset by -1 + std::swap((PyObject*&)fSelf, ((PyObject**)fArgs)[0]); + + if (fFlags & kIsOffset) + fArgs -= 1; + + if (fFlags & kDoItemDecref) { + for (Py_ssize_t iarg = 0; iarg < cpyrt_PyArgs_GET_SIZE(fArgs, fNArgsf); + ++iarg) + Py_DECREF(fArgs[iarg]); + } + + if (fFlags & kDoFree) + PyMem_Free((void*)fArgs); + else if (fFlags & kArgsSwap) { + // if self swap, fArgs has been offset by -1 + int offset = (fFlags & kSelfSwap) ? 1 : 0; + std::swap(((PyObject**)fArgs + offset)[0], ((PyObject**)fArgs + offset)[1]); + } +} + +//- private helpers ---------------------------------------------------------- +inline bool cpyrt::CPPMethod::VerifyArgCount_(Py_ssize_t actual) { + // actual number of arguments must be between required and max args + Py_ssize_t maxargs = (Py_ssize_t)fConverters.size(); + + if (maxargs != actual) { + if (actual < (Py_ssize_t)fArgsRequired) { + SetPyError_(cpyrt_PyText_FromFormat( + "takes at least %d arguments (%zd given)", fArgsRequired, actual)); + return false; + } else if (maxargs < actual) { + SetPyError_(cpyrt_PyText_FromFormat( + "takes at most %zd arguments (%zd given)", maxargs, actual)); + return false; + } + } + return true; +} + +//---------------------------------------------------------------------------- +inline void cpyrt::CPPMethod::Copy_(const CPPMethod& /* other */) { + // fScope and fMethod handled separately + + // do not copy caches + fExecutor = nullptr; + fArgIndices = nullptr; + fArgsRequired = -1; +} + +//---------------------------------------------------------------------------- +inline void cpyrt::CPPMethod::Destroy_() { + // destroy executor and argument converters + if (fExecutor && fExecutor->HasState()) + delete fExecutor; + fExecutor = nullptr; + + for (auto p : fConverters) { + if (p && p->HasState()) + delete p; + } + fConverters.clear(); + + delete fArgIndices; + fArgIndices = nullptr; + fArgsRequired = -1; +} + +//---------------------------------------------------------------------------- +inline PyObject* cpyrt::CPPMethod::ExecuteFast(void* self, ptrdiff_t offset, + CallContext* ctxt) { + // call into C++ through fExecutor; abstracted out from Execute() to prevent + // some code duplication with ProtectedCall() + PyObject* result = nullptr; + + try { // C++ try block + result = fExecutor->Execute( + fMethod, interop::TCppObject_t((void*)((intptr_t)self + offset)), ctxt); + } catch (PyException&) { + ctxt->fFlags |= CallContext::kPyException; + result = nullptr; // error already set + } catch (std::exception& e) { + // attempt to set the exception to the actual type, to allow catching with + // the Python C++ type + static interop::TCppScope_t exc_type = + interop::GetFullScope("std::exception"); + + ctxt->fFlags |= CallContext::kCppException; + + PyObject* pyexc_type = nullptr; + PyObject* pyexc_obj = nullptr; + + // TODO: factor this code with the same in ProxyWrappers (and cache it there + // to be able to look up based on TCppType_t): + interop::TCppScope_t actual = interop::GetActualClass(exc_type, &e); + const std::string& finalname = interop::GetScopedFinalName(actual); + const std::string& parentname = TypeManip::extract_namespace(finalname); + PyObject* parent = CreateScopeProxy(parentname); + if (parent) { + pyexc_type = PyObject_GetAttrString( + parent, + parentname.empty() + ? finalname.c_str() + : finalname.substr(parentname.size() + 2, std::string::npos) + .c_str()); + Py_DECREF(parent); + } + + if (pyexc_type) { + // create a copy of the exception (TODO: factor this code with the same in + // ProxyWrappers) + PyObject* pyclass = cpyrt::GetScopeProxy(actual); + PyObject* source = BindCppObjectNoCast(&e, actual); + PyObject* pyexc_copy = + PyObject_CallFunctionObjArgs(pyclass, source, nullptr); + Py_DECREF(source); + Py_DECREF(pyclass); + if (pyexc_copy) { + pyexc_obj = CPPExcInstance_Type.tp_new((PyTypeObject*)pyexc_type, + nullptr, nullptr); + ((CPPExcInstance*)pyexc_obj)->fCppInstance = (PyObject*)pyexc_copy; + } else + PyErr_Clear(); + } else + PyErr_Clear(); + + if (pyexc_type && pyexc_obj) { + PyErr_SetObject(pyexc_type, pyexc_obj); + Py_DECREF(pyexc_obj); + Py_DECREF(pyexc_type); + } else { + PyErr_Format(PyExc_Exception, "%s (C++ exception)", e.what()); + Py_XDECREF(pyexc_obj); + Py_XDECREF(pyexc_type); + } + + result = nullptr; + } catch (...) { + // don't set the kCppException flag here, as there is basically no useful + // extra information to be had and caller has to catch Exception either way + PyErr_SetString(PyExc_Exception, "unhandled, unknown C++ exception"); + result = nullptr; + } + +// TODO: covers the PyException throw case, which does not seem to work on +// Windows, so instead leaves the error be +#ifdef _WIN32 + if (PyErr_Occurred()) { + if (ResultIsPyObject()) + Py_XDECREF(result); + result = nullptr; + } +#endif + + return result; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::InitConverters_() { + // build buffers for argument dispatching + const size_t nArgs = interop::GetMethodNumArgs(fMethod); + fConverters.resize(nArgs); + + // setup the dispatch cache + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + interop::TCppType_t fullType = interop::GetMethodArgType(fMethod, iarg); + Converter* conv = CreateConverter(fullType); + if (!conv) { + PyErr_Format(PyExc_TypeError, "argument type %s not handled", + interop::GetTypeAsString(fullType).c_str()); + return false; + } + + fConverters[iarg] = conv; + } + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::InitExecutor_(Executor*& executor, + CallContext* /* ctxt */) { + // install executor conform to the return type + executor = (bool)fMethod == true + ? CreateExecutor(interop::GetMethodReturnType(fMethod)) + : CreateExecutor(interop::GetScopedFinalName(fScope)); + + if (!executor) + return false; + + return true; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::CPPMethod::GetSignatureString(bool fa) { + // built a signature representation (used for doc strings) + return interop::GetMethodSignature(fMethod, fa); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPMethod::SetPyError_(PyObject* msg) { + // Helper to report errors in a consistent format (derefs msg). + // + // Handles three cases: + // 1. No Python error occured yet: + // Set a new TypeError with the message "msg" and the docstring of this + // C++ method to give some context. + // 2. A C++ exception has occured: + // Augment the exception message with the docstring of this method + // 3. A Python exception has occurred with a traceback: + // Do nothing, Python exceptions are already informative enough + // 4. If the Python exception has no traceback hinting to an internally set + // error stack, + // extract its message and wrap it with C++ method docstring context. + +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* evalue = PyErr_Occurred() ? PyErr_GetRaisedException() : nullptr; + PyObject* etype = evalue ? (PyObject*)Py_TYPE(evalue) : nullptr; +#else + PyObject* etype = nullptr; + PyObject* evalue = nullptr; + PyObject* etrace = nullptr; + + if (PyErr_Occurred()) { + PyErr_Fetch(&etype, &evalue, &etrace); + } +#endif + + const bool isCppExc = + evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); + std::string details; + + // If the error is not a CPPExcInstance and has a traceback, the error from + // Python itself is already complete and messing with it would only make it + // less informative. + // Just restore and return. + if (evalue && !isCppExc) { +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* tb = PyException_GetTraceback(evalue); + if (tb) { + Py_DECREF(tb); + PyErr_SetRaisedException(evalue); + return; + } +#else + if (etrace) { + PyErr_Restore(etype, evalue, etrace); + return; + } +#endif + // no traceback, extract its message and fall through + PyObject* descr = PyObject_Str(evalue); + if (descr) { + details = cpyrt_PyText_AsString(descr); + Py_DECREF(descr); + } + } + + PyObject* doc = GetDocString(); + const char* cdoc = cpyrt_PyText_AsString(doc); + const char* cmsg = msg ? cpyrt_PyText_AsString(msg) : nullptr; + PyObject* errtype = etype ? etype : PyExc_TypeError; + PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); + const char* cname = pyname ? cpyrt_PyText_AsString(pyname) : "Exception"; + + if (!isCppExc) { + // this is the case where no Python error has occured yet, or an internal + // one without traceback set a new error with context + if (details.empty()) { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : ""); + } else if (cmsg) { + PyErr_Format(errtype, "%s =>\n %s: %s (%s)", cdoc, cname, cmsg, + details.c_str()); + } else { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, details.c_str()); + } + } else { + // augment the top message with context information + PyObject*& topMessage = ((CPPExcInstance*)evalue)->fTopMessage; + Py_XDECREF(topMessage); + if (msg) { + topMessage = + cpyrt_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); + } else { + topMessage = cpyrt_PyText_FromFormat("%s =>\n %s: ", cdoc, cname); + } + // restore the updated error +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(evalue); +#else + PyErr_Restore(etype, evalue, etrace); +#endif + } + + Py_XDECREF(pyname); + Py_DECREF(doc); + Py_XDECREF(msg); +} + +extern std::unordered_map + TypeReductionMap; + +//- constructors and destructor ---------------------------------------------- +cpyrt::CPPMethod::CPPMethod(interop::TCppScope_t scope, + interop::TCppMethod_t method) + : fMethod(method), fScope(scope), fExecutor(nullptr), fArgIndices(nullptr), + fArgsRequired(-1) { + interop::TCppType_t result = + interop::ResolveType(interop::GetMethodReturnType(fMethod)); + if (TypeReductionMap.find(result) != TypeReductionMap.end()) + fMethod = interop::ReduceReturnType(fMethod, TypeReductionMap[result]); + if (result && interop::IsLambdaClass(result)) + fMethod = interop::AdaptFunctionForLambdaReturn(fMethod); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod::CPPMethod(const CPPMethod& other) + : PyCallable(other), fMethod(other.fMethod), fScope(other.fScope) { + Copy_(other); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod& cpyrt::CPPMethod::operator=(const CPPMethod& other) { + if (this != &other) { + Destroy_(); + Copy_(other); + fScope = other.fScope; + fMethod = other.fMethod; + } + + return *this; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod::~CPPMethod() { Destroy_(); } + +//- public members ----------------------------------------------------------- +/** + * @brief Construct a Python string from the method's prototype + * + * @param fa Show formal arguments of the method + * @return PyObject* A Python string with the full method prototype, namespaces + * included. + * + * For example, given: + * + * int foo(int x); + * + * namespace a { + * namespace b { + * namespace c { + * int foo(int x); + * }}} + * + * This function returns: + * + * 'int foo(int x)' + * 'int a::b::c::foo(int x)' + */ +PyObject* cpyrt::CPPMethod::GetPrototype(bool fa) { + // Gather the fully qualified final scope of the method. This includes + // all namespaces up to the one where the method is declared, for example: + // namespace a { namespace b { void foo(); }} + // gives + // a::b + std::string finalscope = interop::GetScopedFinalName(fScope); + return cpyrt_PyText_FromFormat( + "%s%s %s%s", (interop::IsStaticMethod(fMethod) ? "static " : ""), + interop::GetMethodReturnTypeAsString(fMethod).c_str(), + interop::GetScopedFinalName(interop::TCppScope_t(fMethod.data)).c_str(), + GetSignatureString(fa).c_str()); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetTypeName() { + PyObject* cppname = cpyrt_PyText_FromString( + (GetReturnTypeName() + " (" + + (fScope ? interop::GetScopedFinalName(fScope) + "::*)" : "*)")) + .c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t format) { + // C++ reflection tooling for methods. + + if (request == interop::Reflex::RETURN_TYPE) { + std::string rtn = GetReturnTypeName(); + interop::TCppScope_t scope = nullptr; + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_TYPE) + scope = interop::GetScope(rtn); + + if (format == interop::Reflex::AS_STRING || + (format == interop::Reflex::OPTIMAL && !scope)) + return cpyrt_PyText_FromString(rtn.c_str()); + else if (format == interop::Reflex::AS_TYPE || + format == interop::Reflex::OPTIMAL) { + if (scope) + return CreateScopeProxy(scope); + /* TODO: builtins as type */ + } + } + + return PyCallable::Reflex(request, format); +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetPriority() { + // To help with overload selection, methods are given a priority based on the + // affinity of Python and C++ types. Priority only matters for methods that + // have an equal number of arguments and types that are possible substitutes + // (the normal selection mechanisms would simply distinguish them otherwise). + + // The following types are ordered, in favor (variants implicit): + // + // bool >> long >> int >> short + // double >> long double >> float + // const char* >> char + // + // Further, all integer types are preferred over floating point b/c int to + // float is allowed implicitly, float to int is not. + // + // Special cases that are disliked include void* and unknown/incomplete types. + // Also, moves are preferred over references. std::initializer_list is not a + // nice conversion candidate either, but needs to be higher priority to mix + // well with implicit conversions. + // TODO: extend this to favour classes that are not bases. + // TODO: profile this method (it's expensive, but should be called too often) + + int priority = 0; + + const size_t nArgs = interop::GetMethodNumArgs(fMethod); + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + const std::string aname = interop::GetMethodArgTypeAsString(fMethod, iarg); + // FIXME: convert the string comparisons with comparison to the underlying + // type: + // interop::TCppType_t type = interop::GetMethodArgType(fMethod, iarg); + + // Not builtin and spelled "const void *", so match the compacted name. + std::string compact = aname; + compact.erase(std::remove(compact.begin(), compact.end(), ' '), + compact.end()); + + if (compact.find("void*") != std::string::npos) { + priority -= 1000; // void*/void** shouldn't be too greedy + } else if (interop::IsBuiltin(aname)) { + // complex type (note: double penalty: for complex and the template type) + if (strstr(aname.c_str(), "std::complex")) + priority -= 10; // prefer double, float, etc. over conversion + + // integer types + if (strstr(aname.c_str(), "bool")) + priority += 1; // bool over int (does accept 1 and 0) + else if (strstr(aname.c_str(), "long long")) + priority += -5; // will very likely fit + else if (strstr(aname.c_str(), "long")) + priority += -10; // most affine integer type + // no need to compare with int; leave at zero + else if (strstr(aname.c_str(), "short")) + priority += -50; // not really relevant as a type + + // floating point types (note all numbers lower than integer types) + else if (strstr(aname.c_str(), "float")) + priority += -100; // not really relevant as a type + else if (strstr(aname.c_str(), "long double")) + priority += -90; // fits double with least loss of precision + else if (strstr(aname.c_str(), "double")) + priority += -80; // most affine floating point type + + // string/char types + else if (strstr(aname.c_str(), "char") && aname[aname.size() - 1] != '*') + priority += -60; // prefer (const) char* over char + + } else { + // This is a user-defined type (class, struct, enum, etc.). + + // There's a bit of hysteresis here for templates: once GetScope() is + // called, their IsComplete() succeeds, the other way around it does not. + // Since GetPriority() is likely called several times in a sort, the + // GetScope() _must_ come first, or different GetPriority() calls may + // return different results (since the 2nd time, GetScope() will have been + // called from the first), killing the stable_sort. + + // prefer more derived classes + const std::string& clean_name = TypeManip::clean_type(aname, false); + interop::TCppScope_t scope = interop::GetScope(clean_name); + if (scope) + priority += static_cast(interop::GetNumBasesLongestBranch(scope)); + + // GetScope does not resolve enum names, so also detect enums by type + if (interop::IsEnumScope(scope) || + interop::IsEnumType(interop::GetMethodArgType(fMethod, iarg))) + priority -= 100; + + // a couple of special cases as explained above + if (aname.find("initializer_list") != std::string::npos) { + priority += 150; // needed for proper implicit conversion rules + } else if (aname.rfind("&&", aname.size() - 2) != std::string::npos) { + priority += 100; // prefer moves over other ref/ptr + } else if (scope && !interop::IsComplete(scope)) { + // class is known, but no dictionary available, 2 more cases: * and & + if (aname[aname.size() - 1] == '&') + priority += -5000; + else + priority += -2000; // prefer pointer passing over reference + } + } + } + + // prefer methods w/o optional arguments b/c ones with optional arguments are + // easier to select by providing the optional arguments explicitly + priority += ((int)interop::GetMethodReqArgs(fMethod) - (int)nArgs); + + // add a small penalty to prefer non-const methods over const ones for + // get/setitem + if (interop::IsConstMethod(fMethod) && + interop::GetName(interop::TCppScope_t(fMethod.data)) == "operator[]") + priority += -10; + + // constructors are prefered + if (interop::IsConstructor(fMethod)) + priority += 100; + + return priority; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::IsGreedy() { + // Methods will all void*-like arguments should be sorted after template + // instanstations, so that they don't greedily take over pointers to object. + // GetPriority() is too heavy-handed, as it will pull in all the argument + // types, so use this cheaper check. + const size_t nArgs = interop::GetMethodReqArgs(fMethod); + if (!nArgs) + return false; + + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + const std::string aname = interop::GetMethodArgTypeAsString(fMethod, iarg); + if (aname.find("void *") != 0) + return false; + } + return true; +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetMaxArgs() { + return (int)interop::GetMethodNumArgs(fMethod); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetCoVarNames() { + // Build a tuple of the argument types/names. + int co_argcount = (int)GetMaxArgs() /* +1 for self */; + + // TODO: static methods need no 'self' (but is harmless otherwise) + + PyObject* co_varnames = PyTuple_New(co_argcount + 1 /* self */); + PyTuple_SET_ITEM(co_varnames, 0, cpyrt_PyText_FromString("self")); + for (int iarg = 0; iarg < co_argcount; ++iarg) { + std::string argrep = interop::GetMethodArgTypeAsString(fMethod, iarg); + const std::string& parname = interop::GetMethodArgName(fMethod, iarg); + if (!parname.empty()) { + argrep += " "; + argrep += parname; + } + + PyObject* pyspec = cpyrt_PyText_FromString(argrep.c_str()); + PyTuple_SET_ITEM(co_varnames, iarg + 1, pyspec); + } + + return co_varnames; +} + +PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) { + // get and evaluate the default value (if any) of argument iarg of this method + if (iarg >= (int)GetMaxArgs()) + return nullptr; + + // borrowed reference to cppjit.gbl module to use its dictionary to eval in + static PyObject* gbl = + PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppjit.gbl"); + + std::string defvalue = interop::GetMethodArgDefault(fMethod, iarg); + if (!defvalue.empty()) { + PyObject** dctptr = _PyObject_GetDictPtr(gbl); + if (!(dctptr && *dctptr)) + return nullptr; + + PyObject* gdct = *dctptr; + PyObject* scope = nullptr; + + if (defvalue.rfind('(') != std::string::npos) { // constructor-style call + // try to tickle scope creation, just in case, first look in the scope + // where the function lives, then in the global scope + std::string possible_scope = defvalue.substr(0, defvalue.rfind('(')); + if (!interop::IsBuiltin(possible_scope)) { + std::string cand_scope = + interop::GetScopedFinalName(fScope) + "::" + possible_scope; + scope = CreateScopeProxy(cand_scope); + if (!scope) { + PyErr_Clear(); + // search within the global scope instead + scope = CreateScopeProxy(possible_scope); + if (!scope) + PyErr_Clear(); + } else { + // re-scope the scope; alternatively, the expression could be + // compiled in the dictionary of the function's namespace, but + // that would affect arguments passed to the constructor, too + defvalue = cand_scope + + defvalue.substr(defvalue.rfind('('), std::string::npos); + } + } + } + + // replace '::' -> '.' + TypeManip::cppscope_to_pyscope(defvalue); + + if (!scope) { + // a couple of common cases that python doesn't like (technically, 'L' is + // okay with older pythons, but C long will always fit in Python int, so + // no need to bother) + char c = defvalue.back(); + if (c == 'F' || c == 'D' || c == 'L') { + int offset = 1; + if (2 < defvalue.size() && defvalue[defvalue.size() - 2] == 'U') + offset = 2; + defvalue = defvalue.substr(0, defvalue.size() - offset); + } else if (defvalue == "true") { + defvalue = "True"; + } else if (defvalue == "false") { + defvalue = "False"; + } + } + + // attempt to evaluate the string representation (compilation is first to + // code to allow the error message to indicate where it's coming from) + PyObject* pyval = nullptr; + + PyObject* pycode = Py_CompileString( + (char*)defvalue.c_str(), "cppjit_default_compiler", Py_eval_input); + if (pycode) { + pyval = PyEval_EvalCode(pycode, gdct, gdct); + Py_DECREF(pycode); + } + + if (!pyval && PyErr_Occurred() && silent) { + PyErr_Clear(); + pyval = cpyrt_PyText_FromString( + defvalue.c_str()); // allows continuation, but is likely to fail + } + + Py_XDECREF(scope); + return pyval; // may be nullptr + } + + PyErr_Format(PyExc_TypeError, "Could not construct default value for: %s", + interop::GetMethodArgName(fMethod, iarg).c_str()); + return nullptr; +} + +bool cpyrt::CPPMethod::IsConst() { return interop::IsConstMethod(GetMethod()); } + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetScopeProxy() { + // Get or build the scope of this method. + return CreateScopeProxy(fScope); +} + +//---------------------------------------------------------------------------- +interop::TCppFuncAddr_t cpyrt::CPPMethod::GetFunctionAddress() { + // Return the C++ pointer of this function + return interop::GetFunctionAddress(fMethod, + false /* don't check fast path envar */); +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetArgMatchScore(PyObject* args_tuple) { + Py_ssize_t n = PyTuple_Size(args_tuple); + + int req_args = interop::GetMethodReqArgs(fMethod); + + // Not enough arguments supplied: no match + if (req_args > n) + return INT_MAX; + + size_t score = 0; + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(args_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_SetString(PyExc_TypeError, + "argument types should be in string format"); + return INT_MAX; + } + std::string req_type(cpyrt_PyText_AsString(pItem)); + + size_t arg_score = interop::CompareMethodArgType(fMethod, i, req_type); + + // Method is not compatible if even one argument does not match + if (arg_score >= 10) { + score = INT_MAX; + break; + } + + score += arg_score; + } + + return score; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::Initialize(CallContext* ctxt) { + // done if cache is already setup + if (fArgsRequired != -1) + return true; + + if (!InitConverters_()) + return false; + + if (!InitExecutor_(fExecutor, ctxt)) + return false; + + // minimum number of arguments when calling + fArgsRequired = + (int)((bool)fMethod == true ? interop::GetMethodReqArgs(fMethod) : 0); + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ProcessKwds(PyObject* self_in, PyCallArgs& cargs) { + if (!PyTuple_CheckExact(cargs.fKwds)) { + SetPyError_( + cpyrt_PyText_FromString("received unknown keyword names object")); + return false; + } + Py_ssize_t nKeys = PyTuple_GET_SIZE(cargs.fKwds); + + if (nKeys == 0 && !self_in) + return true; + + if (!fArgIndices) { + fArgIndices = new std::unordered_map{}; + for (int iarg = 0; iarg < (int)interop::GetMethodNumArgs(fMethod); ++iarg) + (*fArgIndices)[interop::GetMethodArgName(fMethod, iarg)] = iarg; + } + + Py_ssize_t nArgs = + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) + (self_in ? 1 : 0); + if (!VerifyArgCount_(nArgs + nKeys)) + return false; + + std::vector vArgs{fConverters.size()}; + + // next, insert the keyword values + PyObject *key, *value; + Py_ssize_t maxpos = -1; + + Py_ssize_t npos_args = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + for (Py_ssize_t ikey = 0; ikey < nKeys; ++ikey) { + key = PyTuple_GET_ITEM(cargs.fKwds, ikey); + value = cargs.fArgs[npos_args + ikey]; + const char* ckey = cpyrt_PyText_AsStringChecked(key); + if (!ckey) + return false; + + auto p = fArgIndices->find(ckey); + if (p == fArgIndices->end()) { + SetPyError_(cpyrt_PyText_FromFormat( + "%s::%s got an unexpected keyword argument \'%s\'", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), ckey)); + return false; + } + + maxpos = p->second > maxpos ? p->second : maxpos; + vArgs[p->second] = + value; // no INCREF yet for simple cleanup in case of error + } + + // if maxpos < nArgs, it will be detected & reported as a duplicate below + Py_ssize_t maxargs = maxpos + 1; + cpyrt_PyArgs_t newArgs = cpyrt_PyArgs_New(maxargs); + + // set all values to zero to be able to check them later (this also guarantees + // normal cleanup by the tuple deallocation) + for (Py_ssize_t i = 0; i < maxargs; ++i) + cpyrt_PyArgs_SET_ITEM(newArgs, i, nullptr); + + // fill out the positional arguments + Py_ssize_t start = 0; + if (self_in) { + Py_INCREF(self_in); + cpyrt_PyArgs_SET_ITEM(newArgs, 0, self_in); + start = 1; + } + + for (Py_ssize_t i = start; i < nArgs; ++i) { + if (vArgs[i]) { + SetPyError_(cpyrt_PyText_FromFormat( + "%s::%s got multiple values for argument %d", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), + (int)i + 1)); + cpyrt_PyArgs_DEL(newArgs); + return false; + } + + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } + + // fill out the keyword arguments + for (Py_ssize_t i = nArgs; i < maxargs; ++i) { + PyObject* item = vArgs[i]; + if (item) { + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } else { + // try retrieving the default + item = GetArgDefault((int)i, false /* i.e. not silent */); + if (!item) { + cpyrt_PyArgs_DEL(newArgs); + return false; + } + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } + } + + if (cargs.fFlags & PyCallArgs::kDoFree) { + if (cargs.fFlags & PyCallArgs::kIsOffset) + cargs.fArgs -= 1; + cpyrt_PyArgs_DEL(cargs.fArgs); + } + + cargs.fArgs = newArgs; + cargs.fNArgsf = maxargs; + cargs.fFlags = PyCallArgs::kDoFree | PyCallArgs::kDoItemDecref; + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ProcessArgs(PyCallArgs& cargs) { + // verify existence of self, return if ok + if (cargs.fSelf) { + if (cargs.fKwds) { + return ProcessKwds(nullptr, cargs); + } + return true; + } + + // otherwise, check for a suitable 'self' in args and update accordingly + if (cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) != 0) { + CPPInstance* pyobj = (CPPInstance*)cpyrt_PyArgs_GET_ITEM(cargs.fArgs, 0); + + // demand cpyrt object, and an argument that may match down the road + if (CPPInstance_Check(pyobj)) { + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (fScope == interop::GetGlobalScope() || // free global + oisa == nullptr || // null pointer or ctor call + oisa == fScope || // matching types + interop::IsSubclass(oisa, fScope)) { // id. + + // reset self + Py_INCREF(pyobj); // corresponding Py_DECREF is in CPPOverload + cargs.fSelf = pyobj; + + // offset args by 1 + cargs.fArgs += 1; + cargs.fFlags |= PyCallArgs::kIsOffset; + cargs.fNArgsf -= 1; + + // put the keywords, if any, in their places in the arguments array + if (cargs.fKwds) + return ProcessKwds(nullptr, cargs); + return true; + } + } + } + + // no self, set error and lament + SetPyError_(cpyrt_PyText_FromFormat( + "unbound method %s::%s must be called with a %s instance as first " + "argument", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), + interop::GetFinalName(fScope).c_str())); + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ConvertAndSetArgs(cpyrt_PyArgs_t args, size_t nargsf, + CallContext* ctxt) { + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + if (!VerifyArgCount_(argc)) + return false; + + // pass current scope for which the call is made + ctxt->fCurScope = fScope; + + if (argc == 0) + return true; + + // convert the arguments to the method call array + bool isOK = true; + Parameter* cppArgs = ctxt->GetArgs(argc); + for (int i = 0; i < (int)argc; ++i) { + if (!fConverters[i]->SetArg(cpyrt_PyArgs_GET_ITEM(args, i), cppArgs[i], + ctxt)) { + SetPyError_( + cpyrt_PyText_FromFormat("could not convert argument %d: %s", i + 1, + fConverters[i]->GetFailureMsg().c_str())); + isOK = false; + break; + } + } + + return isOK; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Execute(void* self, ptrdiff_t offset, + CallContext* ctxt) { + // call the interface method; the kProtected signal policy is accepted for + // API compatibility but has no separate path: without a signal handler + // that longjmps back into the call there is nothing to protect + PyObject* result = ExecuteFast(self, offset, ctxt); + + if (!result && PyErr_Occurred()) + SetPyError_(0); + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !Initialize(ctxt)) + return nullptr; + + // fetch self, verify, and put the arguments in usable order + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!ProcessArgs(cargs)) + return nullptr; + + // self provides the python context for lifelines + if (!ctxt->fPyContext) + ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership + + // translate the arguments + if (fArgsRequired || cpyrt_PyArgs_GET_SIZE(args, cargs.fNArgsf)) { + if (!ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + } + + // get the C++ object that this object proxy is a handle for + void* object = self->GetObject(); + + // validity check that should not fail + if (!object) { + PyErr_SetString(PyExc_ReferenceError, "no C++ object available"); + ctxt->fFlags |= CallContext::kCppException; + return nullptr; + } + + // get its class + interop::TCppScope_t derived = self->ObjectIsA(); + + // calculate offset: the generated wrapper casts 'this' to the method's actual + // declaring class, which may differ from fScope. In particular, a method + // brought into fScope through a using-declaration (e.g. `using Base::meth;`) + // is still declared in the base, so 'this' has to be adjusted to that base's + // subobject. Using fScope here would yield a zero offset and corrupt memory. + interop::TCppScope_t declaring = + interop::GetParentScope(interop::TCppScope_t(fMethod.data)); + if (!declaring) + declaring = fScope; + + ptrdiff_t offset = 0; + if (derived && derived != declaring) + offset = + interop::GetBaseOffset(derived, declaring, object, 1 /* up-cast */); + + // actual call; recycle self instead of returning new object for same address + // objects + CPPInstance* pyobj = (CPPInstance*)Execute(object, offset, ctxt); + if (CPPInstance_Check(pyobj) && derived && pyobj->ObjectIsA() == derived && + pyobj->GetObject() == object) { + Py_INCREF((PyObject*)self); + Py_DECREF(pyobj); + return (PyObject*)self; + } + + return (PyObject*)pyobj; +} + +//- protected members -------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetSignature(bool fa) { + // construct python string from the method's signature + return cpyrt_PyText_FromString(GetSignatureString(fa).c_str()); +} + +/** + * @brief Returns a tuple with the names of the input parameters of this method. + * + * For example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * ('a', 'b', 'c') + */ +PyObject* cpyrt::CPPMethod::GetSignatureNames() { + // Build a tuple of the argument names for this signature. + int argcount = GetMaxArgs(); + PyObject* signature_names = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string& argname_cpp = interop::GetMethodArgName(fMethod, iarg); + PyObject* argname_py = cpyrt_PyText_FromString(argname_cpp.c_str()); + PyTuple_SET_ITEM(signature_names, iarg, argname_py); + } + + return signature_names; +} + +/** + * @brief Returns a dictionary with the types of the signature of this method. + * + * This dictionary will store both the return type and the input parameter + * types of this method, respectively with keys "return_type" and + * "input_types", for example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * {'input_types': ('int', 'float', 'double'), 'return_type': 'double'} + */ +PyObject* cpyrt::CPPMethod::GetSignatureTypes() { + + PyObject* signature_types_dict = PyDict_New(); + + // Insert the return type first + std::string return_type = GetReturnTypeName(); + PyObject* return_type_py = cpyrt_PyText_FromString(return_type.c_str()); + PyDict_SetItem(signature_types_dict, cpyrt_PyText_FromString("return_type"), + return_type_py); + + // Build a tuple of the argument types for this signature. + int argcount = GetMaxArgs(); + PyObject* parameter_types = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string& argtype_cpp = + interop::GetMethodArgTypeAsString(fMethod, iarg); + PyObject* argtype_py = cpyrt_PyText_FromString(argtype_cpp.c_str()); + PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); + } + + PyDict_SetItem(signature_types_dict, cpyrt_PyText_FromString("input_types"), + parameter_types); + + // Const-qualification of the method itself (always false for free + // functions and static methods). + PyDict_SetItem(signature_types_dict, cpyrt_PyText_FromString("is_const"), + PyBool_FromLong(IsConst())); + + return signature_types_dict; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::CPPMethod::GetReturnTypeName() { + return interop::GetMethodReturnTypeAsString(fMethod); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h b/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h new file mode 100644 index 0000000000000..16dc2f7fdd5ab --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h @@ -0,0 +1,132 @@ +#ifndef CPYRT_CPPMETHOD_H +#define CPYRT_CPPMETHOD_H + +// Bindings +#include "PyCallable.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +class Executor; +class Converter; + +class PyCallArgs { +public: + PyCallArgs(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds) + : fSelf(self), fArgs(args), fNArgsf(nargsf), fKwds(kwds), fFlags(kNone) {} + ~PyCallArgs(); + + enum ECleanupFlags { + kNone = 0x0000, + kIsOffset = 0x0001, // args were offset by 1 to drop self + kSelfSwap = 0x0002, // args[-1] and self need swapping + kArgsSwap = 0x0004, // args[0] and args[1] need swapping + kDoFree = 0x0008, // args need to be free'd (vector call only) + kDoItemDecref = 0x0010 // items in args need a decref (vector call only) + }; + +public: + CPPInstance*& fSelf; + cpyrt_PyArgs_t fArgs; + size_t fNArgsf; + PyObject* fKwds; + int fFlags; +}; + +class CPPMethod : public PyCallable { +public: + CPPMethod(interop::TCppScope_t scope, interop::TCppMethod_t method); + CPPMethod(const CPPMethod&); + CPPMethod& operator=(const CPPMethod&); + virtual ~CPPMethod(); + +public: + PyObject* GetSignature(bool show_formalargs = true) override; + PyObject* GetSignatureNames() override; + PyObject* GetSignatureTypes() override; + PyObject* GetPrototype(bool show_formalargs = true) override; + PyObject* GetTypeName() override; + PyObject* + Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t = interop::Reflex::OPTIMAL) override; + + int GetPriority() override; + bool IsGreedy() override; + + int GetMaxArgs() override; + PyObject* GetCoVarNames() override; + PyObject* GetArgDefault(int iarg, bool silent = true) override; + bool IsConst() override; + + PyObject* GetScopeProxy() override; + interop::TCppFuncAddr_t GetFunctionAddress() override; + + PyCallable* Clone() override { return new CPPMethod(*this); } + + int GetArgMatchScore(PyObject* args_tuple) override; + + bool IsSimilarFnType(cppjit::interop::TCppType_t fn_type) override { + return cppjit::interop::IsSimilarFnTypes( + fn_type, cppjit::interop::GetTypeFromScope( + cppjit::interop::TCppScope_t(fMethod.data))); + } + +public: + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + virtual bool ProcessArgs(PyCallArgs& args); + + bool Initialize(CallContext* ctxt = nullptr); + bool ProcessKwds(PyObject* self_in, PyCallArgs& args); + bool ConvertAndSetArgs(cpyrt_PyArgs_t, size_t nargsf, + CallContext* ctxt = nullptr); + PyObject* Execute(void* self, ptrdiff_t offset, CallContext* ctxt = nullptr); + + interop::TCppMethod_t GetMethod() { return fMethod; } + interop::TCppScope_t GetScope() { return fScope; } + Executor* GetExecutor() { return fExecutor; } + std::string GetSignatureString(bool show_formalargs = true); + std::string GetReturnTypeName(); + + virtual bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr); + + // whether fExecutor's result is a real PyObject* (ConstructorExecutor + // returns the new object's address instead) + virtual bool ResultIsPyObject() const { return true; } + +private: + void Copy_(const CPPMethod&); + void Destroy_(); + bool VerifyArgCount_(Py_ssize_t); + + PyObject* ExecuteFast(void*, ptrdiff_t, CallContext*); + + bool InitConverters_(); + + void SetPyError_(PyObject* msg); + +private: + // representation + interop::TCppMethod_t fMethod; + interop::TCppScope_t fScope; + Executor* fExecutor; + + // call dispatch buffers + std::vector fConverters; + std::unordered_map* fArgIndices; + +protected: + // cached value that doubles as initialized flag (uninitialized if -1) + int fArgsRequired; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPMETHOD_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.cxx new file mode 100644 index 0000000000000..b48a2436402db --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.cxx @@ -0,0 +1,62 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "CPPOperator.h" +#include "Utility.h" + +//- constructor -------------------------------------------------------------- +cpyrt::CPPOperator::CPPOperator(interop::TCppScope_t scope, + interop::TCppMethod_t method, + const std::string& name) + : CPPMethod(scope, method) { + // a bit silly but doing it this way allows decoupling the initialization + // order + if (name == "__mul__") + fStub = CPPInstance_Type.tp_as_number->nb_multiply; + else if (name == CPPJIT__div__) + fStub = CPPInstance_Type.tp_as_number->nb_true_divide; + else if (name == "__add__") + fStub = CPPInstance_Type.tp_as_number->nb_add; + else if (name == "__sub__") + fStub = CPPInstance_Type.tp_as_number->nb_subtract; + else + fStub = nullptr; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::CPPOperator::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // some operators can be a mix of global and class overloads; this method will + // first try class overloads (the existence of this method means that such + // were defined) and if failed, fall back on the global stubs + // TODO: the fact that this is a method and not an overload means that the + // global ones are tried for each method that fails during the overload + // resolution + PyObject* result = this->CPPMethod::Call(self, args, nargsf, kwds, ctxt); + if (result || !fStub || !self) + return result; + + Py_ssize_t idx_other = 0; + if (cpyrt_PyArgs_GET_SIZE(args, nargsf) != 1) { + if ((cpyrt_PyArgs_GET_SIZE(args, nargsf) == 2 && + cpyrt_PyArgs_GET_ITEM(args, 0) == (PyObject*)self)) + idx_other = 1; + else + return result; + } + + // fetch the current error, resetting the error buffer + auto error = cpyrt::Utility::FetchPyError(); + + result = fStub((PyObject*)self, cpyrt_PyArgs_GET_ITEM(args, idx_other)); + + // if there was still a problem, restore the Python error buffer + if (!result) { + cpyrt::Utility::RestorePyError(error); + } + + return result; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.h b/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.h new file mode 100644 index 0000000000000..b5c7337c73496 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPOperator.h @@ -0,0 +1,28 @@ +#ifndef CPYRT_CPPOPERATOR_H +#define CPYRT_CPPOPERATOR_H + +// Bindings +#include "CPPMethod.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPOperator : public CPPMethod { +public: + CPPOperator(interop::TCppScope_t scope, interop::TCppMethod_t method, + const std::string& name); + +public: + PyCallable* Clone() override { return new CPPOperator(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +private: + binaryfunc fStub; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPOPERATOR_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.cxx new file mode 100644 index 0000000000000..3ce11adbe69e7 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.cxx @@ -0,0 +1,1152 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CallContext.h" +#include "PyStrings.h" +#include "Utility.h" +#include "structmember.h" // from Python +#include "cpyrt/Reflex.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +namespace { + +// from CPython's instancemethod: Free list for method objects to safe +// malloc/free overhead The fSelf field is used to chain the elements. +static CPPOverload* free_list; +static int numfree = 0; +#ifndef CPPOverload_MAXFREELIST +#define CPPOverload_MAXFREELIST 32 +#endif + +// TODO: only used in pythonizations to add Python-side overloads to existing +// C++ overloads, but may be better off integrated with Pythonize.cxx callbacks +class TPythonCallback : public PyCallable { +public: + PyObject* fCallable; + + TPythonCallback(PyObject* callable) : fCallable(nullptr) { + if (!PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return; + } + Py_INCREF(callable); + fCallable = callable; + } + + ~TPythonCallback() override { + Py_DECREF(fCallable); + fCallable = nullptr; + } + + PyObject* GetSignature(bool /*show_formalargs*/ = true) override { + return cpyrt_PyText_FromString("*args, **kwargs"); + } + PyObject* GetSignatureNames() override { return PyTuple_New(0); } + PyObject* GetSignatureTypes() override { return PyTuple_New(0); } + PyObject* GetPrototype(bool /*show_formalargs*/ = true) override { + return cpyrt_PyText_FromString(""); + } + PyObject* GetDocString() override { + if (PyObject_HasAttrString(fCallable, "__doc__")) { + return PyObject_GetAttrString(fCallable, "__doc__"); + } else { + return GetPrototype(); + } + } + + int GetPriority() override { return 100; }; + bool IsGreedy() override { return false; }; + + int GetMaxArgs() override { return 100; }; + PyObject* GetCoVarNames() override { // TODO: pick these up from the callable + Py_RETURN_NONE; + } + PyObject* GetArgDefault(int /* iarg */, bool /* silent */ = true) override { + Py_RETURN_NONE; // TODO: pick these up from the callable + } + + PyObject* GetScopeProxy() override { // should this be the module ?? + Py_RETURN_NONE; + } + + interop::TCppFuncAddr_t GetFunctionAddress() override { + return (interop::TCppFuncAddr_t) nullptr; + } + + PyCallable* Clone() override { return new TPythonCallback(*this); } + + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* /* ctxt = 0 */) override { + + if (self) { + if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? + std::swap(((PyObject**)args - 1)[0], (PyObject*&)self); + nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + args = args - 1; + } else { + Py_ssize_t nkwargs = kwds ? PyTuple_GET_SIZE(kwds) : 0; + Py_ssize_t totalargs = PyVectorcall_NARGS(nargsf) + nkwargs; + PyObject** newArgs = + (PyObject**)PyMem_Malloc((totalargs + 1) * sizeof(PyObject*)); + if (!newArgs) + return nullptr; + + newArgs[0] = (PyObject*)self; + if (0 < totalargs) + memcpy((void*)&newArgs[1], args, totalargs * sizeof(PyObject*)); + args = newArgs; + } + nargsf += 1; + } + + PyObject* result = cpyrt_PyObject_Call(fCallable, args, nargsf, kwds); + if (self) { + if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) + std::swap(((PyObject**)args - 1)[0], (PyObject*&)self); + else + PyMem_Free((void*)args); + } + return result; + } +}; + +// helper to test whether a method is used in a pseudo-function modus +static inline bool IsPseudoFunc(CPPOverload* pymeth) { + return pymeth->fMethodInfo->fFlags & CallContext::kIsPseudoFunc; +} + +// helper to sort on method priority +static int PriorityCmp(const std::pair& left, + const std::pair& right) { + return left.first > right.first; +} + +// return helper +static inline void ResetCallState(CPPInstance* descr_self, + CPPInstance*& im_self) { + // reset self if needed, allowing simple re-use + if (descr_self != im_self) { + Py_XDECREF(im_self); + im_self = descr_self; + } +} + +// helper to factor out return logic of mp_call / mp_vectorcall +static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, + PyObject* result) { + // special case for python exceptions, propagated through C++ layer + if (result) { + CPPInstance* cppres = + (CPPInstance*)(CPPInstance_Check(result) ? result : nullptr); + + // if this method creates new objects, always take ownership + if (IsCreator(pymeth->fMethodInfo->fFlags)) { + + // either be a constructor with a fresh object proxy self ... + if (IsConstructor(pymeth->fMethodInfo->fFlags)) { + if (im_self) + im_self->PythonOwns(); + } + + // ... or be a regular method with an object proxy return value + else if (cppres) + cppres->PythonOwns(); + } + + // if this new object falls inside self, make sure its lifetime is proper + if (!(pymeth->fMethodInfo->fFlags & CallContext::kNeverLifeLine)) { + int ll_action = 0; + if ((PyObject*)im_self != result) { + if (pymeth->fMethodInfo->fFlags & CallContext::kSetLifeLine) + ll_action = 1; + else if (cppres && CPPInstance_Check(im_self)) { + // if self was a by-value return and result is not, pro-actively + // protect result; else if the return value falls within the memory of + // 'this', force a lifeline + if (!(cppres->fFlags & + CPPInstance::kIsValue)) { // no need if the result is temporary + if (im_self->fFlags & CPPInstance::kIsValue) + ll_action = 2; + else if (im_self->fFlags & CPPInstance::kHasLifeLine) + ll_action = 3; + else { + ptrdiff_t offset = (ptrdiff_t)cppres->GetObject() - + (ptrdiff_t)im_self->GetObject(); + if (0 <= offset && + offset < (ptrdiff_t)interop::SizeOf(im_self->ObjectIsA())) + ll_action = 4; + } + } + } + } + + if (!ll_action) + pymeth->fMethodInfo->fFlags |= + CallContext::kNeverLifeLine; // assume invariant semantics + else { + if (PyObject_SetAttr(result, PyStrings::gLifeLine, + (PyObject*)im_self) == -1) + PyErr_Clear(); // ignored + if (cppres) + cppres->fFlags |= CPPInstance::kHasLifeLine; // for chaining + pymeth->fMethodInfo->fFlags |= + CallContext::kSetLifeLine; // for next time + } + } + } + + // reset self as necessary to allow re-use of the CPPOverload + ResetCallState(pymeth->fSelf, im_self); + + return result; +} + +//= cpyrt method proxy object behaviour =================================== +static PyObject* mp_name(CPPOverload* pymeth, void*) { + return cpyrt_PyText_FromString(pymeth->GetName().c_str()); +} + +//---------------------------------------------------------------------------- +static PyObject* mp_module(CPPOverload* /* pymeth */, void*) { + Py_INCREF(PyStrings::gThisModule); + return PyStrings::gThisModule; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_doc(CPPOverload* pymeth, void*) { + if (pymeth->fMethodInfo->fDoc) { + Py_INCREF(pymeth->fMethodInfo->fDoc); + return pymeth->fMethodInfo->fDoc; + } + + // Build python document string ('__doc__') from all C++-side overloads. + CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + // collect doc strings + CPPOverload::Methods_t::size_type nMethods = methods.size(); + if (nMethods == 0) // from template proxy with no instantiations + return nullptr; + PyObject* doc = methods[0]->GetDocString(); + + // simple case + if (nMethods == 1) + return doc; + + // overloaded method + PyObject* separator = cpyrt_PyText_FromString("\n"); + for (CPPOverload::Methods_t::size_type i = 1; i < nMethods; ++i) { + cpyrt_PyText_Append(&doc, separator); + cpyrt_PyText_AppendAndDel(&doc, methods[i]->GetDocString()); + } + Py_DECREF(separator); + + return doc; +} + +static int mp_doc_set(CPPOverload* pymeth, PyObject* val, void*) { + Py_XDECREF(pymeth->fMethodInfo->fDoc); + Py_INCREF(val); + pymeth->fMethodInfo->fDoc = val; + return 0; +} + +/** + * @brief Returns a dictionary with the input parameter names for all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': ('a', 'b', 'c'), + * 'float ::foo(float b)': ('b',), + * 'int ::foo(int a)': ('a',), + * 'int ::foo(int a, float b)': ('a', 'b')} + */ +static PyObject* mp_func_overloads_names(CPPOverload* pymeth) { + + const CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + PyObject* overloads_names_dict = PyDict_New(); + + for (PyCallable* method : methods) { + PyDict_SetItem(overloads_names_dict, method->GetPrototype(), + method->GetSignatureNames()); + } + + return overloads_names_dict; +} + +/** + * @brief Returns a dictionary with the types of all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': {'input_types': ('int', 'float', + * 'double'), 'return_type': 'double'}, 'float ::foo(float b)': {'input_types': + * ('float',), 'return_type': 'float'}, 'int ::foo(int a)': {'input_types': + * ('int',), 'return_type': 'int'}, 'int ::foo(int a, float b)': {'input_types': + * ('int', 'float'), 'return_type': 'int'}} + * + * Each overload's value dict also carries 'is_const': the method's own + * const-qualification (always False for free functions and static methods). + */ +static PyObject* mp_func_overloads_types(CPPOverload* pymeth) { + + const CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + PyObject* overloads_types_dict = PyDict_New(); + + for (PyCallable* method : methods) { + PyDict_SetItem(overloads_types_dict, method->GetPrototype(), + method->GetSignatureTypes()); + } + + return overloads_types_dict; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_func(CPPOverload* pymeth, void*) { + // Create a new method proxy to be returned. + CPPOverload* newPyMeth = + (CPPOverload*)CPPOverload_Type.tp_alloc(&CPPOverload_Type, 0); + + // method info is shared, as it contains the collected overload knowledge + *pymeth->fMethodInfo->fRefCount += 1; + newPyMeth->fMethodInfo = pymeth->fMethodInfo; + + // new method is unbound, track whether this proxy is used in the capacity of + // a method or a function (which normally is a CPPFunction) + newPyMeth->fMethodInfo->fFlags |= CallContext::kIsPseudoFunc; + + return (PyObject*)newPyMeth; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_self(CPPOverload* pymeth, void*) { + // Return the bound self, if any; in case of pseudo-function role, pretend + // that the data member im_self does not exist. + if (IsPseudoFunc(pymeth)) { + PyErr_Format(PyExc_AttributeError, + "function %s has no attribute \'im_self\'", + pymeth->fMethodInfo->fName.c_str()); + return nullptr; + } else if (pymeth->fSelf != 0) { + Py_INCREF((PyObject*)pymeth->fSelf); + return (PyObject*)pymeth->fSelf; + } + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_class(CPPOverload* pymeth, void*) { + // Return scoping class; in case of pseudo-function role, pretend that there + // is no encompassing class (i.e. global scope). + if (!IsPseudoFunc(pymeth) && pymeth->fMethodInfo->fMethods.size()) { + PyObject* pyclass = pymeth->fMethodInfo->fMethods[0]->GetScopeProxy(); + if (!pyclass) + PyErr_Format(PyExc_AttributeError, + "function %s has no attribute \'im_class\'", + pymeth->fMethodInfo->fName.c_str()); + return pyclass; + } + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_closure(CPPOverload* /* pymeth */, void*) { + // Stub only, to fill out the python function interface. + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_code(CPPOverload*, void*) { + // Code details are used in module inspect to fill out interactive help() + // not important for functioning of most code, so not implemented for p3 for + // now (TODO) + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_defaults(CPPOverload* pymeth, void*) { + // Create a tuple of default values, if there is only one method (otherwise + // leave undefined: this is only used by inspect for interactive help()) + CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + if (methods.size() != 1) + return PyTuple_New(0); + + int maxarg = methods[0]->GetMaxArgs(); + + PyObject* defaults = PyTuple_New(maxarg); + + int itup = 0; + for (int iarg = 0; iarg < maxarg; ++iarg) { + PyObject* defvalue = methods[0]->GetArgDefault(iarg); + if (defvalue) + PyTuple_SET_ITEM(defaults, itup++, defvalue); + else + PyErr_Clear(); + } + _PyTuple_Resize(&defaults, itup); + + return defaults; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_globals(CPPOverload* /* pymeth */, void*) { + // Return this function's global dict (hard-wired to be the cppjit module); + // used for lookup of names from co_code indexing into co_names. + PyObject* pyglobal = PyModule_GetDict(PyImport_AddModule((char*)"cppjit")); + Py_XINCREF(pyglobal); + return pyglobal; +} + +//---------------------------------------------------------------------------- +static inline int set_flag(CPPOverload* pymeth, PyObject* value, + CallContext::ECallFlags flag, const char* name) { + // Generic setter of a (boolean) flag. + if (!value) { // accept as false (delete) + pymeth->fMethodInfo->fFlags &= ~flag; + return 0; + } + + long istrue = PyLong_AsLong(value); + if (istrue == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_ValueError, "a boolean 1 or 0 is required for %s", name); + return -1; + } + + if (istrue) + pymeth->fMethodInfo->fFlags |= flag; + else + pymeth->fMethodInfo->fFlags &= ~flag; + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_getcreates(CPPOverload* pymeth, void*) { + // Get '__creates__' boolean, which determines ownership of return values. + return PyInt_FromLong((long)IsCreator(pymeth->fMethodInfo->fFlags)); +} + +//---------------------------------------------------------------------------- +static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*) { + // Set '__creates__' boolean, which determines ownership of return values. + return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__"); +} + +constexpr const char* mempolicy_error_message = + "The __mempolicy__ attribute can't be used, because in the past it was " + "reserved to manage the local memory policy. " + "If you want to do that now, please implement a pythonization for your " + "class that uses SetOwnership() to manage the " + "ownership of arguments according to your needs."; + +//---------------------------------------------------------------------------- +static PyObject* mp_getmempolicy(CPPOverload*, void*) { + PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); + return nullptr; +} + +//---------------------------------------------------------------------------- +static int mp_setmempolicy(CPPOverload*, PyObject*, void*) { + PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); + return -1; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_BOOLEAN_PROPERTY(name, flag, label) \ + static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \ + if (pymeth->fMethodInfo->fFlags & flag) { \ + Py_RETURN_TRUE; \ + } \ + Py_RETURN_FALSE; \ + } \ + \ + static int mp_set##name(CPPOverload* pymeth, PyObject* value, void*) { \ + return set_flag(pymeth, value, flag, label); \ + } + +// clang-format off +CPPJIT_BOOLEAN_PROPERTY(lifeline, CallContext::kSetLifeLine, "__set_lifeline__") +CPPJIT_BOOLEAN_PROPERTY(threaded, CallContext::kReleaseGIL, "__release_gil__") +CPPJIT_BOOLEAN_PROPERTY(useffi, CallContext::kUseFFI, "__useffi__") +CPPJIT_BOOLEAN_PROPERTY(sig2exc, CallContext::kProtected, "__sig2exc__") +// clang-format on + +static PyObject* mp_getcppname(CPPOverload* pymeth, void*) { + if ((void*)pymeth == (void*)&CPPOverload_Type) + return cpyrt_PyText_FromString("CPPOverload_Type"); + + auto& methods = pymeth->fMethodInfo->fMethods; + if (methods.empty()) + return cpyrt_PyText_FromString("void (*)()"); // debatable + + if (methods.size() == 1) + return methods[0]->GetTypeName(); + + return cpyrt_PyText_FromString("void* (*)(...)"); // id. +} + +//---------------------------------------------------------------------------- +static PyGetSetDef mp_getset[] = { + {(char*)"__name__", (getter)mp_name, nullptr, nullptr, nullptr}, + {(char*)"__module__", (getter)mp_module, nullptr, nullptr, nullptr}, + {(char*)"__doc__", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, + + // to be more python-like, where these are duplicated as well; to actually + // derive from the python method or function type is too memory-expensive, + // given that most of the members of those types would not be used + {(char*)"im_func", (getter)mp_meth_func, nullptr, nullptr, nullptr}, + {(char*)"im_self", (getter)mp_meth_self, nullptr, nullptr, nullptr}, + {(char*)"im_class", (getter)mp_meth_class, nullptr, nullptr, nullptr}, + + {(char*)"func_closure", (getter)mp_func_closure, nullptr, nullptr, nullptr}, + {(char*)"func_code", (getter)mp_func_code, nullptr, nullptr, nullptr}, + {(char*)"func_defaults", (getter)mp_func_defaults, nullptr, nullptr, + nullptr}, + {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr}, + {(char*)"func_doc", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, + {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr}, + {(char*)"func_overloads_types", (getter)mp_func_overloads_types, nullptr, + nullptr, nullptr}, + {(char*)"func_overloads_names", (getter)mp_func_overloads_names, nullptr, + nullptr, nullptr}, + + // flags to control behavior + {(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates, + (char*)"For ownership rules of result: if true, objects are python-owned", + nullptr}, + {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, + (char*)"Unused", nullptr}, + {(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline, + (char*)"If true, set a lifeline from the return value onto self", nullptr}, + {(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded, + (char*)"If true, releases GIL on call into C++", nullptr}, + {(char*)"__useffi__", (getter)mp_getuseffi, (setter)mp_setuseffi, + (char*)"not implemented", nullptr}, + {(char*)"__sig2exc__", (getter)mp_getsig2exc, (setter)mp_setsig2exc, + (char*)"If true, turn signals into Python exceptions", nullptr}, + + // basic reflection information + {(char*)"__cpp_name__", (getter)mp_getcppname, nullptr, nullptr, nullptr}, + + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt method proxy function behavior ================================== +static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, + size_t nargsf, PyObject* kwds) { + // Call the appropriate overload of this method. + + // If called from a descriptor, then this could be a bound function with + // non-zero self; otherwise pymeth->fSelf is expected to always be nullptr. + + CPPInstance* im_self = pymeth->fSelf; + + // get local handles to proxy internals + auto& methods = pymeth->fMethodInfo->fMethods; + + CPPOverload::Methods_t::size_type nMethods = methods.size(); + + CallContext ctxt{}; + const auto mflags = pymeth->fMethodInfo->fFlags; + ctxt.fFlags |= (mflags & CallContext::kReleaseGIL); + ctxt.fFlags |= (mflags & CallContext::kProtected); + if (IsConstructor(pymeth->fMethodInfo->fFlags)) + ctxt.fFlags |= CallContext::kIsConstructor; + ctxt.fFlags |= + (pymeth->fFlags & (CallContext::kCallDirect | CallContext::kFromDescr)); + ctxt.fPyContext = (PyObject*)im_self; // no Py_INCREF as no ownership + + // check implicit conversions status (may be disallowed to prevent recursion) + ctxt.fFlags |= (pymeth->fFlags & CallContext::kNoImplicit); + + // simple case + if (nMethods == 1) { + if (!NoImplicit(&ctxt)) + ctxt.fFlags |= CallContext::kAllowImplicit; // no two rounds needed + PyObject* result = methods[0]->Call(im_self, args, nargsf, kwds, &ctxt); + return HandleReturn(pymeth, im_self, result); + } + + // otherwise, handle overloading + uint64_t sighash = HashSignature(args, nargsf); + + // look for known signatures ... + auto& dispatchMap = pymeth->fMethodInfo->fDispatchMap; + PyCallable* memoized_pc = nullptr; + for (const auto& p : dispatchMap) { + if (p.first == sighash) { + memoized_pc = p.second; + break; + } + } + if (memoized_pc) { + // it is necessary to enable implicit conversions as the memoized call may + // be from such a conversion case; if the call fails, the implicit flag is + // reset below + if (!NoImplicit(&ctxt)) + ctxt.fFlags |= CallContext::kAllowImplicit; + PyObject* result = memoized_pc->Call(im_self, args, nargsf, kwds, &ctxt); + if (result) + return HandleReturn(pymeth, im_self, result); + + // fall through: python is dynamic, and so, the hashing isn't infallible + ctxt.fFlags &= ~(CallContext::kAllowImplicit | CallContext::kPyException | + CallContext::kCppException); + PyErr_Clear(); + ResetCallState(pymeth->fSelf, im_self); + } + + // ... otherwise loop over all methods and find the one that does not fail + if (!IsSorted(mflags)) { + // sorting is based on priority, which is not stored on the method as it is + // used only once, so copy the vector of methods into one where the priority + // can be stored during sorting + std::vector> pm; + pm.reserve(methods.size()); + for (auto ptr : methods) + pm.emplace_back(ptr->GetPriority(), ptr); + std::stable_sort(pm.begin(), pm.end(), PriorityCmp); + for (CPPOverload::Methods_t::size_type i = 0; i < methods.size(); ++i) + methods[i] = pm[i].second; + pymeth->fMethodInfo->fFlags |= CallContext::kIsSorted; + } + + // three stages, as the C++ ranking: exact matches, standard conversions + // (kNoImplicit: no user-defined construction from the argument), then + // implicit conversions + std::vector errors; + std::vector implicit_possible(methods.size()); + bool bHaveImplicit = false; + for (int stage = 0; stage < 3; ++stage) { + if (stage == 1) + ctxt.fFlags |= CallContext::kNoImplicit; + else if (stage == 2) { + ctxt.fFlags &= ~CallContext::kNoImplicit; + ctxt.fFlags |= CallContext::kAllowImplicit; + } + for (CPPOverload::Methods_t::size_type i = 0; i < nMethods; ++i) { + if (stage && !implicit_possible[i]) + continue; // did not set implicit conversion, so don't try again + + PyObject* result = methods[i]->Call(im_self, args, nargsf, kwds, &ctxt); + if (result) { + // success: update the dispatch map for subsequent calls + if (!memoized_pc) + dispatchMap.push_back(std::make_pair(sighash, methods[i])); + else { + // debatable: apparently there are two methods that map onto the same + // sighash and preferring the latest may result in "ping pong." + for (auto& p : dispatchMap) { + if (p.first == sighash) { + p.second = methods[i]; + break; + } + } + } + + return HandleReturn(pymeth, im_self, result); + } + + // else failure .. + if (stage != 0) { + PyErr_Clear(); // first stage errors should be the more informative + ResetCallState(pymeth->fSelf, im_self); + continue; + } + + // collect error message/trace (automatically clears exception, too) + if (!PyErr_Occurred()) { + // this should not happen; set an error to prevent core dump and report + PyObject* sig = methods[i]->GetPrototype(); + PyErr_Format(PyExc_SystemError, "%s =>\n %s", + cpyrt_PyText_AsString(sig), + (char*)"nullptr result without error in overload call"); + Py_DECREF(sig); + } + + // retrieve, store, and clear errors + bool callee_error = ctxt.fFlags & (CallContext::kPyException | + CallContext::kCppException); + ctxt.fFlags &= ~(CallContext::kPyException | CallContext::kCppException); + Utility::FetchError(errors, callee_error); + + if (HaveImplicit(&ctxt)) { + bHaveImplicit = true; + implicit_possible[i] = true; + ctxt.fFlags &= ~CallContext::kHaveImplicit; + } else + implicit_possible[i] = false; + ResetCallState(pymeth->fSelf, im_self); + } + + // only move forward if implicit conversions are available + if (!bHaveImplicit) + break; + } + + // first summarize, then add details + PyObject* topmsg = cpyrt_PyText_FromFormat( + "none of the %d overloaded methods succeeded. Full details:", + (int)nMethods); + SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + + // report failure + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_str(CPPOverload* cppinst) { + // Print a description that includes the C++ name + std::ostringstream s; + s << "fMethodInfo->fName << "\" at " + << (void*)cppinst << ">"; + return cpyrt_PyText_FromString(s.str().c_str()); +} + +//---------------------------------------------------------------------------- +static CPPOverload* mp_descr_get(CPPOverload* pymeth, CPPInstance* pyobj, + PyObject*) { + // Descriptor; create and return a new, possibly bound, method proxy. This + // method has evolved with versions of python as follows: + // + // Python version | Action + // <- py2.7 | bound methods need to be first-class objects, so + // create a new + // | method object if self is not nullptr or Py_None + // py3.0-py3.7 | bound methods are no longer a language requirement, + // but + // | still supported: for convenience, retain old behavior + // py3.8 <= | vector calls no longer call the descriptor, so when it + // is + // | called, the method is likely stored, so should be new + // object + + // create a new method object + bool gc_track = false; + CPPOverload* newPyMeth = free_list; + if (newPyMeth != NULL) { + free_list = (CPPOverload*)(newPyMeth->fSelf); + (void)PyObject_INIT(newPyMeth, &CPPOverload_Type); + numfree--; + } else { + newPyMeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); + if (!newPyMeth) + return nullptr; + gc_track = true; + } + + // method info is shared, as it contains the collected overload knowledge + *pymeth->fMethodInfo->fRefCount += 1; + newPyMeth->fMethodInfo = pymeth->fMethodInfo; + + newPyMeth->fVectorCall = pymeth->fVectorCall; + + if (pyobj && (PyObject*)pyobj != Py_None) { + Py_INCREF((PyObject*)pyobj); + newPyMeth->fSelf = pyobj; + newPyMeth->fFlags = CallContext::kNone; + } else { + newPyMeth->fSelf = nullptr; + newPyMeth->fFlags = CallContext::kCallDirect; + } + + // vector calls don't get here, unless a method is looked up on an instance, + // for e.g. class methods (C++ static); notify downstream to expect a 'self' + newPyMeth->fFlags |= CallContext::kFromDescr; + + if (gc_track) + PyObject_GC_Track(newPyMeth); + + return newPyMeth; +} + +//= cpyrt method proxy construction/destruction =========================== +static CPPOverload* mp_new(PyTypeObject*, PyObject*, PyObject*) { + // Create a new method proxy object. + CPPOverload* pymeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); + pymeth->fSelf = nullptr; + pymeth->fFlags = CallContext::kNone; + pymeth->fMethodInfo = new CPPOverload::MethodInfo_t; + + PyObject_GC_Track(pymeth); + return pymeth; +} + +//---------------------------------------------------------------------------- +static void mp_dealloc(CPPOverload* pymeth) { + // Deallocate memory held by method proxy object. + PyObject_GC_UnTrack(pymeth); + + Py_CLEAR(pymeth->fSelf); + + if (--(*pymeth->fMethodInfo->fRefCount) <= 0) { + delete pymeth->fMethodInfo; + } + + if (numfree < CPPOverload_MAXFREELIST) { + pymeth->fSelf = (cpyrt::CPPInstance*)free_list; + free_list = pymeth; + numfree++; + } else { + PyObject_GC_Del(pymeth); + } +} + +//---------------------------------------------------------------------------- +static Py_ssize_t mp_hash(CPPOverload* pymeth) { +// Hash of method proxy object for insertion into dictionaries; with actual +// method (fMethodInfo) shared, its address is best suited. +#if PY_VERSION_HEX >= 0x030d0000 + return Py_HashPointer(pymeth->fMethodInfo); +#else + return _Py_HashPointer(pymeth->fMethodInfo); +#endif +} + +//---------------------------------------------------------------------------- +static int mp_traverse(CPPOverload* pymeth, visitproc visit, void* args) { + // Garbage collector traverse of held python member objects. + if (pymeth->fSelf) + return visit((PyObject*)pymeth->fSelf, args); + + return 0; +} + +//---------------------------------------------------------------------------- +static int mp_clear(CPPOverload* pymeth) { + // Garbage collector clear of held python member objects. + Py_CLEAR(pymeth->fSelf); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_richcompare(CPPOverload* self, CPPOverload* other, int op) { + // Rich set of comparison objects; only equals is defined. + if (op != Py_EQ) + return PyType_Type.tp_richcompare((PyObject*)self, (PyObject*)other, op); + + // defined by type + (shared) MethodInfo + bound self, with special case for + // fSelf (i.e. pseudo-function) + if ((Py_TYPE(self) == Py_TYPE(other) && + self->fMethodInfo == other->fMethodInfo) && + ((IsPseudoFunc(self) && IsPseudoFunc(other)) || + self->fSelf == other->fSelf)) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +//= cpyrt method proxy access to internals ================================ +static PyObject* mp_overload(CPPOverload* pymeth, PyObject* args) { + // Select and call a specific C++ overload, based on its signature. + const char* sigarg = nullptr; + PyObject* sigarg_tuple = nullptr; + int want_const = -1; + Py_ssize_t args_size = PyTuple_GET_SIZE(args); + if (args_size && PyArg_ParseTuple(args, const_cast("s|i:__overload__"), + &sigarg, &want_const)) { + want_const = args_size == 1 ? -1 : want_const; + return pymeth->FindOverload(sigarg ? sigarg : "", want_const); + } else if (args_size && + PyArg_ParseTuple(args, const_cast("O|i:__overload__"), + &sigarg_tuple, &want_const)) { + PyErr_Clear(); + want_const = args_size == 1 ? -1 : want_const; + return pymeth->FindOverload(sigarg_tuple, want_const); + } else { + PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); + return nullptr; + } +} + +static PyObject* mp_add_overload(CPPOverload* pymeth, PyObject* new_overload) { + TPythonCallback* cb = new TPythonCallback(new_overload); + pymeth->AdoptMethod(cb); + Py_RETURN_NONE; +} + +static PyObject* mp_reflex(CPPOverload* pymeth, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + return pymeth->fMethodInfo->fMethods[0]->Reflex(request, format); +} + +//---------------------------------------------------------------------------- +static PyMethodDef mp_methods[] = { + {(char*)"__overload__", (PyCFunction)mp_overload, METH_VARARGS, + (char*)"select overload for dispatch"}, + {(char*)"__add_overload__", (PyCFunction)mp_add_overload, METH_O, + (char*)"add a new overload"}, + {(char*)"__cpp_reflex__", (PyCFunction)mp_reflex, METH_VARARGS, + (char*)"C++ overload reflection information"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +} // unnamed namespace + +//= cpyrt method proxy type =============================================== +PyTypeObject CPPOverload_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPOverload", // tp_name + sizeof(CPPOverload), // tp_basicsize + 0, // tp_itemsize + (destructor)mp_dealloc, // tp_dealloc + offsetof(CPPOverload, fVectorCall), + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + (hashfunc)mp_hash, // tp_hash + (ternaryfunc)PyVectorcall_Call, // tp_call + (reprfunc)mp_str, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL | + Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags + (char*)"cppjit method proxy (internal)", // tp_doc + (traverseproc)mp_traverse, // tp_traverse + (inquiry)mp_clear, // tp_clear + (richcmpfunc)mp_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + mp_methods, // tp_methods + 0, // tp_members + mp_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)mp_descr_get, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)mp_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +//- public members ----------------------------------------------------------- +void cpyrt::CPPOverload::Set(const std::string& name, + std::vector& methods) { + // Fill in the data of a freshly created method proxy. + fMethodInfo->fName = name; + fMethodInfo->fMethods.swap(methods); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; + + // special case: all constructors are considered creators by default + if (name == "__init__") + fMethodInfo->fFlags |= + (CallContext::kIsCreator | CallContext::kIsConstructor); + + // special case, in heuristics mode also tag *Clone* methods as creators. Only + // check that Clone is present in the method name, not in the template + // argument list. + if (CallContext::GlobalPolicyFlags() & CallContext::kUseHeuristics) { + std::string_view name_maybe_template = name; + auto begin_template = name_maybe_template.find_first_of('<'); + if (begin_template <= name_maybe_template.size()) { + name_maybe_template = name_maybe_template.substr(0, begin_template); + } + if (name_maybe_template.find("Clone") != std::string_view::npos) { + fMethodInfo->fFlags |= CallContext::kIsCreator; + } + } + + fVectorCall = (vectorcallfunc)mp_vectorcall; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPOverload::AdoptMethod(PyCallable* pc) { + // Fill in the data of a freshly created method proxy. + fMethodInfo->fMethods.push_back(pc); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPOverload::MergeOverload(CPPOverload* meth) { + if (!HasMethods()) // if fresh method being filled: also copy flags + fMethodInfo->fFlags = meth->fMethodInfo->fFlags; + fMethodInfo->fMethods.insert(fMethodInfo->fMethods.end(), + meth->fMethodInfo->fMethods.begin(), + meth->fMethodInfo->fMethods.end()); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; + meth->fMethodInfo->fDispatchMap.clear(); + meth->fMethodInfo->fMethods.clear(); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPOverload::FindOverload(const std::string& signature, + int want_const) { + bool accept_any = signature == ":any:"; + CPPOverload* newmeth = nullptr; + + std::string sig1{"("}; + if (!accept_any) { + sig1.append(signature); + sig1.append(")"); + sig1.erase(std::remove(sig1.begin(), sig1.end(), ' '), std::end(sig1)); + } + + CPPOverload::Methods_t& methods = fMethodInfo->fMethods; + for (auto& meth : methods) { + bool found = accept_any; + if (!found) { + PyObject* pysig2 = meth->GetSignature(false); + std::string sig2(cpyrt_PyText_AsString(pysig2)); + sig2.erase(std::remove(sig2.begin(), sig2.end(), ' '), std::end(sig2)); + Py_DECREF(pysig2); + if (sig1 == sig2) + found = true; + + if (!found) { + pysig2 = meth->GetSignature(true); + std::string sig3(cpyrt_PyText_AsString(pysig2)); + sig3.erase(std::remove(sig3.begin(), sig3.end(), ' '), std::end(sig3)); + Py_DECREF(pysig2); + if (sig1 == sig3) + found = true; + } + } + + if (found && 0 <= want_const) { + bool isconst = meth->IsConst(); + if (!((want_const && isconst) || (!want_const && !isconst))) + found = false; + } + + if (found) { + if (!newmeth) { + newmeth = mp_new(nullptr, nullptr, nullptr); + CPPOverload::Methods_t vec; + vec.push_back(meth->Clone()); + newmeth->Set(fMethodInfo->fName, vec); + + if (fSelf) { + Py_INCREF(fSelf); + newmeth->fSelf = fSelf; + } + newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; + } else + newmeth->AdoptMethod(meth->Clone()); + + if (!accept_any) + return (PyObject*)newmeth; + } + } + + if (!newmeth) + PyErr_Format(PyExc_LookupError, "signature \"%s\" not found", + signature.c_str()); + + return (PyObject*)newmeth; +} + +PyObject* cpyrt::CPPOverload::FindOverload(PyObject* args_tuple, + int want_const) { + Py_ssize_t n = PyTuple_Size(args_tuple); + + CPPOverload::Methods_t& methods = fMethodInfo->fMethods; + + // This value is set based on the maximum penalty in + // interop::CompareMethodArgType + Py_ssize_t min_score = INT_MAX; + bool found = false; + size_t best_method = 0, method_index = 0; + + for (auto& meth : methods) { + if (0 <= want_const) { + bool isconst = meth->IsConst(); + if (!((want_const && isconst) || (!want_const && !isconst))) + continue; + } + + int score = meth->GetArgMatchScore(args_tuple); + + if (score < min_score) { + found = true; + min_score = score; + best_method = method_index; + } + + method_index++; + } + + if (!found) { + std::string sigargs("("); + + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(args_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_Format(PyExc_LookupError, + "argument types should be in string format"); + return (PyObject*)nullptr; + } + std::string arg_type(cpyrt_PyText_AsString(pItem)); + sigargs += arg_type + ", "; + } + sigargs += ")"; + + PyErr_Format(PyExc_LookupError, "signature with arguments \"%s\" not found", + sigargs.c_str()); + return (PyObject*)nullptr; + } + + CPPOverload* newmeth = mp_new(nullptr, nullptr, nullptr); + CPPOverload::Methods_t vec; + vec.push_back(methods[best_method]->Clone()); + newmeth->Set(fMethodInfo->fName, vec); + + if (fSelf) { + Py_INCREF(fSelf); + newmeth->fSelf = fSelf; + } + newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; + + return (PyObject*)newmeth; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPOverload::MethodInfo_t::~MethodInfo_t() { + // Destructor (this object is reference counted). + for (Methods_t::iterator it = fMethods.begin(); it != fMethods.end(); ++it) { + delete *it; + } + fMethods.clear(); + delete fRefCount; + Py_XDECREF(fDoc); +} + +// TODO: something like PyMethod_Fini to clear up the free_list diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.h b/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.h new file mode 100644 index 0000000000000..87019d26f687b --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.h @@ -0,0 +1,122 @@ +#ifndef CPYRT_CPPOVERLOAD_H +#define CPYRT_CPPOVERLOAD_H + +// Bindings +#include "PyCallable.h" + +// Standard +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +// signature hashes are also used by TemplateProxy +inline uint64_t HashSignature(cpyrt_PyArgs_t args, size_t nargsf) { + // Build a hash from the types of the given python function arguments. + uint64_t hash = 0; + + Py_ssize_t nargs = cpyrt_PyArgs_GET_SIZE(args, nargsf); + for (Py_ssize_t i = 0; i < nargs; ++i) { + // TODO: hashing in the ref-count is for moves; resolve this together with + // the improved overloads for implicit conversions + PyObject* pyobj = cpyrt_PyArgs_GET_ITEM(args, i); + hash += (uint64_t)Py_TYPE(pyobj); +#if PY_VERSION_HEX >= 0x030e0000 + hash += + (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 + : 0); +#else + hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0); +#endif + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash; +} + +class CPPOverload { +public: + typedef std::vector> DispatchMap_t; + typedef std::vector Methods_t; + + struct MethodInfo_t { + MethodInfo_t() : fDoc(nullptr), fFlags(CallContext::kNone) { + fRefCount = new int(1); + } + ~MethodInfo_t(); + + std::string fName; + CPPOverload::DispatchMap_t fDispatchMap; + CPPOverload::Methods_t fMethods; + PyObject* fDoc; + uint32_t fFlags; + + int* fRefCount; + + private: + MethodInfo_t(const MethodInfo_t&) = delete; + MethodInfo_t& operator=(const MethodInfo_t&) = delete; + }; + +public: + void Set(const std::string& name, std::vector& methods); + void AdoptMethod(PyCallable* pc); + void MergeOverload(CPPOverload* meth); + + const std::string& GetName() const { return fMethodInfo->fName; } + bool HasMethods() const { return !fMethodInfo->fMethods.empty(); } + + // find a method based on the provided signature + PyObject* FindOverload(const std::string& signature, int want_const = -1); + PyObject* FindOverload(PyObject* args_tuple, int want_const = -1); + +public: // public, as the python C-API works with C structs + PyObject_HEAD CPPInstance* + fSelf; // must be first (same layout as TemplateProxy) + MethodInfo_t* fMethodInfo; + uint32_t fFlags; + vectorcallfunc fVectorCall; + +private: + CPPOverload() = delete; +}; + +//- method proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPOverload_Type; + +template inline bool CPPOverload_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPOverload_Type); +} + +template inline bool CPPOverload_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPOverload_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPOverload* CPPOverload_New(const std::string& name, + std::vector& methods) { + // Create and initialize a new method proxy from the overloads. + CPPOverload* pymeth = (CPPOverload*)CPPOverload_Type.tp_new(&CPPOverload_Type, + nullptr, nullptr); + pymeth->Set(name, methods); + return pymeth; +} + +inline CPPOverload* CPPOverload_New(const std::string& name, + PyCallable* method) { + // Create and initialize a new method proxy from the method. + std::vector p; + p.push_back(method); + return CPPOverload_New(name, p); +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPOVERLOAD_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPScope.cxx b/bindings/pyroot/cppjit/src/cpyrt/CPPScope.cxx new file mode 100644 index 0000000000000..b58469d3845a8 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPScope.cxx @@ -0,0 +1,739 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPEnum.h" +#include "CPPFunction.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "CustomPyTypes.h" +#include "Dispatcher.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include // for for_each +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +extern PyTypeObject CPPInstance_Type; + +//- helpers ------------------------------------------------------------------ +static inline PyObject* +add_template(PyObject* pyclass, const std::string& name, + std::vector* overloads = nullptr) { + // If templated, the user-facing function must be the template proxy, but the + // specific lookup must be the current overload, if already found. + + const std::string& ncl = TypeManip::clean_type(name); + PyObject* pyncl = cpyrt_PyText_FromString(ncl.c_str()); + TemplateProxy* pytmpl = + (TemplateProxy*)PyType_Type.tp_getattro(pyclass, pyncl); + if (!pytmpl) { + PyErr_Clear(); + pytmpl = TemplateProxy_New(ncl, ncl, pyclass); + // cache the template on its clean name + PyType_Type.tp_setattro(pyclass, pyncl, (PyObject*)pytmpl); + Py_DECREF(pyncl); + } else if (!TemplateProxy_CheckExact((PyObject*)pytmpl)) { + Py_DECREF(pytmpl); + return nullptr; + } + + if (overloads) { + // adopt the new overloads + if (ncl != name) + for (auto clb : *overloads) + pytmpl->AdoptTemplate(clb); + else + for (auto clb : *overloads) + pytmpl->AdoptMethod(clb); + } + + // the caller expects a method matching the full name, thus is a + // specialization was requested, do not return the template yet + if (ncl == name) + return (PyObject*)pytmpl; + + Py_DECREF(pytmpl); + return nullptr; // so that caller caches the method on full name +} + +//= cpyrt type proxy construction/destruction ============================= +static PyObject* meta_alloc(PyTypeObject* meta, Py_ssize_t nitems) { + // pure memory allocation; object initialization is in pt_new + return PyType_Type.tp_alloc(meta, nitems); +} + +//---------------------------------------------------------------------------- +static void meta_dealloc(CPPScope* scope) { + if (scope->fFlags & CPPScope::kIsNamespace) { + if (scope->fImp.fUsing) { + for (auto pyobj : *scope->fImp.fUsing) + Py_DECREF(pyobj); + delete scope->fImp.fUsing; + scope->fImp.fUsing = nullptr; + } + } else if (!(scope->fFlags & CPPScope::kIsPython)) { + delete scope->fImp.fCppObjects; + scope->fImp.fCppObjects = nullptr; + } + delete scope->fOperators; + free(scope->fModuleName); + return PyType_Type.tp_dealloc((PyObject*)scope); +} + +//----------------------------------------------------------------------------- +static PyObject* meta_getcppname(CPPScope* scope, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromString("CPPInstance_Type"); + return cpyrt_PyText_FromString( + interop::GetScopedFinalName(scope->fCppType).c_str()); +} + +//----------------------------------------------------------------------------- +static PyObject* meta_getmodule(CPPScope* scope, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromString("cppjit.gbl"); + + if (scope->fModuleName) + return cpyrt_PyText_FromString(scope->fModuleName); + + // get C++ representation of outer scope + interop::TCppScope_t parent_scope = interop::GetParentScope(scope->fCppType); + if (parent_scope == interop::GetGlobalScope()) + return cpyrt_PyText_FromString(const_cast("cppjit.gbl")); + + // now peel scopes one by one, pulling in the python naming (which will + // simply recurse if not overridden in python) + PyObject* pymodule = nullptr; + PyObject* pyscope = cpyrt::GetScopeProxy(parent_scope); + if (pyscope) { + // get the module of our module + pymodule = PyObject_GetAttr(pyscope, PyStrings::gModule); + if (pymodule) { + // append name of our module + PyObject* pymodname = PyObject_GetAttr(pyscope, PyStrings::gName); + if (pymodname) { + cpyrt_PyText_AppendAndDel(&pymodule, cpyrt_PyText_FromString(".")); + cpyrt_PyText_AppendAndDel(&pymodule, pymodname); + } + } + Py_DECREF(pyscope); + } + + if (pymodule) + return pymodule; + PyErr_Clear(); + + // lookup through python failed, so simply cook up a '::' -> '.' replacement + std::string modname = interop::GetScopedFinalName(parent_scope); + TypeManip::cppscope_to_pyscope(modname); + return cpyrt_PyText_FromString(("cppjit.gbl." + modname).c_str()); +} + +//----------------------------------------------------------------------------- +static int meta_setmodule(CPPScope* scope, PyObject* value, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) { + PyErr_SetString(PyExc_AttributeError, + "attribute \'__module__\' of 'cppjit.CPPScope\' objects is " + "not writable"); + return -1; + } + + const char* newname = cpyrt_PyText_AsStringChecked(value); + if (!value) + return -1; + + free(scope->fModuleName); + Py_ssize_t sz = cpyrt_PyText_GET_SIZE(value); + scope->fModuleName = (char*)malloc(sz + 1); + memcpy(scope->fModuleName, newname, sz + 1); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* meta_repr(CPPScope* scope) { + // Specialized b/c type_repr expects __module__ to live in the dictionary, + // whereas it is a property (to save memory). + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromFormat( + const_cast(""), scope); + + if (scope->fFlags & (CPPScope::kIsMeta | CPPScope::kIsPython)) { + // either meta type or Python-side derived class: use default type printing + return PyType_Type.tp_repr((PyObject*)scope); + } + + // skip in case some Python-side derived meta class + if (!CPPScope_Check(scope) || !scope->fCppType) + return PyType_Type.tp_repr((PyObject*)scope); + + // printing of C++ classes + PyObject* modname = meta_getmodule(scope, nullptr); + std::string clName = interop::GetFinalName(scope->fCppType); + const char* kind = + (scope->fFlags & CPPScope::kIsNamespace) ? "namespace" : "class"; + + PyObject* repr = cpyrt_PyText_FromFormat("<%s %s.%s at %p>", kind, + cpyrt_PyText_AsString(modname), + clName.c_str(), scope); + + Py_DECREF(modname); + return repr; +} + +//= cpyrt type metaclass behavior ========================================= +static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) { + // Called when CPPScope acts as a metaclass; since type_new always resets + // tp_alloc, and since it does not call tp_init on types, the metaclass is + // being fixed up here, and the class is initialized here as well. + + // fixup of metaclass (left permanent, and in principle only called once b/c + // cppjit caches python classes) + subtype->tp_alloc = (allocfunc)meta_alloc; + subtype->tp_dealloc = (destructor)meta_dealloc; + + // creation of the python-side class; extend the size if this is a smart ptr + // + // A namespace can never be a smart pointer, so skip the check for namespaces. + // This matters beyond performance: GetSmartPtrInfo() resolves the scope name + // through a slow interpreter lookup that triggers autoloading of the library + // providing the scope, which we don't want to do unnecessarily. + interop::TCppScope_t raw; + interop::TCppMethod_t deref; + if (CPPScope_CheckExact(subtype) && + !interop::IsNamespace(((CPPScope*)subtype)->fCppType)) { + if (interop::GetSmartPtrInfo( + interop::GetScopedFinalName(((CPPScope*)subtype)->fCppType), &raw, + &deref)) + subtype->tp_basicsize = sizeof(CPPSmartClass); + } + + CPPScope* result = (CPPScope*)PyType_Type.tp_new(subtype, args, kwds); + if (!CPPScope_Check(result)) { + // either failed or custom user-side metaclass that can't be handled here + return nullptr; + } + + result->fFlags = CPPScope::kNone; + result->fOperators = nullptr; + result->fModuleName = nullptr; + + if (raw && deref) { + result->fFlags |= CPPScope::kIsSmart; + ((CPPSmartClass*)result)->fUnderlyingType = raw; + ((CPPSmartClass*)result)->fDereferencer = deref; + } + + // initialization of class (based on metatype) + if (!CPPScope_CheckExact(subtype) || + !strstr(subtype->tp_name, "_meta") /* convention */) { + // there has been a user meta class override in a derived class, so do + // the consistent thing, thus allowing user control over naming + result->fCppType = + interop::GetScope(cpyrt_PyText_AsString(PyTuple_GET_ITEM(args, 0))); + } else { + // coming here from cppjit or from sub-classing in python; take the + // C++ type from the meta class to make sure that the latter category + // has fCppType properly set (it inherits the meta class, but has an + // otherwise unknown (or wrong) C++ type) + result->fCppType = ((CPPScope*)subtype)->fCppType; + + // the following is not robust, but by design, C++ classes get their + // dictionaries filled after creation (chicken & egg problem as they + // can return themselves in methods), whereas a derived Python class + // with method overrides will have a non-empty dictionary (even if it + // has no methods, it will at least have a module name) + if (3 <= PyTuple_GET_SIZE(args)) { + PyObject* dct = PyTuple_GET_ITEM(args, 2); + Py_ssize_t sz = PyDict_Size(dct); + if (0 < sz && !interop::IsNamespace(result->fCppType)) { + result->fFlags |= CPPScope::kIsPython; + if (1 < PyTuple_GET_SIZE(PyTuple_GET_ITEM(args, 1))) + result->fFlags |= CPPScope::kIsMultiCross; + std::ostringstream errmsg; + if (!InsertDispatcher(result, PyTuple_GET_ITEM(args, 1), dct, errmsg)) { + PyErr_Format(PyExc_TypeError, + "no python-side overrides supported (%s)", + errmsg.str().c_str()); + return nullptr; + } else { + // the direct base can be useful for some templates, such as + // shared_ptrs, so make it accessible (the __cpp_cross__ data member + // also signals that this is a cross-inheritance class) + PyObject* bname = cpyrt_PyText_FromString( + interop::GetBaseName(result->fCppType, 0).c_str()); + if (!bname) + PyErr_Clear(); + else { + if (PyObject_SetAttrString((PyObject*)result, "__cpp_cross__", + bname) == -1) + PyErr_Clear(); + Py_DECREF(bname); + } + } + } else if (sz == (Py_ssize_t)-1) + PyErr_Clear(); + } + } + + // if the user messed with the metaclass, then we may not have a C++ type, + // simply return here before more damage gets done + if (!result->fCppType) + return (PyObject*)result; + + // maps for using namespaces and tracking objects + if (!interop::IsNamespace(result->fCppType)) { + static interop::TCppScope_t exc_type = + interop::GetScope("exception", interop::GetScope("std")); + if (interop::IsSubclass(result->fCppType, exc_type)) + result->fFlags |= CPPScope::kIsException; + if (!(result->fFlags & CPPScope::kIsPython)) + result->fImp.fCppObjects = new CppToPyMap_t; + else { + // special case: the C++ objects should be stored with the associated C++, + // not Python, type + CPPClass* kls = (CPPClass*)GetScopeProxy(result->fCppType); + if (kls) { + result->fImp.fCppObjects = kls->fImp.fCppObjects; + Py_DECREF(kls); + } else + result->fImp.fCppObjects = nullptr; + } + } else { + result->fImp.fUsing = nullptr; + result->fFlags |= CPPScope::kIsNamespace; + } + + if (PyErr_Occurred()) { + Py_DECREF((PyObject*)result); + return nullptr; + } + + return (PyObject*)result; +} + +//---------------------------------------------------------------------------- +static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) { + // normal type-based lookup + PyObject* attr = PyType_Type.tp_getattro(pyclass, pyname); + if (pyclass == (PyObject*)&CPPInstance_Type) + return attr; + + PyObject* possibly_shadowed = nullptr; + if (attr) { + if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && + !(((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace)) { + // TODO: the goal here is to prevent typedefs that are shadowed in + // subclasses to be found as from the base class. The better approach + // would be to find all typedefs at class creation and insert placeholders + // to flag here. The current typedef loop in gInterpreter won't do, + // however. + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr_from_dict = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + if (attr_from_dict) { + Py_DECREF(attr); + return attr_from_dict; + } + possibly_shadowed = attr; + attr = nullptr; + } + PyErr_Clear(); + } else if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && + ((CPPScope*)attr)->fFlags & CPPScope::kIsException) { + return CreateExcScopeProxy(attr, pyname, pyclass); + } else + return attr; + } + + if (!cpyrt_PyText_CheckExact(pyname) || !CPPScope_Check(pyclass)) + return possibly_shadowed; + + // filter for python specials + std::string name = cpyrt_PyText_AsString(pyname); + if (name.size() >= 5 && name.compare(0, 2, "__") == 0 && + name.compare(name.size() - 2, name.size(), "__") == 0) + return possibly_shadowed; + + // more elaborate search in case of failure (eg. for inner classes on demand) + std::vector errors; + Utility::FetchError(errors); + attr = CreateScopeProxy(name, pyclass); + + if (CPPScope_Check(attr) && + (((CPPScope*)attr)->fFlags & CPPScope::kIsException)) { + // Instead of the CPPScope, return a fresh exception class derived from + // CPPExcInstance. + return CreateExcScopeProxy(attr, pyname, pyclass); + } + + bool templated_functions_checked = false; + CPPScope* klass = ((CPPScope*)pyclass); + if (!attr) { + Utility::FetchError(errors); + interop::TCppScope_t scope = klass->fCppType; + + // namespaces may have seen updates in their list of global functions, which + // are available as "methods" even though they're not really that + if ((klass->fFlags & CPPScope::kIsNamespace) || + scope == interop::GetGlobalScope()) { + // tickle lazy lookup of functions + const std::vector methods = + interop::GetMethodsFromName(scope, name); + if (!methods.empty()) { + // function exists, now collect overloads + std::vector overloads; + for (auto method : methods) { + overloads.push_back(new CPPFunction(scope, method)); + } + + // Note: can't re-use Utility::AddClass here, as there's the risk of + // a recursive call. Simply add method directly, as we're guaranteed + // that it doesn't exist yet. + if (interop::ExistsMethodTemplate(scope, name)) + attr = add_template(pyclass, name, &overloads); + else + attr = (PyObject*)CPPOverload_New(name, overloads); + templated_functions_checked = true; + } + } + + if (!attr) { + interop::TCppScope_t lookup_result = interop::GetNamed(name, scope); + if (interop::IsVariable(lookup_result) || + interop::IsEnumConstant(lookup_result)) { + attr = (PyObject*)CPPDataMember_New(scope, lookup_result); + } else if (interop::IsTypedefed(lookup_result)) { + interop::TCppType_t resolved_type = + interop::ResolveType(interop::GetTypeFromScope(lookup_result)); + const std::string& cpd = + TypeManip::compound(interop::GetTypeAsString(resolved_type)); + if (cpd == "*") { + interop::TCppScope_t tcl = + interop::GetScopeFromType(interop::ResolveType(resolved_type)); + if (tcl) { + typedefpointertoclassobject* tpc = PyObject_New( + typedefpointertoclassobject, &TypedefPointerToClass_Type); + tpc->fCppType = tcl; + attr = (PyObject*)tpc; + } + } + } + } + + // function templates that have not been instantiated (namespaces _may_ have + // already been taken care of, by their general function lookup above) + if (!attr && !templated_functions_checked) { + if (interop::ExistsMethodTemplate(scope, name)) + attr = add_template(pyclass, name); + else { + // for completeness in error reporting + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ template", + name.c_str()); + Utility::FetchError(errors); + } + } + + // enums types requested as type (rather than the constants) + if (!attr) { + interop::TCppScope_t enumerator = + interop::GetUnderlyingScope(interop::GetNamed(name, scope)); + if (interop::IsEnumScope(enumerator)) { + // enum types (incl. named and class enums) + attr = (PyObject*)CPPEnum_New(name, enumerator); + } else { + // for completeness in error reporting + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ enum", + name.c_str()); + Utility::FetchError(errors); + } + } + + if (attr) { + // cache the result + if (CPPDataMember_Check(attr)) { + PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pyname, attr); + + Py_DECREF(attr); + // The call below goes through "dm_get" + attr = PyType_Type.tp_getattro(pyclass, pyname); + if (!attr && PyErr_Occurred()) + Utility::FetchError(errors); + } else + PyType_Type.tp_setattro(pyclass, pyname, attr); + + } else { + Utility::FetchError(errors); + } + } + + if (!attr && (klass->fFlags & CPPScope::kIsNamespace)) { + // refresh using list as necessary + const std::vector& uv = + interop::GetUsingNamespaces(klass->fCppType); + if (!klass->fImp.fUsing || uv.size() != klass->fImp.fUsing->size()) { + if (klass->fImp.fUsing) { + for (auto pyref : *klass->fImp.fUsing) + Py_DECREF(pyref); + klass->fImp.fUsing->clear(); + } else + klass->fImp.fUsing = new std::vector; + + // reload and reset weak refs + for (auto uid : uv) { + std::string uname = interop::GetScopedFinalName(uid); + PyObject* pyuscope = CreateScopeProxy(uname); + if (pyuscope) { + klass->fImp.fUsing->push_back(PyWeakref_NewRef(pyuscope, nullptr)); + // the namespace may not otherwise be held, so tie the lifetimes + PyObject* llname = + cpyrt_PyText_FromString(("__lifeline_" + uname).c_str()); + PyType_Type.tp_setattro(pyclass, llname, pyuscope); + Py_DECREF(llname); + Py_DECREF(pyuscope); + } else { + PyErr_Clear(); + } + } + } + + // try all outstanding using namespaces in turn to find the attribute (will + // cache locally later; TODO: doing so may cause pathological cases) + for (auto pyref : *klass->fImp.fUsing) { + PyObject* pyuscope = cpyrt_GetWeakRef(pyref); + if (pyuscope) { + attr = PyObject_GetAttr(pyuscope, pyname); + if (!attr) + PyErr_Clear(); + Py_DECREF(pyuscope); + } + if (attr) + break; + } + } + + // if the attribute was not found but could possibly have been shadowed, + // insert it into the dict now, to short-circuit future lookups (TODO: this is + // part of the workaround described above of which the true solution is to + // loop over all typedefs at creation time for the class) + if (possibly_shadowed) { + if (attr) { + Py_DECREF(possibly_shadowed); + } else { + attr = possibly_shadowed; + PyType_Type.tp_setattro(pyclass, pyname, attr); + } + } + + if (attr) { + PyErr_Clear(); + } else { + // not found: prepare a full error report + PyObject* topmsg = nullptr; + PyObject *pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + PyObject* sklass = PyObject_Str(pyclass); + PyErr_Restore(pytype, pyvalue, pytrace); + if (sklass) { + topmsg = cpyrt_PyText_FromFormat( + "%s has no attribute \'%s\'. Full details:", + cpyrt_PyText_AsString(sklass), cpyrt_PyText_AsString(pyname)); + Py_DECREF(sklass); + } else { + topmsg = + cpyrt_PyText_FromFormat("no such attribute \'%s\'. Full details:", + cpyrt_PyText_AsString(pyname)); + } + SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_AttributeError /* default error */); + } + + return attr; +} + +//---------------------------------------------------------------------------- +static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) { + // Global data and static data in namespaces is found lazily, thus if the + // first use is setting of the global data by the user, it will not be + // reflected on the C++ side, b/c there is no descriptor yet. This triggers + // the creation for for such data as necessary. The many checks to narrow down + // the specific case are needed to prevent unnecessary lookups and recursion. + // skip if the given pyval is a descriptor already, or an unassignable class + if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace && + !cpyrt::CPPDataMember_Check(pyval) && !cpyrt::CPPScope_Check(pyval)) { + std::string name = cpyrt_PyText_AsString(pyname); + if (interop::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) { + PyObject* attr = meta_getattro(pyclass, pyname); // triggers creation + if (!attr) + PyErr_Clear(); + else + Py_DECREF(attr); + } + } + + return PyType_Type.tp_setattro(pyclass, pyname, pyval); +} + +//---------------------------------------------------------------------------- +static PyObject* meta_reflex(CPPScope* klass, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + switch (request) { + case interop::Reflex::IS_NAMESPACE: + if (klass->fFlags & CPPScope::kIsNamespace) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + case interop::Reflex::IS_AGGREGATE: + // this is not the strict C++ definition of aggregates, but is closer to + // what is needed for Numba and C calling conventions (TODO: probably have + // to check for all public data types, too, and maybe for no padding?) + if (interop::IsAggregate(klass->fCppType) || + !interop::HasVirtualDestructor(klass->fCppType)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + } + + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; +} + +//---------------------------------------------------------------------------- +// p2.7 does not have a __dir__ in object, and object.__dir__ in p3 does not +// quite what I'd expected of it, so the following pulls in the internal code +#include "PyObjectDir27.inc" + +static PyObject* meta_dir(CPPScope* klass) { + // Collect a list of everything (currently) available in the namespace. + // The backend can filter by returning empty strings. Special care is + // taken for functions, which need not be unique (overloading). + using namespace cppjit::interop; + + if ((void*)klass == (void*)&CPPInstance_Type) + return PyList_New(0); + + if (!cpyrt::CPPScope_Check((PyObject*)klass)) { + PyErr_SetString(PyExc_TypeError, "C++ proxy scope expected"); + return nullptr; + } + + PyObject* dirlist = _generic_dir((PyObject*)klass); + if (!(klass->fFlags & CPPScope::kIsNamespace)) + return dirlist; + + std::set cppnames; + interop::GetAllCppNames(klass->fCppType, cppnames); + + // cleanup names + std::set dir_cppnames; + for (const std::string& name : cppnames) { + if (name.find("__", 0, 2) != std::string::npos || + name.find("<") != std::string::npos || + name.find("operator", 0, 8) != std::string::npos) + continue; + dir_cppnames.insert(name); + } + + // get rid of duplicates + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(dirlist); ++i) + dir_cppnames.insert(cpyrt_PyText_AsString(PyList_GET_ITEM(dirlist, i))); + + Py_DECREF(dirlist); + dirlist = PyList_New(dir_cppnames.size()); + + // copy total onto python list + Py_ssize_t i = 0; + for (const auto& name : dir_cppnames) { + PyList_SET_ITEM(dirlist, i++, cpyrt_PyText_FromString(name.c_str())); + } + return dirlist; +} + +//----------------------------------------------------------------------------- +static PyMethodDef meta_methods[] = { + {(char*)"__cpp_reflex__", (PyCFunction)meta_reflex, METH_VARARGS, + (char*)"C++ datamember reflection information"}, + {(char*)"__dir__", (PyCFunction)meta_dir, METH_NOARGS, nullptr}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//----------------------------------------------------------------------------- +static PyGetSetDef meta_getset[] = { + {(char*)"__cpp_name__", (getter)meta_getcppname, nullptr, nullptr, nullptr}, + {(char*)"__module__", (getter)meta_getmodule, (setter)meta_setmodule, + nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt object proxy type type ========================================== +PyTypeObject CPPScope_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0)(char*) "cppjit.CPPScope", // tp_name + sizeof(cpyrt::CPPScope), // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)meta_repr, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + (getattrofunc)meta_getattro, // tp_getattro + (setattrofunc)meta_setattro, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_TYPE_SUBCLASS, // tp_flags + (char*)"cpyrt metatype (internal)", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + meta_methods, // tp_methods + 0, // tp_members + meta_getset, // tp_getset + &PyType_Type, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)pt_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/CPPScope.h b/bindings/pyroot/cppjit/src/cpyrt/CPPScope.h new file mode 100644 index 0000000000000..04670c113252e --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPScope.h @@ -0,0 +1,117 @@ +#ifndef CPYRT_CPPSCOPE_H +#define CPYRT_CPPSCOPE_H + +#include "Python.h" +#include "cppjit_interop.h" +#include + +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 2 + +// In p2.2, PyHeapTypeObject is not yet part of the interface +#include "structmember.h" + +typedef struct { + PyTypeObject type; + PyNumberMethods as_number; + PySequenceMethods as_sequence; + PyMappingMethods as_mapping; + PyBufferProcs as_buffer; + PyObject *name, *slots; + PyMemberDef members[1]; +} PyHeapTypeObject; + +#endif + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +/** Type object to hold class reference (this is only semantically a + presentation of CPPScope instances, not in a C++ sense) + @author WLAV + @date 07/06/2017 + @version 2.0 + */ + +typedef std::unordered_map CppToPyMap_t; +namespace Utility { +struct PyOperators; +} + +class CPPScope { +public: + enum EFlags { + kNone = 0x0, + kIsMeta = 0x0001, + kIsNamespace = 0x0002, + kIsException = 0x0004, + kIsSmart = 0x0008, + kIsPython = 0x0010, + kIsMultiCross = 0x0020, + kIsInComplete = 0x0040, + kActiveImplicitCall = 0x0080, + kNoOSInsertion = 0x0100, + kGblOSInsertion = 0x0200, + kNoPrettyPrint = 0x0400 + }; + +public: + PyHeapTypeObject fType; + interop::TCppScope_t fCppType; + uint32_t fFlags; + union { + CppToPyMap_t* fCppObjects; // classes only + std::vector* fUsing; // namespaces only + } fImp; + Utility::PyOperators* fOperators; + char* fModuleName; + +private: + CPPScope() = delete; +}; + +typedef CPPScope CPPClass; + +class CPPSmartClass : public CPPClass { +public: + interop::TCppScope_t fUnderlyingType; + interop::TCppMethod_t fDereferencer; +}; + +//- metatype type and type verification -------------------------------------- +extern PyTypeObject CPPScope_Type; + +template inline bool CPPScope_Check(T* object) { + // Short-circuit the type check by checking tp_new which all generated + // subclasses of CPPScope inherit. + return object && (Py_TYPE(object)->tp_new == CPPScope_Type.tp_new || + PyObject_TypeCheck(object, &CPPScope_Type)); +} + +template inline bool CPPScope_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPScope_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPScope* CPPScopeMeta_New(interop::TCppScope_t klass, PyObject* args) { + // Create and initialize a new scope meta class + CPPScope* pymeta = + (CPPScope*)PyType_Type.tp_new(&CPPScope_Type, args, nullptr); + if (!pymeta) + return pymeta; + + // set the klass id, for instances and Python-side derived classes to pick up + pymeta->fCppType = klass; + pymeta->fFlags = CPPScope::kIsMeta; + pymeta->fImp.fCppObjects = nullptr; + pymeta->fOperators = nullptr; + pymeta->fModuleName = nullptr; + + return pymeta; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPSCOPE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CallContext.cxx b/bindings/pyroot/cppjit/src/cpyrt/CallContext.cxx new file mode 100644 index 0000000000000..4a9e646ed691f --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CallContext.cxx @@ -0,0 +1,48 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CallContext.h" + +//----------------------------------------------------------------------------- +uint32_t& cpyrt::CallContext::GlobalPolicyFlags() { + static uint32_t flags = 0; + return flags; +} + +//----------------------------------------------------------------------------- +void cpyrt::CallContext::AddTemporary(PyObject* pyobj) { + if (pyobj) { + if (!fTemps) + fTemps = new Temporary{pyobj, nullptr}; + else { + Temporary* tmp = fTemps; + while (tmp->fNext) + tmp = tmp->fNext; + tmp->fNext = new Temporary{pyobj, nullptr}; + } + } +} + +//----------------------------------------------------------------------------- +void cpyrt::CallContext::Cleanup() { + Temporary* tmp = fTemps; + while (tmp) { + Py_DECREF(tmp->fPyObject); + Temporary* tmp2 = tmp->fNext; + delete tmp; + tmp = tmp2; + } + fTemps = nullptr; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CallContext::SetGlobalPolicy(ECallFlags toggleFlag, bool enabled) { + auto& flags = GlobalPolicyFlags(); + bool old = flags & toggleFlag; + if (enabled) + flags |= toggleFlag; + else + flags &= ~toggleFlag; + return old; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/CallContext.h b/bindings/pyroot/cppjit/src/cpyrt/CallContext.h new file mode 100644 index 0000000000000..f8000a0d82ef3 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CallContext.h @@ -0,0 +1,168 @@ +#ifndef CPYRT_CALLCONTEXT_H +#define CPYRT_CALLCONTEXT_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +#include + +namespace cppjit::cpyrt { + +// Parameter and the call-ABI constants (SMALL_ARGS_N, DIRECT_CALL) come +// from the interop callcontext.h via cppjit_interop.h + +// extra call information +struct CallContext { + CallContext() + : fCurScope(nullptr), fPyContext(nullptr), fFlags(0), fArgsVec(nullptr), + fNArgs(0), fTemps(nullptr) {} + CallContext(const CallContext&) = delete; + CallContext& operator=(const CallContext&) = delete; + ~CallContext() { + if (fTemps) + Cleanup(); + delete fArgsVec; + } + + enum ECallFlags { + kNone = 0x000000, + kIsSorted = 0x000001, // if method overload priority determined + kIsCreator = 0x000002, // if method creates python-owned objects + kIsConstructor = 0x000004, // if method is a C++ constructor + kHaveImplicit = 0x000008, // indicate that implicit converters are available + kAllowImplicit = 0x000010, // indicate that implicit conversions are allowed + kNoImplicit = 0x000020, // disable implicit to prevent recursion + kCallDirect = 0x000040, // call wrapped method directly, no inheritance + kFromDescr = 0x000080, // initiated from a descriptor + kUseHeuristics = 0x000100, // if method applies heuristics memory policy + kImplicitSmartPtrConversion = + 0x000200, // enable implicit conversion to smart pointers + kReleaseGIL = 0x000400, // if method should release the GIL + kSetLifeLine = 0x000800, // if return value is part of 'this' + kNeverLifeLine = 0x001000, // if the return value is never part of 'this' + kPyException = 0x002000, // Python exception during method execution + kCppException = 0x004000, // C++ exception during method execution + kProtected = 0x008000, // if method should return on signals + kUseFFI = 0x010000, // not implemented + kIsPseudoFunc = 0x020000, // internal, used for introspection + }; + + // Policies about memory handling and signal safety + static bool SetGlobalPolicy(ECallFlags e, bool enabled); + + static uint32_t& GlobalPolicyFlags(); + + void AddTemporary(PyObject* pyobj); + void Cleanup(); + + Parameter* GetArgs(size_t sz) { + if (sz != (size_t)-1) + fNArgs = sz; + if (fNArgs <= SMALL_ARGS_N) + return fArgs; + if (!fArgsVec) + fArgsVec = new std::vector(); + fArgsVec->resize(fNArgs); + return fArgsVec->data(); + } + + Parameter* GetArgs() { + if (fNArgs <= SMALL_ARGS_N) + return fArgs; + return fArgsVec->data(); + } + + size_t GetSize() { return fNArgs; } + size_t GetEncodedSize() { + return fNArgs | ((fFlags & kCallDirect) ? DIRECT_CALL : 0); + } + +public: + // info/status + interop::TCppScope_t fCurScope; + PyObject* fPyContext; + uint32_t fFlags; + +private: + struct Temporary { + PyObject* fPyObject; + Temporary* fNext; + }; + + // payload + Parameter fArgs[SMALL_ARGS_N]; + std::vector* fArgsVec; + size_t fNArgs; + Temporary* fTemps; +}; + +inline bool IsSorted(uint64_t flags) { return flags & CallContext::kIsSorted; } + +inline bool IsCreator(uint64_t flags) { + return flags & CallContext::kIsCreator; +} + +inline bool IsConstructor(uint64_t flags) { + return flags & CallContext::kIsConstructor; +} + +inline bool HaveImplicit(CallContext* ctxt) { + return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && + (ctxt->fFlags & CallContext::kHaveImplicit)) + : false; +} + +inline bool AllowImplicit(CallContext* ctxt) { + return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && + (ctxt->fFlags & CallContext::kAllowImplicit)) + : false; +} + +inline bool NoImplicit(CallContext* ctxt) { + return ctxt ? (ctxt->fFlags & CallContext::kNoImplicit) : false; +} + +inline bool ReleasesGIL(CallContext* ctxt) { + return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false; +} + +inline bool UseStrictOwnership() { + using CC = cppjit::cpyrt::CallContext; + return !(CC::GlobalPolicyFlags() & CC::kUseHeuristics); +} + +// kImplicitSmartPtrConversion is a global policy (set through +// cppyy.SetImplicitSmartPointerConversion), but it can also be requested for a +// single call through the call context, so check both words - as is done for +// kProtected in CPPMethod::Execute. +inline bool AllowImplicitSmartPtrConversion(CallContext* ctxt) { + using CC = cppjit::cpyrt::CallContext; + return (CC::GlobalPolicyFlags() & CC::kImplicitSmartPtrConversion) || + (ctxt && (ctxt->fFlags & CC::kImplicitSmartPtrConversion)); +} + +template class CallContextRAII { +public: + CallContextRAII(CallContext* ctxt) : fCtxt(ctxt) { + fPrior = fCtxt->fFlags & F; + fCtxt->fFlags |= F; + } + CallContextRAII(const CallContextRAII&) = delete; + CallContextRAII& operator=(const CallContextRAII&) = delete; + ~CallContextRAII() { + if (!fPrior) + fCtxt->fFlags &= ~F; + } + +private: + CallContext* fCtxt; + bool fPrior; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CALLCONTEXT_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CommonDefs.h b/bindings/pyroot/cppjit/src/cpyrt/CommonDefs.h new file mode 100644 index 0000000000000..0db73202137e5 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CommonDefs.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_COMMONDEFS_H +#define CPYRT_COMMONDEFS_H + +// export macros for our own API +// import/export (after precommondefs.h from PyPy) +#ifdef _MSC_VER +// Windows requires symbols to be explicitly exported +#define CPYRT_EXPORT extern __declspec(dllexport) +#define CPYRT_IMPORT extern __declspec(dllimport) +#define CPYRT_CLASS_EXPORT __declspec(dllexport) + +// CPYRT_EXTERN is dual use in the public API +#ifndef CPYRT_INTERNAL +#define CPYRT_EXTERN extern __declspec(dllexport) +#define CPYRT_CLASS_EXTERN __declspec(dllexport) +#else +#define CPYRT_EXTERN extern __declspec(dllimport) +#define CPYRT_CLASS_EXTERN __declspec(dllimport) +#endif + +#define CPYRT_STATIC + +#else +// Linux, Mac, etc. +#define CPYRT_EXPORT extern +#define CPYRT_IMPORT extern +#define CPYRT_CLASS_EXPORT +#define CPYRT_EXTERN extern +#define CPYRT_CLASS_EXTERN +#define CPYRT_STATIC static + +#endif + +#endif // !CPYRT_COMMONDEFS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Compatibility.h b/bindings/pyroot/cppjit/src/cpyrt/Compatibility.h new file mode 100644 index 0000000000000..e9771d7e6b76d --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Compatibility.h @@ -0,0 +1,48 @@ +#ifndef CPYRT_COMPATIBILITY_H +#define CPYRT_COMPATIBILITY_H + +#include "Python.h" + +#include "cppjit_interop.h" + +#include + +namespace cppjit::cpyrt::compat { + +inline PyObject* GetClingPrintValue() { +#ifdef CPPJIT_USE_CLING + static PyObject* printValue = nullptr; + if (printValue) + return printValue; + + PyObject* gbl = + PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppjit.gbl"); + PyObject* cling = gbl ? PyObject_GetAttrString(gbl, (char*)"cling") : nullptr; + printValue = + cling ? PyObject_GetAttrString(cling, (char*)"printValue") : nullptr; + Py_XDECREF(cling); + + if (printValue) { + Py_DECREF(printValue); // make borrowed + if (!PyCallable_Check(printValue)) + printValue = nullptr; + } + + if (!printValue) + PyErr_Clear(); + + return printValue; +#else + return nullptr; +#endif +} + +// Interpreter::toString is an assert(0) stub (CppInterOp#1100); skip it. +inline std::string ObjToString(interop::TCppScope_t /*klass*/, + interop::TCppObject_t /*obj*/) { + return ""; +} + +} // namespace cppjit::cpyrt::compat + +#endif // CPYRT_COMPATIBILITY_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx b/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx new file mode 100644 index 0000000000000..1cba38cd4afb1 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx @@ -0,0 +1,4532 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CallContext.h" +#include "CustomPyTypes.h" +#include "DeclareConverters.h" +#include "LowLevelViews.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include // for wstring_convert +#include +#include +#include // for ptrdiff_t +#include +#include +#include +#include +#if __cplusplus >= 202002L +#include +#endif + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { + +// factories +typedef std::unordered_map ConvFactories_t; +static ConvFactories_t gConvFactories; + +// special objects +extern PyObject* gNullPtrObject; +extern PyObject* gDefaultObject; +} // namespace cppjit::cpyrt + +// Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the +// Python version is lower than 3.14, the version where that function got +// introduced. +#if PY_VERSION_HEX < 0x030e0000 +const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; +inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject* pyobject) { + return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; +} +#endif + +//- pretend-ctypes helpers --------------------------------------------------- +struct cpyrt_tagCDataObject { // non-public (but stable) + PyObject_HEAD char* b_ptr; + int b_needsfree; +}; + +struct cpyrt_tagPyCArgObject { // not public (but stable; note that older + PyObject_HEAD // Pythons protect 'D' with HAVE_LONG_LONG) + void* pffi_type; + char tag; + union { // for convenience, kept only relevant vals + char c; + char b; + short h; + int i; + long l; + long long q; + long double g; + void* p; +#if PY_VERSION_HEX >= 0x030e0000 + double D[2]; + float F[2]; + long double G[2]; +#endif + } value; + PyObject* obj; +#if PY_VERSION_HEX >= 0x030e0000 + Py_ssize_t size; +#endif +}; + +// indices of ctypes types into the array caches (note that c_complex and +// c_fcomplex do not exist as types in ctypes) +#define ct_c_bool 0 +#define ct_c_char 1 +#define ct_c_shar 1 +#define ct_c_wchar 2 +#define ct_c_byte 3 +#define ct_c_int8 3 +#define ct_c_ubyte 4 +#define ct_c_uchar 4 +#define ct_c_uint8 4 +#define ct_c_short 5 +#define ct_c_ushort 6 +#define ct_c_uint16 7 +#define ct_c_int 8 +#define ct_c_uint 9 +#define ct_c_uint32 10 +#define ct_c_long 11 +#define ct_c_ulong 12 +#define ct_c_longlong 13 +#define ct_c_ulonglong 14 +#define ct_c_float 15 +#define ct_c_double 16 +#define ct_c_longdouble 17 +#define ct_c_char_p 18 +#define ct_c_wchar_p 19 +#define ct_c_void_p 20 +#define ct_c_fcomplex 21 +#define ct_c_complex 22 +#define ct_c_pointer 23 +#define ct_c_funcptr 24 +#define ct_c_int16 25 +#define ct_c_int32 26 +#define NTYPES 27 + +static std::array gCTypesNames = { + "c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", + "c_short", "c_ushort", "c_uint16", "c_int", "c_uint", + "c_uint32", "c_long", "c_ulong", "c_longlong", "c_ulonglong", + "c_float", "c_double", "c_longdouble", "c_char_p", "c_wchar_p", + "c_void_p", "c_fcomplex", "c_complex", "_Pointer", "_CFuncPtr", + "c_int16", "c_int32"}; +static std::array gCTypesTypes; +static std::array gCTypesPtrTypes; + +// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself +// caching the types (thus also making them unique), so no ref-count is needed. +// Further, by keeping a ref-count on the module, it won't be off-loaded until +// the 2nd cleanup cycle. +static PyTypeObject* GetCTypesType(int nidx) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) { + PyErr_Clear(); + return nullptr; + } + PyTypeObject* ct_t = gCTypesTypes[nidx]; + if (!ct_t) { + ct_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, gCTypesNames[nidx]); + if (!ct_t) + PyErr_Clear(); + else { + gCTypesTypes[nidx] = ct_t; + Py_DECREF(ct_t); + } + } + return ct_t; +} + +static PyTypeObject* GetCTypesPtrType(int nidx) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) { + PyErr_Clear(); + return nullptr; + } + PyTypeObject* cpt_t = gCTypesPtrTypes[nidx]; + if (!cpt_t) { + if (strcmp(gCTypesNames[nidx], "c_char") == 0) { + cpt_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_char_p"); + } else { + PyObject* ct_t = (PyObject*)GetCTypesType(nidx); + if (ct_t) { + PyObject* ptrcreat = PyObject_GetAttrString(ctmod, "POINTER"); + cpt_t = + (PyTypeObject*)PyObject_CallFunctionObjArgs(ptrcreat, ct_t, NULL); + Py_DECREF(ptrcreat); + } + } + if (cpt_t) { + gCTypesPtrTypes[nidx] = cpt_t; + Py_DECREF(cpt_t); + } + } + return cpt_t; +} + +static bool IsPyCArgObject(PyObject* pyobject) { + static PyTypeObject* pycarg_type = nullptr; + if (!pycarg_type) { + PyObject* ctmod = PyImport_ImportModule("ctypes"); + if (!ctmod) + PyErr_Clear(); + else { + PyTypeObject* ct_t = + (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_int"); + PyObject* cobj = ct_t->tp_new(ct_t, nullptr, nullptr); + PyObject* byref = PyObject_GetAttrString(ctmod, "byref"); + PyObject* pyptr = PyObject_CallFunctionObjArgs(byref, cobj, NULL); + Py_DECREF(byref); + Py_DECREF(cobj); + Py_DECREF(ct_t); + pycarg_type = Py_TYPE(pyptr); // static, no ref-count needed + Py_DECREF(pyptr); + Py_DECREF(ctmod); + } + } + return Py_TYPE(pyobject) == pycarg_type; +} + +#if PY_VERSION_HEX < 0x30d0000 +static bool IsCTypesArrayOrPointer(PyObject* pyobject) { + static PyTypeObject* cstgdict_type = nullptr; + if (!cstgdict_type) { + // get any pointer type to initialize the extended dictionary type + PyTypeObject* ct_int = GetCTypesType(ct_c_int); + if (ct_int && ct_int->tp_dict) { + cstgdict_type = Py_TYPE(ct_int->tp_dict); + } + } + + PyTypeObject* pytype = Py_TYPE(pyobject); + if (pytype->tp_dict && Py_TYPE(pytype->tp_dict) == cstgdict_type) + return true; + return false; +} +#else +// the internals of ctypes have been redone, requiring a more complex checking +namespace { + +typedef struct { + PyTypeObject* DictRemover_Type; + PyTypeObject* PyCArg_Type; + PyTypeObject* PyCField_Type; + PyTypeObject* PyCThunk_Type; + PyTypeObject* StructParam_Type; + PyTypeObject* PyCType_Type; + PyTypeObject* PyCStructType_Type; + PyTypeObject* UnionType_Type; + PyTypeObject* PyCPointerType_Type; + // ... unused fields omitted ... +} _cppjit_ctypes_state; + +} // unnamed namespace + +static bool IsCTypesArrayOrPointer(PyObject* pyobject) { + static _cppjit_ctypes_state* state = nullptr; + if (!state) { + PyObject* ctmod = PyImport_AddModule( + "_ctypes"); // the extension module, not the Python one + if (ctmod) + state = (_cppjit_ctypes_state*)PyModule_GetState(ctmod); + } + + // verify for object types that have a C payload + if (state && (PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), + (PyObject*)state->PyCType_Type) || + PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), + (PyObject*)state->PyCPointerType_Type))) { + return true; + } + + return false; +} +#endif + +//- helper to establish life lines ------------------------------------------- +static inline bool SetLifeLine(PyObject* holder, PyObject* target, + intptr_t ref) { + // set a lifeline from on the holder to the target, using the ref as label + if (!holder) + return false; + + // 'ref' is expected to be the converter address or data memory location, so + // that the combination of holder and ref is unique, but also identifiable for + // reuse when the C++ side is being overwritten + std::ostringstream attr_name; + attr_name << "__" << ref; + auto res = + PyObject_SetAttrString(holder, (char*)attr_name.str().c_str(), target); + return res != -1; +} + +static bool HasLifeLine(PyObject* holder, intptr_t ref) { + // determine if a lifeline was previously set for the ref on the holder + if (!holder) + return false; + + std::ostringstream attr_name; + attr_name << "__" << ref; + PyObject* res = + PyObject_GetAttrString(holder, (char*)attr_name.str().c_str()); + + if (res) { + Py_DECREF(res); + return true; + } + + PyErr_Clear(); + return false; +} + +//- helper to work with both CPPInstance and CPPExcInstance ------------------ +static inline cpyrt::CPPInstance* +GetCppInstance(PyObject* pyobject, + interop::TCppScope_t klass = interop::TCppScope_t{}, + bool accept_rvalue = false) { + using namespace cppjit::cpyrt; + if (CPPInstance_Check(pyobject)) + return (CPPInstance*)pyobject; + if (CPPExcInstance_Check(pyobject)) + return (CPPInstance*)((CPPExcInstance*)pyobject)->fCppInstance; + + // this is not a C++ proxy; allow custom cast to C++ + PyObject* castobj = PyObject_CallMethodNoArgs(pyobject, PyStrings::gCastCpp); + if (castobj) { + if (CPPInstance_Check(castobj)) + return (CPPInstance*)castobj; + else if (klass && PyTuple_CheckExact(castobj)) { + // allow implicit conversion from a tuple of arguments + PyObject* pyclass = GetScopeProxy(klass); + if (pyclass) { + CPPInstance* pytmp = + (CPPInstance*)PyObject_Call(pyclass, castobj, NULL); + Py_DECREF(pyclass); + if (CPPInstance_Check(pytmp)) { + if (accept_rvalue) + pytmp->fFlags |= CPPInstance::kIsRValue; + Py_DECREF(castobj); + return pytmp; + } + Py_XDECREF(pytmp); + } + } + + Py_DECREF(castobj); + return nullptr; + } + + PyErr_Clear(); + return nullptr; +} + +//- custom helpers to check ranges ------------------------------------------- +static inline bool IsCPPEnumInstance(PyObject* pyobject) { + // cppjit enums are int subclasses created with __underlying on the class + if (!PyLong_Check(pyobject) || PyLong_CheckExact(pyobject) || + PyBool_Check(pyobject)) + return false; + return PyObject_HasAttr((PyObject*)Py_TYPE(pyobject), + cppjit::cpyrt::PyStrings::gUnderlying); +} + +// bools and enum instances match integer parameters in the implicit round +// only; a nested call under kNoImplicit is that round (an implicit +// construction from the argument), so enum instances pass there +static inline bool ImplicitBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { + using namespace cppjit::cpyrt; + if (!AllowImplicit(ctxt) && + (PyBool_Check(pyobject) || + (IsCPPEnumInstance(pyobject) && !NoImplicit(ctxt)))) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + return true; +} + +static inline bool StrictBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { + using namespace cppjit::cpyrt; + if (!AllowImplicit(ctxt) && !PyBool_Check(pyobject)) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + return true; +} + +static inline bool cpyrt_PyLong_AsBool(PyObject* pyobject) { + // range-checking python integer to C++ bool conversion + long l = PyLong_AsLong(pyobject); + // fail to pass float -> bool; the problem is rounding (0.1 -> 0 -> False) + if (!(l == 0 || l == 1) || PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_ValueError, + "boolean value should be bool, or integer 1 or 0"); + return (bool)-1; + } + return (bool)l; +} + +// range-checking python integer to C++ integer conversion (prevents p2.7 silent +// conversions) +#define CPPJIT_PYLONG_AS_TYPE(name, type, limit_low, limit_high) \ + static inline type cpyrt_PyLong_As##name(PyObject* pyobject) { \ + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { \ + if (pyobject == cpyrt::gDefaultObject) \ + return (type)0; \ + PyErr_SetString(PyExc_TypeError, \ + #type " conversion expects an integer object"); \ + return (type) - 1; \ + } \ + long l = PyLong_AsLong(pyobject); \ + if (l < limit_low || limit_high < l) { \ + PyErr_Format(PyExc_ValueError, "integer %ld out of range for " #type, \ + l); \ + return (type) - 1; \ + } \ + return (type)l; \ + } + +CPPJIT_PYLONG_AS_TYPE(UInt8, uint8_t, 0, UCHAR_MAX) +CPPJIT_PYLONG_AS_TYPE(Int8, int8_t, SCHAR_MIN, SCHAR_MAX) +CPPJIT_PYLONG_AS_TYPE(UInt16, uint16_t, 0, UINT16_MAX) +CPPJIT_PYLONG_AS_TYPE(Int16, int16_t, INT16_MIN, INT16_MAX) +CPPJIT_PYLONG_AS_TYPE(UInt32, uint32_t, 0, UINT32_MAX) +CPPJIT_PYLONG_AS_TYPE(Int32, int32_t, INT32_MIN, INT32_MAX) +CPPJIT_PYLONG_AS_TYPE(UShort, unsigned short, 0, USHRT_MAX) +CPPJIT_PYLONG_AS_TYPE(Short, short, SHRT_MIN, SHRT_MAX) +CPPJIT_PYLONG_AS_TYPE(StrictInt, int, INT_MIN, INT_MAX) + +static inline long cpyrt_PyLong_AsStrictLong(PyObject* pyobject) { + // strict python integer to C++ long integer conversion + + // prevent float -> long (see cpyrt_PyLong_AsStrictInt) + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { + if (pyobject == cpyrt::gDefaultObject) + return (long)0; + PyErr_SetString(PyExc_TypeError, + "int/long conversion expects an integer object"); + return (long)-1; + } + + return (long)PyLong_AsLong(pyobject); // already does long range check +} + +static inline PY_LONG_LONG cpyrt_PyLong_AsStrictLongLong(PyObject* pyobject) { + // strict python integer to C++ long long integer conversion + + // prevent float -> long (see cpyrt_PyLong_AsStrictInt) + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { + if (pyobject == cpyrt::gDefaultObject) + return (PY_LONG_LONG)0; + PyErr_SetString(PyExc_TypeError, + "int/long conversion expects an integer object"); + return (PY_LONG_LONG)-1; + } + + return PyLong_AsLongLong(pyobject); // already does long range check +} + +//- helper for pointer/array/reference conversions --------------------------- +static inline bool CArraySetArg(PyObject* pyobject, cpyrt::Parameter& para, + char tc, int size, bool check = true) { + // Case of LowLevelView. In general, they also implement the buffer protocol, + // but for views around nullptr or C-style arrays without size info the buffer + // protocol implementation is incomplete and PyObject_GetBuffer will fail. + if (cpyrt::LowLevelView_Check(pyobject)) { + auto llview = ((cpyrt::LowLevelView*)pyobject); + if (llview->fBufInfo.itemsize != size || + !strchr(llview->fBufInfo.format, tc)) { + PyErr_Format(PyExc_TypeError, + "could not convert argument to buffer or nullptr"); + return false; + } + + para.fValue.fVoidp = llview->get_buf(); + } + // general case of loading a C array pointer (void* + type code) as function + // argument + else if (pyobject == cpyrt::gNullPtrObject || + pyobject == cpyrt::gDefaultObject) + para.fValue.fVoidp = nullptr; + else { + Py_ssize_t buflen = cpyrt::Utility::GetBuffer(pyobject, tc, size, + para.fValue.fVoidp, check); + if (!buflen) { + // stuck here as it's the least common + if (cpyrt_PyLong_AsStrictInt(pyobject) == 0) + para.fValue.fVoidp = nullptr; + else { + PyErr_Format(PyExc_TypeError, // ValueError? + "could not convert argument to buffer or nullptr"); + return false; + } + } + } + para.fTypeCode = 'p'; + return true; +} + +//- helper for implicit conversions ------------------------------------------ +static inline cpyrt::CPPInstance* ConvertImplicit(interop::TCppScope_t klass, + PyObject* pyobject, + cpyrt::Parameter& para, + cpyrt::CallContext* ctxt, + bool manage = true) { + using namespace cppjit::cpyrt; + + // filter out copy and move constructors + if (IsConstructor(ctxt->fFlags) && klass == ctxt->fCurScope && + ctxt->GetSize() == 1) + return nullptr; + + // never attempt implicit conversion to standard library implementation + // details (leading underscore, e.g. MSVC's std::_Swc_base, the base of its + // random engines, which has an unconstrained generator constructor that + // accepts anything): no user-facing API expects them, and every failed + // attempt JIT-compiles a constructor wrapper, which is prohibitively slow + // when it happens per call in overload resolution (seen as pyunittests + // timeouts on the Windows CI). The one legitimate case is a conversion + // between instantiations of the _same_ implementation-detail template, + // e.g. libc++'s iterator to const_iterator (std::__wrap_iter to + // std::__wrap_iter), which uses a converting constructor. + { + const std::string& clName = interop::GetScopedFinalName(klass); + if (clName.compare(0, 6, "std::_") == 0 || + (!clName.empty() && clName[0] == '_')) { + bool sameTemplate = false; + if (CPPInstance_Check(pyobject)) { + interop::TCppScope_t src = ((CPPInstance*)pyobject)->ObjectIsA(); + if (src && src != klass) { + const std::string& srcName = interop::GetScopedFinalName(src); + std::string::size_type lt = clName.find('<'); + sameTemplate = lt != std::string::npos && srcName.size() > lt && + srcName[lt] == '<' && + srcName.compare(0, lt, clName, 0, lt) == 0; + } + } + if (!sameTemplate) + return nullptr; + } + } + + // only proceed if implicit conversions are allowed (in "round 2") or if the + // argument is exactly a tuple or list, as these are the equivalent of + // initializer lists and thus "syntax" not a conversion + if (!AllowImplicit(ctxt)) { + PyTypeObject* pytype = (PyTypeObject*)Py_TYPE(pyobject); + if (!(pytype == &PyList_Type || + pytype == &PyTuple_Type)) { // || !CPPInstance_Check(pyobject))) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return nullptr; + } + } + + // exercise implicit conversion + PyObject* pyscope = CreateScopeProxy(klass); + if (!CPPScope_Check(pyscope)) { + Py_XDECREF(pyscope); + return nullptr; + } + + // call constructor of argument type to attempt implicit conversion (disallow + // any implicit conversions by the scope's constructor itself) + PyObject* args = PyTuple_New(1); + Py_INCREF(pyobject); + PyTuple_SET_ITEM(args, 0, pyobject); + + ((CPPScope*)pyscope)->fFlags |= CPPScope::kActiveImplicitCall; + CPPInstance* pytmp = (CPPInstance*)PyObject_Call(pyscope, args, NULL); + if (!pytmp && PyTuple_CheckExact(pyobject)) { + // special case: allow implicit conversion from given set of arguments in + // tuple + PyErr_Clear(); + pytmp = (CPPInstance*)PyObject_Call(pyscope, pyobject, NULL); + } + ((CPPScope*)pyscope)->fFlags &= ~CPPScope::kActiveImplicitCall; + + Py_DECREF(args); + Py_DECREF(pyscope); + + if (pytmp) { + // implicit conversion succeeded! + if (manage) + ctxt->AddTemporary((PyObject*)pytmp); + para.fValue.fVoidp = pytmp->GetObjectRaw(); + para.fTypeCode = 'V'; + return pytmp; + } + + PyErr_Clear(); + return nullptr; +} + +//- base converter implementation -------------------------------------------- +cppjit::cpyrt::Converter::~Converter() { /* empty */ } + +//---------------------------------------------------------------------------- +PyObject* cppjit::cpyrt::Converter::FromMemory(void*) { + // could happen if no derived class override + PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted from memory"); + return nullptr; +} + +//---------------------------------------------------------------------------- +bool cppjit::cpyrt::Converter::ToMemory(PyObject*, void*, + PyObject* /* ctxt */) { + // could happen if no derived class override + PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted to memory"); + return false; +} + +//- helper macro's ----------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + /* convert to C++ 'type', set arg for call */ \ + type val = (type)F2(pyobject); \ + if (val == (type) - 1 && PyErr_Occurred()) { \ + static PyTypeObject* ctypes_type = nullptr; \ + if (!ctypes_type) { \ + auto error = cpyrt::Utility::FetchPyError(); \ + ctypes_type = GetCTypesType(ct_##ctype); \ + cpyrt::Utility::RestorePyError(error); \ + } \ + if (Py_TYPE(pyobject) == ctypes_type) { \ + PyErr_Clear(); \ + val = *((type*)((cpyrt_tagCDataObject*)pyobject)->b_ptr); \ + } else if (pyobject == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + val = (type)0; \ + } else \ + return false; \ + } \ + para.fValue.f##name = val; \ + para.fTypeCode = tc; \ + return true; + +#define CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + return F1((stype) * ((type*)address)); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + type s = (type)F2(value); \ + if (s == (type) - 1 && PyErr_Occurred()) { \ + if (value == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + s = (type)0; \ + } else \ + return false; \ + } \ + *((type*)address) = (type)s; \ + return true; \ + } + +#define CPPJIT_IMPL_BASIC_CONVERTER_NI(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (!StrictBool(pyobject, ctxt)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +#define CPPJIT_IMPL_BASIC_CONVERTER_IB(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (!ImplicitBool(pyobject, ctxt)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +#define CPPJIT_IMPL_BASIC_CONVERTER_NB(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /*ctxt*/) { \ + if (PyBool_Check(pyobject)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +//---------------------------------------------------------------------------- +static inline int ExtractChar(PyObject* pyobject, const char* tname, int low, + int high) { + int lchar = -1; + if (PyBytes_Check(pyobject)) { + if (PyBytes_GET_SIZE(pyobject) == 1) + lchar = (int)(PyBytes_AsString(pyobject)[0]); + else + PyErr_Format(PyExc_ValueError, + "%s expected, got bytes of size " PY_SSIZE_T_FORMAT, tname, + PyBytes_GET_SIZE(pyobject)); + } else if (cpyrt_PyText_Check(pyobject)) { + if (cpyrt_PyText_GET_SIZE(pyobject) == 1) + lchar = (int)(cpyrt_PyText_AsString(pyobject)[0]); + else + PyErr_Format(PyExc_ValueError, + "%s expected, got str of size " PY_SSIZE_T_FORMAT, tname, + cpyrt_PyText_GET_SIZE(pyobject)); + } else if (pyobject == cpyrt::gDefaultObject) { + lchar = (int)'\0'; + } else if (!PyFloat_Check(pyobject)) { // don't allow truncating conversion + lchar = (int)PyLong_AsLong(pyobject); + if (lchar == -1 && PyErr_Occurred()) + ; // empty, as error already set + else if (!(low <= lchar && lchar <= high)) { + PyErr_Format(PyExc_ValueError, + "integer to character: value %d not in range [%d,%d]", lchar, + low, high); + lchar = -1; + } + } else + PyErr_SetString(PyExc_TypeError, "char or small int type expected"); + + return lchar; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) \ + PyObject* cpyrt::name##RefConverter::FromMemory(void* ptr) { \ + /* convert a reference to int to Python through ctypes pointer object */ \ + PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ + if (!ctypes_type) { \ + PyErr_SetString(PyExc_RuntimeError, "no ctypes available"); \ + return nullptr; \ + } \ + PyObject* ref = ctypes_type->tp_new(ctypes_type, nullptr, nullptr); \ + ((cpyrt_tagCDataObject*)ref)->b_ptr = (char*)ptr; \ + ((cpyrt_tagCDataObject*)ref)->b_needsfree = 0; \ + return ref; \ + } + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(name, type, ctype, F1) \ + bool cpyrt::Const##name##RefConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) { \ + type val = (type)F1(pyobject); \ + if (val == (type) - 1 && PyErr_Occurred()) { \ + if (pyobject == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + val = (type)0; \ + } else \ + return false; \ + } \ + para.fValue.f##name = val; \ + para.fRef = ¶.fValue.f##name; \ + para.fTypeCode = 'r'; \ + return true; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(name, type, ctype, low, \ + high) \ + bool cpyrt::Const##name##RefConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) { \ + /* convert to C++ <>, set arg for call, allow int -> char \ + */ \ + type val = (type)ExtractChar(pyobject, #type, low, high); \ + if (val == (type) - 1 && PyErr_Occurred()) \ + return false; \ + para.fValue.fLong = val; \ + para.fTypeCode = 'l'; \ + return true; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CHAR_CONVERTER(name, type, low, high) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* convert to C++ <>, set arg for call, allow int -> char \ + */ \ + long val = ExtractChar(pyobject, #type, low, high); \ + if (val == -1 && PyErr_Occurred()) \ + return false; \ + para.fValue.fLong = val; \ + para.fTypeCode = 'l'; \ + return true; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + /* return char in "native" str type as that's more natural in use */ \ + return cpyrt_PyText_FromFormat("%c", *((type*)address)); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + Py_ssize_t len; \ + const char* cstr = nullptr; \ + if (PyBytes_Check(value)) \ + PyBytes_AsStringAndSize(value, (char**)&cstr, &len); \ + else \ + cstr = cpyrt_PyText_AsStringAndSize(value, &len); \ + if (cstr) { \ + if (len != 1) { \ + PyErr_Format(PyExc_TypeError, \ + #type " expected, got string of size %zd", len); \ + return false; \ + } \ + *((type*)address) = (type)cstr[0]; \ + } else { \ + PyErr_Clear(); \ + long l = PyLong_AsLong(value); \ + if (l == -1 && PyErr_Occurred()) { \ + if (value == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + l = (long)0; \ + } else \ + return false; \ + } \ + if (!(low <= l && l <= high)) { \ + PyErr_Format(PyExc_ValueError, \ + "integer to character: value %ld not in range [%d,%d]", \ + l, low, high); \ + return false; \ + } \ + *((type*)address) = (type)l; \ + } \ + return true; \ + } + +//- converters for built-ins ------------------------------------------------- +CPPJIT_IMPL_BASIC_CONVERTER_IB(Long, long, long, c_long, PyLong_FromLong, + cpyrt_PyLong_AsStrictLong, 'l') + +//---------------------------------------------------------------------------- +bool cpyrt::LongRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ long&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_long)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + if (CArraySetArg(pyobject, para, 'l', sizeof(long))) { + para.fTypeCode = 'V'; + return true; + } + + PyErr_SetString(PyExc_TypeError, + "use ctypes.c_long for pass-by-ref of longs"); + return false; +} + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(Char, char, c_char, CHAR_MIN, CHAR_MAX) +CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(UChar, unsigned char, c_uchar, 0, UCHAR_MAX) + +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Bool, bool, c_bool, cpyrt_PyLong_AsBool) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int8, int8_t, c_int8, cpyrt_PyLong_AsInt8) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt8, uint8_t, c_uint8, cpyrt_PyLong_AsUInt8) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int16, int16_t, c_int16, cpyrt_PyLong_AsInt16) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt16, uint16_t, c_uint16, cpyrt_PyLong_AsUInt16) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int32, int32_t, c_int32, cpyrt_PyLong_AsInt32) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt32, uint32_t, c_uint32, cpyrt_PyLong_AsUInt32) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Short, short, c_short, cpyrt_PyLong_AsShort) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UShort, unsigned short, c_ushort, cpyrt_PyLong_AsUShort) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int, int, c_int, cpyrt_PyLong_AsStrictInt) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt, unsigned int, c_uint, PyLongOrInt_AsULong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Long, long, c_long, cpyrt_PyLong_AsStrictLong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(ULong, unsigned long, c_ulong, PyLongOrInt_AsULong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(LLong, PY_LONG_LONG, c_longlong, cpyrt_PyLong_AsStrictLongLong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(ULLong, PY_ULONG_LONG, c_ulonglong, PyLongOrInt_AsULong64) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::IntRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ (pseudo)int&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_int)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // alternate, pass pointer from buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, 'i', sizeof(int), para.fValue.fVoidp); + if (para.fValue.fVoidp && buflen) { + para.fTypeCode = 'V'; + return true; + }; + + PyErr_SetString(PyExc_TypeError, "use ctypes.c_int for pass-by-ref of ints"); + return false; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFCONVERTER(name, ctype, type, code) \ + bool cpyrt::name##RefConverter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* convert a reference to int to Python through ctypes pointer object */ \ + if (Py_TYPE(pyobject) == GetCTypesType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'V'; \ + return true; \ + } \ + bool res = CArraySetArg(pyobject, para, code, sizeof(type)); \ + if (!res) { \ + PyErr_SetString(PyExc_TypeError, \ + "use ctypes." #ctype " for pass-by-ref of " #type); \ + return false; \ + } \ + para.fTypeCode = 'V'; \ + return res; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) + +// clang-format off +CPPJIT_IMPL_REFCONVERTER(Bool, c_bool, bool, '?'); +CPPJIT_IMPL_REFCONVERTER(Char, c_char, char, 'b'); +CPPJIT_IMPL_REFCONVERTER(WChar, c_wchar, wchar_t, 'u'); +CPPJIT_IMPL_REFCONVERTER(Char16, c_uint16, char16_t, 'H'); +CPPJIT_IMPL_REFCONVERTER(Char32, c_uint32, char32_t, 'I'); +CPPJIT_IMPL_REFCONVERTER(SChar, c_byte, signed char, 'b'); +CPPJIT_IMPL_REFCONVERTER(UChar, c_ubyte, unsigned char, 'B'); +CPPJIT_IMPL_REFCONVERTER(Int8, c_int8, int8_t, 'b'); +CPPJIT_IMPL_REFCONVERTER(UInt8, c_uint8, uint8_t, 'B'); +CPPJIT_IMPL_REFCONVERTER(Int16, c_int16, int16_t, 'h'); +CPPJIT_IMPL_REFCONVERTER(UInt16, c_uint16, uint16_t, 'H'); +CPPJIT_IMPL_REFCONVERTER(Int32, c_int32, int32_t, 'i'); +CPPJIT_IMPL_REFCONVERTER(UInt32, c_uint32, uint32_t, 'I'); +CPPJIT_IMPL_REFCONVERTER(Short, c_short, short, 'h'); +CPPJIT_IMPL_REFCONVERTER(UShort, c_ushort, unsigned short, 'H'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Int, c_int); +CPPJIT_IMPL_REFCONVERTER(UInt, c_uint, unsigned int, 'I'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Long, c_long); +CPPJIT_IMPL_REFCONVERTER(ULong, c_ulong, unsigned long, 'L'); +CPPJIT_IMPL_REFCONVERTER(LLong, c_longlong, long long, 'q'); +CPPJIT_IMPL_REFCONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q'); +CPPJIT_IMPL_REFCONVERTER(Float, c_float, float, 'f'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Double, c_double); +CPPJIT_IMPL_REFCONVERTER(LDouble, c_longdouble, PY_LONG_DOUBLE, 'g'); +// clang-format on + +//---------------------------------------------------------------------------- +// convert to C++ bool, allow int/long -> bool, set arg for call +CPPJIT_IMPL_BASIC_CONVERTER_NI(Bool, bool, long, c_bool, PyBool_FromLong, + cpyrt_PyLong_AsBool, 'l') + +//---------------------------------------------------------------------------- +CPPJIT_IMPL_BASIC_CHAR_CONVERTER(Char, char, CHAR_MIN, CHAR_MAX) +CPPJIT_IMPL_BASIC_CHAR_CONVERTER(UChar, unsigned char, 0, UCHAR_MAX) + +PyObject* cpyrt::SCharAsIntConverter::FromMemory(void* address) { + // special case to be used with arrays: return a Python int instead of str + // (following the same convention as module array.array) + return PyInt_FromLong((long)*((signed char*)address)); +} + +PyObject* cpyrt::UCharAsIntConverter::FromMemory(void* address) { + // special case to be used with arrays: return a Python int instead of str + // (following the same convention as module array.array) + return PyInt_FromLong((long)*((unsigned char*)address)); +} + +//---------------------------------------------------------------------------- +bool cpyrt::WCharConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || cpyrt_PyUnicode_GET_SIZE(pyobject) != 1) { + PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); + return false; + } + wchar_t val; + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(pyobject, &val, 1); + if (res == -1) + return false; + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::WCharConverter::FromMemory(void* address) { + return PyUnicode_FromWideChar((const wchar_t*)address, 1); +} + +bool cpyrt::WCharConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || cpyrt_PyUnicode_GET_SIZE(value) != 1) { + PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); + return false; + } + wchar_t val; + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(value, &val, 1); + if (res == -1) + return false; + *((wchar_t*)address) = val; + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Char16Converter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || cpyrt_PyUnicode_GET_SIZE(pyobject) != 1) { + PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF16String(pyobject); + if (!bstr) + return false; + + char16_t val = + *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); + Py_DECREF(bstr); + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::Char16Converter::FromMemory(void* address) { + return PyUnicode_DecodeUTF16((const char*)address, sizeof(char16_t), nullptr, + nullptr); +} + +bool cpyrt::Char16Converter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || cpyrt_PyUnicode_GET_SIZE(value) != 1) { + PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF16String(value); + if (!bstr) + return false; + + *((char16_t*)address) = + *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); + Py_DECREF(bstr); + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Char32Converter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || 2 < cpyrt_PyUnicode_GET_SIZE(pyobject)) { + PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF32String(pyobject); + if (!bstr) + return false; + + char32_t val = + *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); + Py_DECREF(bstr); + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::Char32Converter::FromMemory(void* address) { + return PyUnicode_DecodeUTF32((const char*)address, sizeof(char32_t), nullptr, + nullptr); +} + +bool cpyrt::Char32Converter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || 2 < cpyrt_PyUnicode_GET_SIZE(value)) { + PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF32String(value); + if (!bstr) + return false; + + *((char32_t*)address) = + *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); + Py_DECREF(bstr); + return true; +} + +//---------------------------------------------------------------------------- +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int8, int8_t, long, c_int8, PyInt_FromLong, + cpyrt_PyLong_AsInt8, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UInt8, uint8_t, long, c_uint8, PyInt_FromLong, + cpyrt_PyLong_AsUInt8, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int16, int16_t, long, c_int16, PyInt_FromLong, + cpyrt_PyLong_AsInt16, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UInt16, uint16_t, long, c_uint16, PyInt_FromLong, + cpyrt_PyLong_AsUInt16, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int32, int32_t, long, c_int32, PyInt_FromLong, + cpyrt_PyLong_AsInt32, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UInt32, uint32_t, unsigned long, c_uint32, + PyLong_FromUnsignedLong, cpyrt_PyLong_AsUInt32, + 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Short, short, long, c_short, PyInt_FromLong, + cpyrt_PyLong_AsShort, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UShort, unsigned short, long, c_ushort, + PyInt_FromLong, cpyrt_PyLong_AsUShort, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int, int, long, c_uint, PyInt_FromLong, + cpyrt_PyLong_AsStrictInt, 'l') + +//---------------------------------------------------------------------------- +bool cpyrt::ULongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ unsigned long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fULong = PyLongOrInt_AsULong(pyobject); + if (para.fValue.fULong == (unsigned long)-1 && PyErr_Occurred()) + return false; + para.fTypeCode = 'L'; + return true; +} + +PyObject* cpyrt::ULongConverter::FromMemory(void* address) { + // construct python object from C++ unsigned long read at
+ return PyLong_FromUnsignedLong(*((unsigned long*)address)); +} + +bool cpyrt::ULongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned long, write it at
+ unsigned long u = PyLongOrInt_AsULong(value); + if (u == (unsigned long)-1 && PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + u = (unsigned long)0; + } else + return false; + } + *((unsigned long*)address) = u; + return true; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UIntConverter::FromMemory(void* address) { + // construct python object from C++ unsigned int read at
+ return PyLong_FromUnsignedLong(*((unsigned int*)address)); +} + +bool cpyrt::UIntConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned int, write it at
+ unsigned long u = PyLongOrInt_AsULong(value); + if (u == (unsigned long)-1 && PyErr_Occurred()) + return false; + + if (u > (unsigned long)UINT_MAX) { + PyErr_SetString(PyExc_OverflowError, "value too large for unsigned int"); + return false; + } + + *((unsigned int*)address) = (unsigned int)u; + return true; +} + +//- floating point converters ------------------------------------------------ +CPPJIT_IMPL_BASIC_CONVERTER_NB(Float, float, double, c_float, + PyFloat_FromDouble, PyFloat_AsDouble, 'f') +CPPJIT_IMPL_BASIC_CONVERTER_NB(Double, double, double, c_double, + PyFloat_FromDouble, PyFloat_AsDouble, 'd') + +CPPJIT_IMPL_BASIC_CONVERTER_NB(LDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, + c_longdouble, PyFloat_FromDouble, + PyFloat_AsDouble, 'g') + +cpyrt::ComplexDConverter::ComplexDConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::complex"), + keepControl) {} + +// special case for std::complex, maps it to/from Python's complex +bool cpyrt::ComplexDConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + const Py_complex& pc = PyComplex_AsCComplex(pyobject); + if (pc.real != -1.0 || !PyErr_Occurred()) { + fBuffer.real(pc.real); + fBuffer.imag(pc.imag); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + return this->InstanceConverter::SetArg(pyobject, para, ctxt); +} + +PyObject* cpyrt::ComplexDConverter::FromMemory(void* address) { + std::complex* dc = (std::complex*)address; + return PyComplex_FromDoubles(dc->real(), dc->imag()); +} + +bool cpyrt::ComplexDConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + const Py_complex& pc = PyComplex_AsCComplex(value); + if (pc.real != -1.0 || !PyErr_Occurred()) { + std::complex* dc = (std::complex*)address; + dc->real(pc.real); + dc->imag(pc.imag); + return true; + } + return this->InstanceConverter::ToMemory(value, address, ctxt); +} + +//---------------------------------------------------------------------------- +bool cpyrt::DoubleRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ double&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_double)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // alternate, pass pointer from buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, 'd', sizeof(double), para.fValue.fVoidp); + if (buflen && para.fValue.fVoidp) { + para.fTypeCode = 'V'; + return true; + } + + PyErr_SetString(PyExc_TypeError, + "use ctypes.c_double for pass-by-ref of doubles"); + return false; +} + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Float, float, c_float, PyFloat_AsDouble) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Double, double, c_double, PyFloat_AsDouble) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(LDouble, PY_LONG_DOUBLE, c_longdouble, PyFloat_AsDouble) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::VoidConverter::SetArg(PyObject*, Parameter&, CallContext*) { + // can't happen (unless a type is mapped wrongly), but implemented for + // completeness + PyErr_SetString(PyExc_SystemError, "void/unknown arguments can\'t be set"); + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::LLongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ long long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fLLong = cpyrt_PyLong_AsStrictLongLong(pyobject); + if (PyErr_Occurred()) + return false; + para.fTypeCode = 'q'; + return true; +} + +PyObject* cpyrt::LLongConverter::FromMemory(void* address) { + // construct python object from C++ long long read at
+ return PyLong_FromLongLong(*(PY_LONG_LONG*)address); +} + +bool cpyrt::LLongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ long long, write it at
+ PY_LONG_LONG ll = PyLong_AsLongLong(value); + if (ll == -1 && PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + ll = (PY_LONG_LONG)0; + } else + return false; + } + *((PY_LONG_LONG*)address) = ll; + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::ULLongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ unsigned long long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fULLong = PyLongOrInt_AsULong64(pyobject); + if (PyErr_Occurred()) + return false; + para.fTypeCode = 'Q'; + return true; +} + +PyObject* cpyrt::ULLongConverter::FromMemory(void* address) { + // construct python object from C++ unsigned long long read at
+ return PyLong_FromUnsignedLongLong(*(PY_ULONG_LONG*)address); +} + +bool cpyrt::ULLongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned long long, write it at
+ PY_ULONG_LONG ull = PyLongOrInt_AsULong64(value); + if (PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + ull = (PY_ULONG_LONG)0; + } else + return false; + } + *((PY_ULONG_LONG*)address) = ull; + return true; +} + +//---------------------------------------------------------------------------- +cpyrt::EnumConverter::~EnumConverter() { DestroyConverter(fBase); } + +bool cpyrt::EnumConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // an instance of the same enum is an exact match; anything else, plain + // integers included, converts in the implicit round only, as in C++ + if (ctxt && !AllowImplicit(ctxt)) { + bool exact = false; + if (IsCPPEnumInstance(pyobject)) { + PyObject* pycppname = + PyObject_GetAttr((PyObject*)Py_TYPE(pyobject), PyStrings::gCppName); + if (pycppname) { + exact = (fEnumName == cpyrt_PyText_AsString(pycppname)); + Py_DECREF(pycppname); + } else + PyErr_Clear(); + } + if (!exact) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + // the base integer converter defers enum instances, so lift the gate + CallContextRAII allow(ctxt); + return fBase->SetArg(pyobject, para, ctxt); + } + return fBase->SetArg(pyobject, para, ctxt); +} + +PyObject* cpyrt::EnumConverter::FromMemory(void* address) { + return fBase->FromMemory(address); +} + +bool cpyrt::EnumConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + return fBase->ToMemory(value, address, ctxt); +} + +std::string cpyrt::EnumConverter::GetFailureMsg() { + return fBase->GetFailureMsg(); +} + +//---------------------------------------------------------------------------- +bool cpyrt::CStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // construct a new string and copy it in new memory + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(pyobject, &len); + if (!cstr) { + // special case: allow ctypes c_char_p + auto error = cpyrt::Utility::FetchPyError(); + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + cpyrt::Utility::RestorePyError(error); + return false; + } + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for char array (truncated)", + 1) < 0) + return false; + + if (!ctxt->fPyContext) { + // use internal buffer as workaround + fBuffer = std::string(cstr, len); + if (fMaxSize != std::string::npos) + fBuffer.resize(fMaxSize, '\0'); // pad remainder of buffer as needed + cstr = fBuffer.c_str(); + } else + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + + // set the value and declare success + para.fValue.fVoidp = (void*)cstr; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::CStringConverter::FromMemory(void* address) { + // construct python object from C++ const char* read at
+ if (address && *(void**)address) { + if (fMaxSize != + std::string::npos) // need to prevent reading beyond boundary + return cpyrt_PyText_FromStringAndSize(*(char**)address, + (Py_ssize_t)fMaxSize); + + if (*(void**)address == + (void*)fBuffer.data()) // if we're buffering, we know the size + return cpyrt_PyText_FromStringAndSize((char*)fBuffer.data(), + fBuffer.size()); + + // no idea about lentgth: cut on \0 + return cpyrt_PyText_FromString(*(char**)address); + } + + // empty string in case there's no address + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; +} + +bool cpyrt::CStringConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // convert to C++ const char*, write it at
+ Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len); + if (!cstr) + return false; + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for char array (truncated)", + 1) < 0) + return false; + + // if address is available, and it wasn't set by this converter, assume a + // byte-wise copy; otherwise assume a pointer copy (this relies on the + // converter to be used for properties, or for argument passing, but not both + // at the same time; this is currently the case) + void* ptrval = *(void**)address; + if (ptrval == (void*)fBuffer.data()) { + fBuffer = std::string(cstr, len); + *(void**)address = (void*)fBuffer.data(); + return true; + } else if (ptrval && HasLifeLine(ctxt, (intptr_t)ptrval)) { + ptrval = nullptr; + // fall through; ptrval is nullptr means we're managing it + } + + // the string is (going to be) managed by us: assume pointer copy + if (!ptrval) { + SetLifeLine(ctxt, value, (intptr_t)address); + *(void**)address = (void*)cstr; + return true; + } + + // the pointer value is non-zero and not ours: assume byte copy + if (fMaxSize != std::string::npos) + strncpy(*(char**)address, cstr, fMaxSize); // pads remainder + else + // coverity[secure_coding] - can't help it, it's intentional. + strcpy(*(char**)address, cstr); + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::WCStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // construct a new string and copy it in new memory + Py_ssize_t len = PyUnicode_GetSize(pyobject); + if (len == (Py_ssize_t)-1 && PyErr_Occurred()) + return false; + + fBuffer = (wchar_t*)realloc(fBuffer, sizeof(wchar_t) * (len + 1)); + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(pyobject, fBuffer, len); + if (res == -1) + return false; // could free the buffer here + + // set the value and declare success + fBuffer[len] = L'\0'; + para.fValue.fVoidp = (void*)fBuffer; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::WCStringConverter::FromMemory(void* address) { + // construct python object from C++ wchar_t* read at
+ if (address && *(wchar_t**)address) { + if (fMaxSize != + std::wstring::npos) // need to prevent reading beyond boundary + return PyUnicode_FromWideChar(*(wchar_t**)address, (Py_ssize_t)fMaxSize); + // with unknown size + return PyUnicode_FromWideChar(*(wchar_t**)address, + wcslen(*(wchar_t**)address)); + } + + // empty string in case there's no valid address + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); +} + +bool cpyrt::WCStringConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ wchar_t*, write it at
+ Py_ssize_t len = PyUnicode_GetSize(value); + if (len == (Py_ssize_t)-1 && PyErr_Occurred()) + return false; + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for wchar_t array (truncated)", + 1) < 0) + return false; + + Py_ssize_t res = -1; + if (fMaxSize != std::wstring::npos) + res = cpyrt_PyUnicode_AsWideChar(value, *(wchar_t**)address, + (Py_ssize_t)fMaxSize); + else + // coverity[secure_coding] - can't help it, it's intentional. + res = cpyrt_PyUnicode_AsWideChar(value, *(wchar_t**)address, len); + + if (res == -1) + return false; + return true; +} + +//---------------------------------------------------------------------------- +#define CPYRT_WIDESTRING_CONVERTER(name, type, encode, decode, snull) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* change string encoding and copy into local buffer */ \ + PyObject* bstr = encode(pyobject); \ + if (!bstr) \ + return false; \ + \ + Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ + fBuffer = (type*)realloc(fBuffer, len + sizeof(type)); \ + memcpy(fBuffer, PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len); \ + Py_DECREF(bstr); \ + \ + fBuffer[len / sizeof(type)] = snull; \ + para.fValue.fVoidp = (void*)fBuffer; \ + para.fTypeCode = 'p'; \ + return true; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + /* construct python object from C++ * read at
*/ \ + if (address && *(type**)address) { \ + if (fMaxSize != std::wstring::npos) \ + return decode(*(const char**)address, \ + (Py_ssize_t)fMaxSize * sizeof(type), nullptr, nullptr); \ + return decode(*(const char**)address, \ + std::char_traits::length(*(type**)address) * \ + sizeof(type), \ + nullptr, nullptr); \ + } \ + \ + /* empty string in case there's no valid address */ \ + type w = snull; \ + return decode((const char*)&w, 0, nullptr, nullptr); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + /* convert to C++ *, write it at
*/ \ + PyObject* bstr = encode(value); \ + if (!bstr) \ + return false; \ + \ + Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ + Py_ssize_t maxbytes = (Py_ssize_t)fMaxSize * sizeof(type); \ + \ + /* verify (too long string will cause truncation, no crash) */ \ + if (fMaxSize != std::wstring::npos && maxbytes < len) { \ + if (PyErr_WarnEx(PyExc_RuntimeWarning, \ + (char*)"string too long for " #type \ + " array (truncated)", \ + 1) < 0) { \ + Py_DECREF(bstr); \ + return false; \ + } \ + len = maxbytes; \ + } \ + \ + memcpy(*((void**)address), PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, \ + len); \ + Py_DECREF(bstr); \ + /* debatable, but probably more convenient in most cases to null-terminate \ + * if enough space */ \ + if (len / sizeof(type) < fMaxSize) \ + (*(type**)address)[len / sizeof(type)] = snull; \ + return true; \ + } + +CPYRT_WIDESTRING_CONVERTER(CString16, char16_t, PyUnicode_AsUTF16String, + PyUnicode_DecodeUTF16, u'\0') +CPYRT_WIDESTRING_CONVERTER(CString32, char32_t, PyUnicode_AsUTF32String, + PyUnicode_DecodeUTF32, U'\0') + +//---------------------------------------------------------------------------- +bool cpyrt::NonConstCStringConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // attempt base class first (i.e. passing a string), but if that fails, try a + // buffer + if (this->CStringConverter::SetArg(pyobject, para, ctxt)) + return true; + + // apparently failed, try char buffer + PyErr_Clear(); + return CArraySetArg(pyobject, para, 'c', sizeof(char)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::NonConstCStringConverter::FromMemory(void* address) { + // assume this is a buffer access if the size is known; otherwise assume + // string + if (fMaxSize != std::string::npos) + return cpyrt_PyText_FromStringAndSize(*(char**)address, + (Py_ssize_t)fMaxSize); + return this->CStringConverter::FromMemory(address); +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, + void*& address) { + // (1): C++11 style "null pointer" + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + address = nullptr; + return true; + } + + // (2): allow integer zero to act as a null pointer (C NULL), no deriveds + if (PyInt_CheckExact(pyobject) || PyLong_CheckExact(pyobject)) { + intptr_t val = (intptr_t)PyLong_AsLongLong(pyobject); + if (val == 0l) { + address = (void*)val; + return true; + } + + return false; + } + + // (3): opaque PyCapsule (CObject in older pythons) from somewhere + if (cpyrt_PyCapsule_CheckExact(pyobject)) { + address = (void*)cpyrt_PyCapsule_GetPointer(pyobject, nullptr); + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /*ctxt*/) { + // just convert pointer if it is a C++ object + CPPInstance* pyobj = GetCppInstance(pyobject); + para.fValue.fVoidp = nullptr; + if (pyobj) { + // depending on memory policy, some objects are no longer owned when passed + // to C++ + if (!fKeepControl && !UseStrictOwnership()) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + para.fValue.fVoidp = pyobj->GetObject(); + para.fTypeCode = 'p'; + return true; + } + + // handle special cases + if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = 'p'; + return true; + } + + // allow ctypes voidp (which if got as a buffer will return void**, not + // void*); use isintance instead of an exact check, b/c c_void_p is the type + // mapper for typedefs of void* (typically opaque handles) + if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_void_p))) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // allow any other ctypes pointer type + if (IsCTypesArrayOrPointer(pyobject)) { + void** payload = (void**)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + if (payload) { + para.fValue.fVoidp = *payload; + para.fTypeCode = 'p'; + return true; + } + } + + // final try: attempt to get buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + para.fTypeCode = 'p'; + return true; + } + + // give up + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidArrayConverter::FromMemory(void* address) { + // nothing sensible can be done, just return
as pylong + if (!address || *(uintptr_t*)address == 0) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + return CreatePointerView(*(uintptr_t**)address); +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // just convert pointer if it is a C++ object + CPPInstance* pyobj = GetCppInstance(value); + if (pyobj) { + // depending on memory policy, some objects are no longer owned when passed + // to C++ + if (!fKeepControl && !UseStrictOwnership()) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + *(void**)address = pyobj->GetObject(); + return true; + } + + // handle special cases + void* ptr = nullptr; + if (GetAddressSpecialCase(value, ptr)) { + *(void**)address = ptr; + return true; + } + + // final try: attempt to get buffer + void* buf = nullptr; + Py_ssize_t buflen = Utility::GetBuffer(value, '*', 1, buf, false); + if (!buf || buflen == 0) + return false; + + *(void**)address = buf; + return true; +} + +#if __cplusplus >= 202002L + +namespace cppjit::cpyrt { + +class StdSpanConverter : public InstanceConverter { +public: + StdSpanConverter(std::string const& typeName, interop::TCppScope_t klass, + bool keepControl = false) + : InstanceConverter{klass, keepControl}, fTypeName{typeName} {} + + ~StdSpanConverter() { + if (fHasBuffer) { + PyBuffer_Release(&fBufinfo); + } + } + + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + bool HasState() override { return true; } + +private: + std::string fTypeName; + std::span fBuffer; + bool fHasBuffer = false; + Py_buffer fBufinfo; +}; + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::StdSpanConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + auto typecodeFound = Utility::TypecodeMap().find(fTypeName); + + // attempt to get buffer if the C++ type maps to a buffer type + if (typecodeFound == Utility::TypecodeMap().end() || + !PyObject_CheckBuffer(pyobject)) { + // Fall back to regular InstanceConverter + return this->InstanceConverter::SetArg(pyobject, para, ctxt); + } + + Py_ssize_t buflen = 0; + char typecode = typecodeFound->second; + memset(&fBufinfo, 0, sizeof(Py_buffer)); + + if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { + if (!strchr(fBufinfo.format, typecode)) { + PyErr_Format(PyExc_TypeError, + "buffer has incompatible type: expected '%c' for C++ type " + "'%s', but got format '%s'", + typecode, fTypeName.c_str(), + fBufinfo.format ? fBufinfo.format : ""); + PyBuffer_Release(&fBufinfo); + return false; + } + buflen = + Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); + } + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + // We assume the layout for any std::span is the same, and just use + // std::span as a placeholder. Not elegant, but works. + fBuffer = std::span{(std::size_t*)para.fValue.fVoidp, + static_cast(buflen)}; + fHasBuffer = true; + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +#endif // __cplusplus >= 202002L + +namespace { + +template +bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, + const void* buf, Py_ssize_t buflen, cpyrt::dims_t& shape, + bool isFixed) { + if (buflen == 0) + return false; + + Py_ssize_t oldsz = 1; + for (Py_ssize_t idim = 0; idim < shape.ndim(); ++idim) { + if (shape[idim] == cpyrt::UNKNOWN_SIZE) { + oldsz = -1; + break; + } + oldsz *= shape[idim]; + } + if (shape.ndim() != cpyrt::UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); + return false; + } + + if (isFixed) + memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1) * sizeof(type)); + else { + *(type**)address = (type*)buf; + shape.ndim(1); + shape[0] = buflen; + SetLifeLine(ctxt, owner, (intptr_t)address); + } + return true; +} + +} // namespace + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ + cpyrt::name##ArrayConverter::name##ArrayConverter(cdims_t dims) \ + : fShape(dims) { \ + fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ + } \ + \ + bool cpyrt::name##ArrayConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* ctxt) { \ + /* filter ctypes first b/c their buffer conversion will be wrong */ \ + bool convOk = false; \ + \ + /* 2-dim case: ptr-ptr types */ \ + if (!convOk && fShape.ndim() == 2) { \ + if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ + /* special case: pass address of c_void_p buffer to return the address \ + */ \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (LowLevelView_Check(pyobject) && \ + ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ + strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ + if (!convOk) { \ + PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ + if (Py_TYPE(pyobject) == ctypes_type) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'V'; \ + convOk = true; \ + } else if (IsPyCArgObject(pyobject)) { \ + cpyrt_tagPyCArgObject* carg = (cpyrt_tagPyCArgObject*)pyobject; \ + if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ + para.fValue.fVoidp = \ + (void*)((cpyrt_tagCDataObject*)carg->obj)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } else if (LowLevelView_Check(pyobject) && \ + strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* cast pointer type */ \ + if (!convOk) { \ + bool ismulti = fShape.ndim() > 1; \ + convOk = CArraySetArg(pyobject, para, code, \ + ismulti ? sizeof(void*) : sizeof(type), true); \ + } \ + \ + /* memory management and offsetting */ \ + if (convOk) \ + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ + \ + return convOk; \ + } \ + \ + PyObject* cpyrt::name##ArrayConverter::FromMemory(void* address) { \ + if (!fIsFixed) \ + return CreateLowLevelView##suffix((type**)address, fShape); \ + return CreateLowLevelView##suffix(*(type**)address, fShape); \ + } \ + \ + bool cpyrt::name##ArrayConverter::ToMemory(PyObject* value, void* address, \ + PyObject* ctxt) { \ + if (fShape.ndim() <= 1 || fIsFixed) { \ + void* buf = nullptr; \ + Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf); \ + if (buflen == 0) \ + return false; \ + \ + Py_ssize_t oldsz = 1; \ + for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ + if (fShape[idim] == UNKNOWN_SIZE) { \ + oldsz = -1; \ + break; \ + } \ + oldsz *= fShape[idim]; \ + } \ + if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ + return false; \ + } \ + \ + if (fIsFixed) \ + memcpy(*(type**)address, buf, \ + (0 < buflen ? buflen : 1) * sizeof(type)); \ + else { \ + *(type**)address = (type*)buf; \ + fShape.ndim(1); \ + fShape[0] = buflen; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + \ + } else { /* multi-dim, non-flat array; assume structure matches */ \ + void* buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ + Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(void*), buf); \ + if (buflen == 0) \ + return false; \ + *(type**)address = (type*)buf; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + return true; \ + } + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_ARRAY_CONVERTER(Bool, c_bool, bool, '?', ) +CPPJIT_IMPL_ARRAY_CONVERTER(SChar, c_char, signed char, 'b', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UChar, c_ubyte, unsigned char, 'B', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Byte, c_ubyte, std::byte, 'B', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Int8, c_byte, int8_t, 'b', _i8) +CPPJIT_IMPL_ARRAY_CONVERTER(Int16, c_int16, int16_t, 'h', _i16) +CPPJIT_IMPL_ARRAY_CONVERTER(Int32, c_int32, int32_t, 'i', _i32) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt8, c_ubyte, uint8_t, 'B', _i8) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt16, c_uint16, uint16_t, 'H', _i16) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt32, c_uint32, uint32_t, 'I', _i32) +CPPJIT_IMPL_ARRAY_CONVERTER(Short, c_short, short, 'h', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UShort, c_ushort, unsigned short, 'H', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Int, c_int, int, 'i', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt, c_uint, unsigned int, 'I', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Long, c_long, long, 'l', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ULong, c_ulong, unsigned long, 'L', ) +CPPJIT_IMPL_ARRAY_CONVERTER(LLong, c_longlong, long long, 'q', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Float, c_float, float, 'f', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Double, c_double, double, 'd', ) +CPPJIT_IMPL_ARRAY_CONVERTER(LDouble, c_longdouble, long double, 'g', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ComplexF, c_fcomplex, std::complex, 'z', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ComplexD, c_complex, std::complex, 'Z', ) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::CStringArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_c_char_p) || + (1 < fShape.ndim() && + PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_pointer)))) { + // 2nd predicate is ebatable: it's a catch-all for ctypes-styled + // multi-dimensional objects, which at this point does not check further + // dimensionality + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + + } else if (PySequence_Check(pyobject) && !cpyrt_PyText_Check(pyobject) && + !PyBytes_Check(pyobject)) { + // for (auto& p : fBuffer) free(p); + fBuffer.clear(); + + size_t len = (size_t)PySequence_Size(pyobject); + if (len == (size_t)-1) { + PyErr_SetString(PyExc_ValueError, + "can not convert sequence object of unknown length"); + return false; + } + + fBuffer.reserve(len); + for (size_t i = 0; i < len; ++i) { + PyObject* item = PySequence_GetItem(pyobject, i); + if (item) { + Py_ssize_t sz; + const char* p = cpyrt_PyText_AsStringAndSize(item, &sz); + Py_DECREF(item); + + if (p) + fBuffer.push_back(p); + else { + PyErr_Format(PyExc_TypeError, "could not convert item %d to string", + (int)i); + return false; + } + + } else + return false; + } + + para.fValue.fVoidp = (void*)fBuffer.data(); + para.fTypeCode = 'p'; + return true; + } + + return SCharArrayConverter::SetArg(pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringArrayConverter::FromMemory(void* address) { + if (fIsFixed) + return CreateLowLevelView(*(char**)address, fShape); + else if (fShape[0] == UNKNOWN_SIZE) + return CreateLowLevelViewString((const char**)address, fShape); + return CreateLowLevelViewString(*(const char***)address, fShape); +} + +bool cpyrt::CStringArrayConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // Unlike the other array converters, this one also accepts a Python string. + Py_ssize_t len; + if (const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len)) { + return ToArrayFromBuffer(value, address, ctxt, cstr, len, fShape, + fIsFixed); + } + return SCharArrayConverter::ToMemory(value, address, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::NonConstCStringArrayConverter::FromMemory(void* address) { + if (fIsFixed) + return CreateLowLevelView(*(char**)address, fShape); + else if (fShape[0] == UNKNOWN_SIZE) + return CreateLowLevelViewString((char**)address, fShape); + return CreateLowLevelViewString(*(char***)address, fShape); +} + +//- converters for special cases --------------------------------------------- +bool cpyrt::NullptrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // Only allow C++11 style nullptr to pass + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +template +static inline bool cpyrt_PyUnicodeAsBytes2Buffer(PyObject* pyobject, + T& buffer) { + PyObject* pybytes = nullptr; + if (PyBytes_Check(pyobject)) { + Py_INCREF(pyobject); + pybytes = pyobject; + } else if (PyUnicode_Check(pyobject)) { + pybytes = PyUnicode_AsUTF8String(pyobject); + } + + if (pybytes) { + Py_ssize_t len; + const char* cstr = nullptr; + PyBytes_AsStringAndSize(pybytes, (char**)&cstr, &len); + if (cstr) + buffer = T{cstr, (typename T::size_type)len}; + Py_DECREF(pybytes); + return (bool)cstr; + } + + return false; +} + +#define CPPJIT_IMPL_STRING_AS_PRIMITIVE_CONVERTER(name, type, F1, F2) \ + cpyrt::name##Converter::name##Converter(bool keepControl) \ + : InstanceConverter(interop::GetFullScope(#type), keepControl) {} \ + \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (cpyrt_PyUnicodeAsBytes2Buffer(pyobject, fBuffer)) { \ + para.fValue.fVoidp = &fBuffer; \ + para.fTypeCode = 'V'; \ + return true; \ + } \ + \ + PyErr_Clear(); \ + if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { \ + bool result = InstanceConverter::SetArg(pyobject, para, ctxt); \ + para.fTypeCode = 'V'; \ + return result; \ + } \ + \ + return false; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + if (address) \ + return InstanceConverter::FromMemory(address); \ + auto* empty = new type(); \ + return BindCppObjectNoCast(interop::TCppObject_t((void*)empty), fClass, \ + CPPInstance::kIsOwner); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* ctxt) { \ + if (cpyrt_PyUnicodeAsBytes2Buffer(value, *((type*)address))) \ + return true; \ + return InstanceConverter::ToMemory(value, address, ctxt); \ + } + +CPPJIT_IMPL_STRING_AS_PRIMITIVE_CONVERTER(STLString, std::string, c_str, size) + +cpyrt::STLWStringConverter::STLWStringConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::wstring"), keepControl) {} + +bool cpyrt::STLWStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + if (PyUnicode_Check(pyobject)) { + Py_ssize_t len = cpyrt_PyUnicode_GET_SIZE(pyobject); + fBuffer.resize(len); + cpyrt_PyUnicode_AsWideChar(pyobject, &fBuffer[0], len); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { + bool result = InstancePtrConverter::SetArg(pyobject, para, ctxt); + para.fTypeCode = 'V'; + return result; + } + + return false; +} + +PyObject* cpyrt::STLWStringConverter::FromMemory(void* address) { + if (address) + return PyUnicode_FromWideChar(((std::wstring*)address)->c_str(), + ((std::wstring*)address)->size()); + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); +} + +bool cpyrt::STLWStringConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + if (PyUnicode_Check(value)) { + Py_ssize_t len = cpyrt_PyUnicode_GET_SIZE(value); + wchar_t* buf = new wchar_t[len + 1]; + cpyrt_PyUnicode_AsWideChar(value, buf, len); + *((std::wstring*)address) = std::wstring(buf, len); + delete[] buf; + return true; + } + return InstanceConverter::ToMemory(value, address, ctxt); +} + +cpyrt::STLStringViewConverter::STLStringViewConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::string_view"), + keepControl) {} + +bool cpyrt::STLStringViewConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // normal instance convertion (eg. string_view object passed) + if (!PyInt_Check(pyobject) && !PyLong_Check(pyobject)) { + CallContextRAII noimp(ctxt); + if (InstanceConverter::SetArg(pyobject, para, ctxt)) { + para.fTypeCode = 'V'; + return true; + } else + PyErr_Clear(); + } + + // passing of a Python string; buffering done Python-side b/c str is immutable + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(pyobject, &len); + if (cstr) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + fBuffer = std::string_view(cstr, (std::string_view::size_type)len); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + if (!CPPInstance_Check(pyobject)) + return false; + + // special case of a C++ std::string object; life-time management is left to + // the caller to ensure any external changes propagate correctly + if (CPPInstance_Check(pyobject)) { + static interop::TCppScope_t sStringID = + interop::GetUnderlyingScope(interop::GetFullScope("std::string")); + CPPInstance* pyobj = (CPPInstance*)pyobject; + if (pyobj->ObjectIsA() == sStringID) { + void* ptr = pyobj->GetObject(); + if (!ptr) + return false; // leaves prior conversion error for report + + PyErr_Clear(); + + fBuffer = *((std::string*)ptr); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + } + + return false; +} + +PyObject* cpyrt::STLStringViewConverter::FromMemory(void* address) { + if (address) + return InstanceConverter::FromMemory(address); + auto* empty = new std::string_view(); + return BindCppObjectNoCast(empty, fClass, CPPInstance::kIsOwner); +} + +bool cpyrt::STLStringViewConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // common case of simple object assignment + if (InstanceConverter::ToMemory(value, address, ctxt)) + return true; + + // assignment of a Python string; buffering done Python-side b/c str is + // immutable + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len); + if (cstr) { + SetLifeLine(ctxt, value, (intptr_t)this); + *reinterpret_cast(address) = + std::string_view(cstr, (std::string_view::size_type)len); + return true; + } + + return false; +} + +bool cpyrt::STLStringMoveConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ std::string&&, set arg for call + int moveit_reason = 3; // move on temporary fBuffer + if (CPPInstance_Check(pyobject)) { + CPPInstance* pyobj = (CPPInstance*)pyobject; + if (pyobj->fFlags & CPPInstance::kIsRValue) { + pyobj->fFlags &= ~CPPInstance::kIsRValue; + moveit_reason = 2; + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + moveit_reason = 1; + } else + moveit_reason = 0; + } + + if (moveit_reason) { + bool result = this->STLStringConverter::SetArg(pyobject, para, ctxt); + if (!result && moveit_reason == 2) // restore the movability flag? + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + return result; + } + + PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); + return false; // not a temporary or movable object +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // convert to C++ instance*, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, ISCONST ? fClass : interop::TCppScope_t{}); + if (!pyobj) { + if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = 'p'; // allow special cases such as nullptr + return true; + } + + // not a cppjit object (TODO: handle SWIG etc.) + return false; + } + + // smart pointers should only extract the pointer if this is NOT an implicit + // conversion to another smart pointer + if (pyobj->IsSmart() && IsConstructor(ctxt->fFlags) && + interop::IsSmartPtr(ctxt->fCurScope)) + return false; + + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (oisa && (oisa == fClass || interop::IsSubclass(oisa, fClass))) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership()) + pyobj->CppOwns(); + + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (oisa != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + oisa, fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +template +PyObject* cpyrt::InstancePtrConverter::FromMemory(void* address) { + // construct python object from C++ instance read at
+ if (ISCONST) + return BindCppObject(*(void**)address, fClass); // by pointer value + + // a writable pointer variable's proxy tracks the pointer itself, so it can + // only down-cast where the actual class shares the address; abstract + // declared types stay put as Python may replace the pointee (see test21) + void* pobj = *(void**)address; + interop::TCppScope_t klass = fClass; + if (pobj && !interop::IsAbstract(fClass)) { + interop::TCppScope_t actual = interop::GetActualClass(fClass, pobj); + if (actual && actual != fClass && + interop::GetBaseOffset(actual, fClass, pobj, -1 /* down-cast */) == 0) + klass = actual; + } + return BindCppObject(address, klass, CPPInstance::kIsReference); // modifiable +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrConverter::ToMemory(PyObject* value, + void* address, + PyObject* /* ctxt */) { + // convert to C++ instance, write it at
+ CPPInstance* pyobj = + GetCppInstance(value, ISCONST ? fClass : interop::TCppScope_t{}); + if (!pyobj) { + void* ptr = nullptr; + if (GetAddressSpecialCase(value, ptr)) { + *(void**)address = ptr; // allow special cases such as nullptr + return true; + } + + // not a cppjit object (TODO: handle SWIG etc.) + return false; + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership()) + ((CPPInstance*)value)->CppOwns(); + + *(void**)address = pyobj->GetObject(); + return true; + } + + return false; +} + +// TODO: CONSOLIDATE Instance, InstanceRef, InstancePtr ... + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject, fClass); + if (pyobj) { + auto oisa = pyobj->ObjectIsA(); + if (oisa && ((oisa == (interop::IsTypedefed(fClass) + ? interop::GetUnderlyingScope(fClass) + : fClass)) || + interop::IsSubclass(oisa, fClass))) { + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (!para.fValue.fVoidp) + return false; + + if (oisa != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + pyobj->ObjectIsA(), fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + para.fTypeCode = 'V'; + return true; + } + } + + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceConverter::FromMemory(void* address) { + // This should not need a cast (ie. BindCppObjectNoCast), but performing the + // cast here means callbacks receive down-casted object when passed by-ptr, + // which is needed for object identity. The latter case is assumed to be more + // common than conversion of (global) objects. + return BindCppObject((interop::TCppObject_t)address, fClass); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // assign value to C++ instance living at
through assignment + // operator + PyObject* pyobj = BindCppObjectNoCast(address, fClass); + PyObject* result = + PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); + Py_DECREF(pyobj); + + if (result) { + Py_DECREF(result); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance&, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, fIsConst ? fClass : interop::TCppScope_t{}); + if (pyobj) { + + // reject moves + if (pyobj->fFlags & CPPInstance::kIsRValue) + return false; + + // smart pointers can end up here in case of a move, so preferentially match + // the smart type directly + bool argset = false; + interop::TCppScope_t cls; + if (pyobj->IsSmart()) { + cls = pyobj->ObjectIsA(false); + if (cls && interop::IsSubclass(cls, fClass)) { + para.fValue.fVoidp = pyobj->GetObjectRaw(); + argset = true; + } + } + + if (!argset) { + cls = pyobj->ObjectIsA(); + if (cls && interop::IsSubclass(cls, fClass)) { + para.fValue.fVoidp = pyobj->GetObject(); + argset = true; + } + } + + if (argset) { + // do not allow null pointers through references + if (!para.fValue.fVoidp) { + PyErr_SetString(PyExc_ReferenceError, + "attempt to access a null-pointer"); + return false; + } + + // calculate offset between formal and actual arguments + if (cls != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + cls, fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + para.fTypeCode = 'V'; + return true; + } + } + + if (!fIsConst) // no implicit conversion possible + return false; + + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceRefConverter::FromMemory(void* address) { + return BindCppObjectNoCast((interop::TCppObject_t)address, fClass, + CPPInstance::kIsReference); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceMoveConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance&&, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, fClass, true /* accept_rvalue */); + if (!pyobj || (pyobj->fFlags & CPPInstance::kIsLValue)) { + // implicit conversion is fine as the temporary by definition is moveable + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); + } + + // moving is same as by-ref, but have to check that move is allowed + int moveit_reason = 0; + if (pyobj->fFlags & CPPInstance::kIsRValue) { + pyobj->fFlags &= ~CPPInstance::kIsRValue; + moveit_reason = 2; + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + moveit_reason = 1; + } + + if (moveit_reason) { + bool result = this->InstanceRefConverter::SetArg(pyobject, para, ctxt); + if (!result && moveit_reason == 2) // restore the movability flag? + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + return result; + } + + PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); + return false; // not a temporary or movable object +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrPtrConverter::SetArg( + PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) { + // convert to C++ instance**, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (!pyobj) { + if (!ISREFERENCE && + (pyobject == gNullPtrObject || pyobject == gDefaultObject)) { + // allow nullptr as a special case + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + return false; // not a cppjit object (TODO: handle SWIG etc.) + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership()) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + if (pyobj->fFlags & CPPInstance::kIsReference) // already a ptr to object? + para.fValue.fVoidp = pyobj->GetObjectRaw(); + else + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = ISREFERENCE ? 'V' : 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +template +PyObject* +cpyrt::InstancePtrPtrConverter::FromMemory(void* address) { + // construct python object from C++ instance* read at
+ return BindCppObject(*(void**)address, fClass, + CPPInstance::kIsReference | CPPInstance::kIsPtrPtr); +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrPtrConverter::ToMemory( + PyObject* value, void* address, PyObject* /* ctxt */) { + // convert to C++ instance*, write it at
+ CPPInstance* pyobj = GetCppInstance(value); + if (!pyobj) { + if (value == gNullPtrObject || value == gDefaultObject) { + // allow nullptr as a special case + *(void**)address = nullptr; + return true; + } + return false; // not a cppjit object (TODO: handle SWIG etc.) + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership()) + pyobj->CppOwns(); + + // register the value for potential recycling + MemoryRegulator::RegisterPyObject(pyobj, pyobj->GetObject()); + + // set pointer (may be null) and declare success + *(void**)address = pyobj->GetObject(); + return true; + } + + return false; +} + +namespace cppjit::cpyrt { +// Instantiate the templates +template class cpyrt::InstancePtrConverter; +template class cpyrt::InstancePtrConverter; +template class cpyrt::InstancePtrPtrConverter; +template class cpyrt::InstancePtrPtrConverter; +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* txt */) { + // convert to C++ instance**, set arg for call + while (PyTuple_Check(pyobject) && !TupleOfInstances_CheckExact(pyobject)) { + if (PyTuple_Size(pyobject) > 0) + pyobject = PyTuple_GetItem(pyobject, 0); + else + return false; + } + if (!TupleOfInstances_CheckExact(pyobject)) + return false; // no guarantee that the tuple is okay + + // treat the first instance of the tuple as the start of the array, and pass + // it by pointer (TODO: store and check sizes) + if (PyTuple_Size(pyobject) < 1) + return false; + + PyObject* first = PyTuple_GetItem(pyobject, 0); + if (!CPPInstance_Check(first)) + return false; // should not happen + + if (interop::IsSubclass(((CPPInstance*)first)->ObjectIsA(), fClass)) { + // no memory policies supported; set pointer (may be null) and declare + // success + para.fValue.fVoidp = ((CPPInstance*)first)->GetObject(); + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceArrayConverter::FromMemory(void* address) { + // construct python tuple of instances from C++ array read at
+ return BindCppObjectArray(*(char**)address, fClass, fShape); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceArrayConverter::ToMemory(PyObject* /* value */, + void* /* address */, + PyObject* /* ctxt */) { + // convert to C++ array of instances, write it at
+ + // TODO: need to have size both for the array and from the input + PyErr_SetString(PyExc_NotImplementedError, + "access to C-arrays of objects not yet implemented!"); + return false; +} + +//___________________________________________________________________________ +// Cling WORKAROUND -- classes for STL iterators are completely undefined in +// that they come in a bazillion different guises, so just do whatever +bool cpyrt::STLIteratorConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + if (!CPPInstance_Check(pyobject)) + return false; + + // just set the pointer value, no check + CPPInstance* pyobj = (CPPInstance*)pyobject; + para.fValue.fVoidp = pyobj->GetObject(); + para.fTypeCode = 'V'; + return true; +} +// -- END Cling WORKAROUND + +//---------------------------------------------------------------------------- +bool cpyrt::VoidPtrRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ void*&, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (pyobj) { + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +cpyrt::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims, + const std::string& failureMsg) + : fShape(dims), fFailureMsg(failureMsg) { + fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidPtrPtrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ void**, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (pyobj) { + // this is a C++ object, take and set its address + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = 'p'; + return true; + } else if (IsPyCArgObject(pyobject)) { + cpyrt_tagPyCArgObject* carg = (cpyrt_tagPyCArgObject*)pyobject; + if (carg->obj) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)carg->obj)->b_ptr; + para.fTypeCode = 'p'; + return true; + } + } + + // buffer objects are allowed under "user knows best" (this includes the + // buffer interface to ctypes.c_void_p, which results in a void**) + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidPtrPtrConverter::FromMemory(void* address) { + // read a void** from address; since this is unknown, uintptr_t is used (user + // can cast) + if (!address || *(ptrdiff_t*)address == 0) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + if (!fIsFixed) + return CreatePointerView((uintptr_t**)address, fShape); + return CreatePointerView(*(uintptr_t**)address, fShape); +} + +//---------------------------------------------------------------------------- +bool cpyrt::PyObjectConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // by definition: set and declare success + para.fValue.fVoidp = pyobject; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::PyObjectConverter::FromMemory(void* address) { + // construct python object from C++ PyObject* read at
+ PyObject* pyobject = *((PyObject**)address); + + if (!pyobject) { + Py_RETURN_NONE; + } + + Py_INCREF(pyobject); + return pyobject; +} + +bool cpyrt::PyObjectConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // no conversion needed, write at
+ Py_INCREF(value); + Py_XDECREF(*((PyObject**)address)); + *((PyObject**)address) = value; + return true; +} + +//- function pointer converter ----------------------------------------------- +static unsigned int sWrapperCounter = 0; +// cache mapping signature/return type to python callable and corresponding +// wrapper +typedef std::string RetSigKey_t; +static std::unordered_map> sWrapperFree; +static std::unordered_map> + sWrapperLookup; +static std::unordered_map> + sWrapperWeakRefs; +static std::unordered_map sWrapperReference; + +static void GetSignatureFromFnType(interop::TCppType_t fn_type, + std::string& ret, std::string& sig) { + std::vector types; + interop::GetFnTypeSig(fn_type, types); + assert(types.size() >= 1); + ret = interop::GetTypeAsString(types[0]); + sig = "("; + bool f = false; + for (size_t i = 1; i < types.size(); i++) { + if (f) + sig += ", "; + else + f = true; + sig += interop::GetTypeAsString(types[i]); + } + sig += ")"; +} + +static PyObject* WrapperCacheEraser(PyObject*, PyObject* pyref) { + auto ipos = sWrapperWeakRefs.find(pyref); + if (ipos != sWrapperWeakRefs.end()) { + auto key = ipos->second.second; + + // disable this callback and store on free list for possible re-use + void* wpraddress = ipos->second.first; + PyObject** oldref = sWrapperReference[wpraddress]; + const auto& lookup = sWrapperLookup.find(key); + if (lookup != sWrapperLookup.end()) + lookup->second.erase(*oldref); + *oldref = nullptr; // to detect deletions + sWrapperFree[ipos->second.second].push_back(wpraddress); + + // clean up and remove weak reference from admin + Py_DECREF(ipos->first); + sWrapperWeakRefs.erase(ipos); + } + + Py_RETURN_NONE; +} +static PyMethodDef gWrapperCacheEraserMethodDef = { + const_cast("internal_WrapperCacheEraser"), + (PyCFunction)WrapperCacheEraser, METH_O, nullptr}; + +static void* PyFunction_AsCPointer(PyObject* pyobject, + const interop::TCppType_t fn_type, + bool allowCppInstance) { + // Convert a bound C++ function pointer or callable python object to a C-style + // function pointer. The former is direct, the latter involves a JIT-ed + // wrapper. + static PyObject* sWrapperCacheEraser = + PyCFunction_New(&gWrapperCacheEraserMethodDef, nullptr); + + using namespace cppjit::cpyrt; + + if (CPPOverload_Check(pyobject)) { + CPPOverload* ol = (CPPOverload*)pyobject; + if (!ol->fMethodInfo || ol->fMethodInfo->fMethods.empty()) + return nullptr; + + // find the overload with matching signature + for (auto& m : ol->fMethodInfo->fMethods) { + if (m->IsSimilarFnType(fn_type)) { + void* fptr = (void*)m->GetFunctionAddress(); + if (fptr) + return fptr; + } + } + return nullptr; + } + + if (TemplateProxy_Check(pyobject)) { + // get the actual underlying template matching the signature + TemplateProxy* pytmpl = (TemplateProxy*)pyobject; + std::string fullname = pytmpl->fTI->fCppName; + if (pytmpl->fTemplateArgs) + fullname += cpyrt_PyText_AsString(pytmpl->fTemplateArgs); + interop::TCppScope_t scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + std::string ret{}, sig{}; + GetSignatureFromFnType(fn_type, ret, sig); + interop::TCppMethod_t cppmeth = interop::GetMethodTemplate( + scope, fullname, sig.substr(1, sig.size() - 2)); + if (cppmeth) { + void* fptr = (void*)interop::GetFunctionAddress(cppmeth, false); + if (fptr) + return fptr; + } + return nullptr; + } + + if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { + // ctypes function pointer + void* fptr = *(void**)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + return fptr; + } + + if (PyCallable_Check(pyobject) && + (allowCppInstance || !CPPInstance_Check(pyobject))) { + // generic python callable: create a C++ wrapper function + // Sometimes we don't want to take this branch if the object is a C++ + // instance, because C++ doesn't allow converting functor objects to + // function pointers, but only to std::function. + void* wpraddress = nullptr; + + std::string ret{}, sig{}; + GetSignatureFromFnType(fn_type, ret, sig); + sig = "(void)" == sig ? "()" : sig; + + // re-use existing wrapper if possible + auto key = ret + sig; + const auto& lookup = sWrapperLookup.find(key); + if (lookup != sWrapperLookup.end()) { + const auto& existing = lookup->second.find(pyobject); + if (existing != lookup->second.end() && + *sWrapperReference[existing->second] == pyobject) + wpraddress = existing->second; + } + + // check for a pre-existing, unused, wrapper if not found + if (!wpraddress) { + const auto& freewrap = sWrapperFree.find(key); + if (freewrap != sWrapperFree.end() && !freewrap->second.empty()) { + wpraddress = freewrap->second.back(); + freewrap->second.pop_back(); + *(sWrapperReference[wpraddress]) = pyobject; + sWrapperLookup[key][pyobject] = wpraddress; + PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); + if (wref) + sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); + else + PyErr_Clear(); // happens for builtins which don't need this + } + } + + // create wrapper if no re-use possible + if (!wpraddress) { + if (!Utility::IncludePython()) + return nullptr; + + // extract argument types + const std::vector& argtypes = + TypeManip::extract_arg_types(sig); + int nArgs = (int)argtypes.size(); + + // wrapper name + std::ostringstream wname; + wname << "fptr_wrap" << ++sWrapperCounter; + + // build wrapper function code + std::ostringstream code; + code << "namespace __cppjit_internal {\n " << ret << " " << wname.str() + << "("; + for (int i = 0; i < nArgs; ++i) { + code << argtypes[i] << " arg" << i; + if (i != nArgs - 1) + code << ", "; + } + code << ") {\n" + << " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n"; + + // start function body + Utility::ConstructCallbackPreamble(ret, argtypes, code); + + // create a referenceable pointer + PyObject** ref = new PyObject* {pyobject}; + + // function call itself and cleanup + code << " PyObject** ref = (PyObject**)" << (intptr_t)ref + << ";\n" + " PyObject* pyresult = nullptr;\n" + " if (*ref) pyresult = PyObject_CallFunctionObjArgs(*ref"; + for (int i = 0; i < nArgs; ++i) + code << ", pyargs[" << i << "]"; + code << ", NULL);\n" + " else PyErr_SetString(PyExc_TypeError, \"callable was " + "deleted\");\n"; + + // close + Utility::ConstructCallbackReturn(ret, nArgs, code); + + // end of namespace + code << "}"; + + // finally, compile the code + if (!interop::Compile(code.str())) + return nullptr; + + // TODO: is there no easier way? + static interop::TCppScope_t scope = + interop::GetScope("__cppjit_internal"); + const auto& methods = interop::GetMethodsFromName(scope, wname.str()); + wpraddress = interop::GetFunctionAddress(methods[0], false); + sWrapperReference[wpraddress] = ref; + + // cache the new wrapper + sWrapperLookup[key][pyobject] = wpraddress; + PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); + if (wref) + sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); + else + PyErr_Clear(); // happens for builtins which don't need this + } + + // now pass the pointer to the wrapper function (may be null) + return wpraddress; + } + + return nullptr; +} + +bool cpyrt::FunctionPointerConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // special case: allow nullptr singleton: + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + + // normal case, get a function pointer + void* fptr = PyFunction_AsCPointer(pyobject, fFnType, fAllowCppInstance); + if (fptr) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + para.fValue.fVoidp = fptr; + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +PyObject* cpyrt::FunctionPointerConverter::FromMemory(void* address) { + // A function pointer in clang is represented by a Type, not a FunctionDecl + // and it's not possible to get the latter from the former: the backend will + // need to support both. Since that is far in the future, we'll use a + // std::function instead. + if (address) { + std::string ret{}, sig{}; + GetSignatureFromFnType(fFnType, ret, sig); + return Utility::FuncPtr2StdFunction(ret, sig, *(void**)address); + } + PyErr_SetString(PyExc_TypeError, "can not convert null function pointer"); + return nullptr; +} + +bool cpyrt::FunctionPointerConverter::ToMemory(PyObject* pyobject, + void* address, PyObject* ctxt) { + // special case: allow nullptr singleton: + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + *((void**)address) = nullptr; + return true; + } + + // normal case, get a function pointer + void* fptr = PyFunction_AsCPointer(pyobject, fFnType, fAllowCppInstance); + if (fptr) { + SetLifeLine(ctxt, pyobject, (intptr_t)address); + *((void**)address) = fptr; + return true; + } + + return false; +} + +//- std::function converter -------------------------------------------------- +bool cpyrt::StdFunctionConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // prefer normal "object" conversion + CallContextRAII noimp(ctxt); + if (fConverter->SetArg(pyobject, para, ctxt)) + return true; + + PyErr_Clear(); + + // else create a wrapper function + if (this->FunctionPointerConverter::SetArg(pyobject, para, ctxt)) { + // retrieve the wrapper pointer and capture it in a temporary std::function, + // then try normal conversion a second time + PyObject* func = + this->FunctionPointerConverter::FromMemory(¶.fValue.fVoidp); + if (func) { + SetLifeLine(ctxt->fPyContext, func, (intptr_t)this); + bool result = fConverter->SetArg(func, para, ctxt); + if (result) + ctxt->AddTemporary(func); + else + Py_DECREF(func); + return result; + } + } + + return false; +} + +PyObject* cpyrt::StdFunctionConverter::FromMemory(void* address) { + return fConverter->FromMemory(address); +} + +bool cpyrt::StdFunctionConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // if the value is not an std::function<> but a generic Python callable, the + // conversion is done through the assignment, which may involve a temporary + if (address) + SetLifeLine(ctxt, value, (intptr_t)address); + return fConverter->ToMemory(value, address, ctxt); +} + +//- smart pointer converters ------------------------------------------------- +bool cpyrt::SmartPtrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + char typeCode = fIsRef ? 'p' : 'V'; + + if (!CPPInstance_Check(pyobject)) { + // TODO: not sure how this is correct for pass-by-ref nor does it help with + // implicit conversions for pass-by-value + if (fIsRef && GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = typeCode; // allow special cases such as nullptr + return true; + } + + return false; + } + + CPPInstance* pyobj = (CPPInstance*)pyobject; + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + + // for the case where we have a 'hidden' smart pointer: + if (interop::TCppScope_t tsmart = pyobj->GetSmartIsA()) { + if (interop::IsSubclass(tsmart, fSmartPtrType)) { + // depending on memory policy, some objects need releasing when passed + // into functions + if (!fKeepControl && !UseStrictOwnership()) + ((CPPInstance*)pyobject)->CppOwns(); + + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetSmartObject(); + if (tsmart != fSmartPtrType) { + para.fValue.fIntPtr += interop::GetBaseOffset( + tsmart, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = typeCode; + return true; + } + } + + // for the case where we have an 'exposed' smart pointer: + if (!pyobj->IsSmart() && interop::IsSubclass(oisa, fSmartPtrType)) { + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (oisa != fSmartPtrType) { + para.fValue.fIntPtr += interop::GetBaseOffset( + oisa, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = typeCode; + return true; + } + + // for the case where we have an ordinary object to convert + if (AllowImplicitSmartPtrConversion(ctxt) && !pyobj->IsSmart() && + interop::IsSubclass(oisa, fUnderlyingType)) { + // create the relevant smart pointer and make the pyobject "smart" + CPPInstance* pysmart = (CPPInstance*)ConvertImplicit( + fSmartPtrType, pyobject, para, ctxt, false); + if (!CPPInstance_Check(pysmart)) { + Py_XDECREF(pysmart); + return false; + } + + // copy internals from the fresh smart object to the original, making it + // smart + pyobj->GetObjectRaw() = pysmart->GetSmartObject(); + pyobj->SetSmart( + CreateScopeProxy(fSmartPtrType)); //(PyObject*)Py_TYPE(pysmart)); + pyobj->PythonOwns(); + pysmart->CppOwns(); + Py_DECREF(pysmart); + + return true; + } + + // final option, try mapping pointer types held (TODO: do not allow for + // non-const ref) Match on the declared underlying type, not the (possibly + // downcast) dereferenced object: a unique_ptr holding a Derived is + // still a unique_ptr and must not pass where a unique_ptr is + // expected. + if (pyobj->IsSmart() && + interop::IsSubclass(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { + para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +PyObject* cpyrt::SmartPtrConverter::FromMemory(void* address) { + if (!address || !fSmartPtrType) + return nullptr; + + return BindCppObjectNoCast(address, fSmartPtrType); +} + +bool cpyrt::SmartPtrConverter::ToMemory(PyObject* value, void* address, + PyObject*) { + // assign value to C++ instance living at
through assignment + // operator (this is similar to InstanceConverter::ToMemory, but prevents + // wrapping the smart ptr) + PyObject* pyobj = + BindCppObjectNoCast(address, fSmartPtrType, CPPInstance::kNoWrapConv); + PyObject* result = + PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); + Py_DECREF(pyobj); + + if (result) { + Py_DECREF(result); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +namespace { + +// clang libcpp and gcc use the same structure (ptr, size) +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) +struct faux_initlist { + typedef size_t size_type; + typedef void* iterator; + iterator _M_array; + size_type _M_len; +}; +#elif defined(_MSC_VER) +struct faux_initlist { + typedef char* iterator; + iterator _M_array; // ie. _First; + iterator _Last; +}; +#else +#define NO_KNOWN_INITIALIZER_LIST 1 +#endif + +} // unnamed namespace + +cpyrt::InitializerListConverter::InitializerListConverter( + interop::TCppScope_t klass, std::string const& value_type) + + : InstanceConverter{klass}, fValueTypeName{value_type}, + fValueType{interop::GetScope(value_type)}, + fValueSize{interop::SizeOfType(interop::GetType(value_type, true))} {} + +cpyrt::InitializerListConverter::~InitializerListConverter() { + for (Converter* converter : fConverters) { + if (converter && converter->HasState()) + delete converter; + } + if (fBuffer) + Clear(); +} + +void cpyrt::InitializerListConverter::Clear() { + if (fValueType) { + faux_initlist* fake = (faux_initlist*)fBuffer; +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { +#elif defined(_MSC_VER) + for (size_t i = 0; (fake->_M_array + i * fValueSize) != fake->_Last; ++i) { +#endif + void* memloc = (char*)fake->_M_array + i * fValueSize; + interop::CallDestructor(fValueType, (interop::TCppObject_t)memloc); + } + } + + free(fBuffer); + fBuffer = nullptr; +} + +bool cpyrt::InitializerListConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { +#ifdef NO_KNOWN_INITIALIZER_LIST + return false; +#else + if (fBuffer) + Clear(); + + // convert the given argument to an initializer list temporary; this is purely + // meant to be a syntactic thing, so only _python_ sequences are allowed; + // bound C++ proxies (likely explicitly created std::initializer_list, go + // through an instance converter + if (!PySequence_Check(pyobject) || cpyrt_PyText_Check(pyobject) || + PyBytes_Check(pyobject)) + return false; + + if (CPPInstance_Check(pyobject)) + return this->InstanceConverter::SetArg(pyobject, para, ctxt); + + void* buf = nullptr; + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', (int)fValueSize, buf, true); + faux_initlist* fake = nullptr; + size_t entries = 0; + if (buf && buflen) { + // dealing with an array here, pass on whole-sale + fake = (faux_initlist*)malloc(sizeof(faux_initlist)); + fBuffer = (void*)fake; + fake->_M_array = (faux_initlist::iterator)buf; +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)buflen; +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + buflen * fValueSize; +#endif + } else if (fValueSize) { + // Remove any errors set by GetBuffer(); note that if the argument was an + // array that failed to extract because of a type mismatch, the following + // will perform a (rather inefficient) copy. No warning is issued b/c e.g. + // numpy doesn't do so either. + PyErr_Clear(); + + // Can only construct empty lists, so use a fake initializer list. For that + // we need to construct default objects. Fail early if that cannot work. + if (fValueType && !interop::IsDefaultConstructable(fValueType)) { + PyErr_SetString( + PyExc_TypeError, + "default constructor needed for initializer list of objects"); + return false; + } + + size_t len = (size_t)PySequence_Size(pyobject); + fake = (faux_initlist*)malloc(sizeof(faux_initlist) + fValueSize * len); + fBuffer = (void*)fake; + fake->_M_array = + (faux_initlist::iterator)((char*)fake + sizeof(faux_initlist)); +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)len; + for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + len * fValueSize; + for (size_t i = 0; (fake->_M_array + i * fValueSize) != fake->_Last; ++i) { +#endif + PyObject* item = PySequence_GetItem(pyobject, i); + bool convert_ok = false; + if (item) { + if (i >= fConverters.size()) + fConverters.emplace_back(CreateConverter(fValueTypeName)); + Converter* converter = fConverters[i]; + if (!converter) { + if (CPPInstance_Check(item)) { + // by convention, use byte copy + memcpy((char*)fake->_M_array + i * fValueSize, + ((CPPInstance*)item)->GetObject(), fValueSize); + convert_ok = true; + } + } else { + void* memloc = (char*)fake->_M_array + i * fValueSize; + if (fValueType) { + // we need to construct a default object for the constructor to + // assign into; this is clunky, but the use of a copy constructor + // isn't much better as the Python object need not be a C++ object + memloc = (void*)interop::Construct(fValueType, memloc).data; + // We checked above that we are able to construct default objects of + // fValueType. + assert(memloc && ("failed to default construct object for type " + + fValueTypeName) + .c_str()); + entries += 1; + } + if (memloc) + convert_ok = converter->ToMemory(item, memloc); + } + + Py_DECREF(item); + } else + PyErr_Format(PyExc_TypeError, "failed to get item %d from sequence", + (int)i); + + if (!convert_ok) { +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)entries; +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + entries * fValueSize; +#endif + Clear(); + return false; + } + } + } + + if (!fake) // no buffer and value size indeterminate + return false; + + para.fValue.fVoidp = (void*)fake; + para.fTypeCode = 'V'; // means ptr that backend has to free after call + return true; +#endif +} + +namespace cppjit::cpyrt { + +// raising converter to take out overloads +class NotImplementedConverter : public Converter { +public: + NotImplementedConverter(PyObject* errorType, std::string const& message) + : fErrorType{errorType}, fMessage{message} {} + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + +private: + PyObject* fErrorType; + std::string fMessage; +}; + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::NotImplementedConverter::SetArg(PyObject*, Parameter&, + CallContext*) { + // raise a NotImplemented exception to take a method out of overload + // resolution + PyErr_SetString(fErrorType, fMessage.c_str()); + return false; +} + +//- helper to refactor some code from CreateConverter ------------------------ +static inline cppjit::cpyrt::Converter* +selectInstanceCnv(interop::TCppScope_t klass, const std::string& cpd, + cpyrt::cdims_t dims, bool isConst, bool control) { + using namespace cppjit::cpyrt; + Converter* result = nullptr; + + if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrConverter(klass, control); + else if (cpd == "*&") + result = new InstancePtrPtrConverter(klass, control); + else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + if (isConst) + result = new InstancePtrConverter(klass, control); + else + result = new InstancePtrConverter(klass, control); + } else if (cpd == "&") + result = new InstanceRefConverter(klass, isConst); + else if (cpd == "&&") + result = new InstanceMoveConverter(klass); + else if (cpd == "[]" || dims) + result = new InstanceArrayConverter(klass, dims, false); + else if (cpd == "") // by value + result = new InstanceConverter(klass, true); + + return result; +} + +//- factories ---------------------------------------------------------------- +CPYRT_EXPORT +cppjit::cpyrt::Converter* +cppjit::cpyrt::CreateConverter(const std::string& fullType, cdims_t dims) { + // The matching of the fulltype to a converter factory goes through up to five + // levels: + // 1) full, exact match + // 2) match of decorated, unqualified type + // 3) accept const ref as by value + // 4) accept ref as pointer + // 5) generalized cases (covers basically all C++ classes) + // + // If all fails, void is used, which will generate a run-time warning when + // used. + + // an exactly matching converter is best + ConvFactories_t::iterator h = gConvFactories.find(fullType); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + + // resolve typedefs etc. + const std::string& resolvedType = interop::ResolveName(fullType); + + // a full, qualified matching converter is preferred + if (resolvedType != fullType) { + h = gConvFactories.find(resolvedType); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedType); + std::string realType = TypeManip::clean_type(resolvedType, false, true); + + // accept unqualified type (as python does not know about qualifiers) + h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + + // mutable pointer references (T*&) are incompatible with Python's object + // model; a string that resolves to no type keeps the ban + if (!isConst && cpd == "*&") { + interop::TCppType_t refType = + interop::GetType(resolvedType, /* enable_slow_lookup */ true); + if (!refType || interop::IsMutablePtrRefType(refType)) { + return new NotImplementedConverter{ + PyExc_TypeError, + "argument type '" + resolvedType + + "' is not supported: non-const references to pointers (T*&) " + "allow a function to replace the pointer itself. Python cannot " + "represent this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + } + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + h = gConvFactories.find(realType + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd.compare(0, 3, "*[]") == 0) { + // special case, array of pointers + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) { + // upstream treats the pointer type as the array element type, but that + // pointer is treated as a low-level view as well, unless it's a + // void*/char* so adjust the dims + if (realType != "void" && realType != "char") { + dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim() + 1; + dims_t newdims = dims_t(newdim); + // TODO: sometimes the array size is known and can thus be verified; + // however, currently the meta layer does not provide this information + newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array + newdims[1] = UNKNOWN_SIZE; // the pointer + if (2 < newdim) { + for (int i = 2; i < (newdim - 1); ++i) + newdims[i] = dims[i - 1]; + } + + return (h->second)(newdims); + } + return (h->second)(dims); + } + + } else if (!cpd.empty() && (std::string::size_type)std::count( + cpd.begin(), cpd.end(), '*') == cpd.size()) { + // simple array; set or resize as necessary + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) + return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); + + } else if (2 <= cpd.size() && + (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == + cpd.size() / 2) { + // fixed array, dims will have size if available + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- special case: initializer list + if (realType.compare(0, 21, "std::initializer_list") == 0) { + // get the type of the list and create a converter (TODO: get hold of + // value_type?) + auto pos = realType.find('<'); + std::string value_type = + realType.substr(pos + 1, realType.size() - pos - 2); + return new InitializerListConverter(interop::GetScope(realType), + value_type); + } + + //-- still nothing? use a generalized converter + bool control = cpd == "&" || isConst; + + //-- special case: std::function + auto pos = resolvedType.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */) { + + // get actual converter for normal passing + Converter* cnv = selectInstanceCnv(interop::GetScope(realType), cpd, dims, + isConst, control); + + if (cnv) { + // get the type of the underlying (TODO: use target_type?) + auto pos1 = resolvedType.find("(", pos + 14); + auto pos2 = resolvedType.rfind(")"); + if (pos1 != std::string::npos && pos2 != std::string::npos) { + auto sz1 = pos1 - pos - 14; + if (resolvedType[pos + 14 + sz1 - 1] == ' ') + sz1 -= 1; + + return new StdFunctionConverter( + cnv, interop::GetType(fullType), resolvedType.substr(pos + 14, sz1), + resolvedType.substr(pos1, pos2 - pos1 + 1)); + } else if (cnv->HasState()) + delete cnv; + } + } + +#if __cplusplus >= 202002L + //-- special case: std::span + pos = resolvedType.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */) { + + // std::span::value_type == std::remove_cv_t: look it up + interop::TCppScope_t span_scope = interop::GetScope(realType); + interop::TCppType_t vtype = interop::ResolveType( + interop::GetTypeFromScope(interop::GetNamed("value_type", span_scope))); + if (span_scope && vtype) { + std::string value_type = interop::GetTypeAsString(vtype); + return new StdSpanConverter{value_type, span_scope}; + } + } +#endif + + // converters for known C++ classes and default (void*) + Converter* result = nullptr; + if (interop::TCppScope_t klass = interop::GetFullScope(realType)) { + interop::TCppScope_t raw; + if (interop::GetSmartPtrInfo(realType, &raw, nullptr)) { + if (cpd == "") { + result = new SmartPtrConverter(klass, raw, control); + } else if (cpd == "&") { + result = new SmartPtrConverter(klass, raw); + } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + result = new SmartPtrConverter(klass, raw, control, true); + } + } + + if (!result) { + // Cling WORKAROUND -- special case for STL iterators + if (Utility::IsSTLIterator(realType)) { + static STLIteratorConverter c; + result = &c; + } else + // -- Cling WORKAROUND + result = selectInstanceCnv(klass, cpd, dims, isConst, control); + } + } else { + interop::TCppType_t typ = interop::GetType(fullType); + if (interop::IsFunctionType(typ)) { + // this is a function pointer or reference + result = new FunctionPointerConverter(typ); + } + } + const std::string failure_msg("Failed to convert type: " + fullType + + "; resolved: " + resolvedType + + "; real: " + realType + "; cpd: " + cpd); + + if (!result && cpd == "&&") { + // for builtin, can use const-ref for r-ref + h = gConvFactories.find("const " + realType + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + // else, unhandled moves + result = new NotImplementedConverter{PyExc_NotImplementedError, + "this method cannot (yet) be called"}; + } + + if (!result && h != gConvFactories.end()) + // converter factory available, use it to create converter + result = (h->second)(dims); + else if (!result) { + // default to something reasonable, assuming "user knows best" + if (cpd.size() == 2 && cpd != "&&") // "**", "*[]", "*&" + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); + else if (!cpd.empty()) + result = new VoidArrayConverter(/* keepControl= */ true, + failure_msg); // "user knows best" + else + // fails on use + result = new NotImplementedConverter{ + PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + + return result; +} + +CPYRT_EXPORT +cppjit::cpyrt::Converter* +cppjit::cpyrt::CreateConverter(interop::TCppType_t type, cdims_t dims) { + // The matching of the fulltype to a converter factory goes through up to five + // levels: + // 1) full, exact match + // 2) match of decorated, unqualified type + // 3) accept const ref as by value + // 4) accept ref as pointer + // 5) generalized cases (covers basically all C++ classes) + // + // If all fails, void is used, which will generate a run-time warning when + // used. + + // an exactly matching converter is best + std::string fullType = interop::GetTypeAsString(type); + ConvFactories_t::iterator h = gConvFactories.find(fullType); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + + // resolve typedefs etc. + interop::TCppType_t resolvedType = interop::ResolveType(type); + const std::string& resolvedTypeStr = interop::GetTypeAsString(resolvedType); + + // a by-value enum parameter exact-matches instances of the same enum; the + // resolved type is its underlying integer, which carries the conversion + if (interop::IsEnumType(type) && fullType != "std::byte") { + h = gConvFactories.find(resolvedTypeStr); + if (h != gConvFactories.end()) + return new EnumConverter( + (h->second)(dims), + interop::GetScopedFinalName(interop::GetScopeFromType(type))); + } + + // a full, qualified matching converter is preferred + if (resolvedTypeStr != fullType) { + h = gConvFactories.find(resolvedTypeStr); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + interop::TCppType_t realType = + interop::ResolveType(interop::GetRealType(type)); + std::string realTypeStr = interop::GetTypeAsString(realType); + std::string realUnresolvedTypeStr = + TypeManip::clean_type(fullType, false, true); + + // accept unqualified type (as python does not know about qualifiers) + h = gConvFactories.find((isConst ? "const " : "") + realTypeStr + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + + // mutable pointer references (T*&) are incompatible with Python's object + // model + if (!isConst && cpd == "*&" && interop::IsMutablePtrRefType(resolvedType)) { + return new NotImplementedConverter{ + PyExc_TypeError, + "argument type '" + resolvedTypeStr + + "' is not supported: non-const references to pointers (T*&) allow a" + " function to replace the pointer itself. Python cannot represent " + "this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + h = gConvFactories.find(realTypeStr + cpd); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd.compare(0, 3, "*[]") == 0) { + // special case, array of pointers + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) { + // upstream treats the pointer type as the array element type, but that + // pointer is treated as a low-level view as well, unless it's a + // void*/char* so adjust the dims + if (realTypeStr != "void" && realTypeStr != "char") { + dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim() + 1; + dims_t newdims = dims_t(newdim); + // TODO: sometimes the array size is known and can thus be verified; + // however, currently the meta layer does not provide this information + newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array + newdims[1] = UNKNOWN_SIZE; // the pointer + if (2 < newdim) { + for (int i = 2; i < (newdim - 1); ++i) + newdims[i] = dims[i - 1]; + } + + return (h->second)(newdims); + } + return (h->second)(dims); + } + + } else if (!cpd.empty() && (std::string::size_type)std::count( + cpd.begin(), cpd.end(), '*') == cpd.size()) { + // simple array; set or resize as necessary + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); + + } else if (2 <= cpd.size() && + (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == + cpd.size() / 2) { + // fixed array, dims will have size if available + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- special case: initializer list + if (realTypeStr.compare(0, 21, "std::initializer_list") == 0) { + // get the type of the list and create a converter (TODO: get hold of + // value_type?) + auto pos = realTypeStr.find('<'); + std::string value_type = + realTypeStr.substr(pos + 1, realTypeStr.size() - pos - 2); + Converter* cnv = nullptr; + bool use_byte_cnv = false; + if (cpd == "" && interop::GetScope(value_type)) { + // initializer list of object values does not work as the target is raw + // memory; simply use byte copies + + // by convention, leave cnv as nullptr + use_byte_cnv = true; + } else + cnv = CreateConverter(value_type); + if (cnv || use_byte_cnv) + return new InitializerListConverter(interop::GetScopeFromType(realType), + value_type); + } + + //-- still nothing? use a generalized converter + bool control = cpd == "&" || isConst; + + //-- special case: std::function + auto pos = resolvedTypeStr.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */) { + + // get actual converter for normal passing + Converter* cnv = selectInstanceCnv(interop::GetScopeFromType(realType), cpd, + dims, isConst, control); + + if (cnv) { + // get the type of the underlying (TODO: use target_type?) + auto pos1 = resolvedTypeStr.find("(", pos + 14); + auto pos2 = resolvedTypeStr.rfind(")"); + if (pos1 != std::string::npos && pos2 != std::string::npos) { + auto sz1 = pos1 - pos - 14; + if (resolvedTypeStr[pos + 14 + sz1 - 1] == ' ') + sz1 -= 1; + + const std::string& argsStr = + resolvedTypeStr.substr(pos1, pos2 - pos1 + 1).c_str(); + return new StdFunctionConverter(cnv, resolvedType, + resolvedTypeStr.substr(pos + 14, sz1), + argsStr == "(void)" ? "()" : argsStr); + } else if (cnv->HasState()) + delete cnv; + } + } + +#if __cplusplus >= 202002L + //-- special case: std::span + pos = resolvedTypeStr.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */) { + + // std::span::value_type == std::remove_cv_t: look it up + interop::TCppScope_t span_scope = interop::GetScopeFromType(realType); + interop::TCppType_t vtype = interop::ResolveType( + interop::GetTypeFromScope(interop::GetNamed("value_type", span_scope))); + if (span_scope && vtype) { + std::string value_type = interop::GetTypeAsString(vtype); + return new StdSpanConverter{value_type, span_scope}; + } + } +#endif + + // converters for known C++ classes and default (void*) + Converter* result = nullptr; + interop::TCppScope_t klass = interop::GetScopeFromType(realType); + if (interop::IsFunctionType(resolvedType)) { + // this is a function function pointer or reference + result = new FunctionPointerConverter(resolvedType); + } else if ((realTypeStr != "std::byte") && + (klass || (klass = interop::GetFullScope(realTypeStr)))) { + // std::byte is a special enum class used to access raw memory + interop::TCppScope_t raw; + if (interop::GetSmartPtrInfo(realTypeStr, &raw, nullptr)) { + if (cpd == "") { + result = new SmartPtrConverter(klass, raw, control); + } else if (cpd == "&") { + result = new SmartPtrConverter(klass, raw); + } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + result = new SmartPtrConverter(klass, raw, control, true); + } + } + + if (!result) { + // Cling WORKAROUND -- special case for STL iterators + if (realTypeStr.rfind("__gnu_cxx::__normal_iterator", 0) /* vector */ == 0 +#ifdef __APPLE__ + || realTypeStr.rfind("__wrap_iter", 0) == 0 +#endif + // TODO: Windows? + ) { + static STLIteratorConverter c; + result = &c; + } else if (realTypeStr != "int8_t" and realTypeStr != "uint8_t") { + // -- Cling WORKAROUND + result = selectInstanceCnv(klass, cpd, dims, isConst, control); + } + } + } + const std::string failure_msg( + "Failed to convert type: " + fullType + "; resolved: " + resolvedTypeStr + + "; real: " + realTypeStr + + "; realUnresolvedType: " + realUnresolvedTypeStr + "; cpd: " + cpd); + + if (!result && cpd == "&&") { + // for builtin, can use const-ref for r-ref + h = gConvFactories.find("const " + realTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + h = gConvFactories.find("const " + realUnresolvedTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + // else, unhandled moves + result = new NotImplementedConverter{PyExc_NotImplementedError, + "this method cannot (yet) be called"}; + } + + if (!result && h != gConvFactories.end()) { + // converter factory available, use it to create converter + result = (h->second)(dims); + } else if (!result) { + // default to something reasonable, assuming "user knows best" + if (cpd.size() == 2 && cpd != "&&") { // "**", "*[]", "*&" + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); + } else if (!cpd.empty()) { + result = new VoidArrayConverter(/* keepControl= */ true, + failure_msg); // "user knows best" + } else { + // fails on use + result = new NotImplementedConverter{ + PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + } + + return result; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void cppjit::cpyrt::DestroyConverter(Converter* p) { + if (p && p->HasState()) + delete p; // state-less converters are always shared +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterConverter(const std::string& name, cf_t fac) { + // register a custom converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) + return false; + + gConvFactories[name] = fac; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterConverterAlias(const std::string& name, + const std::string& target) { + // register a custom converter that is a reference to an existing converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) + return false; + + auto t = gConvFactories.find(target); + if (t == gConvFactories.end()) + return false; + + gConvFactories[name] = t->second; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::UnregisterConverter(const std::string& name) { + // remove a custom converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) { + gConvFactories.erase(f); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +namespace { + +using namespace cppjit::cpyrt; + +inline static std::string::size_type dims2stringsz(cdims_t d) { + return (d && d.ndim() != UNKNOWN_SIZE) ? d[0] : std::string::npos; +} + +#define STRINGVIEW "std::basic_string_view" +#define WSTRING1 "std::basic_string" +#define WSTRING2 \ + "std::basic_string,std::allocator>" + +//-- aliasing special case: C complex (is binary compatible with C++ +// std::complex) +#ifndef _WIN32 +#define CCOMPLEX_D "_Complex double" +#define CCOMPLEX_F "_Complex float" +#else +#define CCOMPLEX_D "_C_double_complex" +#define CCOMPLEX_F "_C_float_complex" +#endif + +static struct InitConvFactories_t { +public: + InitConvFactories_t() { + // load all converter factories in the global map 'gConvFactories' + cpyrt::ConvFactories_t& gf = gConvFactories; + + // factories for built-ins + gf["bool"] = (cf_t) + [](cdims_t) { + static BoolConverter c{}; + return &c; + }; + gf["const bool&"] = (cf_t) + [](cdims_t) { + static ConstBoolRefConverter c{}; + return &c; + }; + gf["bool&"] = (cf_t) + [](cdims_t) { + static BoolRefConverter c{}; + return &c; + }; + gf["char"] = (cf_t) + [](cdims_t) { + static CharConverter c{}; + return &c; + }; + gf["const char&"] = (cf_t) + [](cdims_t) { + static ConstCharRefConverter c{}; + return &c; + }; + gf["char&"] = (cf_t) + [](cdims_t) { + static CharRefConverter c{}; + return &c; + }; + gf["signed char&"] = (cf_t) + [](cdims_t) { + static SCharRefConverter c{}; + return &c; + }; + gf["unsigned char"] = (cf_t) + [](cdims_t) { + static UCharConverter c{}; + return &c; + }; + gf["const unsigned char&"] = (cf_t) + [](cdims_t) { + static ConstUCharRefConverter c{}; + return &c; + }; + gf["unsigned char&"] = (cf_t) + [](cdims_t) { + static UCharRefConverter c{}; + return &c; + }; + gf["SCharAsInt"] = (cf_t) + [](cdims_t) { + static SCharAsIntConverter c{}; + return &c; + }; + gf["UCharAsInt"] = (cf_t) + [](cdims_t) { + static UCharAsIntConverter c{}; + return &c; + }; + gf["wchar_t"] = (cf_t) + [](cdims_t) { + static WCharConverter c{}; + return &c; + }; + gf["char16_t"] = (cf_t) + [](cdims_t) { + static Char16Converter c{}; + return &c; + }; + gf["char32_t"] = (cf_t) + [](cdims_t) { + static Char32Converter c{}; + return &c; + }; + gf["wchar_t&"] = (cf_t) + [](cdims_t) { + static WCharRefConverter c{}; + return &c; + }; + gf["char16_t&"] = (cf_t) + [](cdims_t) { + static Char16RefConverter c{}; + return &c; + }; + gf["char32_t&"] = (cf_t) + [](cdims_t) { + static Char32RefConverter c{}; + return &c; + }; + gf["int8_t"] = (cf_t) + [](cdims_t) { + static Int8Converter c{}; + return &c; + }; + gf["const int8_t&"] = (cf_t) + [](cdims_t) { + static ConstInt8RefConverter c{}; + return &c; + }; + gf["int8_t&"] = (cf_t) + [](cdims_t) { + static Int8RefConverter c{}; + return &c; + }; + gf["uint8_t"] = (cf_t) + [](cdims_t) { + static UInt8Converter c{}; + return &c; + }; + gf["const uint8_t&"] = (cf_t) + [](cdims_t) { + static ConstUInt8RefConverter c{}; + return &c; + }; + gf["uint8_t&"] = (cf_t) + [](cdims_t) { + static UInt8RefConverter c{}; + return &c; + }; + gf["int16_t"] = (cf_t) + [](cdims_t) { + static Int16Converter c{}; + return &c; + }; + gf["const int16_t&"] = (cf_t) + [](cdims_t) { + static ConstInt16RefConverter c{}; + return &c; + }; + gf["int16_t&"] = (cf_t) + [](cdims_t) { + static Int16RefConverter c{}; + return &c; + }; + gf["int32_t"] = (cf_t) + [](cdims_t) { + static Int32Converter c{}; + return &c; + }; + gf["const int32_t&"] = (cf_t) + [](cdims_t) { + static ConstInt32RefConverter c{}; + return &c; + }; + gf["int32_t&"] = (cf_t) + [](cdims_t) { + static Int32RefConverter c{}; + return &c; + }; + gf["uint16_t"] = (cf_t) + [](cdims_t) { + static UInt16Converter c{}; + return &c; + }; + gf["const uint16_t&"] = (cf_t) + [](cdims_t) { + static ConstUInt16RefConverter c{}; + return &c; + }; + gf["uint16_t&"] = (cf_t) + [](cdims_t) { + static UInt16RefConverter c{}; + return &c; + }; + gf["uint32_t"] = (cf_t) + [](cdims_t) { + static UInt32Converter c{}; + return &c; + }; + gf["const uint32_t&"] = (cf_t) + [](cdims_t) { + static ConstUInt32RefConverter c{}; + return &c; + }; + gf["uint32_t&"] = (cf_t) + [](cdims_t) { + static UInt32RefConverter c{}; + return &c; + }; + gf["short"] = (cf_t) + [](cdims_t) { + static ShortConverter c{}; + return &c; + }; + gf["const short&"] = (cf_t) + [](cdims_t) { + static ConstShortRefConverter c{}; + return &c; + }; + gf["short&"] = (cf_t) + [](cdims_t) { + static ShortRefConverter c{}; + return &c; + }; + gf["unsigned short"] = (cf_t) + [](cdims_t) { + static UShortConverter c{}; + return &c; + }; + gf["const unsigned short&"] = (cf_t) + [](cdims_t) { + static ConstUShortRefConverter c{}; + return &c; + }; + gf["unsigned short&"] = (cf_t) + [](cdims_t) { + static UShortRefConverter c{}; + return &c; + }; + gf["int"] = (cf_t) + [](cdims_t) { + static IntConverter c{}; + return &c; + }; + gf["int&"] = (cf_t) + [](cdims_t) { + static IntRefConverter c{}; + return &c; + }; + gf["const int&"] = (cf_t) + [](cdims_t) { + static ConstIntRefConverter c{}; + return &c; + }; + gf["unsigned int"] = (cf_t) + [](cdims_t) { + static UIntConverter c{}; + return &c; + }; + gf["const unsigned int&"] = (cf_t) + [](cdims_t) { + static ConstUIntRefConverter c{}; + return &c; + }; + gf["unsigned int&"] = (cf_t) + [](cdims_t) { + static UIntRefConverter c{}; + return &c; + }; + gf["long"] = (cf_t) + [](cdims_t) { + static LongConverter c{}; + return &c; + }; + gf["long&"] = (cf_t) + [](cdims_t) { + static LongRefConverter c{}; + return &c; + }; + gf["const long&"] = (cf_t) + [](cdims_t) { + static ConstLongRefConverter c{}; + return &c; + }; + gf["unsigned long"] = (cf_t) + [](cdims_t) { + static ULongConverter c{}; + return &c; + }; + gf["const unsigned long&"] = (cf_t) + [](cdims_t) { + static ConstULongRefConverter c{}; + return &c; + }; + gf["unsigned long&"] = (cf_t) + [](cdims_t) { + static ULongRefConverter c{}; + return &c; + }; + gf["long long"] = (cf_t) + [](cdims_t) { + static LLongConverter c{}; + return &c; + }; + gf["const long long&"] = (cf_t) + [](cdims_t) { + static ConstLLongRefConverter c{}; + return &c; + }; + gf["long long&"] = (cf_t) + [](cdims_t) { + static LLongRefConverter c{}; + return &c; + }; + gf["unsigned long long"] = (cf_t) + [](cdims_t) { + static ULLongConverter c{}; + return &c; + }; + gf["const unsigned long long&"] = (cf_t) + [](cdims_t) { + static ConstULLongRefConverter c{}; + return &c; + }; + gf["unsigned long long&"] = (cf_t) + [](cdims_t) { + static ULLongRefConverter c{}; + return &c; + }; + + gf["float"] = (cf_t) + [](cdims_t) { + static FloatConverter c{}; + return &c; + }; + gf["const float&"] = (cf_t) + [](cdims_t) { + static ConstFloatRefConverter c{}; + return &c; + }; + gf["float&"] = (cf_t) + [](cdims_t) { + static FloatRefConverter c{}; + return &c; + }; + gf["double"] = (cf_t) + [](cdims_t) { + static DoubleConverter c{}; + return &c; + }; + gf["double&"] = (cf_t) + [](cdims_t) { + static DoubleRefConverter c{}; + return &c; + }; + gf["const double&"] = (cf_t) + [](cdims_t) { + static ConstDoubleRefConverter c{}; + return &c; + }; + gf["long double"] = (cf_t) + [](cdims_t) { + static LDoubleConverter c{}; + return &c; + }; + gf["const long double&"] = (cf_t) + [](cdims_t) { + static ConstLDoubleRefConverter c{}; + return &c; + }; + gf["long double&"] = (cf_t) + [](cdims_t) { + static LDoubleRefConverter c{}; + return &c; + }; + gf["std::complex"] = + (cf_t) + [](cdims_t) { return new ComplexDConverter{}; }; + gf["const std::complex&"] = + (cf_t) + [](cdims_t) { return new ComplexDConverter{}; }; + gf["void"] = (cf_t) + [](cdims_t) { + static VoidConverter c{}; + return &c; + }; + + // pointer/array factories + gf["bool ptr"] = + (cf_t) + [](cdims_t d) { return new BoolArrayConverter{d}; }; + gf["signed char ptr"] = + (cf_t) + [](cdims_t d) { return new SCharArrayConverter{d}; }; + gf["signed char**"] = (cf_t) + [](cdims_t) { + return new SCharArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}}; + }; + gf["const unsigned char*"] = + (cf_t) + [](cdims_t d) { return new UCharArrayConverter{d}; }; + gf["unsigned char ptr"] = + (cf_t) + [](cdims_t d) { return new UCharArrayConverter{d}; }; + gf["SCharAsInt*"] = gf["signed char ptr"]; + gf["SCharAsInt[]"] = gf["signed char ptr"]; + gf["UCharAsInt*"] = gf["unsigned char ptr"]; + gf["UCharAsInt[]"] = gf["unsigned char ptr"]; + gf["std::byte ptr"] = + (cf_t) + [](cdims_t d) { return new ByteArrayConverter{d}; }; + gf["int8_t ptr"] = + (cf_t) + [](cdims_t d) { return new Int8ArrayConverter{d}; }; + gf["int16_t ptr"] = + (cf_t) + [](cdims_t d) { return new Int16ArrayConverter{d}; }; + gf["int32_t ptr"] = + (cf_t) + [](cdims_t d) { return new Int32ArrayConverter{d}; }; + gf["uint8_t ptr"] = + (cf_t) + [](cdims_t d) { return new UInt8ArrayConverter{d}; }; + gf["uint16_t ptr"] = + (cf_t) + [](cdims_t d) { return new UInt16ArrayConverter{d}; }; + gf["uint32_t ptr"] = + (cf_t) + [](cdims_t d) { return new UInt32ArrayConverter{d}; }; + gf["short ptr"] = + (cf_t) + [](cdims_t d) { return new ShortArrayConverter{d}; }; + gf["unsigned short ptr"] = + (cf_t) + [](cdims_t d) { return new UShortArrayConverter{d}; }; + gf["int ptr"] = (cf_t) + [](cdims_t d) { return new IntArrayConverter{d}; }; + gf["unsigned int ptr"] = + (cf_t) + [](cdims_t d) { return new UIntArrayConverter{d}; }; + gf["long ptr"] = + (cf_t) + [](cdims_t d) { return new LongArrayConverter{d}; }; + gf["unsigned long ptr"] = + (cf_t) + [](cdims_t d) { return new ULongArrayConverter{d}; }; + gf["long long ptr"] = + (cf_t) + [](cdims_t d) { return new LLongArrayConverter{d}; }; + gf["unsigned long long ptr"] = + (cf_t) + [](cdims_t d) { return new ULLongArrayConverter{d}; }; + gf["float ptr"] = + (cf_t) + [](cdims_t d) { return new FloatArrayConverter{d}; }; + gf["double ptr"] = + (cf_t) + [](cdims_t d) { return new DoubleArrayConverter{d}; }; + gf["long double ptr"] = + (cf_t) + [](cdims_t d) { return new LDoubleArrayConverter{d}; }; + gf["std::complex ptr"] = + (cf_t) + [](cdims_t d) { return new ComplexFArrayConverter{d}; }; + gf["std::complex ptr"] = + (cf_t) + [](cdims_t d) { return new ComplexDArrayConverter{d}; }; + gf["void*"] = + (cf_t) + [](cdims_t d) { return new VoidArrayConverter{(bool)d}; }; + + // aliases + gf["signed char"] = gf["char"]; + gf["const signed char&"] = gf["const char&"]; + gf["std::byte"] = gf["uint8_t"]; + gf["const std::byte&"] = gf["const uint8_t&"]; + gf["std::byte&"] = gf["uint8_t&"]; + gf["std::int8_t"] = gf["int8_t"]; + gf["const std::int8_t&"] = gf["const int8_t&"]; + gf["std::int8_t&"] = gf["int8_t&"]; + gf["std::uint8_t"] = gf["uint8_t"]; + gf["const std::uint8_t&"] = gf["const uint8_t&"]; + gf["std::uint8_t&"] = gf["uint8_t&"]; + gf["internal_enum_type_t"] = gf["int"]; + gf["internal_enum_type_t&"] = gf["int&"]; + gf["const internal_enum_type_t&"] = gf["const int&"]; + gf["internal_enum_type_t ptr"] = gf["int ptr"]; +#ifdef _WIN32 + gf["__int64"] = gf["long long"]; + gf["const __int64&"] = gf["const long long&"]; + gf["__int64&"] = gf["long long&"]; + gf["__int64 ptr"] = gf["long long ptr"]; + gf["unsigned __int64"] = gf["unsigned long long"]; + gf["const unsigned __int64&"] = gf["const unsigned long long&"]; + gf["unsigned __int64&"] = gf["unsigned long long&"]; + gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; +#endif + gf[CCOMPLEX_D] = gf["std::complex"]; + gf["const " CCOMPLEX_D "&"] = gf["const std::complex&"]; + gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; + gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; + + // We always need these converters when cppjit is based on an unpatched + // ROOT, because the "long long" types are always converted to Long64_t + // and ULong64_t already at the ROOT Meta level. + // See + // https://github.com/root-project/root/issues/15872#issuecomment-2174092763 + gf["Long64_t"] = gf["long long"]; + gf["Long64_t ptr"] = gf["long long ptr"]; + gf["Long64_t&"] = gf["long long&"]; + gf["const Long64_t&"] = gf["const long long&"]; + gf["ULong64_t"] = gf["unsigned long long"]; + gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; + gf["ULong64_t&"] = gf["unsigned long long&"]; + gf["const ULong64_t&"] = gf["const unsigned long long&"]; + + // factories for special cases + gf["nullptr_t"] = (cf_t) + [](cdims_t) { + static NullptrConverter c{}; + return &c; + }; + gf["const char*"] = (cf_t) + [](cdims_t) { return new CStringConverter{}; }; + gf["const signed char*"] = gf["const char*"]; + gf["const char*&&"] = gf["const char*"]; + gf["const char[]"] = + (cf_t) + [](cdims_t) { return new CStringConverter{}; }; + gf["char*"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringConverter{dims2stringsz(d)}; + }; + gf["char[]"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringArrayConverter{d, true}; + }; + gf["signed char*"] = gf["char*"]; + gf["wchar_t*"] = (cf_t) + [](cdims_t) { return new WCStringConverter{}; }; + // TODO: The libc++ library that is used most prominently by Apple for + // macOS is using the _Nonnull Clang attribute for some of its string + // constructors (_LIBCPP_DIAGNOSE_NULLPTR is a preprocessor macro that + // resolves to _Nullable). The cppjit backend doesn't strip away such Clang + // attributes, so the wchar_t* converter fails to match. To make the + // construction of std::wstring work on macOS, we are explicitly creating + // a converter for the wchar_t*_Nonnull type here, but we should try to + // find a more general solution for dealing with Clang attributes. + // [1] https://clang.llvm.org/docs/AttributeReference.html#id656 + // [2] + // https://github.com/llvm/llvm-project/blob/2078da43e25a4623cab2d0d60decddf709aaea28/libcxx/include/string#L1061 + gf["wchar_t*_Nonnull"] = + (cf_t) + [](cdims_t) { return new WCStringConverter{}; }; + gf["char16_t*"] = (cf_t) + [](cdims_t) { return new CString16Converter{}; }; + gf["char16_t[]"] = (cf_t) + [](cdims_t d) { + return new CString16Converter{dims2stringsz(d)}; + }; + gf["char32_t*"] = (cf_t) + [](cdims_t) { return new CString32Converter{}; }; + gf["char32_t[]"] = (cf_t) + [](cdims_t d) { + return new CString32Converter{dims2stringsz(d)}; + }; + // TODO: the following are handled incorrectly upstream (char16_t** where + // char16_t* intended)?! + gf["char16_t**"] = gf["char16_t*"]; + gf["char32_t**"] = gf["char32_t*"]; + gf["const char**"] = (cf_t) + [](cdims_t) { + return new CStringArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}, false}; + }; + gf["char**"] = gf["const char**"]; + gf["const char*[]"] = + (cf_t) + [](cdims_t d) { return new CStringArrayConverter{d, false}; }; + gf["char*[]"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringArrayConverter{d, false}; + }; + gf["char ptr"] = gf["char*[]"]; + gf["std::basic_string"] = + (cf_t) + [](cdims_t) { return new STLStringConverter{}; }; + gf["const std::basic_string&"] = gf["std::basic_string"]; + gf["std::basic_string&&"] = + (cf_t) + [](cdims_t) { return new STLStringMoveConverter{}; }; + gf["const std::basic_string &"] = gf["std::basic_string"]; + gf["std::basic_string &&"] = + (cf_t) + [](cdims_t) { return new STLStringMoveConverter{}; }; + gf["std::basic_string_view"] = + (cf_t) + [](cdims_t) { return new STLStringViewConverter{}; }; + gf[STRINGVIEW] = gf["std::basic_string_view"]; + gf["std::basic_string_view&"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW "&"] = gf["std::basic_string_view"]; + gf["std::basic_string_view &"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW " &"] = gf["std::basic_string_view"]; + gf["std::wstring"] = + (cf_t) + [](cdims_t) { return new STLWStringConverter{}; }; + gf[WSTRING1] = gf["std::wstring"]; + gf[WSTRING2] = gf["std::wstring"]; + gf["const std::wstring&"] = gf["std::wstring"]; + gf["const " WSTRING1 "&"] = gf["std::wstring"]; + gf["const " WSTRING2 "&"] = gf["std::wstring"]; + gf["const std::wstring &"] = gf["std::wstring"]; + gf["const " WSTRING1 " &"] = gf["std::wstring"]; + gf["const " WSTRING2 " &"] = gf["std::wstring"]; + // void*& is exempt from the T*& ban: the callee updates the proxy's + // held pointer through the opaque handle + gf["void*&"] = (cf_t) + [](cdims_t) { + static VoidPtrRefConverter c{}; + return &c; + }; + gf["const void*&"] = gf["void*&"]; + gf["void**"] = + (cf_t) + [](cdims_t d) { return new VoidPtrPtrConverter{d}; }; + gf["void ptr"] = gf["void**"]; + gf["PyObject*"] = (cf_t) + [](cdims_t) { + static PyObjectConverter c{}; + return &c; + }; + gf["_object*"] = gf["PyObject*"]; + gf["FILE*"] = (cf_t) + [](cdims_t) { return new VoidArrayConverter{}; }; + } +} initConvFactories_; + +} // unnamed namespace diff --git a/bindings/pyroot/cppjit/src/cpyrt/Converters.h b/bindings/pyroot/cppjit/src/cpyrt/Converters.h new file mode 100644 index 0000000000000..bc2f65d61e20d --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Converters.h @@ -0,0 +1,103 @@ +#ifndef CPYRT_CONVERTERS_H +#define CPYRT_CONVERTERS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +struct Parameter; +struct CallContext; + +class CPYRT_CLASS_EXPORT Converter { +public: + virtual ~Converter(); + + Converter() = default; + + Converter(Converter const& other) = delete; + Converter(Converter&& other) = delete; + Converter& operator=(Converter const& other) = delete; + Converter& operator=(Converter&& other) = delete; + +public: + virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; + virtual PyObject* FromMemory(void* address); + virtual bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr); + virtual bool HasState() { return false; } + virtual std::string GetFailureMsg() { return "[Converter]"; } +}; + +// create/destroy converter from fully qualified type (public API) +CPYRT_EXPORT Converter* CreateConverter(const std::string& fullType, + cdims_t dims = 0); +CPYRT_EXPORT Converter* CreateConverter(interop::TCppType_t type, + cdims_t dims = 0); +CPYRT_EXPORT void DestroyConverter(Converter* p); +typedef Converter* (*cf_t)(cdims_t d); +CPYRT_EXPORT bool RegisterConverter(const std::string& name, cf_t fac); +CPYRT_EXPORT bool RegisterConverterAlias(const std::string& name, + const std::string& target); +CPYRT_EXPORT bool UnregisterConverter(const std::string& name); + +// converters for special cases (only here b/c of external use of +// StrictInstancePtrConverter) +class VoidArrayConverter : public Converter { +public: + VoidArrayConverter(bool keepControl = true, + const std::string& failureMsg = std::string()) + : fFailureMsg(failureMsg) { + fKeepControl = keepControl; + } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { + return "[VoidArrayConverter] " + fFailureMsg; + } + +protected: + virtual bool GetAddressSpecialCase(PyObject* pyobject, void*& address); + bool KeepControl() { return fKeepControl; } + const std::string fFailureMsg; + +private: + bool fKeepControl; +}; + +template class InstancePtrConverter : public VoidArrayConverter { +public: + InstancePtrConverter(interop::TCppScope_t klass, bool keepControl = false, + const std::string& failureMsg = std::string()) + : VoidArrayConverter(keepControl, failureMsg), + fClass(interop::GetUnderlyingScope(klass)) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + +protected: + interop::TCppScope_t fClass; +}; + +class StrictInstancePtrConverter : public InstancePtrConverter { +public: + using InstancePtrConverter::InstancePtrConverter; + +protected: + bool GetAddressSpecialCase(PyObject*, void*&) override { return false; } +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CONVERTERS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.cxx b/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.cxx new file mode 100644 index 0000000000000..dcb64b8b619cf --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.cxx @@ -0,0 +1,456 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" + +// As of Python 3.12, we can't use the PyMethod_GET_FUNCTION and +// PyMethod_GET_SELF macros anymore, as the contain asserts that check if the +// Python type is actually PyMethod_Type. If the Python type is +// CustomInstanceMethod_Type, we need our own macros. Technically they do they +// same, because the actual C++ type of the PyObject is PyMethodObject anyway. +#define CustomInstanceMethod_GET_SELF(meth) \ + reinterpret_cast(meth)->im_self +#define CustomInstanceMethod_GET_FUNCTION(meth) \ + reinterpret_cast(meth)->im_func +// TODO: this will break functionality +#define CustomInstanceMethod_GET_CLASS(meth) Py_None + +namespace cppjit::cpyrt { + +//- custom type representing typedef to pointer of class --------------------- +static PyObject* tptc_call(typedefpointertoclassobject* self, PyObject* args, + PyObject* /* kwds */) { + long long addr = 0; + if (!PyArg_ParseTuple(args, const_cast("|L"), &addr)) + return nullptr; + return BindCppObjectNoCast(interop::TCppObject_t((void*)addr), + self->fCppType); +} + +//----------------------------------------------------------------------------- +static PyObject* tptc_getcppname(typedefpointertoclassobject* self, void*) { + return cpyrt_PyText_FromString( + (interop::GetScopedFinalName(self->fCppType) + "*").c_str()); +} + +//----------------------------------------------------------------------------- +static PyObject* tptc_name(typedefpointertoclassobject* self, void*) { + PyObject* pyclass = cpyrt::GetScopeProxy(self->fCppType); + if (pyclass) { + PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gName); + Py_DECREF(pyclass); + return pyname; + } + + return cpyrt_PyText_FromString("*"); +} + +//----------------------------------------------------------------------------- +static PyGetSetDef tptc_getset[] = { + {(char*)"__name__", (getter)tptc_name, nullptr, nullptr, nullptr}, + {(char*)"__cpp_name__", (getter)tptc_getcppname, nullptr, nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +PyTypeObject TypedefPointerToClass_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.TypedefPointerToClass", // tp_name + sizeof(typedefpointertoclassobject), // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + (ternaryfunc)tptc_call, // tp_call + 0, // tp_str + PyObject_GenericGetAttr, // tp_getattro + PyObject_GenericSetAttr, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT, // tp_flags + 0, // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + 0, // tp_methods + 0, // tp_members + tptc_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + 0, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +//= instancemethod object with a more efficient call function ================ +static PyMethodObject* free_list; +static int numfree = 0; +#ifndef PyMethod_MAXFREELIST +#define PyMethod_MAXFREELIST 256 +#endif + +//----------------------------------------------------------------------------- +PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject*) { + // from instancemethod, but with custom type (at issue is that instancemethod + // is not meant to be derived from) + PyMethodObject* im; + if (!PyCallable_Check(func)) { + PyErr_Format(PyExc_SystemError, "%s:%d: bad argument to internal function", + __FILE__, __LINE__); + return nullptr; + } + + im = free_list; + if (im != nullptr) { + free_list = (PyMethodObject*)(im->im_self); + (void)PyObject_INIT(im, &CustomInstanceMethod_Type); + } else { + im = PyObject_GC_New(PyMethodObject, &CustomInstanceMethod_Type); + if (im == nullptr) + return nullptr; + } + + im->im_weakreflist = nullptr; + Py_INCREF(func); + im->im_func = func; + Py_XINCREF(self); + im->im_self = self; + PyObject_GC_Track(im); + return (PyObject*)im; +} + +//----------------------------------------------------------------------------- +static void im_dealloc(PyMethodObject* im) { + // from instancemethod, but with custom type (at issue is that instancemethod + // is not meant to be derived from) + PyObject_GC_UnTrack(im); + + if (im->im_weakreflist != nullptr) + PyObject_ClearWeakRefs((PyObject*)im); + + Py_DECREF(im->im_func); + Py_XDECREF(im->im_self); + + if (numfree < PyMethod_MAXFREELIST) { + im->im_self = (PyObject*)free_list; + free_list = im; + numfree++; + } else { + PyObject_GC_Del(im); + } +} + +//----------------------------------------------------------------------------- +static PyObject* im_call(PyObject* meth, PyObject* args, PyObject* kw) { + // The mapping from a method to a function involves reshuffling of self back + // into the list of arguments. However, the pythonized methods will then have + // to undo that shuffling, which is inefficient. This method is the same as + // the one for the instancemethod object, except for the shuffling. + PyObject* self = CustomInstanceMethod_GET_SELF(meth); + + if (!self) { + // unbound methods must be called with an instance of the class (or a + // derived class) as first argument + Py_ssize_t argc = PyTuple_GET_SIZE(args); + PyObject* pyclass = CustomInstanceMethod_GET_CLASS(meth); + if (1 <= argc && + PyObject_IsInstance(PyTuple_GET_ITEM(args, 0), pyclass) == 1) { + self = PyTuple_GET_ITEM(args, 0); + + PyObject* newArgs = PyTuple_New(argc - 1); + for (int i = 1; i < argc; ++i) { + PyObject* v = PyTuple_GET_ITEM(args, i); + Py_INCREF(v); + PyTuple_SET_ITEM(newArgs, i - 1, v); + } + + args = newArgs; + + } else + return PyMethod_Type.tp_call(meth, args, kw); // will set proper error msg + + } else + Py_INCREF(args); + + PyCFunctionObject* func = + (PyCFunctionObject*)CustomInstanceMethod_GET_FUNCTION(meth); + + // the function is globally shared, so set and reset its "self" (ok, b/c of + // GIL) + Py_INCREF(self); + func->m_self = self; + PyObject* result = cpyrt_PyCFunction_Call((PyObject*)func, args, kw); + func->m_self = nullptr; + Py_DECREF(self); + Py_DECREF(args); + return result; +} + +//----------------------------------------------------------------------------- +static PyObject* im_descr_get(PyObject* meth, PyObject* obj, + PyObject* pyclass) { + // from instancemethod: don't rebind an already bound method, or an unbound + // method of a class that's not a base class of pyclass + if (CustomInstanceMethod_GET_SELF(meth)) { + Py_INCREF(meth); + return meth; + } + + if (obj == Py_None) + obj = nullptr; + + return CustomInstanceMethod_New(CustomInstanceMethod_GET_FUNCTION(meth), obj, + pyclass); +} + +//= cpyrt custom instance method type ===================================== +PyTypeObject CustomInstanceMethod_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.InstanceMethod", // tp_name + 0, + 0, + (destructor)im_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + im_call, // tp_call + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"cpyrt custom instance method (internal)", // tp_doc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &PyMethod_Type, // tp_base + 0, + im_descr_get, // tp_descr_get + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +//= cpyrt custom iterator for performance ================================= +static void indexiter_dealloc(indexiterobject* ii) { + PyObject_GC_UnTrack(ii); + Py_XDECREF(ii->ii_container); + PyObject_GC_Del(ii); +} + +static int indexiter_traverse(indexiterobject* ii, visitproc visit, void* arg) { + Py_VISIT(ii->ii_container); + return 0; +} + +static PyObject* indexiter_iternext(indexiterobject* ii) { + if (ii->ii_pos >= ii->ii_len) + return nullptr; + + PyObject* pyindex = PyLong_FromSsize_t(ii->ii_pos); + PyObject* result = PyObject_CallMethodOneArg((PyObject*)ii->ii_container, + PyStrings::gGetItem, pyindex); + Py_DECREF(pyindex); + + ii->ii_pos += 1; + return result; +} + +PyTypeObject IndexIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0)(char*) "cppjit.indexiter", // tp_name + sizeof(indexiterobject), // tp_basicsize + 0, + (destructor)indexiter_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)indexiter_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)indexiter_iternext, // tp_iternext + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +static void vectoriter_dealloc(vectoriterobject* vi) { + if (vi->vi_converter && vi->vi_converter->HasState()) + delete vi->vi_converter; + indexiter_dealloc(vi); +} + +static PyObject* vectoriter_iternext(vectoriterobject* vi) { + if (vi->ii_pos >= vi->ii_len) + return nullptr; + + PyObject* result = nullptr; + + if (vi->vi_data && vi->vi_converter) { + void* location = + (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos); + result = vi->vi_converter->FromMemory(location); + } else if (vi->vi_data && vi->vi_klass) { + // The CPPInstance::kNoMemReg by-passes the memory regulator; the assumption + // here is that objects in vectors are simple and thus do not need to + // maintain object identity (or at least not during the loop anyway). This + // gains 2x in performance. + interop::TCppObject_t cppobj( + (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos)); + if (vi->vi_flags & vectoriterobject::kIsPolymorphic) + result = cpyrt::BindCppObject(*(void**)cppobj.data, vi->vi_klass, + cpyrt::CPPInstance::kNoMemReg); + else + result = cpyrt::BindCppObjectNoCast(cppobj, vi->vi_klass, + cpyrt::CPPInstance::kNoMemReg); + if ((vi->vi_flags & vectoriterobject::kNeedLifeLine) && result) + PyObject_SetAttr(result, PyStrings::gLifeLine, vi->ii_container); + } else { + PyObject* pyindex = PyLong_FromSsize_t(vi->ii_pos); + result = PyObject_CallMethodOneArg((PyObject*)vi->ii_container, + PyStrings::gGetNoCheck, pyindex); + Py_DECREF(pyindex); + } + + vi->ii_pos += 1; + return result; +} + +PyTypeObject VectorIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.vectoriter", // tp_name + sizeof(vectoriterobject), // tp_basicsize + 0, + (destructor)vectoriter_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)indexiter_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)vectoriter_iternext, // tp_iternext + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.h b/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.h new file mode 100644 index 0000000000000..ee7a12286cf9e --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.h @@ -0,0 +1,70 @@ +#ifndef CPYRT_CUSTOMPYTYPES_H +#define CPYRT_CUSTOMPYTYPES_H + +#include "Python.h" +#include "cppjit_interop.h" + +namespace cppjit::cpyrt { + +/** Custom "builtins," detectable by type, for pass by ref and improved + performance. + */ + +//- custom type representing typedef to pointer of class --------------------- +struct typedefpointertoclassobject { + PyObject_HEAD interop::TCppScope_t fCppType; +}; + +extern PyTypeObject TypedefPointerToClass_Type; + +template inline bool TypedefPointerToClass_Check(T* object) { + return object && PyObject_TypeCheck(object, &TypedefPointerToClass_Type); +} + +template inline bool TypedefPointerToClass_CheckExact(T* object) { + return object && Py_TYPE(object) == &TypedefPointerToClass_Type; +} + +//- custom instance method object type and type verification ----------------- +extern PyTypeObject CustomInstanceMethod_Type; + +template inline bool CustomInstanceMethod_Check(T* object) { + return object && PyObject_TypeCheck(object, &CustomInstanceMethod_Type); +} + +template inline bool CustomInstanceMethod_CheckExact(T* object) { + return object && Py_TYPE(object) == &CustomInstanceMethod_Type; +} + +PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, + PyObject* pyclass); + +//- custom iterator for high performance std::vector iteration --------------- +struct indexiterobject { + PyObject_HEAD PyObject* ii_container; + Py_ssize_t ii_pos; + Py_ssize_t ii_len; +}; + +extern PyTypeObject IndexIter_Type; + +class Converter; +struct vectoriterobject : public indexiterobject { + void* vi_data; + Py_ssize_t vi_stride; + Converter* vi_converter; + interop::TCppScope_t vi_klass; + int vi_flags; + + enum EFlags { + kDefault = 0x0000, + kNeedLifeLine = 0x0001, + kIsPolymorphic = 0x0002, + }; +}; + +extern PyTypeObject VectorIter_Type; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CUSTOMPYTYPES_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/DeclareConverters.h b/bindings/pyroot/cppjit/src/cpyrt/DeclareConverters.h new file mode 100644 index 0000000000000..c339463464ade --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/DeclareConverters.h @@ -0,0 +1,544 @@ +#ifndef CPYRT_DECLARECONVERTERS_H +#define CPYRT_DECLARECONVERTERS_H + +// Bindings +#include "Converters.h" +#include "Dimensions.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +namespace { + +#define CPPJIT_DECLARE_BASIC_CONVERTER(name) \ + class name##Converter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ + }; \ + \ + class Const##name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { \ + return "[Const" #name "RefConverter]"; \ + } \ + } + +#define CPPJIT_DECLARE_BASIC_CONVERTER2(name, base) \ + class name##Converter : public base##Converter { \ + public: \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ + }; \ + \ + class Const##name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { \ + return "[Const" #name "RefConverter]"; \ + } \ + } + +#define CPPJIT_DECLARE_REFCONVERTER(name) \ + class name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { return "[" #name "RefConverter]"; } \ + }; + +#define CPPJIT_DECLARE_ARRAY_CONVERTER(name) \ + class name##ArrayConverter : public Converter { \ + public: \ + name##ArrayConverter(cdims_t dims); \ + name##ArrayConverter(const name##ArrayConverter&) = delete; \ + name##ArrayConverter& operator=(const name##ArrayConverter&) = delete; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() override { return true; } \ + std::string GetFailureMsg() override { \ + return "[" #name "ArrayConverter]"; \ + } \ + \ + protected: \ + dims_t fShape; \ + bool fIsFixed; \ + }; + +// converters for built-ins +CPPJIT_DECLARE_BASIC_CONVERTER(Long); +CPPJIT_DECLARE_BASIC_CONVERTER(Bool); +CPPJIT_DECLARE_BASIC_CONVERTER(Char); +class SCharAsIntConverter : public CharConverter { +public: + using CharConverter::CharConverter; + PyObject* FromMemory(void*) override; +}; +CPPJIT_DECLARE_BASIC_CONVERTER(UChar); +class UCharAsIntConverter : public UCharConverter { +public: + using UCharConverter::UCharConverter; + PyObject* FromMemory(void*) override; +}; +CPPJIT_DECLARE_BASIC_CONVERTER(WChar); +CPPJIT_DECLARE_BASIC_CONVERTER(Char16); +CPPJIT_DECLARE_BASIC_CONVERTER(Char32); +CPPJIT_DECLARE_BASIC_CONVERTER(Int8); +CPPJIT_DECLARE_BASIC_CONVERTER(Int16); +CPPJIT_DECLARE_BASIC_CONVERTER(Int32); +CPPJIT_DECLARE_BASIC_CONVERTER(UInt8); +CPPJIT_DECLARE_BASIC_CONVERTER(UInt16); +CPPJIT_DECLARE_BASIC_CONVERTER(UInt32); +CPPJIT_DECLARE_BASIC_CONVERTER(Short); +CPPJIT_DECLARE_BASIC_CONVERTER(UShort); +CPPJIT_DECLARE_BASIC_CONVERTER(Int); +CPPJIT_DECLARE_BASIC_CONVERTER(ULong); +CPPJIT_DECLARE_BASIC_CONVERTER2(UInt, ULong); +CPPJIT_DECLARE_BASIC_CONVERTER(LLong); +CPPJIT_DECLARE_BASIC_CONVERTER(ULLong); +CPPJIT_DECLARE_BASIC_CONVERTER(Double); +CPPJIT_DECLARE_BASIC_CONVERTER(Float); +CPPJIT_DECLARE_BASIC_CONVERTER(LDouble); + +CPPJIT_DECLARE_REFCONVERTER(Bool); +CPPJIT_DECLARE_REFCONVERTER(Char); +CPPJIT_DECLARE_REFCONVERTER(WChar); +CPPJIT_DECLARE_REFCONVERTER(Char16); +CPPJIT_DECLARE_REFCONVERTER(Char32); +CPPJIT_DECLARE_REFCONVERTER(SChar); +CPPJIT_DECLARE_REFCONVERTER(UChar); +CPPJIT_DECLARE_REFCONVERTER(Int8); +CPPJIT_DECLARE_REFCONVERTER(Int16); +CPPJIT_DECLARE_REFCONVERTER(Int32); +CPPJIT_DECLARE_REFCONVERTER(UInt8); +CPPJIT_DECLARE_REFCONVERTER(UInt16); +CPPJIT_DECLARE_REFCONVERTER(UInt32); +CPPJIT_DECLARE_REFCONVERTER(Short); +CPPJIT_DECLARE_REFCONVERTER(UShort); +CPPJIT_DECLARE_REFCONVERTER(UInt); +CPPJIT_DECLARE_REFCONVERTER(Int); +CPPJIT_DECLARE_REFCONVERTER(Long); +CPPJIT_DECLARE_REFCONVERTER(ULong); +CPPJIT_DECLARE_REFCONVERTER(LLong); +CPPJIT_DECLARE_REFCONVERTER(ULLong); +CPPJIT_DECLARE_REFCONVERTER(Float); +CPPJIT_DECLARE_REFCONVERTER(Double); +CPPJIT_DECLARE_REFCONVERTER(LDouble); + +// by-value enum parameters: exact-match instances of the same enum in the +// first resolution round, delegate storage to the underlying integer converter +class EnumConverter : public Converter { +public: + EnumConverter(Converter* base, const std::string& enum_name) + : fBase(base), fEnumName(enum_name) {} + ~EnumConverter() override; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override; + +private: + Converter* fBase; + std::string fEnumName; +}; + +class VoidConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; +}; + +class CStringConverter : public Converter { +public: + CStringConverter(std::string::size_type maxSize = std::string::npos) + : fMaxSize(maxSize) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CStringConverter]"; } + +protected: + std::string fBuffer; + std::string::size_type fMaxSize; +}; + +class NonConstCStringConverter : public CStringConverter { +public: + using CStringConverter::CStringConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + std::string GetFailureMsg() override { return "[NonConstCStringConverter]"; } +}; + +class WCStringConverter : public Converter { +public: + WCStringConverter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + WCStringConverter(const WCStringConverter&) = delete; + WCStringConverter& operator=(const WCStringConverter&) = delete; + virtual ~WCStringConverter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[WCStringConverter]"; }; + +protected: + wchar_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +class CString16Converter : public Converter { +public: + CString16Converter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + CString16Converter(const CString16Converter&) = delete; + CString16Converter& operator=(const CString16Converter&) = delete; + virtual ~CString16Converter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CString16Converter]"; }; + +protected: + char16_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +class CString32Converter : public Converter { +public: + CString32Converter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + CString32Converter(const CString32Converter&) = delete; + CString32Converter& operator=(const CString32Converter&) = delete; + virtual ~CString32Converter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CString32Converter]"; }; + +protected: + char32_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +// pointer/array conversions +CPPJIT_DECLARE_ARRAY_CONVERTER(Bool); +CPPJIT_DECLARE_ARRAY_CONVERTER(SChar); +CPPJIT_DECLARE_ARRAY_CONVERTER(UChar); +CPPJIT_DECLARE_ARRAY_CONVERTER(Byte); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int8); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int16); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int32); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt8); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt16); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt32); +CPPJIT_DECLARE_ARRAY_CONVERTER(Short); +CPPJIT_DECLARE_ARRAY_CONVERTER(UShort); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt); +CPPJIT_DECLARE_ARRAY_CONVERTER(Long); +CPPJIT_DECLARE_ARRAY_CONVERTER(ULong); +CPPJIT_DECLARE_ARRAY_CONVERTER(LLong); +CPPJIT_DECLARE_ARRAY_CONVERTER(ULLong); +CPPJIT_DECLARE_ARRAY_CONVERTER(Float); +CPPJIT_DECLARE_ARRAY_CONVERTER(Double); +CPPJIT_DECLARE_ARRAY_CONVERTER(LDouble); +CPPJIT_DECLARE_ARRAY_CONVERTER(ComplexF); +CPPJIT_DECLARE_ARRAY_CONVERTER(ComplexD); + +class CStringArrayConverter : public SCharArrayConverter { +public: + CStringArrayConverter(cdims_t dims, bool fixed) : SCharArrayConverter(dims) { + fIsFixed = fixed; // overrides SCharArrayConverter decision + } + using SCharArrayConverter::SCharArrayConverter; + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[CStringArrayConverter]"; }; + +private: + std::vector fBuffer; +}; + +class NonConstCStringArrayConverter : public CStringArrayConverter { +public: + using CStringArrayConverter::CStringArrayConverter; + PyObject* FromMemory(void* address) override; + std::string GetFailureMsg() override { + return "[NonConstCStringArrayConverter]"; + }; +}; + +// converters for special cases +class NullptrConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; +}; + +class InstanceConverter : public StrictInstancePtrConverter { +public: + using StrictInstancePtrConverter::StrictInstancePtrConverter; + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void*) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceConverter]"; }; +}; + +class InstanceRefConverter : public Converter { +public: + InstanceRefConverter(interop::TCppScope_t klass, bool isConst) + : fClass(klass), fIsConst(isConst) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[InstanceRefConverter]"; }; + +protected: + interop::TCppScope_t fClass; + bool fIsConst; +}; + +class InstanceMoveConverter : public InstanceRefConverter { +public: + InstanceMoveConverter(interop::TCppScope_t klass) + : InstanceRefConverter(klass, true) {} + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceMoveConverter]"; }; +}; + +template +class InstancePtrPtrConverter : public InstancePtrConverter { +public: + using InstancePtrConverter::InstancePtrConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstancePtrPtrConverter]"; }; +}; + +class InstanceArrayConverter : public InstancePtrConverter { +public: + InstanceArrayConverter(interop::TCppScope_t klass, cdims_t dims, + bool keepControl = false) + : InstancePtrConverter(klass, keepControl), fShape(dims) {} + InstanceArrayConverter(const InstanceArrayConverter&) = delete; + InstanceArrayConverter& operator=(const InstanceArrayConverter&) = delete; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceArrayConverter]"; }; + +protected: + dims_t fShape; +}; + +class ComplexDConverter : public InstanceConverter { +public: + ComplexDConverter(bool keepControl = false); + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[ComplexDConverter]"; }; + +private: + std::complex fBuffer; +}; + +// Cling WORKAROUND -- classes for STL iterators are completely undefined in +// that they come in a bazillion different guises, so just do whatever +class STLIteratorConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[STLIteratorConverter]"; }; +}; +// -- END Cling WORKAROUND + +class VoidPtrRefConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[VoidPtrRefConverter]"; }; +}; + +class VoidPtrPtrConverter : public Converter { +public: + VoidPtrPtrConverter(cdims_t dims, + const std::string& failureMsg = std::string()); + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { + return "[VoidPtrPtrConverter] " + fFailureMsg; + } + +protected: + dims_t fShape; + bool fIsFixed; + const std::string fFailureMsg; +}; + +CPPJIT_DECLARE_BASIC_CONVERTER(PyObject); + +#define CPPJIT_DECLARE_STRING_CONVERTER(name, strtype) \ + class name##Converter : public InstanceConverter { \ + public: \ + name##Converter(bool keepControl = true); \ + \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void* address) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() override { return true; } \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; }; \ + \ + protected: \ + strtype fBuffer; \ + } + +CPPJIT_DECLARE_STRING_CONVERTER(STLString, std::string); +CPPJIT_DECLARE_STRING_CONVERTER(STLWString, std::wstring); +CPPJIT_DECLARE_STRING_CONVERTER(STLStringView, std::string_view); + +class STLStringMoveConverter : public STLStringConverter { +public: + using STLStringConverter::STLStringConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[STLStringMoveConverter]"; }; +}; + +// function pointers +class FunctionPointerConverter : public Converter { +public: + FunctionPointerConverter(cppjit::interop::TCppType_t FnType) + : fFnType(FnType) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; + +protected: + cppjit::interop::TCppType_t fFnType; + bool fAllowCppInstance = false; +}; + +// std::function +class StdFunctionConverter : public FunctionPointerConverter { +public: + StdFunctionConverter(Converter* cnv, cppjit::interop::TCppType_t fn, + const std::string& ret, const std::string& sig) + : FunctionPointerConverter(cppjit::interop::GetFnTypeFromStdFn(fn)), + fRetType(ret), fSignature(sig), fConverter(cnv) { + fAllowCppInstance = true; + } + StdFunctionConverter(const StdFunctionConverter&) = delete; + StdFunctionConverter& operator=(const StdFunctionConverter&) = delete; + virtual ~StdFunctionConverter() { delete fConverter; } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[StdFunctionConverter]"; }; + +protected: + std::string fRetType; + std::string fSignature; + Converter* fConverter; +}; + +// smart pointer converter +class SmartPtrConverter : public Converter { +public: + SmartPtrConverter(interop::TCppScope_t smart, interop::TCppScope_t underlying, + bool keepControl = false, bool isRef = false) + : fSmartPtrType(smart), fUnderlyingType(underlying), + fKeepControl(keepControl), fIsRef(isRef) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[SmartPtrConverter]"; }; + +protected: + virtual bool GetAddressSpecialCase(PyObject*, void*&) { return false; } + + interop::TCppScope_t fSmartPtrType; + interop::TCppScope_t fUnderlyingType; + bool fKeepControl; + bool fIsRef; +}; + +// initializer lists +class InitializerListConverter : public InstanceConverter { +public: + InitializerListConverter(interop::TCppScope_t klass, + std::string const& value_type); + InitializerListConverter(const InitializerListConverter&) = delete; + InitializerListConverter& operator=(const InitializerListConverter&) = delete; + virtual ~InitializerListConverter(); + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; + +protected: + void Clear(); + +protected: + void* fBuffer = nullptr; + std::vector fConverters; + std::string fValueTypeName; + interop::TCppScope_t fValueType; + size_t fValueSize; +}; + +} // unnamed namespace + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DECLARECONVERTERS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/DeclareExecutors.h b/bindings/pyroot/cppjit/src/cpyrt/DeclareExecutors.h new file mode 100644 index 0000000000000..c20c0f58002e4 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/DeclareExecutors.h @@ -0,0 +1,204 @@ +#ifndef CPYRT_DECLAREEXECUTORS_H +#define CPYRT_DECLAREEXECUTORS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "CallContext.h" +#include "Dimensions.h" +#include "Executors.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +namespace { + +#define CPPJIT_DECL_EXEC(name) \ + class name##Executor : public Executor { \ + public: \ + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, \ + CallContext*) override; \ + } + +// executors for built-ins +CPPJIT_DECL_EXEC(Bool); +CPPJIT_DECL_EXEC(BoolConstRef); +CPPJIT_DECL_EXEC(Char); +CPPJIT_DECL_EXEC(CharConstRef); +CPPJIT_DECL_EXEC(UChar); +CPPJIT_DECL_EXEC(UCharConstRef); +CPPJIT_DECL_EXEC(WChar); +CPPJIT_DECL_EXEC(Char16); +CPPJIT_DECL_EXEC(Char32); +CPPJIT_DECL_EXEC(Int8); +CPPJIT_DECL_EXEC(Int8ConstRef); +CPPJIT_DECL_EXEC(UInt8); +CPPJIT_DECL_EXEC(UInt8ConstRef); +CPPJIT_DECL_EXEC(Short); +CPPJIT_DECL_EXEC(Int); +CPPJIT_DECL_EXEC(Long); +CPPJIT_DECL_EXEC(ULong); +CPPJIT_DECL_EXEC(LongLong); +CPPJIT_DECL_EXEC(ULongLong); +CPPJIT_DECL_EXEC(Float); +CPPJIT_DECL_EXEC(Double); +CPPJIT_DECL_EXEC(LongDouble); +CPPJIT_DECL_EXEC(Void); +CPPJIT_DECL_EXEC(CString); +CPPJIT_DECL_EXEC(CStringRef); +CPPJIT_DECL_EXEC(WCString); +CPPJIT_DECL_EXEC(CString16); +CPPJIT_DECL_EXEC(CString32); + +// pointer/array executors +#define CPPJIT_ARRAY_DECL_EXEC(name) \ + class name##ArrayExecutor : public Executor { \ + dims_t fShape; \ + \ + public: \ + name##ArrayExecutor(dims_t dims) : fShape(dims) {} \ + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, \ + CallContext*) override; \ + bool HasState() override { return true; } \ + } +CPPJIT_ARRAY_DECL_EXEC(Void); +CPPJIT_ARRAY_DECL_EXEC(Bool); +CPPJIT_ARRAY_DECL_EXEC(SChar); +CPPJIT_ARRAY_DECL_EXEC(UChar); +CPPJIT_ARRAY_DECL_EXEC(Byte); +CPPJIT_ARRAY_DECL_EXEC(Int8); +CPPJIT_ARRAY_DECL_EXEC(UInt8); +CPPJIT_ARRAY_DECL_EXEC(Short); +CPPJIT_ARRAY_DECL_EXEC(UShort); +CPPJIT_ARRAY_DECL_EXEC(Int); +CPPJIT_ARRAY_DECL_EXEC(UInt); +CPPJIT_ARRAY_DECL_EXEC(Long); +CPPJIT_ARRAY_DECL_EXEC(ULong); +CPPJIT_ARRAY_DECL_EXEC(LLong); +CPPJIT_ARRAY_DECL_EXEC(ULLong); +CPPJIT_ARRAY_DECL_EXEC(Float); +CPPJIT_ARRAY_DECL_EXEC(Double); +CPPJIT_ARRAY_DECL_EXEC(LDouble); +CPPJIT_ARRAY_DECL_EXEC(ComplexF); +CPPJIT_ARRAY_DECL_EXEC(ComplexD); +CPPJIT_ARRAY_DECL_EXEC(ComplexI); +CPPJIT_ARRAY_DECL_EXEC(ComplexL); + +// special cases +CPPJIT_DECL_EXEC(ComplexD); +CPPJIT_DECL_EXEC(STLString); +CPPJIT_DECL_EXEC(STLWString); + +class InstancePtrExecutor : public Executor { +public: + InstancePtrExecutor(interop::TCppScope_t klass) : fClass(klass) {} + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; + bool HasState() override { return true; } + +protected: + interop::TCppScope_t fClass; +}; + +class InstanceExecutor : public Executor { +public: + InstanceExecutor(interop::TCppScope_t klass); + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; + bool HasState() override { return true; } + +protected: + interop::TCppScope_t fClass; + uint32_t fFlags; +}; + +class IteratorExecutor : public InstanceExecutor { +public: + IteratorExecutor(interop::TCppScope_t klass); +}; + +CPPJIT_DECL_EXEC(Constructor); +CPPJIT_DECL_EXEC(PyObject); + +#define CPPJIT_DECL_REFEXEC(name) \ + class name##RefExecutor : public RefExecutor { \ + public: \ + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, \ + CallContext*) override; \ + } + +CPPJIT_DECL_REFEXEC(Bool); +CPPJIT_DECL_REFEXEC(Char); +CPPJIT_DECL_REFEXEC(UChar); +CPPJIT_DECL_REFEXEC(Int8); +CPPJIT_DECL_REFEXEC(UInt8); +CPPJIT_DECL_REFEXEC(Short); +CPPJIT_DECL_REFEXEC(UShort); +CPPJIT_DECL_REFEXEC(Int); +CPPJIT_DECL_REFEXEC(UInt); +CPPJIT_DECL_REFEXEC(Long); +CPPJIT_DECL_REFEXEC(ULong); +CPPJIT_DECL_REFEXEC(LongLong); +CPPJIT_DECL_REFEXEC(ULongLong); +CPPJIT_DECL_REFEXEC(Float); +CPPJIT_DECL_REFEXEC(Double); +CPPJIT_DECL_REFEXEC(LongDouble); +CPPJIT_DECL_REFEXEC(ComplexD); +CPPJIT_DECL_REFEXEC(STLString); + +// special cases +class InstanceRefExecutor : public RefExecutor { +public: + InstanceRefExecutor(interop::TCppScope_t klass) : fClass(klass) {} + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; + +protected: + interop::TCppScope_t fClass; +}; + +class InstancePtrPtrExecutor : public InstanceRefExecutor { +public: + using InstanceRefExecutor::InstanceRefExecutor; + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; +}; + +class InstancePtrRefExecutor : public InstanceRefExecutor { +public: + using InstanceRefExecutor::InstanceRefExecutor; + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; +}; + +class InstanceArrayExecutor : public InstancePtrExecutor { +public: + InstanceArrayExecutor(interop::TCppScope_t klass, dim_t array_size) + : InstancePtrExecutor(klass), fSize(array_size) {} + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; + +protected: + dim_t fSize; +}; + +class FunctionPointerExecutor : public Executor { +public: + FunctionPointerExecutor(const std::string& ret, const std::string& sig) + : fRetType(ret), fSignature(sig) {} + PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) override; + +protected: + std::string fRetType; + std::string fSignature; +}; + +} // unnamed namespace + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DECLAREEXECUTORS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Dimensions.h b/bindings/pyroot/cppjit/src/cpyrt/Dimensions.h new file mode 100644 index 0000000000000..a63e636097ed5 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Dimensions.h @@ -0,0 +1,90 @@ +#ifndef CPYRT_DIMENSIONS_H +#define CPYRT_DIMENSIONS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "cpyrt/CommonDefs.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +static const dim_t UNKNOWN_SIZE = (dim_t)-1; + +class CPYRT_CLASS_EXPORT Dimensions { + dim_t* fDims; + +public: + Dimensions(dim_t ndim = 0, dim_t* dims = nullptr) : fDims(nullptr) { + if (ndim && ndim != UNKNOWN_SIZE) { + fDims = new dim_t[ndim + 1]; + fDims[0] = ndim; + if (dims) + std::copy(dims, dims + ndim, fDims + 1); + else + std::fill_n(fDims + 1, ndim, UNKNOWN_SIZE); + } + } + Dimensions(std::initializer_list l) { + fDims = new dim_t[l.size() + 1]; + fDims[0] = l.size(); + std::copy(l.begin(), l.end(), fDims + 1); + } + Dimensions(const Dimensions& d) : fDims(nullptr) { + if (d.fDims) { + fDims = new dim_t[d.fDims[0] + 1]; + std::copy(d.fDims, d.fDims + d.fDims[0] + 1, fDims); + } + } + Dimensions(Dimensions&& d) : fDims(d.fDims) { d.fDims = nullptr; } + Dimensions& operator=(const Dimensions& d) { + if (this != &d) { + if (!d.fDims) { + delete[] fDims; + fDims = nullptr; + } else { + if (!fDims || (fDims && fDims[0] != d.fDims[0])) { + delete[] fDims; + fDims = new dim_t[d.fDims[0] + 1]; + } + std::copy(d.fDims, d.fDims + d.fDims[0] + 1, fDims); + } + } + return *this; + } + ~Dimensions() { delete[] fDims; } + +public: + operator bool() const { return (bool)fDims; } + + dim_t ndim() const { return fDims ? fDims[0] : UNKNOWN_SIZE; } + void ndim(dim_t d) { + if (fDims) { + if (fDims[0] == d) + return; + delete[] fDims; + } + + fDims = new dim_t[d + 1]; + fDims[0] = d; + std::fill_n(fDims + 1, d, UNKNOWN_SIZE); + } + + dim_t operator[](dim_t i) const { return fDims[i + 1]; } + dim_t& operator[](dim_t i) { return fDims[i + 1]; } + + Dimensions sub() const { + return fDims ? Dimensions(fDims[0] - 1, fDims + 2) : Dimensions(); + } +}; + +typedef Dimensions dims_t; +typedef const dims_t& cdims_t; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DIMENSIONS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.cxx b/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.cxx new file mode 100644 index 0000000000000..cc7c25c358fdb --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.cxx @@ -0,0 +1,114 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +#undef CPYRT_INTERNAL +#include "CPPInstance.h" +#include "CPPScope.h" + +//----------------------------------------------------------------------------- +PyObject* cppjit::cpyrt::DispatchPtr::Get(bool borrowed) const { + PythonGILRAII python_gil_raii; + if (fPyHardRef) { + if (!borrowed) + Py_INCREF(fPyHardRef); + return fPyHardRef; + } + if (fPyWeakRef) { + PyObject* disp = cpyrt_GetWeakRef(fPyWeakRef); + if (disp) { // dispatcher object disappeared? + if (borrowed) + Py_DECREF(disp); + return disp; + } + } + return nullptr; +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) + : fPyHardRef(nullptr) { + PythonGILRAII python_gil_raii; + if (strong) { + Py_INCREF(pyobj); + fPyHardRef = pyobj; + fPyWeakRef = nullptr; + } else { + fPyHardRef = nullptr; + fPyWeakRef = PyWeakref_NewRef(pyobj, nullptr); + } + ((CPPInstance*)pyobj)->SetDispatchPtr(this); +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) + : fPyWeakRef(nullptr) { + PythonGILRAII python_gil_raii; + PyObject* pyobj = other.Get(false /* not borrowed */); + fPyHardRef = + pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; + if (fPyHardRef) + ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); + Py_XDECREF(pyobj); +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::~DispatchPtr() { + // if we're holding a hard reference, or holding weak reference while being + // part of a dispatcher intermediate, then this delete is from the C++ side, + // and Python is "notified" by nulling out the reference and an exception will + // be raised on continued access + PythonGILRAII python_gil_raii; + if (fPyWeakRef) { + PyObject* pyobj = cpyrt_GetWeakRef(fPyWeakRef); + if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) + ((CPPInstance*)pyobj)->GetObjectRaw() = nullptr; + Py_XDECREF(pyobj); + Py_DECREF(fPyWeakRef); + } else if (fPyHardRef) { + ((CPPInstance*)fPyHardRef)->GetObjectRaw() = nullptr; + Py_DECREF(fPyHardRef); + } +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr& +cppjit::cpyrt::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) { + PythonGILRAII python_gil_raii; + if (this != &other) { + Py_XDECREF(fPyWeakRef); + fPyWeakRef = nullptr; + Py_XDECREF(fPyHardRef); + PyObject* pyobj = other.Get(false /* not borrowed */); + fPyHardRef = + pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; + if (fPyHardRef) + ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); + Py_XDECREF(pyobj); + } + return *this; +} + +//----------------------------------------------------------------------------- +void cppjit::cpyrt::DispatchPtr::PythonOwns() { + PythonGILRAII python_gil_raii; + // Python maintains the hardref, so only allowed a weakref here + if (fPyHardRef) { + fPyWeakRef = PyWeakref_NewRef(fPyHardRef, nullptr); + Py_DECREF(fPyHardRef); + fPyHardRef = nullptr; + } +} + +//----------------------------------------------------------------------------- +void cppjit::cpyrt::DispatchPtr::CppOwns() { + PythonGILRAII python_gil_raii; + // C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref + if (fPyWeakRef) { + fPyHardRef = cpyrt_GetWeakRef(fPyWeakRef); + Py_DECREF(fPyWeakRef); + fPyWeakRef = nullptr; + } +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.h b/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.h new file mode 100644 index 0000000000000..75cbe9dba9ff4 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.h @@ -0,0 +1,80 @@ +#ifndef CPYRT_DISPATCHPTR_H +#define CPYRT_DISPATCHPTR_H + +////////////////////////////////////////////////////////////////////////////// +// // +// DispatchPtr // +// // +// Smart pointer for reference management and C++ instance tracking when // +// cross-inheriting. The carried pointer is always expected to be derived // +// from CPPInstance, and the DispatchPtr to be embedded in the C++ instance // +// derived dispatcher to which it points (ownership is two-way; life-times // +// are equal). The C++ dispatcher then uses the DispatchPtr to call Python // +// functions for virtual methods. // +// // +////////////////////////////////////////////////////////////////////////////// + +// Bindings +#include +#include "cpyrt/CommonDefs.h" + +namespace cppjit::cpyrt { +class PythonGILRAII { + PyGILState_STATE state; + +public: + PythonGILRAII() : state(PyGILState_Ensure()) {} + ~PythonGILRAII() { PyGILState_Release(state); } +}; + +class CPYRT_CLASS_EXTERN DispatchPtr { +public: + // Default constructor: only ever created from C++, as by definition, creation + // from the Python side makes the relevant Python instance available. Calls to + // the default ctor happen eg. in STL containers. It is expected that the + // pointer to the Python object is filled in later, eg. through assign(). + DispatchPtr() : fPyHardRef(nullptr), fPyWeakRef(nullptr) {} + + // Conversion constructor: called with C++ object construction when the + // PyObject is known (eg. when instantiating from Python), with pyobj the + // Python-side representation of the C++ object. + explicit DispatchPtr(PyObject* pyobj, bool strong = false); + + // Copy constructor: only ever called from C++. The Python object needs to be + // copied, in case it has added state, and rebound to the new C++ instance. + DispatchPtr(const DispatchPtr& other, void* cppinst); + + // Assignment: only ever called from C++. Similarly to the copy constructor, + // the Pythonb object needs to be copied and rebound. + DispatchPtr& assign(const DispatchPtr& other, void* cppinst); + + // Do not otherwise allow straight copies/assignment. + DispatchPtr(DispatchPtr&) = delete; + DispatchPtr(DispatchPtr&&) = delete; + DispatchPtr& operator=(const DispatchPtr& other) = delete; + + // lifetime is directly bound to the lifetime of the dispatcher object + ~DispatchPtr(); + + // either C++ owns the Python object through a reference count (on fPyHardRef) + // or Python owns the C++ object and we only have a weak reference (through + // fPyWeakRef) + void PythonOwns(); + void CppOwns(); + + // access to underlying object: cast and dereferencing + operator PyObject*() const { return Get(); } + + PyObject* operator->() const { return Get(); } + +private: + PyObject* Get(bool borrowed = true) const; + +private: + PyObject* fPyHardRef; + PyObject* fPyWeakRef; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DISPATCHPTR_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.cxx b/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.cxx new file mode 100644 index 0000000000000..9017ae626cb54 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.cxx @@ -0,0 +1,572 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPScope.h" +#include "Dispatcher.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +//---------------------------------------------------------------------------- +static inline void InjectMethod(interop::TCppMethod_t method, + const std::string& mtCppName, + std::ostringstream& code) { + // inject implementation for an overridden method + using namespace cppjit::cpyrt; + + // method declaration + std::string retType = interop::GetMethodReturnTypeAsString(method); + code << " " << retType << " " << mtCppName << "("; + + // build out the signature with predictable formal names + interop::TCppIndex_t nArgs = interop::GetMethodNumArgs(method); + std::vector argtypes; + argtypes.reserve(nArgs); + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + argtypes.push_back(interop::GetMethodArgCanonTypeAsString(method, i)); + if (i != 0) + code << ", "; + code << argtypes.back() << " arg" << i; + } + code << ") "; + if (interop::IsConstMethod(method)) + code << "const "; + code << "{\n"; + + // on destruction, the Python object may go first, in which case provide a + // diagnostic warning (raising a PyException may not be possible as this could + // happen during program shutdown); note that this means that the actual + // result will be the default and the caller may need to act on that, but + // that's still an improvement over a possible crash + code << " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" // acquire GIL + " PyObject* iself = (PyObject*)_internal_self;\n" + " if (!iself || iself == Py_None) {\n" + " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on " + "deleted python-side proxy\");\n" + " return"; + if (retType != "void") { + if (retType.back() != '*') + code << " (" << cpyrt::TypeManip::remove_const(retType) << "){}"; + else + code << " nullptr"; + } + code << ";\n" + " }\n" + " Py_INCREF(iself);\n"; + + // start actual function body + Utility::ConstructCallbackPreamble(retType, argtypes, code); + + // perform actual method call + code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName + << "\");\n" + " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName"; + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) + code << ", pyargs[" << i << "]"; + code << ", NULL);\n Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; + + // close + Utility::ConstructCallbackReturn(retType, (int)nArgs, code); +} + +//---------------------------------------------------------------------------- +namespace { +struct BaseInfo { + BaseInfo(interop::TCppScope_t t, std::string&& bn, std::string&& bns) + : btype(t), bname(bn), bname_scoped(bns) {} + interop::TCppScope_t btype; + std::string bname; + std::string bname_scoped; +}; + +typedef std::vector BaseInfos_t; +typedef std::vector Ctors_t; +typedef std::vector AllCtors_t; +typedef std::vector> CtorInfos_t; +} // unnamed namespace + +static void build_constructors(const std::string& derivedName, + const BaseInfos_t& base_infos, + const AllCtors_t& ctors, + std::ostringstream& code, + const CtorInfos_t& methods = CtorInfos_t{}, + size_t idx = 0) { + if (idx < ctors.size()) { + for (const auto& method : ctors[idx]) { + size_t argsmin = (size_t)interop::GetMethodReqArgs(method); + size_t argsmax = (size_t)interop::GetMethodNumArgs(method); + for (size_t i = argsmin; i <= argsmax; ++i) { + CtorInfos_t methods1{methods}; + methods1.emplace_back(method, i); + build_constructors(derivedName, base_infos, ctors, code, methods1, + idx + 1); + } + } + } else { + // this is as deep as we go; start writing + code << " " << derivedName << "("; + + // declare arguments + std::vector arg_tots; + arg_tots.reserve(methods.size()); + for (Ctors_t::size_type i = 0; i < methods.size(); ++i) { + const auto& cinfo = methods[i]; + if (i != 0 && (arg_tots.back() || 1 < arg_tots.size())) + code << ", "; + size_t nArgs = cinfo.second; + arg_tots.push_back(i == 0 ? nArgs : nArgs + arg_tots.back()); + + if (i != 0) + code << "__cppjit_internal::Sep*"; + size_t offset = (i != 0 ? arg_tots[i - 1] : 0); + for (size_t j = 0; j < nArgs; ++j) { + if (i != 0 || j != 0) + code << ", "; + code << interop::GetMethodArgCanonTypeAsString(cinfo.first, j) << " a" + << (j + offset); + } + } + code << ") : "; + + // pass arguments to base constructors + for (BaseInfos_t::size_type i = 0; i < base_infos.size(); ++i) { + if (i != 0) + code << ", "; + code << base_infos[i].bname << "("; + size_t first = (i != 0 ? arg_tots[i - 1] : 0); + for (size_t j = first; j < arg_tots[i]; ++j) { + if (j != first) + code << ", "; + bool isRValue = + cpyrt::TypeManip::compound(interop::GetMethodArgCanonTypeAsString( + methods[i].first, j - first)) == "&&"; + if (isRValue) + code << "std::move("; + code << "a" << j; + if (isRValue) + code << ")"; + } + code << ")"; + } + code << " {}\n"; + } +} + +namespace { + +using namespace cppjit::interop; + +static inline std::vector +FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) { + // Recursively walk the inheritance tree to find the overloads of the named + // method + std::vector result; + result = GetMethodsFromName(tbase, mtCppName); + if (result.empty()) { + for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) { + TCppScope_t b = interop::GetBaseScope(tbase, ibase); + result = FindBaseMethod(b, mtCppName); + if (!result.empty()) + break; + } + } + return result; +} + +} // unnamed namespace + +bool cpyrt::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, + std::ostringstream& err) { + // Scan all methods in dct and where it overloads base methods in klass, + // create dispatchers on the C++ side. Then interject the dispatcher class. + + if (!PyTuple_Check(bases) || !PyTuple_GET_SIZE(bases) || !dct || + !PyDict_Check(dct)) { + err << "internal error: expected tuple of bases and proper dictionary"; + return false; + } + + if (!Utility::IncludePython()) { + err << "failed to include Python.h"; + return false; + } + + // collect all bases, error checking the hierarchy along the way + const Py_ssize_t nBases = PyTuple_GET_SIZE(bases); + BaseInfos_t base_infos; + base_infos.reserve(nBases); + for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) { + if (!CPPScope_Check(PyTuple_GET_ITEM(bases, ibase))) + continue; + + interop::TCppScope_t basetype = + ((CPPScope*)PyTuple_GET_ITEM(bases, ibase))->fCppType; + + if (!basetype) { + err << "base class is incomplete"; + break; + } + + if (interop::IsNamespace(basetype)) { + err << interop::GetScopedFinalName(basetype) << " is a namespace"; + break; + } + + if (!interop::HasVirtualDestructor(basetype)) { + const std::string& bname = interop::GetScopedFinalName(basetype); + if (PyErr_WarnEx( + PyExc_RuntimeWarning, + (char*)("class \"" + bname + "\" has no virtual destructor") + .c_str(), + 1) < 0) + return false; + } + + base_infos.emplace_back( + basetype, TypeManip::template_base(interop::GetFinalName(basetype)), + interop::GetScopedFinalName(basetype)); + } + + // TODO: check deep hierarchy for multiple inheritance + bool isDeepHierarchy = + klass->fCppType && base_infos.front().btype != klass->fCppType; + + // once classes can be extended, should consider re-use; for now, since + // derived python classes can differ in what they override, simply use + // different shims + static int counter = 0; + std::ostringstream osname; + osname << "Dispatcher" << ++counter; + const std::string& derivedName = osname.str(); + + // generate proxy class with the relevant method dispatchers + std::ostringstream code; + + // start class declaration + code << "namespace __cppjit_internal {\n" + << "class " << derivedName << " : "; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + if (ibase != 0) + code << ", "; + code << "public ::" << base_infos[ibase].bname_scoped; + } + code << " {\n"; + if (!isDeepHierarchy) + code << "protected:\n cppjit::cpyrt::DispatchPtr _internal_self;\n"; + code << "public:\n"; + + // add a virtual destructor for good measure, which is allowed to be + // "overridden" by the conventional __destruct__ method (note that __del__ is + // always called, too, if provided, but only when the Python object goes away; + // furthermore, if the Python object goes before the C++ one, only __del__ is + // called) + if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { + code << " virtual ~" << derivedName + << "() {\n" + " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" + " PyObject* iself = (PyObject*)_internal_self;\n" + " if (!iself || iself == Py_None)\n" + " return;\n" // safe, as destructor always returns void + " Py_INCREF(iself);\n" + " PyObject* mtPyName = PyUnicode_FromString(\"__destruct__\");\n" + " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, " + "mtPyName, NULL);\n" + " Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; + + // this being a destructor, print on exception rather than propagate using + // the magic C++ exception ... + code << " if (!pyresult) PyErr_Print();\n" + " else { Py_DECREF(pyresult); }\n" + " }\n"; + } else + code << " virtual ~" << derivedName << "() {}\n"; + + // methods: first collect all callables, then get overrides from base classes, + // for those that are still missing, search the hierarchy + PyObject* clbs = PyDict_New(); + PyObject* items = PyDict_Items(dct); + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) { + PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1); + if (PyCallable_Check(value)) + PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), + value); + } + Py_DECREF(items); + if (PyDict_DelItem(clbs, PyStrings::gInit) != 0) + PyErr_Clear(); + + // protected methods and data need their access changed in the C++ trampoline + // and then exposed on the Python side; so, collect their names as we go along + std::set protected_names; + + // simple case: methods from current class (collect constructors along the + // way) + int has_default = 0, has_cctor = 0, has_ctors = 0, has_tmpl_ctors = 0; + AllCtors_t ctors{base_infos.size()}; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + const auto& binfo = base_infos[ibase]; + + std::vector methods; + interop::GetClassMethods(binfo.btype, methods); + bool cctor_found = false, default_found = false, any_ctor_found = false; + for (auto& method : methods) { + if (interop::IsConstructor(method)) { + any_ctor_found = true; + if ((interop::IsPublicMethod(method) || + interop::IsProtectedMethod(method)) && + !interop::IsDeletedMethod(method)) { + interop::TCppIndex_t nreq = interop::GetMethodReqArgs(method); + if (nreq == 0) + default_found = true; + else if (!cctor_found && nreq == 1) { + const std::string& argtype = + interop::GetMethodArgCanonTypeAsString(method, 0); + if (TypeManip::compound(argtype) == "&" && + TypeManip::clean_type(argtype, false) == binfo.bname_scoped) + cctor_found = true; + } + ctors[ibase].push_back(method); + } + continue; + } + + std::string mtCppName = + interop::GetName(interop::TCppScope_t(method.data)); + PyObject* key = cpyrt_PyText_FromString(mtCppName.c_str()); + int contains = PyDict_Contains(dct, key); + if (contains == -1) + PyErr_Clear(); + if (contains != 1) { + Py_DECREF(key); + + // if the method is protected, we expose it through re-declaration and + // forwarding (using does not work here b/c there may be private + // overloads) + if (interop::IsProtectedMethod(method)) { + protected_names.insert(mtCppName); + + code << " " << interop::GetMethodReturnTypeAsString(method) << " " + << mtCppName << "("; + interop::TCppIndex_t nArgs = interop::GetMethodNumArgs(method); + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + if (i != 0) + code << ", "; + code << interop::GetMethodArgCanonTypeAsString(method, i) << " arg" + << i; + } + code << ") "; + if (interop::IsConstMethod(method)) + code << "const "; + code << "{\n return " << binfo.bname << "::" << mtCppName << "("; + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + if (i != 0) + code << ", "; + if (interop::IsRValueReferenceType( + interop::GetMethodArgType(method, i))) + code << "std::move(arg" << i << ")"; + else + code << "arg" << i; + } + code << ");\n }\n"; + } + + continue; + } + + InjectMethod(method, mtCppName, code); + + if (PyDict_DelItem(clbs, key) != 0) + PyErr_Clear(); // happens for overloads + Py_DECREF(key); + } + + // support for templated ctors in single inheritance (TODO: also multi + // possible?) + if (base_infos.size() == 1) { + std::vector templ_methods; + interop::GetTemplatedMethods(binfo.btype, templ_methods); + for (auto& method : templ_methods) { + if (interop::IsConstructor(method)) { + any_ctor_found = true; + has_tmpl_ctors += 1; + break; // one suffices to map as argument packs are used + } + } + } + + // count the cctors and default ctors to determine whether each base has one + if (cctor_found || (!cctor_found && !any_ctor_found)) + has_cctor += 1; + if (default_found || (!default_found && !any_ctor_found)) + has_default += 1; + if (any_ctor_found && !has_tmpl_ctors) + has_ctors += 1; + } + + // try to locate left-overs in base classes + for (const auto& binfo : base_infos) { + if (PyDict_Size(clbs)) { + size_t nbases = interop::GetNumBases(binfo.btype); + for (size_t ibase = 0; ibase < nbases; ++ibase) { + interop::TCppScope_t tbase = + (interop::TCppScope_t)interop::GetBaseScope(binfo.btype, ibase); + + PyObject* keys = PyDict_Keys(clbs); + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) { + // TODO: should probably invert this looping; but that makes handling + // overloads clunky + PyObject* key = PyList_GET_ITEM(keys, i); + std::string mtCppName = cpyrt_PyText_AsString(key); + const auto& methods = FindBaseMethod(tbase, mtCppName); + for (auto method : methods) + InjectMethod(method, mtCppName, code); + if (!methods.empty()) { + if (PyDict_DelItem(clbs, key) != 0) + PyErr_Clear(); + } + } + Py_DECREF(keys); + } + } + } + Py_DECREF(clbs); + + // constructors: build up from the argument types of the base class, for use + // by the Python derived class (inheriting with/ "using" does not work b/c + // base class constructors may have been deleted), + build_constructors(derivedName, base_infos, ctors, code); + + // for working with C++ templates, additional constructors are needed to make + // sure the python object is properly carried, but they can only be generated + // if the base class supports them + if (1 < nBases && (!has_ctors || has_default == nBases)) + code << " " << derivedName << "() {}\n"; + if (has_cctor == nBases) { + code << " " << derivedName << "(const " << derivedName << "& other) : "; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + if (ibase != 0) + code << ", "; + code << base_infos[ibase].bname << "(other)"; + } + if (!isDeepHierarchy) + code << ", _internal_self(other._internal_self, this)"; + code << " {}\n"; + } + if (has_tmpl_ctors && base_infos.size() == 1) { + // support for templated ctors in single inheritance (TODO: also multi + // possible?) + code << " template\n " << derivedName + << "(Args... args) : " << base_infos[0].bname << "(args...) {}\n"; + } + + // destructor: default is fine + + // pull in data members that are protected + bool setPublic = false; + for (const auto& binfo : base_infos) { + std::vector datamems; + interop::GetDatamembers(binfo.btype, datamems); + for (auto data : datamems) { + if (interop::IsProtectedData(data)) { + const std::string dm_name = interop::GetFinalName(data); + if (dm_name != "_internal_self") { + const std::string& dname = interop::GetFinalName(data); + protected_names.insert(dname); + if (!setPublic) { + code << "public:\n"; + setPublic = true; + } + code << " using " << binfo.bname << "::" << dname << ";\n"; + } + } + } + } + + // initialize the dispatch pointer for all direct bases that have one + BaseInfos_t::size_type disp_inited = 0; + code << "public:\n static void _init_dispatchptr(" << derivedName + << "* inst, PyObject* self) {\n"; + if (1 < base_infos.size()) { + for (const auto& binfo : base_infos) { + if (interop::CheckDatamember(binfo.btype, "_internal_self")) { + code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n"; + disp_inited += 1; + } + } + } + // The dispatch initializer is only used in constructors, and C++ object start + // out as owned by C++, with Python ownership explicitly set only later. To + // match, the dispatch pointer needs to start out with a hard reference, i.e. + // C++ ownership of the dispatch object. If the constructor has __creates__ + // set to True (default), then a call to PythonOwns() will switch the hard ref + // to a weak ref, preventing accidental circular references. + if (disp_inited != base_infos.size()) + code << " new ((void*)&inst->_internal_self) " + "cppjit::cpyrt::DispatchPtr{self, true};\n"; + code << " }"; + + // provide an accessor to re-initialize after round-tripping from C++ + // (internal) + code << "\n static PyObject* _get_dispatch(" << derivedName + << "* inst) {\n" + " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" + " PyObject* res = (PyObject*)inst->_internal_self;\n" + " Py_XINCREF(res); return res;\n }"; + + // finish class declaration + code << "};\n}"; + + // finally, compile the code + if (!interop::Compile(code.str())) { + err << "failed to compile the dispatcher code"; + return false; + } + + // keep track internally of the actual C++ type (this is used in + // CPPConstructor to call the dispatcher's one instead of the base) + interop::TCppScope_t disp = + interop::GetFullScope("__cppjit_internal::" + derivedName); + if (!disp) { + err << "failed to retrieve the internal dispatcher"; + return false; + } + klass->fCppType = disp; + + // at this point, the dispatcher only lives in C++, as opposed to regular + // classes that are part of the hierarchy in Python, so create it, which will + // cache it for later use by e.g. the MemoryRegulator + unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross); + PyObject* disp_proxy = cpyrt::CreateScopeProxy(disp, 0, flags); + if (flags) + ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross; + ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython; + + // finally, to expose protected members, copy them over from the C++ + // dispatcher base to the Python dictionary (the C++ dispatcher's Python proxy + // is not a base of the Python class to keep the inheritance tree intact) + for (const auto& name : protected_names) { + PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict); +#if PY_VERSION_HEX < 0x30d00f0 + PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); +#else + PyObject* pyf = nullptr; + PyMapping_GetOptionalItemString(disp_dct, (char*)name.c_str(), &pyf); +#endif + if (pyf) { + PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf); + Py_DECREF(pyf); + } + Py_DECREF(disp_dct); + } + + Py_XDECREF(disp_proxy); + + return true; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.h b/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.h new file mode 100644 index 0000000000000..3621ee86496dd --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.h @@ -0,0 +1,17 @@ +#ifndef CPYRT_DISPATCHER_H +#define CPYRT_DISPATCHER_H + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPScope; + +// helper that inserts dispatchers for virtual methods +bool InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, + std::ostringstream& err); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DISPATCHER_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Executors.cxx b/bindings/pyroot/cppjit/src/cpyrt/Executors.cxx new file mode 100644 index 0000000000000..02c59c3ca963d --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Executors.cxx @@ -0,0 +1,1472 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "DeclareExecutors.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +typedef std::unordered_map ExecFactories_t; +static ExecFactories_t gExecFactories; + +extern PyObject* gNullPtrObject; + +extern std::set gIteratorTypes; +} // namespace cppjit::cpyrt + +//- helpers ------------------------------------------------------------------ +namespace { + +#ifdef WITH_THREAD +class GILControl { +public: + GILControl() : fSave(PyEval_SaveThread()) {} + ~GILControl() { PyEval_RestoreThread(fSave); } + +private: + PyThreadState* fSave; +}; +#endif + +} // unnamed namespace + +#ifdef WITH_THREAD +#define CPPJIT_IMPL_GILCALL(rtype, tcode) \ + static inline rtype GILCall##tcode(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + cpyrt::CallContext* ctxt) { \ + if (!ReleasesGIL(ctxt)) \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + GILControl gc{}; \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + } +#else +#define CPPJIT_IMPL_GILCALL(rtype, tcode) \ + static inline rtype GILCall##tcode(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + cpyrt::CallContext* ctxt) { \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + } +#endif + +// clang-format off +CPPJIT_IMPL_GILCALL(void, V) +CPPJIT_IMPL_GILCALL(unsigned char, B) +CPPJIT_IMPL_GILCALL(char, C) +CPPJIT_IMPL_GILCALL(short, H) +CPPJIT_IMPL_GILCALL(int, I) +CPPJIT_IMPL_GILCALL(long, L) +CPPJIT_IMPL_GILCALL(PY_LONG_LONG, LL) +CPPJIT_IMPL_GILCALL(float, F) +CPPJIT_IMPL_GILCALL(double, D) +CPPJIT_IMPL_GILCALL(PY_LONG_DOUBLE, LD) +CPPJIT_IMPL_GILCALL(void*, R) +// clang-format on + +static inline interop::TCppObject_t GILCallO(interop::TCppMethod_t method, + interop::TCppObject_t self, + cpyrt::CallContext* ctxt, + interop::TCppScope_t klass) { + interop::TCppType_t klass_ty = interop::GetTypeFromScope(klass); +#ifdef WITH_THREAD + if (!ReleasesGIL(ctxt)) +#endif + return interop::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), + klass_ty); +#ifdef WITH_THREAD + GILControl gc{}; + return interop::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), + klass_ty); +#endif +} + +static inline interop::TCppObject_t +GILCallConstructor(interop::TCppMethod_t method, interop::TCppScope_t klass, + cpyrt::CallContext* ctxt) { +#ifdef WITH_THREAD + if (!ReleasesGIL(ctxt)) +#endif + return interop::CallConstructor(method, klass, ctxt->GetEncodedSize(), + ctxt->GetArgs()); +#ifdef WITH_THREAD + GILControl gc{}; + return interop::CallConstructor(method, klass, ctxt->GetEncodedSize(), + ctxt->GetArgs()); +#endif +} + +static inline PyObject* cpyrt_PyText_FromLong(long cl) { + // python chars are range(256) + if (cl < -256 || cl > 255) { + PyErr_SetString(PyExc_ValueError, "char conversion out of range"); + return nullptr; + } + int c = (int)cl; + if (c < 0) + return cpyrt_PyText_FromFormat("%c", 256 - std::abs(c)); + return cpyrt_PyText_FromFormat("%c", c); +} + +static inline PyObject* cpyrt_PyText_FromULong(unsigned long uc) { + // TODO: range check here? + if (255 < uc) { + PyErr_SetString(PyExc_ValueError, "char conversion out of range"); + return nullptr; + } + int c = (int)uc; + return cpyrt_PyText_FromFormat("%c", c); +} + +static inline PyObject* cpyrt_PyBool_FromLong(long b) { + PyObject* result = (bool)b ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +//- base executor implementation --------------------------------------------- +cpyrt::Executor::~Executor() { /* empty */ } + +//- executors for built-ins -------------------------------------------------- +PyObject* cpyrt::BoolExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python bool return + // value + bool retval = GILCallB(method, self, ctxt); + PyObject* result = retval ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BoolConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python bool return + // value + return cpyrt_PyBool_FromLong(*((bool*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute , construct python string return + // value with the single char + return cpyrt_PyText_FromLong((int)GILCallC(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CharConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char + return cpyrt_PyText_FromLong(*((char*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UCharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char + return cpyrt_PyText_FromLong((unsigned char)GILCallB(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UCharConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char from the pointer return + return cpyrt_PyText_FromLong(*((unsigned char*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Int8ConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + return PyInt_FromLong(*((int8_t*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UInt8ConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + return PyInt_FromLong(*((uint8_t*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::WCharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single wide char + wchar_t res = (wchar_t)GILCallL(method, self, ctxt); + return PyUnicode_FromWideChar(&res, 1); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Char16Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char16 + char16_t res = (char16_t)GILCallL(method, self, ctxt); + return PyUnicode_DecodeUTF16((const char*)&res, sizeof(char16_t), nullptr, + nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Char32Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char32 + char32_t res = (char32_t)GILCallL(method, self, ctxt); + return PyUnicode_DecodeUTF32((const char*)&res, sizeof(char32_t), nullptr, + nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::IntExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((int)GILCallI(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Int8Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((int8_t)GILCallC(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UInt8Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((uint8_t)GILCallB(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ShortExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((short)GILCallH(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long return + // value + return PyLong_FromLong((long)GILCallL(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ULongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unsigned long + // return value + return PyLong_FromUnsignedLong((unsigned long)GILCallLL(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongLongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long long + // return value + PY_LONG_LONG result = GILCallLL(method, self, ctxt); + return PyLong_FromLongLong(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ULongLongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unsigned long + // long return value + PY_ULONG_LONG result = (PY_ULONG_LONG)GILCallLL(method, self, ctxt); + return PyLong_FromUnsignedLongLong(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::FloatExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallF(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::DoubleExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallD(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongDoubleExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallLD(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +bool cpyrt::RefExecutor::SetAssignable(PyObject* pyobject) { + // prepare "buffer" for by-ref returns, used with __setitem__ + if (pyobject) { + Py_INCREF(pyobject); + fAssignable = pyobject; + return true; + } + + fAssignable = nullptr; + return false; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFEXEC(name, type, stype, F1, F2) \ + PyObject* cpyrt::name##RefExecutor::Execute(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + CallContext* ctxt) { \ + type* ref = (type*)GILCallR(method, self, ctxt); \ + if (!ref) { /* can happen if wrapper compilation fails */ \ + PyErr_SetString(PyExc_ReferenceError, \ + "attempt to access a null-pointer"); \ + return nullptr; \ + } \ + if (!fAssignable) \ + return F1((stype) * ref); \ + else { \ + *ref = (type)F2(fAssignable); \ + Py_DECREF(fAssignable); \ + fAssignable = nullptr; \ + if (*ref == (type) - 1 && PyErr_Occurred()) \ + return nullptr; \ + Py_INCREF(Py_None); \ + return Py_None; \ + } \ + } + +// clang-format off +CPPJIT_IMPL_REFEXEC(Bool, bool, long, cpyrt_PyBool_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(Char, char, long, cpyrt_PyText_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UChar, unsigned char, unsigned long, cpyrt_PyText_FromULong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Int8, int8_t, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UInt8, uint8_t, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Short, short, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UShort, unsigned short, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Int, int, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UInt, unsigned int, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Long, long, long, PyLong_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(ULong, unsigned long, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(LongLong, PY_LONG_LONG, PY_LONG_LONG, PyLong_FromLongLong, PyLong_AsLongLong) +CPPJIT_IMPL_REFEXEC(ULongLong, PY_ULONG_LONG, PY_ULONG_LONG, PyLong_FromUnsignedLongLong, PyLongOrInt_AsULong64) +CPPJIT_IMPL_REFEXEC(Float, float, double, PyFloat_FromDouble, PyFloat_AsDouble) +CPPJIT_IMPL_REFEXEC(Double, double, double, PyFloat_FromDouble, PyFloat_AsDouble) +CPPJIT_IMPL_REFEXEC(LongDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, PyFloat_FromDouble, PyFloat_AsDouble) +// clang-format on + +template +static inline PyObject* PyComplex_FromComplex(const std::complex& c) { + return PyComplex_FromDoubles(c.real(), c.imag()); +} + +template +static inline std::complex PyComplex_AsComplex(PyObject* pycplx) { + Py_complex cplx = PyComplex_AsCComplex(pycplx); + return std::complex(cplx.real, cplx.imag); +} + +CPPJIT_IMPL_REFEXEC(ComplexD, std::complex, std::complex, + PyComplex_FromComplex, PyComplex_AsComplex) + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLStringRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return python string return + // value + std::string* result = (std::string*)GILCallR(method, self, ctxt); + if (!fAssignable) { + return cpyrt_PyText_FromStringAndSize(result->c_str(), result->size()); + } + + if (!cpyrt_PyText_Check(fAssignable)) { + PyErr_Format(PyExc_TypeError, "wrong type in assignment (string expected)"); + return nullptr; + } + + *result = std::string(cpyrt_PyText_AsString(fAssignable), + cpyrt_PyText_GET_SIZE(fAssignable)); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return None + GILCallV(method, self, ctxt); + if (PyErr_Occurred()) + return nullptr; + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + char* result = (char*)GILCallR(method, self, ctxt); + if (!result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; + } + + return cpyrt_PyText_FromString(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + char** result = (char**)GILCallR(method, self, ctxt); + if (!result || !*result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; + } + + return cpyrt_PyText_FromString(*result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::WCStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + wchar_t* result = (wchar_t*)GILCallR(method, self, ctxt); + if (!result) { + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); + } + + return PyUnicode_FromWideChar(result, wcslen(result)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CString16Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + char16_t* result = (char16_t*)GILCallR(method, self, ctxt); + if (!result) { + char16_t w = u'\0'; + return PyUnicode_DecodeUTF16((const char*)&w, 0, nullptr, nullptr); + } + + return PyUnicode_DecodeUTF16((const char*)result, + std::char_traits::length(result) * + sizeof(char16_t), + nullptr, nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CString32Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + char32_t* result = (char32_t*)GILCallR(method, self, ctxt); + if (!result) { + char32_t w = U'\0'; + return PyUnicode_DecodeUTF32((const char*)&w, 0, nullptr, nullptr); + } + + return PyUnicode_DecodeUTF32((const char*)result, + std::char_traits::length(result) * + sizeof(char32_t), + nullptr, nullptr); +} + +//- pointer/array executors -------------------------------------------------- +PyObject* cpyrt::VoidArrayExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long return + // value + intptr_t* result = (intptr_t*)GILCallR(method, self, ctxt); + if (!result) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + return CreatePointerView(result, fShape); +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_ARRAY_EXEC(name, type, suffix) \ + PyObject* cpyrt::name##ArrayExecutor::Execute(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + CallContext* ctxt) { \ + return CreateLowLevelView##suffix((type*)GILCallR(method, self, ctxt), \ + fShape); \ + } + +// clang-format off +CPPJIT_IMPL_ARRAY_EXEC(Bool, bool, ) +CPPJIT_IMPL_ARRAY_EXEC(SChar, signed char, ) +CPPJIT_IMPL_ARRAY_EXEC(UChar, unsigned char, ) +CPPJIT_IMPL_ARRAY_EXEC(Byte, std::byte, ) +CPPJIT_IMPL_ARRAY_EXEC(Int8, int8_t, _i8) +CPPJIT_IMPL_ARRAY_EXEC(UInt8, uint8_t, _i8) +CPPJIT_IMPL_ARRAY_EXEC(Short, short, ) +CPPJIT_IMPL_ARRAY_EXEC(UShort, unsigned short, ) +CPPJIT_IMPL_ARRAY_EXEC(Int, int, ) +CPPJIT_IMPL_ARRAY_EXEC(UInt, unsigned int, ) +CPPJIT_IMPL_ARRAY_EXEC(Long, long, ) +CPPJIT_IMPL_ARRAY_EXEC(ULong, unsigned long, ) +CPPJIT_IMPL_ARRAY_EXEC(LLong, long long, ) +CPPJIT_IMPL_ARRAY_EXEC(ULLong, unsigned long long, ) +CPPJIT_IMPL_ARRAY_EXEC(Float, float, ) +CPPJIT_IMPL_ARRAY_EXEC(Double, double, ) +CPPJIT_IMPL_ARRAY_EXEC(LDouble, long double, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexF, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexD, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexI, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexL, std::complex, ) +// clang-format on + +//- special cases ------------------------------------------------------------ +#define CPPJIT_COMPLEX_EXEC(code, type) \ + PyObject* cpyrt::Complex##code##Executor::Execute( \ + interop::TCppMethod_t method, interop::TCppObject_t self, \ + CallContext* ctxt) { \ + static interop::TCppScope_t scopeid = \ + interop::GetScope("std::complex<" #type ">"); \ + std::complex* result = \ + (std::complex*)GILCallO(method, self, ctxt, scopeid).data; \ + if (!result) { \ + PyErr_SetString(PyExc_ValueError, \ + "NULL result where temporary expected"); \ + return nullptr; \ + } \ + \ + PyObject* pyres = PyComplex_FromDoubles(result->real(), result->imag()); \ + ::operator delete(result); /* interop::CallO calls ::operator new */ \ + return pyres; \ + } + +CPPJIT_COMPLEX_EXEC(D, double) + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + + // TODO: make use of GILLCallS (?!) + static interop::TCppScope_t sSTLStringScope = + interop::GetFullScope("std::string"); + std::string* result = + (std::string*)GILCallO(method, self, ctxt, sSTLStringScope).data; + if (!result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; + } + if (PyErr_Occurred()) { + delete result; + return nullptr; + } + + PyObject* pyresult = + cpyrt_PyText_FromStringAndSize(result->c_str(), result->size()); + delete result; // interop::CallO allocates and constructs a string, so it must + // be properly destroyed + + return pyresult; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLWStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + static interop::TCppScope_t sSTLWStringScope = + interop::GetFullScope("std::wstring"); + std::wstring* result = + (std::wstring*)GILCallO(method, self, ctxt, sSTLWStringScope).data; + if (!result) { + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); + } + + PyObject* pyresult = PyUnicode_FromWideChar(result->c_str(), result->size()); + delete result; // interop::CallO allocates and constructs a string, so it must + // be properly destroyed + + return pyresult; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstancePtrExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python proxy object + // return value + return BindCppObject((void*)GILCallR(method, self, ctxt), fClass); +} + +//---------------------------------------------------------------------------- +cpyrt::InstanceExecutor::InstanceExecutor(interop::TCppScope_t klass) + : fClass(klass), fFlags(CPPInstance::kIsValue | CPPInstance::kIsOwner) { + /* empty */ +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execution will bring a temporary in existence + interop::TCppObject_t value = GILCallO(method, self, ctxt, fClass); + + if (!value) { + if (!PyErr_Occurred()) // callee may have set a python error itself + PyErr_Format(PyExc_ValueError, + "cannot allocate the temporary returned by '%s': the size " + "of its return type is unknown", + interop::GetMethodSignature(method, true).c_str()); + return nullptr; + } + + // the result can then be bound + PyObject* pyobj = BindCppObjectNoCast(value, fClass, fFlags); + if (!pyobj) + return nullptr; + + // python ref counting will now control this object's life span; it will be + // deleted b/c it is marked as a by-value object owned by python (from fFlags) + return pyobj; +} + +//---------------------------------------------------------------------------- +cpyrt::IteratorExecutor::IteratorExecutor(interop::TCppScope_t klass) + : InstanceExecutor(klass) { + fFlags |= CPPInstance::kNoMemReg | + CPPInstance::kNoWrapConv; // adds to flags from base class +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // executor binds the result to the left-hand side, overwriting if an old + // object + PyObject* result = BindCppObject((void*)GILCallR(method, self, ctxt), fClass); + if (!result || !fAssignable) + return result; + else { + // this generic code is quite slow compared to its C++ equivalent ... + PyObject* assign = PyObject_GetAttr(result, PyStrings::gAssign); + if (!assign) { + PyErr_Clear(); + PyObject* descr = PyObject_Str(result); + if (descr && cpyrt_PyText_CheckExact(descr)) { + PyErr_Format(PyExc_TypeError, "cannot assign to return object (%s)", + cpyrt_PyText_AsString(descr)); + } else { + PyErr_SetString(PyExc_TypeError, "cannot assign to result"); + } + Py_XDECREF(descr); + Py_DECREF(result); + Py_DECREF(fAssignable); + fAssignable = nullptr; + return nullptr; + } + + PyObject* res2 = + PyObject_CallFunction(assign, const_cast("O"), fAssignable); + + Py_DECREF(assign); + Py_DECREF(result); + Py_DECREF(fAssignable); + fAssignable = nullptr; + + if (res2) { + Py_DECREF(res2); // typically, *this from operator=() + Py_RETURN_NONE; + } + + return nullptr; + } +} + +//---------------------------------------------------------------------------- +static inline PyObject* SetInstanceCheckError(PyObject* pyobj) { + PyObject* pystr = PyObject_Str(pyobj); + if (pystr) { + PyErr_Format(PyExc_TypeError, "C++ object expected, got %s", + cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + } else + PyErr_SetString(PyExc_TypeError, "C++ object expected"); + return nullptr; +} + +PyObject* cpyrt::InstancePtrPtrExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python C++ proxy + // object return ptr value + if (fAssignable && !CPPInstance_Check(fAssignable)) + return SetInstanceCheckError(fAssignable); + + void** result = (void**)GILCallR(method, self, ctxt); + if (!fAssignable) + return BindCppObject((void*)result, fClass, + CPPInstance::kIsPtrPtr | CPPInstance::kIsReference); + + CPPInstance* cppinst = (CPPInstance*)fAssignable; + *result = cppinst->GetObject(); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstancePtrRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python C++ proxy + // object ignoring ref) return ptr value + if (fAssignable && !CPPInstance_Check(fAssignable)) + return SetInstanceCheckError(fAssignable); + + void** result = (void**)GILCallR(method, self, ctxt); + if (!fAssignable) + return BindCppObject(*result, fClass); + + CPPInstance* cppinst = (CPPInstance*)fAssignable; + *result = cppinst->GetObject(); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceArrayExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct TupleOfInstances + // from return value + return BindCppObjectArray((void*)GILCallR(method, self, ctxt), fClass, + {fSize}); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ConstructorExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t klass, + CallContext* ctxt) { + // package return address in PyObject* for caller to handle appropriately (see + // CPPConstructor for the actual build of the PyObject) + return (PyObject*)GILCallConstructor(method, interop::TCppScope_t(klass.data), + ctxt) + .data; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::PyObjectExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return python object + return (PyObject*)GILCallR(method, self, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::FunctionPointerExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return std::function from func + // ptr + + // A function pointer in clang is represented by a Type, not a FunctionDecl + // and it's not possible to get the latter from the former: the backend will + // need to support both. Since that is far in the future, we'll use a + // std::function instead. + void* address = (void*)GILCallR(method, self, ctxt); + if (address) + return Utility::FuncPtr2StdFunction(fRetType, fSignature, address); + + // There's currently now way to return a typed function nullptr, but given + // that this is a return value (thus can not be set) and overloading function + // based on function pointer return type is likely uncommon ... + Py_INCREF(gNullPtrObject); + return gNullPtrObject; +} + +//- factories ---------------------------------------------------------------- +cpyrt::Executor* cpyrt::CreateExecutor(const std::string& fullType, + cdims_t dims) { + // The matching of the fulltype to an executor factory goes through up to 4 + // levels: + // 1) full, qualified match + // 2) drop '&' as by ref/full type is often pretty much the same python-wise + // 3) C++ classes, either by ref/ptr or by value + // 4) additional special case for enums + // + // If all fails, void is used, which will cause the return type to be ignored + // on use + + if (fullType.empty()) + return nullptr; + + // an exactly matching executor is best + ExecFactories_t::iterator h = gExecFactories.find(fullType); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // resolve typedefs etc. + const std::string resolvedType = interop::ResolveName(fullType); + + // a full, qualified matching executor is preferred + if (resolvedType != fullType) { + h = gExecFactories.find(resolvedType); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedType); + std::string realType = TypeManip::clean_type(resolvedType, false); + + // accept unqualified type (as python does not know about qualifiers) + h = gExecFactories.find(realType + cpd); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + realType = TypeManip::remove_const(realType); + h = gExecFactories.find(realType + cpd); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // simple array types + if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), + '*') == cpd.size()) { + h = gExecFactories.find(realType + " ptr"); + if (h != gExecFactories.end()) + return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) + ? dims_t(cpd.size()) + : dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd == "[]") { + h = gExecFactories.find(realType + "*"); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // C++ classes and special cases + Executor* result = 0; + if (interop::TCppScope_t klass = interop::GetFullScope(realType)) { + if (Utility::IsSTLIterator(realType) || + gIteratorTypes.find(fullType) != gIteratorTypes.end()) { + if (cpd == "") + return new IteratorExecutor(klass); + } + + if (cpd == "") + result = new InstanceExecutor(klass); + else if (cpd == "&") + result = new InstanceRefExecutor(klass); + else if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrExecutor(klass); + else if (cpd == "*&") + result = new InstancePtrRefExecutor(klass); + else if (cpd == "[]") { + Py_ssize_t asize = TypeManip::array_size(resolvedType); + if (0 < asize) + result = new InstanceArrayExecutor(klass, asize); + else + result = new InstancePtrRefExecutor(klass); + } else + result = new InstancePtrExecutor(klass); + } else if (resolvedType.find("(*)") != std::string::npos || + (resolvedType.find("::*)") != std::string::npos)) { + // this is a function pointer + // TODO: find better way of finding the type + auto pos1 = resolvedType.find('('); + auto pos2 = resolvedType.find("*)"); + auto pos3 = resolvedType.rfind(')'); + result = new FunctionPointerExecutor( + resolvedType.substr(0, pos1), + resolvedType.substr(pos2 + 2, pos3 - pos2 - 1)); + } else { + // unknown: void* may work ("user knows best"), void will fail on use of + // return value + h = (cpd == "") ? gExecFactories.find("void") + : gExecFactories.find("void ptr"); + } + + if (!result && h != gExecFactories.end()) + // executor factory available, use it to create executor + result = (h->second)(dims); + + return result; // may still be null +} + +cpyrt::Executor* cpyrt::CreateExecutor(interop::TCppType_t type, cdims_t dims) { + // The matching of the fulltype to an executor factory goes through up to 4 + // levels: + // 1) full, qualified match + // 2) drop '&' as by ref/full type is often pretty much the same python-wise + // 3) C++ classes, either by ref/ptr or by value + // 4) additional special case for enums + // + // If all fails, void is used, which will cause the return type to be ignored + // on use + + // an exactly matching executor is best + std::string fullType = interop::GetTypeAsString(type); + if (fullType.size() >= 2 && + fullType.compare(fullType.size() - 2, 2, " &") == 0) + fullType = fullType.substr(0, fullType.size() - 2) + "&"; + + ExecFactories_t::iterator h = gExecFactories.find(fullType); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // resolve typedefs etc. + interop::TCppType_t resolvedType = interop::ResolveType(type); + { + // if resolvedType is a reference to enum + // then it should be reduced to reference + // to the underlying interger + resolvedType = interop::ResolveEnumReferenceType(resolvedType); + // similarly for pointers + resolvedType = interop::ResolveEnumPointerType(resolvedType); + } + // FIXME: avoid string comparisons and parsing + std::string resolvedTypeStr = interop::GetTypeAsString(resolvedType); + if (interop::IsFunctionPointerType(resolvedType)) { + resolvedTypeStr.erase( + std::remove(resolvedTypeStr.begin(), resolvedTypeStr.end(), ' '), + resolvedTypeStr.end()); + if (resolvedTypeStr.rfind("(void)") != std::string::npos) + resolvedTypeStr = + resolvedTypeStr.substr(0, resolvedTypeStr.size() - 6) + "()"; + } + + // a full, qualified matching executor is preferred + if (resolvedTypeStr != fullType) { + h = gExecFactories.find(resolvedTypeStr); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + interop::TCppType_t realType = interop::IsFunctionPointerType(resolvedType) + ? resolvedType + : interop::GetRealType(resolvedType); + std::string realTypeStr = interop::IsFunctionPointerType(resolvedType) + ? resolvedTypeStr + : interop::GetTypeAsString(realType); + const std::string compounded = cpd.empty() ? realTypeStr : realTypeStr + cpd; + + // accept unqualified type (as python does not know about qualifiers) + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + realTypeStr = TypeManip::remove_const(realTypeStr); + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // simple array types + if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), + '*') == cpd.size()) { + h = gExecFactories.find(realTypeStr + " ptr"); + if (h != gExecFactories.end()) + return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) + ? dims_t(cpd.size()) + : dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd == "[]") { + h = gExecFactories.find(realTypeStr + "*"); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // C++ classes and special cases + Executor* result = 0; + if (interop::IsClassType(realType)) { + interop::TCppScope_t klass = interop::GetScopeFromType(realType); + if (resolvedTypeStr.find("iterator") != std::string::npos || + gIteratorTypes.find(fullType) != gIteratorTypes.end()) { + if (cpd == "") + return new IteratorExecutor(klass); + } + + if (cpd == "") + result = new InstanceExecutor(klass); + else if (cpd == "&") + result = new InstanceRefExecutor(klass); + else if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrExecutor(klass); + else if (cpd == "*&") + result = new InstancePtrRefExecutor(klass); + else if (cpd == "[]") { + Py_ssize_t asize = TypeManip::array_size(resolvedTypeStr); + if (0 < asize) + result = new InstanceArrayExecutor(klass, asize); + else + result = new InstancePtrRefExecutor(klass); + } else + result = new InstancePtrExecutor(klass); + } else if (realTypeStr.find("(*)") != std::string::npos || + (realTypeStr.find("::*)") != std::string::npos)) { + // this is a function pointer + // TODO: find better way of finding the type + auto pos1 = realTypeStr.find('('); + auto pos2 = realTypeStr.find("*)"); + auto pos3 = realTypeStr.rfind(')'); + result = new FunctionPointerExecutor( + realTypeStr.substr(0, pos1), + realTypeStr.substr(pos2 + 2, pos3 - pos2 - 1)); + } else { + // unknown: void* may work ("user knows best"), void will fail on use of + // return value + h = (cpd == "") ? gExecFactories.find("void") + : gExecFactories.find("void ptr"); + } + + if (!result && h != gExecFactories.end()) + // executor factory available, use it to create executor + result = (h->second)(dims); + + return result; // may still be null +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void cpyrt::DestroyExecutor(Executor* p) { + if (p && p->HasState()) + delete p; // state-less executors are always shared +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterExecutor(const std::string& name, ef_t fac) { + // register a custom executor + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) + return false; + + gExecFactories[name] = fac; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterExecutorAlias(const std::string& name, + const std::string& target) { + // register a custom executor that is a reference to an existing converter + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) + return false; + + auto t = gExecFactories.find(target); + if (t == gExecFactories.end()) + return false; + + gExecFactories[name] = t->second; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::UnregisterExecutor(const std::string& name) { + // remove a custom executor + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) { + gExecFactories.erase(f); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void* cpyrt::CallVoidP(interop::TCppMethod_t meth, interop::TCppObject_t obj, + CallContext* ctxt) { + return GILCallR(meth, obj, ctxt); +} + +//---------------------------------------------------------------------------- +namespace { + +using namespace cppjit::cpyrt; + +#define WSTRING1 "std::basic_string" +#define WSTRING2 \ + "std::basic_string,std::allocator>" + +//-- aliasing special case: C complex (is binary compatible with C++ +// std::complex) +#ifndef _WIN32 +#define CCOMPLEX_D "_Complex double" +#define CCOMPLEX_F "_Complex float" +#else +#define CCOMPLEX_D "_C_double_complex" +#define CCOMPLEX_F "_C_float_complex" +#endif + +struct InitExecFactories_t { +public: + InitExecFactories_t() { + // load all executor factories in the global map 'gExecFactories' + cpyrt::ExecFactories_t& gf = gExecFactories; + + // factories for built-ins + gf["bool"] = (ef_t) + [](cdims_t) { + static BoolExecutor e{}; + return &e; + }; + gf["bool&"] = (ef_t) + [](cdims_t) { return new BoolRefExecutor{}; }; + gf["const bool&"] = (ef_t) + [](cdims_t) { + static BoolConstRefExecutor e{}; + return &e; + }; + gf["char"] = (ef_t) + [](cdims_t) { + static CharExecutor e{}; + return &e; + }; + gf["signed char"] = gf["char"]; + gf["unsigned char"] = (ef_t) + [](cdims_t) { + static UCharExecutor e{}; + return &e; + }; + gf["char&"] = (ef_t) + [](cdims_t) { return new CharRefExecutor{}; }; + gf["signed char&"] = gf["char&"]; + gf["unsigned char&"] = + (ef_t) + [](cdims_t) { return new UCharRefExecutor{}; }; + gf["const char&"] = (ef_t) + [](cdims_t) { + static CharConstRefExecutor e{}; + return &e; + }; + gf["const signed char&"] = gf["const char&"]; + gf["const unsigned char&"] = (ef_t) + [](cdims_t) { + static UCharConstRefExecutor e{}; + return &e; + }; + gf["wchar_t"] = (ef_t) + [](cdims_t) { + static WCharExecutor e{}; + return &e; + }; + gf["char16_t"] = (ef_t) + [](cdims_t) { + static Char16Executor e{}; + return &e; + }; + gf["char32_t"] = (ef_t) + [](cdims_t) { + static Char32Executor e{}; + return &e; + }; + gf["int8_t"] = (ef_t) + [](cdims_t) { + static Int8Executor e{}; + return &e; + }; + gf["int8_t&"] = (ef_t) + [](cdims_t) { return new Int8RefExecutor{}; }; + gf["const int8_t&"] = (ef_t) + [](cdims_t) { + static Int8ConstRefExecutor e{}; + return &e; + }; + gf["uint8_t"] = (ef_t) + [](cdims_t) { + static UInt8Executor e{}; + return &e; + }; + gf["uint8_t&"] = (ef_t) + [](cdims_t) { return new UInt8RefExecutor{}; }; + gf["const uint8_t&"] = (ef_t) + [](cdims_t) { + static UInt8ConstRefExecutor e{}; + return &e; + }; + gf["short"] = (ef_t) + [](cdims_t) { + static ShortExecutor e{}; + return &e; + }; + gf["short&"] = (ef_t) + [](cdims_t) { return new ShortRefExecutor{}; }; + gf["int"] = (ef_t) + [](cdims_t) { + static IntExecutor e{}; + return &e; + }; + gf["int&"] = (ef_t) + [](cdims_t) { return new IntRefExecutor{}; }; + gf["unsigned short"] = gf["int"]; + gf["unsigned short&"] = + (ef_t) + [](cdims_t) { return new UShortRefExecutor{}; }; + gf["unsigned long"] = (ef_t) + [](cdims_t) { + static ULongExecutor e{}; + return &e; + }; + gf["unsigned long&"] = + (ef_t) + [](cdims_t) { return new ULongRefExecutor{}; }; + gf["unsigned int"] = gf["unsigned long"]; + gf["unsigned int&"] = + (ef_t) + [](cdims_t) { return new UIntRefExecutor{}; }; + gf["long"] = (ef_t) + [](cdims_t) { + static LongExecutor e{}; + return &e; + }; + gf["long&"] = (ef_t) + [](cdims_t) { return new LongRefExecutor{}; }; + gf["unsigned long"] = (ef_t) + [](cdims_t) { + static ULongExecutor e{}; + return &e; + }; + gf["unsigned long&"] = + (ef_t) + [](cdims_t) { return new ULongRefExecutor{}; }; + gf["long long"] = (ef_t) + [](cdims_t) { + static LongLongExecutor e{}; + return &e; + }; + gf["long long&"] = + (ef_t) + [](cdims_t) { return new LongLongRefExecutor{}; }; + gf["unsigned long long"] = (ef_t) + [](cdims_t) { + static ULongLongExecutor e{}; + return &e; + }; + gf["unsigned long long&"] = + (ef_t) + [](cdims_t) { return new ULongLongRefExecutor{}; }; + + gf["float"] = (ef_t) + [](cdims_t) { + static FloatExecutor e{}; + return &e; + }; + gf["float&"] = (ef_t) + [](cdims_t) { return new FloatRefExecutor{}; }; + gf["double"] = (ef_t) + [](cdims_t) { + static DoubleExecutor e{}; + return &e; + }; + gf["double&"] = (ef_t) + [](cdims_t) { return new DoubleRefExecutor{}; }; + gf["long double"] = (ef_t) + [](cdims_t) { + static LongDoubleExecutor e{}; + return &e; + }; // TODO: lost precision + gf["long double&"] = + (ef_t) + [](cdims_t) { return new LongDoubleRefExecutor{}; }; + gf["std::complex"] = (ef_t) + [](cdims_t) { + static ComplexDExecutor e{}; + return &e; + }; + gf["std::complex&"] = + (ef_t) + [](cdims_t) { return new ComplexDRefExecutor{}; }; + gf["void"] = (ef_t) + [](cdims_t) { + static VoidExecutor e{}; + return &e; + }; + + // pointer/array factories + gf["void ptr"] = + (ef_t) + [](cdims_t d) { return new VoidArrayExecutor{d}; }; + gf["bool ptr"] = + (ef_t) + [](cdims_t d) { return new BoolArrayExecutor{d}; }; + gf["unsigned char ptr"] = + (ef_t) + [](cdims_t d) { return new UCharArrayExecutor{d}; }; + gf["const unsigned char ptr"] = gf["unsigned char ptr"]; + gf["std::byte ptr"] = + (ef_t) + [](cdims_t d) { return new ByteArrayExecutor{d}; }; + gf["const std::byte ptr"] = gf["std::byte ptr"]; + gf["int8_t ptr"] = + (ef_t) + [](cdims_t d) { return new Int8ArrayExecutor{d}; }; + gf["uint8_t ptr"] = + (ef_t) + [](cdims_t d) { return new UInt8ArrayExecutor{d}; }; + gf["short ptr"] = + (ef_t) + [](cdims_t d) { return new ShortArrayExecutor{d}; }; + gf["unsigned short ptr"] = + (ef_t) + [](cdims_t d) { return new UShortArrayExecutor{d}; }; + gf["int ptr"] = (ef_t) + [](cdims_t d) { return new IntArrayExecutor{d}; }; + gf["unsigned int ptr"] = + (ef_t) + [](cdims_t d) { return new UIntArrayExecutor{d}; }; + gf["long ptr"] = + (ef_t) + [](cdims_t d) { return new LongArrayExecutor{d}; }; + gf["unsigned long ptr"] = + (ef_t) + [](cdims_t d) { return new ULongArrayExecutor{d}; }; + gf["long long ptr"] = + (ef_t) + [](cdims_t d) { return new LLongArrayExecutor{d}; }; + gf["unsigned long long ptr"] = + (ef_t) + [](cdims_t d) { return new ULLongArrayExecutor{d}; }; + gf["float ptr"] = + (ef_t) + [](cdims_t d) { return new FloatArrayExecutor{d}; }; + gf["double ptr"] = + (ef_t) + [](cdims_t d) { return new DoubleArrayExecutor{d}; }; + gf["long double ptr"] = + (ef_t) + [](cdims_t d) { return new LDoubleArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexFArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexDArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexIArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexLArrayExecutor{d}; }; + + // aliases + gf["internal_enum_type_t"] = gf["int"]; + gf["internal_enum_type_t&"] = gf["int&"]; + gf["internal_enum_type_t ptr"] = gf["int ptr"]; + gf["std::byte"] = gf["uint8_t"]; + gf["std::byte&"] = gf["uint8_t&"]; + gf["const std::byte&"] = gf["const uint8_t&"]; + gf["const byte&"] = gf["const uint8_t&"]; + gf["std::int8_t"] = gf["int8_t"]; + gf["std::int8_t&"] = gf["int8_t&"]; + gf["const std::int8_t&"] = gf["const int8_t&"]; + gf["std::int8_t ptr"] = gf["int8_t ptr"]; + gf["std::uint8_t"] = gf["uint8_t"]; + gf["std::uint8_t&"] = gf["uint8_t&"]; + gf["const std::uint8_t&"] = gf["const uint8_t&"]; + gf["std::uint8_t ptr"] = gf["uint8_t ptr"]; +#ifdef _WIN32 + gf["__int64"] = gf["long long"]; + gf["__int64&"] = gf["long long&"]; + gf["__int64 ptr"] = gf["long long ptr"]; + gf["unsigned __int64"] = gf["unsigned long long"]; + gf["unsigned __int64&"] = gf["unsigned long long&"]; + gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; +#endif + gf[CCOMPLEX_D] = gf["std::complex"]; + gf[CCOMPLEX_D "&"] = gf["std::complex&"]; + gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; + gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; + + // We always need these executors when cppjit is based on an unpatched + // ROOT, because the "long long" types are always converted to Long64_t + // and ULong64_t already at the ROOT Meta level. + // See + // https://github.com/root-project/root/issues/15872#issuecomment-2174092763 + gf["Long64_t"] = gf["long long"]; + gf["Long64_t&"] = gf["long long&"]; + gf["Long64_t ptr"] = gf["long long ptr"]; + gf["ULong64_t"] = gf["unsigned long long"]; + gf["ULong64_t&"] = gf["unsigned long long&"]; + gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; + + // factories for special cases + gf["const char*"] = (ef_t) + [](cdims_t) { + static CStringExecutor e{}; + return &e; + }; + gf["char*"] = gf["const char*"]; + gf["const char*&"] = (ef_t) + [](cdims_t) { + static CStringRefExecutor e{}; + return &e; + }; + gf["char*&"] = gf["const char*&"]; + gf["const signed char*"] = gf["const char*"]; + // gf["signed char*"] = gf["char*"]; + gf["signed char ptr"] = + (ef_t) + [](cdims_t d) { return new SCharArrayExecutor{d}; }; + gf["wchar_t*"] = (ef_t) + [](cdims_t) { + static WCStringExecutor e{}; + return &e; + }; + gf["char16_t*"] = (ef_t) + [](cdims_t) { + static CString16Executor e{}; + return &e; + }; + gf["char32_t*"] = (ef_t) + [](cdims_t) { + static CString32Executor e{}; + return &e; + }; + gf["std::basic_string"] = (ef_t) + [](cdims_t) { + static STLStringExecutor e{}; + return &e; + }; + gf["std::basic_string&"] = + (ef_t) + [](cdims_t) { return new STLStringRefExecutor{}; }; + gf["std::basic_string"] = (ef_t) + [](cdims_t) { + static STLWStringExecutor e{}; + return &e; + }; + gf[WSTRING1] = gf["std::basic_string"]; + gf[WSTRING2] = gf["std::basic_string"]; + gf["__init__"] = (ef_t) + [](cdims_t) { + static ConstructorExecutor e{}; + return &e; + }; + gf["PyObject*"] = (ef_t) + [](cdims_t) { + static PyObjectExecutor e{}; + return &e; + }; + gf["_object*"] = gf["PyObject*"]; + gf["FILE*"] = gf["void ptr"]; + } +} initExecvFactories_; + +} // unnamed namespace diff --git a/bindings/pyroot/cppjit/src/cpyrt/Executors.h b/bindings/pyroot/cppjit/src/cpyrt/Executors.h new file mode 100644 index 0000000000000..c8b0702b5fee7 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Executors.h @@ -0,0 +1,52 @@ +#ifndef CPYRT_EXECUTORS_H +#define CPYRT_EXECUTORS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +struct CallContext; + +class CPYRT_CLASS_EXPORT Executor { +public: + virtual ~Executor(); + virtual PyObject* Execute(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*) = 0; + virtual bool HasState() { return false; } +}; + +// special case needed for CPPSetItem +class RefExecutor : public Executor { +public: + RefExecutor() : fAssignable(nullptr) {} + virtual bool SetAssignable(PyObject*); + bool HasState() override { return true; } + +protected: + PyObject* fAssignable; +}; + +// create/destroy executor from fully qualified type (public API) +CPYRT_EXPORT Executor* CreateExecutor(const std::string& fullType, cdims_t = 0); +CPYRT_EXPORT Executor* CreateExecutor(interop::TCppType_t type, cdims_t = 0); +CPYRT_EXPORT void DestroyExecutor(Executor* p); +typedef Executor* (*ef_t)(cdims_t); +CPYRT_EXPORT bool RegisterExecutor(const std::string& name, ef_t fac); +CPYRT_EXPORT bool RegisterExecutorAlias(const std::string& name, + const std::string& target); +CPYRT_EXPORT bool UnregisterExecutor(const std::string& name); + +// helper for the actual call +CPYRT_EXPORT void* CallVoidP(interop::TCppMethod_t, interop::TCppObject_t, + CallContext*); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_EXECUTORS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.cxx b/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.cxx new file mode 100644 index 0000000000000..11f725c633082 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.cxx @@ -0,0 +1,1283 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "PyStrings.h" + +// Standard +#include +#include +#include +#include + +//= memoryview-like object =================================================== +// This is largely setup just like Python builtin memory view objects, with +// the exceptions that there is no need of a "base" object (it views on C++ +// memory, not a Python object with a buffer interface), it uses the cpyrt +// converters, and typed results and assignments are supported. Code reused +// under PSF License Version 2. + +//- helpers ------------------------------------------------------------------ +static inline void set_strides(Py_buffer& view, size_t itemsize, bool isfix) { + if (isfix) { + Py_ssize_t stride = itemsize; + for (Py_ssize_t idim = view.ndim - 1; 0 <= idim; --idim) { + view.strides[idim] = stride; + stride *= view.shape[idim]; + } + } else { + view.strides[view.ndim - 1] = itemsize; + for (Py_ssize_t idim = 0; idim < view.ndim - 1; ++idim) + view.strides[idim] = view.itemsize; + } +} + +// The creators mark an outermost dimension of unknown extent with this cap +// rather than with UNKNOWN_SIZE, keeping byte lengths and loops non-negative. +static inline Py_ssize_t fake_max(size_t elemsize) { + return INT_MAX / (Py_ssize_t)elemsize; +} + +//= cpyrt low level view construction/destruction ========================= +static cpyrt::LowLevelView* ll_new(PyTypeObject* subtype, PyObject*, + PyObject*) { + // Create a new low level ptr type + cpyrt::LowLevelView* pyobj = + (cpyrt::LowLevelView*)subtype->tp_alloc(subtype, 0); + if (!pyobj) + return nullptr; + + memset(&pyobj->fBufInfo, 0, sizeof(Py_buffer)); + (intptr_t&)pyobj->fBufInfo.internal |= cpyrt::LowLevelView::kIsCppArray; + pyobj->fBuf = nullptr; + pyobj->fConverter = nullptr; + pyobj->fElemCnv = nullptr; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static void ll_dealloc(cpyrt::LowLevelView* pyobj) { + // Destruction requires the deletion of the converter (if any) + PyMem_Free(pyobj->fBufInfo.shape); + PyMem_Free(pyobj->fBufInfo.strides); + if ((intptr_t)pyobj->fBufInfo.internal & cpyrt::LowLevelView::kIsOwner) { + if ((intptr_t)pyobj->fBufInfo.internal & cpyrt::LowLevelView::kIsCppArray) + delete[] pyobj->fBuf; + else + free(pyobj->fBuf); + } + + if (pyobj->fElemCnv != pyobj->fConverter && pyobj->fElemCnv && + pyobj->fElemCnv->HasState()) + delete pyobj->fElemCnv; + + if (pyobj->fConverter && pyobj->fConverter->HasState()) + delete pyobj->fConverter; + + Py_TYPE(pyobj)->tp_free((PyObject*)pyobj); +} + +//---------------------------------------------------------------------------- +#define CPYRT_LL_FLAG_GETSET(name, flag, doc) \ + static PyObject* ll_get##name(cpyrt::LowLevelView* pyobj) { \ + return PyBool_FromLong((long)((intptr_t)pyobj->fBufInfo.internal & flag)); \ + } \ + \ + static int ll_set##name(cpyrt::LowLevelView* pyobj, PyObject* value, \ + void*) { \ + long settrue = PyLong_AsLong(value); \ + if (settrue == -1 && PyErr_Occurred()) { \ + PyErr_SetString(PyExc_ValueError, \ + #doc " should be either True or False"); \ + return -1; \ + } \ + \ + if ((bool)settrue) \ + (intptr_t&)pyobj->fBufInfo.internal |= flag; \ + else \ + (intptr_t&)pyobj->fBufInfo.internal &= ~flag; \ + \ + return 0; \ + } + +// clang-format off +CPYRT_LL_FLAG_GETSET(ownership, cpyrt::LowLevelView::kIsOwner, __python_owns__) +CPYRT_LL_FLAG_GETSET(cpparray, cpyrt::LowLevelView::kIsCppArray, __cpp_array__) +// clang-format on + +//--------------------------------------------------------------------------- +static PyObject* ll_typecode(cpyrt::LowLevelView* self, void*) { + return cpyrt_PyText_FromString((char*)self->fBufInfo.format); +} + +//- Copy memoryview buffers ================================================= + +// The functions in this section take a source and a destination buffer +// with the same logical structure: format, itemsize, ndim and shape +// are identical, with ndim > 0. + +// Check for the presence of suboffsets in the first dimension. +#define HAVE_PTR(suboffsets, dim) (suboffsets && suboffsets[dim] >= 0) +// Adjust ptr if suboffsets are present. +#define ADJUST_PTR(ptr, suboffsets, dim) \ + (HAVE_PTR(suboffsets, dim) ? *((char**)ptr) + suboffsets[dim] : ptr) + +// Assumptions: ndim >= 1. The macro tests for a corner case that should +// perhaps be explicitly forbidden in the PEP. +#define HAVE_SUBOFFSETS_IN_LAST_DIM(view) \ + (view->suboffsets && view->suboffsets[dest->ndim - 1] >= 0) + +//--------------------------------------------------------------------------- +static inline int last_dim_is_contiguous(const Py_buffer* dest, + const Py_buffer* src) { + assert(dest->ndim > 0 && src->ndim > 0); + return (!HAVE_SUBOFFSETS_IN_LAST_DIM(dest) && + !HAVE_SUBOFFSETS_IN_LAST_DIM(src) && + dest->strides[dest->ndim - 1] == dest->itemsize && + src->strides[src->ndim - 1] == src->itemsize); +} + +//--------------------------------------------------------------------------- +static inline bool equiv_shape(const Py_buffer* dest, const Py_buffer* src) { + // Two shapes are equivalent if they are either equal or identical up + // to a zero element at the same position. For example, in NumPy arrays + // the shapes [1, 0, 5] and [1, 0, 7] are equivalent. + if (dest->ndim != src->ndim) + return false; + + for (int i = 0; i < dest->ndim; i++) { + if (dest->shape[i] != src->shape[i]) + return 0; + if (dest->shape[i] == 0) + break; + } + + return true; +} + +//--------------------------------------------------------------------------- +static bool equiv_structure(const Py_buffer* dest, const Py_buffer* src) { + // Check that the logical structure of the destination and source buffers + // is identical. + if (strcmp(dest->format, src->format) != 0 || + dest->itemsize != src->itemsize || !equiv_shape(dest, src)) { + PyErr_SetString(PyExc_ValueError, "low level pointer assignment: lvalue " + "and rvalue have different structures"); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +static void copy_base(const Py_ssize_t* shape, Py_ssize_t itemsize, char* dptr, + const Py_ssize_t* dstrides, const Py_ssize_t* dsuboffsets, + char* sptr, const Py_ssize_t* sstrides, + const Py_ssize_t* ssuboffsets, char* mem) { + // Base case for recursive multi-dimensional copying. Contiguous arrays are + // copied with very little overhead. Assumptions: ndim == 1, mem == nullptr or + // sizeof(mem) == shape[0] * itemsize. + if (!mem) { // contiguous + Py_ssize_t size = shape[0] * itemsize; + if (dptr + size < sptr || sptr + size < dptr) + memcpy(dptr, sptr, size); // no overlapping + else + memmove(dptr, sptr, size); + } else { + char* p; + Py_ssize_t i; + for (i = 0, p = mem; i < shape[0]; + p += itemsize, sptr += sstrides[0], i++) { + char* xsptr = ADJUST_PTR(sptr, ssuboffsets, 0); + memcpy(p, xsptr, itemsize); + } + for (i = 0, p = mem; i < shape[0]; + p += itemsize, dptr += dstrides[0], i++) { + char* xdptr = ADJUST_PTR(dptr, dsuboffsets, 0); + memcpy(xdptr, p, itemsize); + } + } +} + +//--------------------------------------------------------------------------- +static int copy_single(Py_buffer* dest, Py_buffer* src) { + // Faster copying of one-dimensional arrays. + char* mem = nullptr; + + assert(dest->ndim == 1); + + if (!equiv_structure(dest, src)) + return -1; + + if (!last_dim_is_contiguous(dest, src)) { + mem = (char*)PyMem_Malloc(dest->shape[0] * dest->itemsize); + if (!mem) { + PyErr_NoMemory(); + return -1; + } + } + + copy_base(dest->shape, dest->itemsize, (char*)dest->buf, dest->strides, + dest->suboffsets, (char*)src->buf, src->strides, src->suboffsets, + mem); + + if (mem) + PyMem_Free(mem); + + return 0; +} + +//- Indexing and slicing ---------------------------------------------------- +static char* lookup_dimension(Py_buffer& view, char* ptr, int dim, + Py_ssize_t index) { + Py_ssize_t nitems; // items in the given dimension + + assert(view.shape); + assert(view.strides); + + nitems = view.shape[dim]; + if (index < 0) { + if (nitems != cpyrt::UNKNOWN_SIZE) + index += nitems; + else { + PyErr_Format( + PyExc_IndexError, + "negative index not supported on dimension %d with unknown size", + dim + 1); + return nullptr; + } + } + + if (view.strides[dim] == cpyrt::UNKNOWN_SIZE) { + PyErr_Format( + PyExc_IndexError, + "multi index not supported on dimension %d with unknown stride", + dim + 1); + return nullptr; + } + + if (nitems != cpyrt::UNKNOWN_SIZE && (index < 0 || index >= nitems)) { + PyErr_Format(PyExc_IndexError, "index out of bounds on dimension %d", + dim + 1); + return nullptr; + } + + ptr += view.strides[dim] * index; + ptr = ADJUST_PTR(ptr, view.suboffsets, dim); + + return ptr; +} + +// Get the pointer to the item at index. +//--------------------------------------------------------------------------- +static inline void* ptr_from_index(cpyrt::LowLevelView* llview, + Py_ssize_t index) { + Py_buffer& view = llview->fBufInfo; + return lookup_dimension(view, (char*)llview->get_buf(), 0, index); +} + +// Get the pointer to the item at tuple. +//--------------------------------------------------------------------------- +static void* ptr_from_tuple(cpyrt::LowLevelView* llview, PyObject* tup) { + Py_buffer& view = llview->fBufInfo; + + Py_ssize_t nindices = PyTuple_GET_SIZE(tup); + if (nindices > view.ndim) { + PyErr_Format(PyExc_TypeError, + "cannot index %d-dimension view with %zd-element tuple", + view.ndim, nindices); + return nullptr; + } + + char* ptr = (char*)llview->get_buf(); + for (Py_ssize_t dim = 0; dim < nindices; dim++) { + Py_ssize_t index; + index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(tup, dim), PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return nullptr; + + ptr = lookup_dimension(view, ptr, (int)dim, index); + if (!ptr) + return nullptr; + + if (!((intptr_t)view.internal & cpyrt::LowLevelView::kIsFixed) && + dim != view.ndim - 1) + ptr = *(char**)ptr; + } + return ptr; +} + +//= mapping methods ========================================================= +static Py_ssize_t ll_length(cpyrt::LowLevelView* self) { + if (!self->get_buf()) + return 0; + return self->fBufInfo.ndim == 0 ? 1 : self->fBufInfo.shape[0]; +} + +//--------------------------------------------------------------------------- +static inline int init_slice(Py_buffer* base, PyObject* _key, int dim) { + Py_ssize_t start, stop, step, slicelength; + + PyObject* key = _key; + + if (PySlice_GetIndicesEx(key, base->shape[dim], &start, &stop, &step, + &slicelength) < 0) + return -1; + + if (!base->suboffsets || dim == 0) { + adjust_buf: + base->buf = (char*)base->buf + base->strides[dim] * start; + } else { + Py_ssize_t n = dim - 1; + while (n >= 0 && base->suboffsets[n] < 0) + n--; + if (n < 0) + goto adjust_buf; // all suboffsets are negative + base->suboffsets[n] = base->suboffsets[n] + base->strides[dim] * start; + } + base->shape[dim] = slicelength; + base->strides[dim] = base->strides[dim] * step; + + return 0; +} + +//--------------------------------------------------------------------------- +static bool is_multislice(PyObject* key) { + if (!PyTuple_Check(key)) + return false; + + Py_ssize_t size = PyTuple_GET_SIZE(key); + if (size == 0) + return false; + + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* x = PyTuple_GET_ITEM(key, i); + if (!PySlice_Check(x)) + return false; + } + return true; +} + +//--------------------------------------------------------------------------- +static Py_ssize_t is_multiindex(PyObject* key) { + if (!PyTuple_Check(key)) + return 0; + + Py_ssize_t size = PyTuple_GET_SIZE(key); + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* x = PyTuple_GET_ITEM(key, i); + if (!PyIndex_Check(x)) + return 0; + } + return 1; +} + +// Return the item at index. In a one-dimensional view, this is an object +// with the type specified by view->format. Otherwise, the item is a sub-view. +// The function is used in ll_subscript() and ll_as_sequence. +//--------------------------------------------------------------------------- +static PyObject* ll_item(cpyrt::LowLevelView* self, Py_ssize_t index) { + Py_buffer& view = self->fBufInfo; + + if (!self->get_buf()) { + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + return nullptr; + } + + if (view.ndim == 0) { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return nullptr; + } + + void* ptr = ptr_from_index(self, index); + if (ptr) { + bool isfix = (intptr_t)view.internal & cpyrt::LowLevelView::kIsFixed; + if (self->fBufInfo.ndim == 1 || !isfix) + return self->fConverter->FromMemory(ptr); + return self->fConverter->FromMemory((void*)&ptr); + } + + return nullptr; // error already set by lookup_dimension +} + +// Return the item at position *key* (a tuple of indices). +//--------------------------------------------------------------------------- +static PyObject* ll_item_multi(cpyrt::LowLevelView* self, PyObject* tup) { + Py_buffer& view = self->fBufInfo; + Py_ssize_t nindices = PyTuple_GET_SIZE(tup); + + if (nindices < view.ndim) { + // TODO: implement + PyErr_SetString(PyExc_NotImplementedError, "sub-views are not implemented"); + return nullptr; + } + + void* ptr = ptr_from_tuple(self, tup); + + // if there's an error, it was already set by lookup_dimension + return ptr ? self->fElemCnv->FromMemory(ptr) : nullptr; +} + +// llp[obj] returns an object holding the data for one element if obj +// fully indexes the lowlevelptr or another lowlevelptr object if it +// does not. +// +// 0-d lowlevelptr objects can be referenced using llp[...] or llp[()] +// but not with anything else. +//--------------------------------------------------------------------------- +static PyObject* ll_subscript(cpyrt::LowLevelView* self, PyObject* key) { + Py_buffer& view = self->fBufInfo; + + if (view.ndim == 0) { + if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0) { + return self->fConverter->FromMemory(self->get_buf()); + } else if (key == Py_Ellipsis) { + Py_INCREF(self); + return (PyObject*)self; + } else { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return nullptr; + } + } + + if (PyIndex_Check(key)) { + Py_ssize_t index = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return nullptr; + return ll_item(self, index); + } else if (PySlice_Check(key)) { + if (view.ndim == 1) { + Py_ssize_t start, stop, step, slicelen; + if (PySlice_Unpack(key, &start, &stop, &step) < 0) + return nullptr; + + slicelen = PySlice_AdjustIndices(view.shape[0], &start, &stop, step); + if (slicelen <= 0) + slicelen = view.shape[0]; + + char* buf = (char*)self->get_buf(); + char* slice_buf = new char[slicelen * view.itemsize]; + size_t isize = view.itemsize; + for (size_t i = 0, cur = 0; i < (size_t)slicelen; cur += step, ++i) { + for (size_t j = 0; j < isize; ++j) + slice_buf[i * isize + j] = buf[(start + cur) * isize + j]; + } + + cpyrt::LowLevelView* ll = self->fCreator(slice_buf, {1, slicelen}); + if (!ll) + delete[] slice_buf; + else + (intptr_t&)ll->fBufInfo.internal |= cpyrt::LowLevelView::kIsOwner; + + return (PyObject*)ll; + + } else { + // TODO: handle slicing. This should be simpler than the memoryview + // case as there is no Python object holding the buffer. + PyErr_SetString(PyExc_NotImplementedError, + "multi-dimensional slicing is not implemented"); + return nullptr; + } + } else if (is_multiindex(key)) { + return ll_item_multi(self, key); + } else if (is_multislice(key)) { + PyErr_SetString(PyExc_NotImplementedError, + "multi-dimensional slicing is not implemented"); + return nullptr; + } + + PyErr_SetString(PyExc_TypeError, "invalid slice key"); + return nullptr; +} + +//--------------------------------------------------------------------------- +static int ll_ass_sub(cpyrt::LowLevelView* self, PyObject* key, + PyObject* value) { + Py_buffer& view = self->fBufInfo; + Py_buffer src; + + if (view.readonly) { + PyErr_SetString(PyExc_TypeError, "cannot modify read-only memory"); + return -1; + } + + if (value == nullptr) { + PyErr_SetString(PyExc_TypeError, "cannot delete memory"); + return -1; + } + + if (view.ndim == 0) { + if (key == Py_Ellipsis || + (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0)) { + return self->fConverter->ToMemory(value, self->get_buf()) ? 0 : -1; + } else { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return -1; + } + } + + if (PyIndex_Check(key)) { + Py_ssize_t index; + if (1 < view.ndim) { + PyErr_SetString(PyExc_NotImplementedError, + "sub-views are not implemented"); + return -1; + } + index = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return -1; + void* ptr = ptr_from_index(self, index); + if (ptr == nullptr) + return -1; + return self->fConverter->ToMemory(value, ptr) ? 0 : -1; + } + + // one-dimensional: fast path + if (PySlice_Check(key) && view.ndim == 1) { + Py_buffer dest; // sliced view + Py_ssize_t arrays[3]; + int ret = -1; + + // rvalue must be an exporter + if (PyObject_GetBuffer(value, &src, PyBUF_FULL_RO) < 0) { + if (src.obj) + PyBuffer_Release(&src); + return ret; + } + + dest = view; + dest.shape = &arrays[0]; + dest.shape[0] = view.shape[0]; + dest.strides = &arrays[1]; + dest.strides[0] = view.strides[0]; + if (view.suboffsets) { + dest.suboffsets = &arrays[2]; + dest.suboffsets[0] = view.suboffsets[0]; + } + + if (init_slice(&dest, key, 0) < 0) + return -1; + dest.len = dest.shape[0] * dest.itemsize; + + ret = copy_single(&dest, &src); + PyBuffer_Release(&src); + return ret; + } + + if (is_multiindex(key)) { + // TODO: implement + if (PyTuple_GET_SIZE(key) < view.ndim) { + PyErr_SetString(PyExc_NotImplementedError, + "sub-views are not implemented"); + return -1; + } + void* ptr = ptr_from_tuple(self, key); + if (ptr == nullptr) + return -1; + return self->fElemCnv->ToMemory(value, ptr) ? 0 : -1; + } + + if (PySlice_Check(key) || is_multislice(key)) { + // TODO: implement + PyErr_SetString(PyExc_NotImplementedError, + "LowLevelView slice assignments are currently restricted " + "to ndim = 1"); + return -1; + } + + PyErr_SetString(PyExc_TypeError, "invalid slice key"); + return -1; +} + +//--------------------------------------------------------------------------- +static int ll_getbuf(cpyrt::LowLevelView* self, Py_buffer* view, int flags) { + // Simplified from memoryobject, as we're always dealing with C arrays. + + // start with full copy + *view = self->fBufInfo; + + if (!(flags & PyBUF_FORMAT)) { + /* NULL indicates that the buffer's data type has been cast to 'B'. + view->itemsize is the _previous_ itemsize. If shape is present, + the equality product(shape) * itemsize = len still holds at this + point. The equality calcsize(format) = itemsize does _not_ hold + from here on! */ + view->format = NULL; + } + + if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { + PyErr_SetString(PyExc_BufferError, + "underlying buffer is not Fortran contiguous"); + return -1; + } + + if (!(flags & PyBUF_FORMAT)) { + /* PyBUF_SIMPLE or PyBUF_WRITABLE: at this point buf is C-contiguous, + so base->buf = ndbuf->data. */ + if (view->format != NULL) { + /* PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT do + not make sense. */ + PyErr_Format( + PyExc_BufferError, + "cannot cast to unsigned bytes if the format flag is present"); + return -1; + } + /* product(shape) * itemsize = len and calcsize(format) = itemsize + do _not_ hold from here on! */ + view->ndim = 1; + view->shape = NULL; + } + + view->obj = (PyObject*)self; + Py_INCREF(view->obj); + + return 0; +} + +//= iterator protocol ======================================================= +static PyObject* ll_iter(PyObject* self) { + // The index iterator indexes through getitem, just like python would do by + // default, except that it checks the size externally to raise StopIteration, + // rather than geitem failing. + + using namespace cppjit::cpyrt; + + indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); + if (!ii) + return nullptr; + + Py_INCREF(self); + ii->ii_container = self; + ii->ii_pos = 0; + ii->ii_len = ll_length((LowLevelView*)self); + + PyObject_GC_Track(ii); + return (PyObject*)ii; +} + +//- mapping methods --------------------------------------------------------- +static PyMappingMethods ll_as_mapping = { + (lenfunc)ll_length, // mp_length + (binaryfunc)ll_subscript, // mp_subscript + (objobjargproc)ll_ass_sub, // mp_ass_subscript +}; + +//- sequence methods -------------------------------------------------------- +static PySequenceMethods ll_as_sequence = { + (lenfunc)ll_length, // sq_length + 0, // sq_concat + 0, // sq_repeat + (ssizeargfunc)ll_item, // sq_item + 0, // sq_slice + 0, // sq_ass_item + 0, // sq_ass_slice + 0, // sq_contains + 0, // sq_inplace_concat + 0, // sq_inplace_repeat +}; + +//- buffer methods ---------------------------------------------------------- +static PyBufferProcs ll_as_buffer = { + (getbufferproc)ll_getbuf, // bf_getbuffer + 0, // bf_releasebuffer +}; + +//--------------------------------------------------------------------------- +static PyObject* ll_shape(cpyrt::LowLevelView* self) { + Py_buffer& view = self->fBufInfo; + + PyObject* shape = PyTuple_New(view.ndim); + if (!shape) + return nullptr; + for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) { + PyObject* pydim = PyInt_FromSsize_t(view.shape[idim]); + if (!pydim) { + Py_DECREF(shape); + return nullptr; + } + PyTuple_SET_ITEM(shape, idim, pydim); + } + + return shape; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_reshape_error(cpyrt::LowLevelView* self, PyObject* shape, + const char* why) { + PyObject* current = ll_shape(self); + if (!current) + return nullptr; + PyErr_Format(PyExc_ValueError, + "cannot reshape array of shape %S into shape %S: %s", current, + shape, why); + Py_DECREF(current); + return nullptr; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_reshape(cpyrt::LowLevelView* self, PyObject* shape) { + // Fill in the dimensions of the buffer that are not known from its type. + // + // A view is created with the rank and layout of its C++ type: a flat block + // for a rank-1 or fixed-size array, an array of row pointers otherwise. The + // strides and the converter projecting sub-views are derived from that and + // cannot be re-derived from a shape alone, so the rank of a view never + // changes and a dimension, once known, stays what it is. What reshaping can + // do is provide the extent of a dimension that the type leaves open, such as + // the size of an array behind a pointer. + if (!PyTuple_Check(shape)) { + if (shape) { + PyObject* pystr = PyObject_Str(shape); + if (pystr) { + PyErr_Format(PyExc_TypeError, "tuple object expected, received %s", + cpyrt_PyText_AsStringChecked(pystr)); + Py_DECREF(pystr); + return nullptr; + } + } + PyErr_SetString(PyExc_TypeError, "tuple object expected"); + return nullptr; + } + + Py_buffer& view = self->fBufInfo; + if (view.ndim < 1 || !view.shape || !view.strides) { + PyErr_SetString(PyExc_TypeError, + "this low level view has no dimensions to set"); + return nullptr; + } + + // An unknown outermost dimension holds the fake max for the element size + // (the innermost stride), any other unknown dimension UNKNOWN_SIZE. An + // empty dimension of a non-fixed view may also be filled in: it came from + // a pointer, with nothing behind it yet that a size could contradict. + bool isfix = (intptr_t)view.internal & cpyrt::LowLevelView::kIsFixed; + Py_ssize_t elemsize = view.strides[view.ndim - 1]; + Py_ssize_t fakemax = fake_max(elemsize); + Py_ssize_t unit0 = view.ndim == 1 ? elemsize : view.itemsize; + + Py_ssize_t ndim = PyTuple_GET_SIZE(shape); + cpyrt::dims_t dims(ndim); + for (Py_ssize_t idim = 0; idim < ndim; ++idim) { + Py_ssize_t nlen = PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); + if (nlen == -1 && PyErr_Occurred()) + return nullptr; + if (nlen < cpyrt::UNKNOWN_SIZE) { + PyErr_SetString(PyExc_ValueError, "negative dimensions are not allowed"); + return nullptr; + } + if (nlen == cpyrt::UNKNOWN_SIZE) // store as the creators would + nlen = idim == 0 ? fakemax : cpyrt::UNKNOWN_SIZE; + else if (PY_SSIZE_T_MAX / (idim == 0 ? unit0 : elemsize) < nlen) + return ll_reshape_error(self, shape, "the shape is too large"); + dims[idim] = nlen; + } + + if (ndim != view.ndim) + return ll_reshape_error( + self, shape, + "the number of dimensions of a low level view is fixed by its type"); + + for (Py_ssize_t idim = 0; idim < ndim; ++idim) { + Py_ssize_t cur = view.shape[idim]; + bool unknown = cur == cpyrt::UNKNOWN_SIZE || + (idim == 0 && cur == fakemax) || (cur == 0 && !isfix); + if (!unknown && dims[idim] != cur) + return ll_reshape_error(self, shape, + "only dimensions of unknown size can be set"); + } + + for (Py_ssize_t idim = 0; idim < ndim; ++idim) + view.shape[idim] = dims[idim]; + + // the byte length counts the outermost dimension as the creators set it; + // the strides depend only on the layout and stay as they were laid down + view.len = dims[0] * unit0; + + Py_RETURN_NONE; +} + +//--------------------------------------------------------------------------- +static int ll_setshape(cpyrt::LowLevelView* self, PyObject* value, void*) { + if (!value) { + PyErr_SetString(PyExc_TypeError, "cannot delete the shape of a view"); + return -1; + } + + PyObject* result = ll_reshape(self, value); + if (!result) + return -1; + Py_DECREF(result); + return 0; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_array(cpyrt::LowLevelView* self, PyObject* args, + PyObject* kwds) { + // Construct a numpy array from the lowlevelview (w/o copy if possible); this + // uses the Python methods to avoid depending on numpy directly + + // Expect as most a dtype from the arguments; + static PyObject* npmod = PyImport_ImportModule("numpy"); // ref-count kept + if (!npmod) + return nullptr; + + bool docopy = false; + if (kwds) { + PyObject* pycp = PyObject_GetItem(kwds, cpyrt::PyStrings::gCopy); + if (!pycp) { + PyErr_SetString(PyExc_TypeError, + "__array__ only supports the \"copy\" keyword"); + return nullptr; + } + + docopy = PyObject_IsTrue(pycp); + Py_DECREF(pycp); + } + + if (!docopy) { // view requested + // expect possible dtype from the arguments, otherwise take it from the type + // code + PyObject* dtype; + if (!args || PyTuple_GET_SIZE(args) != 1) { + PyObject* npdtype = PyObject_GetAttr(npmod, cpyrt::PyStrings::gDType); + PyObject* typecode = ll_typecode(self, nullptr); + dtype = PyObject_CallFunctionObjArgs(npdtype, typecode, nullptr); + Py_DECREF(typecode); + Py_DECREF(npdtype); + } else { + dtype = PyTuple_GET_ITEM(args, 0); + Py_INCREF(dtype); + } + + if (!dtype) + return nullptr; + + PyObject* npfrombuf = + PyObject_GetAttr(npmod, cpyrt::PyStrings::gFromBuffer); + PyObject* view = PyObject_CallFunctionObjArgs(npfrombuf, (PyObject*)self, + dtype, nullptr); + Py_DECREF(dtype); + Py_DECREF(npfrombuf); + + return view; + + } else { // copy requested + PyObject* npcopy = PyObject_GetAttr(npmod, cpyrt::PyStrings::gCopy); + PyObject* newarr = + PyObject_CallFunctionObjArgs(npcopy, (PyObject*)self, nullptr); + Py_DECREF(npcopy); + + return newarr; + } + + // never get here + return nullptr; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_as_string(cpyrt::LowLevelView* self) { + // Interpret memory as a null-terminated char string. + Py_buffer& view = self->fBufInfo; + + if (strcmp(view.format, "b") != 0 || view.ndim != 1) { + PyErr_Format( + PyExc_TypeError, + "as_string only supported for 1-dim char strings (format: %s, dim: %d)", + view.format, (int)view.ndim); + return nullptr; + } + + char* buf = (char*)self->get_buf(); + size_t sz = strnlen(buf, (size_t)view.shape[0]); + return cpyrt_PyText_FromStringAndSize(buf, sz); +} + +//--------------------------------------------------------------------------- +static PyMethodDef ll_methods[] = { + {(char*)"reshape", (PyCFunction)ll_reshape, METH_O, + (char*)"change the shape (not layout) of the low level view"}, + {(char*)"as_string", (PyCFunction)ll_as_string, METH_NOARGS, + (char*)"interpret memory as a null-terminated char string and return " + "Python str"}, + {(char*)"__array__", (PyCFunction)ll_array, METH_VARARGS | METH_KEYWORDS, + (char*)"return a numpy array from the low level view"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//--------------------------------------------------------------------------- +static PyGetSetDef ll_getset[] = { + {(char*)"__python_owns__", (getter)ll_getownership, (setter)ll_setownership, + (char*)"If true, python manages the life time of this buffer", nullptr}, + {(char*)"__cpp_array__", (getter)ll_getcpparray, (setter)ll_setcpparray, + (char*)"If true, this array was allocated with C++\'s new[]", nullptr}, + {(char*)"format", (getter)ll_typecode, nullptr, nullptr, nullptr}, + {(char*)"typecode", (getter)ll_typecode, nullptr, nullptr, nullptr}, + {(char*)"shape", (getter)ll_shape, (setter)ll_setshape, nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +namespace cppjit::cpyrt { + +//= cpyrt low level view type ============================================ +PyTypeObject LowLevelView_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)( + char*) "cppjit.LowLevelView", // tp_name + sizeof(cpyrt::LowLevelView), // tp_basicsize + 0, // tp_itemsize + (destructor)ll_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // itp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + &ll_as_sequence, // tp_as_sequence + &ll_as_mapping, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + &ll_as_buffer, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"memory view on C++ pointer", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + (getiterfunc)ll_iter, // tp_iter + 0, // tp_iternext + ll_methods, // tp_methods + 0, // tp_members + ll_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)ll_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +namespace { + +template struct typecode_traits {}; +template <> struct typecode_traits { + static constexpr const char* format = "?"; + static constexpr const char* name = "bool"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "char"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "SCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "B"; + static constexpr const char* name = "UCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "B"; + static constexpr const char* name = "UCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "char*"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "const char*"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "h"; + static constexpr const char* name = "short"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "H"; + static constexpr const char* name = "unsigned short"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "i"; + static constexpr const char* name = "int"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "I"; + static constexpr const char* name = "unsigned int"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "l"; + static constexpr const char* name = "long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "L"; + static constexpr const char* name = "unsigned long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "q"; + static constexpr const char* name = "long long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "Q"; + static constexpr const char* name = "unsigned long long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "f"; + static constexpr const char* name = "float"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "d"; + static constexpr const char* name = "double"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "D"; + static constexpr const char* name = "long double"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zf"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zd"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zi"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zl"; + static constexpr const char* name = "std::complex"; +}; + +} // unnamed namespace + +//--------------------------------------------------------------------------- +bool cpyrt::LowLevelView::resize(size_t sz) { + Py_buffer& bi = this->fBufInfo; + if (bi.ndim == 1 && bi.shape) { + bi.len = sz * bi.itemsize; + bi.shape[0] = sz; + return true; + } + + return false; +} + +//--------------------------------------------------------------------------- +template +static inline cpyrt::LowLevelView* +CreateLowLevelViewT(T* address, cpyrt::cdims_t shape, + const char* format = nullptr, const char* name = nullptr, + Py_ssize_t itemsize = -1) { + using namespace cppjit::cpyrt; + Py_ssize_t nx = + (shape.ndim() != UNKNOWN_SIZE) ? shape[0] : fake_max(sizeof(T)); + if (nx == UNKNOWN_SIZE) + nx = fake_max(sizeof(T)); + PyObject* args = PyTuple_New(0); + LowLevelView* llp = (LowLevelView*)LowLevelView_Type.tp_new( + &LowLevelView_Type, args, nullptr); + Py_DECREF(args); + + Py_buffer& view = llp->fBufInfo; + view.buf = address; + view.obj = nullptr; + view.readonly = 0; + view.format = (char*)(format ? format : typecode_traits::format); + view.ndim = int(shape.ndim() != UNKNOWN_SIZE ? shape.ndim() : 1); + view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + view.shape[0] = nx; // view.len / view.itemsize + view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + view.suboffsets = nullptr; + (intptr_t&)view.internal = cpyrt::LowLevelView::kIsCppArray; // assumed + bool isfix = shape.ndim() != UNKNOWN_SIZE; + if (isfix) { + for (int i = 0; i < shape.ndim(); ++i) + isfix = isfix && (shape[i] != UNKNOWN_SIZE); + if (isfix) + (intptr_t&)view.internal |= cpyrt::LowLevelView::kIsFixed; + } + + llp->fElemCnv = CreateConverter(name ? name : typecode_traits::name); + if (view.ndim == 1) { + // simple 1-dim array of the declared type + view.len = nx * sizeof(T); + view.itemsize = (itemsize > 0 ? (size_t)itemsize : sizeof(T)); + llp->fConverter = llp->fElemCnv; + } else { + // multi-dim array; sub-views are projected by using more LLViews + const size_t elemsize = isfix ? sizeof(T) : sizeof(void*); + view.len = nx * elemsize; + view.itemsize = elemsize; + for (Py_ssize_t idim = 1; idim < view.ndim; ++idim) + view.shape[idim] = shape[idim]; + + // peel off one dimension and create a new LLView converter + std::string tname{name ? name : typecode_traits::name}; + tname.append("[]"); // make sure to ask for another array + // TODO: although this will work, it means that "naive" loops are expensive + llp->fConverter = CreateConverter(tname, shape.sub()); + } + + set_strides(view, sizeof(T), isfix); + + return llp; +} + +//--------------------------------------------------------------------------- +template +static inline cpyrt::LowLevelView* +CreateLowLevelViewT(T** address, cpyrt::cdims_t shape, + const char* format = nullptr, const char* name = nullptr) { + using namespace cppjit::cpyrt; + LowLevelView* llp = + (LowLevelView*)CreateLowLevelViewT((T*)address, shape, format, name); + llp->set_buf((void**)address); + return llp; +} + +//--------------------------------------------------------------------------- +#define CPPJIT_RET_W_CREATOR(type, fname) \ + PyObject* (*c)(type, cdims_t) = &fname; \ + ll->fCreator = (LowLevelView::Creator_t)c; \ + return (PyObject*)ll + +#define CPPJIT_IMPL_VIEW_CREATOR(type) \ + PyObject* cpyrt::CreateLowLevelView(type* address, cdims_t shape) { \ + LowLevelView* ll = CreateLowLevelViewT(address, shape); \ + CPPJIT_RET_W_CREATOR(type*, CreateLowLevelView); \ + } \ + PyObject* cpyrt::CreateLowLevelView(type** address, cdims_t shape) { \ + LowLevelView* ll = CreateLowLevelViewT(address, shape); \ + CPPJIT_RET_W_CREATOR(type**, CreateLowLevelView); \ + } + +CPPJIT_IMPL_VIEW_CREATOR(bool); +CPPJIT_IMPL_VIEW_CREATOR(signed char); +CPPJIT_IMPL_VIEW_CREATOR(unsigned char); +CPPJIT_IMPL_VIEW_CREATOR(std::byte); +CPPJIT_IMPL_VIEW_CREATOR(short); +CPPJIT_IMPL_VIEW_CREATOR(unsigned short); +CPPJIT_IMPL_VIEW_CREATOR(int); +CPPJIT_IMPL_VIEW_CREATOR(unsigned int); +CPPJIT_IMPL_VIEW_CREATOR(long); +CPPJIT_IMPL_VIEW_CREATOR(unsigned long); +CPPJIT_IMPL_VIEW_CREATOR(long long); +CPPJIT_IMPL_VIEW_CREATOR(unsigned long long); +CPPJIT_IMPL_VIEW_CREATOR(float); +CPPJIT_IMPL_VIEW_CREATOR(double); +CPPJIT_IMPL_VIEW_CREATOR(long double); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); + +PyObject* cpyrt::CreateLowLevelView(char* address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape); + CPPJIT_RET_W_CREATOR(char*, CreateLowLevelView); +} + +PyObject* cpyrt::CreateLowLevelView(char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape); + CPPJIT_RET_W_CREATOR(char**, CreateLowLevelView); +} + +PyObject* cpyrt::CreateLowLevelViewString(char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, + nullptr, sizeof(char)); + CPPJIT_RET_W_CREATOR(char**, CreateLowLevelViewString); +} + +PyObject* cpyrt::CreateLowLevelViewString(const char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, + nullptr, sizeof(char)); + CPPJIT_RET_W_CREATOR(const char**, CreateLowLevelViewString); +} + +PyObject* cpyrt::CreateLowLevelView_i8(int8_t* address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); + CPPJIT_RET_W_CREATOR(int8_t*, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(int8_t** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); + CPPJIT_RET_W_CREATOR(int8_t**, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(uint8_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "B", "uint8_t"); + CPPJIT_RET_W_CREATOR(uint8_t*, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(uint8_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "B", "uint8_t"); + CPPJIT_RET_W_CREATOR(uint8_t**, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i16(int16_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "h", "int16_t"); + CPPJIT_RET_W_CREATOR(int16_t*, CreateLowLevelView_i16); +} + +PyObject* cpyrt::CreateLowLevelView_i16(int16_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "h", "int16_t"); + CPPJIT_RET_W_CREATOR(int16_t**, CreateLowLevelView_i16); +} + +PyObject* cpyrt::CreateLowLevelView_i16(uint16_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "H", "uint16_t"); + CPPJIT_RET_W_CREATOR(uint16_t*, CreateLowLevelView_i16); +} + +PyObject* cpyrt::CreateLowLevelView_i16(uint16_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "H", "uint16_t"); + CPPJIT_RET_W_CREATOR(uint16_t**, CreateLowLevelView_i16); +} + +PyObject* cpyrt::CreateLowLevelView_i32(int32_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "i", "int32_t"); + CPPJIT_RET_W_CREATOR(int32_t*, CreateLowLevelView_i32); +} + +PyObject* cpyrt::CreateLowLevelView_i32(int32_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "i", "int32_t"); + CPPJIT_RET_W_CREATOR(int32_t**, CreateLowLevelView_i32); +} + +PyObject* cpyrt::CreateLowLevelView_i32(uint32_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "I", "uint32_t"); + CPPJIT_RET_W_CREATOR(uint32_t*, CreateLowLevelView_i32); +} + +PyObject* cpyrt::CreateLowLevelView_i32(uint32_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "I", "uint32_t"); + CPPJIT_RET_W_CREATOR(uint32_t**, CreateLowLevelView_i32); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.h b/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.h new file mode 100644 index 0000000000000..80e7f02bcc4de --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.h @@ -0,0 +1,102 @@ +#ifndef CPYRT_LOWLEVELVIEWS_H +#define CPYRT_LOWLEVELVIEWS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +class Converter; + +class LowLevelView { +public: + enum EFlags { + kDefault = 0x0000, + kIsCppArray = 0x0001, // allocated with new[] + kIsFixed = 0x0002, // fixed size array (assumed flat) + kIsOwner = 0x0004 + }; // Python owns + +public: + PyObject_HEAD Py_buffer fBufInfo; + void** fBuf; + Converter* fConverter; + Converter* fElemCnv; + + typedef LowLevelView* (*Creator_t)(void*, cdims_t); + Creator_t fCreator; // for slicing, which requires copying + +public: + void* get_buf() { return fBuf ? *fBuf : fBufInfo.buf; } + void set_buf(void** buf) { + fBuf = buf; + fBufInfo.buf = get_buf(); + } + + bool resize(size_t sz); +}; + +#define CPPJIT_DECL_VIEW_CREATOR(type) \ + PyObject* CreateLowLevelView(type*, cdims_t shape); \ + PyObject* CreateLowLevelView(type**, cdims_t shape) + +CPPJIT_DECL_VIEW_CREATOR(bool); +CPPJIT_DECL_VIEW_CREATOR(char); +CPPJIT_DECL_VIEW_CREATOR(signed char); +CPPJIT_DECL_VIEW_CREATOR(unsigned char); +CPPJIT_DECL_VIEW_CREATOR(std::byte); +PyObject* CreateLowLevelView_i8(int8_t*, cdims_t shape); +PyObject* CreateLowLevelView_i8(int8_t**, cdims_t shape); +PyObject* CreateLowLevelView_i8(uint8_t*, cdims_t shape); +PyObject* CreateLowLevelView_i8(uint8_t**, cdims_t shape); +PyObject* CreateLowLevelView_i16(int16_t*, cdims_t shape); +PyObject* CreateLowLevelView_i16(int16_t**, cdims_t shape); +PyObject* CreateLowLevelView_i16(uint16_t*, cdims_t shape); +PyObject* CreateLowLevelView_i16(uint16_t**, cdims_t shape); +PyObject* CreateLowLevelView_i32(int32_t*, cdims_t shape); +PyObject* CreateLowLevelView_i32(int32_t**, cdims_t shape); +PyObject* CreateLowLevelView_i32(uint32_t*, cdims_t shape); +PyObject* CreateLowLevelView_i32(uint32_t**, cdims_t shape); + +CPPJIT_DECL_VIEW_CREATOR(short); +CPPJIT_DECL_VIEW_CREATOR(unsigned short); +CPPJIT_DECL_VIEW_CREATOR(int); +CPPJIT_DECL_VIEW_CREATOR(unsigned int); +CPPJIT_DECL_VIEW_CREATOR(long); +CPPJIT_DECL_VIEW_CREATOR(unsigned long); +CPPJIT_DECL_VIEW_CREATOR(long long); +CPPJIT_DECL_VIEW_CREATOR(unsigned long long); +CPPJIT_DECL_VIEW_CREATOR(float); +CPPJIT_DECL_VIEW_CREATOR(double); +CPPJIT_DECL_VIEW_CREATOR(long double); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); + +PyObject* CreateLowLevelViewString(char**, cdims_t shape); +PyObject* CreateLowLevelViewString(const char**, cdims_t shape); + +inline PyObject* CreatePointerView(void* ptr, cdims_t shape = 0) { + return CreateLowLevelView((uintptr_t*)ptr, shape); +} + +//- low level view type and type verification -------------------------------- +extern PyTypeObject LowLevelView_Type; + +template inline bool LowLevelView_Check(T* object) { + return object && PyObject_TypeCheck(object, &LowLevelView_Type); +} + +template inline bool LowLevelView_CheckExact(T* object) { + return object && Py_TYPE(object) == &LowLevelView_Type; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_LOWLEVELVIEWS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.cxx b/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.cxx new file mode 100644 index 0000000000000..6ea76716f431f --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.cxx @@ -0,0 +1,256 @@ +// Bindings +#include "cppjit_interop.h" +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" + +// Standard +#include +#include +#include + +//= pseudo-None type for masking out objects on the python side =============== +static PyTypeObject cpyrt_NoneType; + +//----------------------------------------------------------------------------- +static Py_ssize_t AlwaysNullLength(PyObject*) { return 0; } + +//----------------------------------------------------------------------------- +static PyMappingMethods cpyrt_NoneType_mapping = { + AlwaysNullLength, (binaryfunc)0, (objobjargproc)0}; + +// silence warning about some cast operations +#if defined(__GNUC__) && \ + (__GNUC__ >= 5 || \ + (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || \ + (__GNUC_MINOR__ >= 3)))) && \ + !__INTEL_COMPILER +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +//----------------------------------------------------------------------------- +namespace { + +struct Initcpyrt_NoneType_t { + Initcpyrt_NoneType_t() { + // create a cpyrt NoneType (for references that went dodo) from NoneType + memset(&cpyrt_NoneType, 0, sizeof(cpyrt_NoneType)); + + ((PyObject&)cpyrt_NoneType).ob_type = &PyType_Type; + Py_SET_REFCNT((PyObject*)&cpyrt_NoneType, 1); + ((PyVarObject&)cpyrt_NoneType).ob_size = 0; + + cpyrt_NoneType.tp_name = const_cast("cpyrt_NoneType"); + cpyrt_NoneType.tp_flags = Py_TPFLAGS_HAVE_RICHCOMPARE; + + cpyrt_NoneType.tp_traverse = (traverseproc)0; + cpyrt_NoneType.tp_clear = (inquiry)0; + cpyrt_NoneType.tp_dealloc = (destructor)&Initcpyrt_NoneType_t::DeAlloc; + cpyrt_NoneType.tp_repr = Py_TYPE(Py_None)->tp_repr; + cpyrt_NoneType.tp_richcompare = + (richcmpfunc)&Initcpyrt_NoneType_t::RichCompare; + cpyrt_NoneType.tp_hash = (hashfunc)&Initcpyrt_NoneType_t::PtrHash; + + cpyrt_NoneType.tp_as_mapping = &cpyrt_NoneType_mapping; + + PyType_Ready(&cpyrt_NoneType); + } + + static void DeAlloc(PyObject* pyobj) { Py_TYPE(pyobj)->tp_free(pyobj); } + static int PtrHash(PyObject* pyobj) { return (int)ptrdiff_t(pyobj); } + + static PyObject* RichCompare(PyObject*, PyObject* other, int opid) { + return PyObject_RichCompare(other, Py_None, opid); + } + + static int Compare(PyObject*, PyObject* other) { + // TODO the following isn't correct as it doesn't order, but will do for now + // ... + return !PyObject_RichCompareBool(other, Py_None, Py_EQ); + } +}; + +} // unnamed namespace + +// Memory regulation hooks +cpyrt::MemHook_t cpyrt::MemoryRegulator::registerHook = nullptr; +cpyrt::MemHook_t cpyrt::MemoryRegulator::unregisterHook = nullptr; + +//- ctor/dtor ---------------------------------------------------------------- +cpyrt::MemoryRegulator::MemoryRegulator() { + // setup NoneType for referencing and create weakref cache + static Initcpyrt_NoneType_t initcpyrt_NoneType; +} + +//- public members ----------------------------------------------------------- +bool cpyrt::MemoryRegulator::RecursiveRemove(interop::TCppObject_t cppobj, + interop::TCppScope_t klass) { + // if registered by the framework, called whenever a cppobj gets destroyed + if (!cppobj) + return false; + + PyObject* pyscope = GetScopeProxy(klass); + if (!CPPScope_Check(pyscope)) { + Py_XDECREF(pyscope); + return false; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)pyscope)->fImp.fCppObjects; + if (!cppobjs) { // table may have been deleted on shutdown + Py_DECREF(pyscope); + return false; + } + + // see whether we're tracking this object + CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); + + if (ppo != cppobjs->end()) { + // get the tracked object + CPPInstance* pyobj = (CPPInstance*)ppo->second; + + // erase the object from tracking + pyobj->fFlags &= ~CPPInstance::kIsRegulated; + cppobjs->erase(ppo); + + // nullify the object + if (!cpyrt_NoneType.tp_traverse) { + // take a reference as we're copying its function pointers + Py_INCREF(Py_TYPE(pyobj)); + + // all object that arrive here are expected to be of the same type + // ("instance") + cpyrt_NoneType.tp_traverse = Py_TYPE(pyobj)->tp_traverse; + cpyrt_NoneType.tp_clear = Py_TYPE(pyobj)->tp_clear; + cpyrt_NoneType.tp_free = Py_TYPE(pyobj)->tp_free; + cpyrt_NoneType.tp_flags |= Py_TYPE(pyobj)->tp_flags; + } else if (cpyrt_NoneType.tp_traverse != Py_TYPE(pyobj)->tp_traverse) { + // TODO: SystemError? + std::cerr << "in cpyrt::MemoryRegulater, unexpected object of type: " + << Py_TYPE(pyobj)->tp_name << std::endl; + + // drop object and leave before too much damage is done + Py_DECREF(pyscope); + return false; + } + + // notify any other weak referents by playing dead + Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); + Py_SET_REFCNT((PyObject*)pyobj, 0); + PyObject_ClearWeakRefs((PyObject*)pyobj); + Py_SET_REFCNT((PyObject*)pyobj, refcnt); + + // cleanup object internals + pyobj->CppOwns(); // held object is out of scope now anyway + op_dealloc_nofree(pyobj); // normal object cleanup, while keeping memory + + // reset type object + Py_INCREF((PyObject*)(void*)&cpyrt_NoneType); + Py_DECREF(Py_TYPE(pyobj)); + ((PyObject*)pyobj)->ob_type = &cpyrt_NoneType; + + Py_DECREF(pyscope); + return true; + } + + // unregulated cppobj + Py_DECREF(pyscope); + return false; +} + +//----------------------------------------------------------------------------- +bool cpyrt::MemoryRegulator::RegisterPyObject(CPPInstance* pyobj, + interop::TCppObject_t cppobj) { + // start tracking proxied by + if (!(pyobj && cppobj)) + return false; + + if (registerHook) { + auto res = registerHook(cppobj, pyobj->ObjectIsA(false)); + if (!res.second) + return res.first; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)Py_TYPE(pyobj))->fImp.fCppObjects; + if (!cppobjs) + return false; + + // if an address was already associated with a different object, then stop + // following the old and force insert the new proxy for following + const auto& res = cppobjs->insert(std::make_pair(cppobj, (PyObject*)pyobj)); + if (!res.second) { + ((CPPInstance*)res.first->second)->fFlags &= ~CPPInstance::kIsRegulated; + (*cppobjs)[cppobj] = (PyObject*)pyobj; + } + + pyobj->fFlags |= CPPInstance::kIsRegulated; + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::MemoryRegulator::UnregisterPyObject(CPPInstance* pyobj, + PyObject* pyclass) { + // called when the proxy is garbage collected or is about delete the C++ + // instance + if (!(pyobj && pyclass)) + return false; + + interop::TCppObject_t cppobj = + pyobj->IsSmart() ? pyobj->GetObjectRaw() : pyobj->GetObject(); + if (!cppobj) + return false; + + if (unregisterHook) { + auto res = unregisterHook(cppobj, ((CPPClass*)pyclass)->fCppType); + if (!res.second) + return res.first; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; + if (!cppobjs) + return false; + + // erase if tracked + if (cppobjs->erase(cppobj)) { + pyobj->fFlags &= ~CPPInstance::kIsRegulated; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::MemoryRegulator::RetrievePyObject(interop::TCppObject_t cppobj, + PyObject* pyclass) { + // lookup to see if a C++ address is already known, return old proxy if + // tracked + if (!(cppobj && pyclass)) + return nullptr; + + CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; + if (!cppobjs) + return nullptr; + + CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); + if (ppo != cppobjs->end()) { + Py_INCREF(ppo->second); + return ppo->second; + } + + return nullptr; +} + +//----------------------------------------------------------------------------- +void cpyrt::MemoryRegulator::SetRegisterHook(MemHook_t h) { + // Set custom register hook; called when a regulated object is to be tracked + registerHook = h; +} + +//----------------------------------------------------------------------------- +void cpyrt::MemoryRegulator::SetUnregisterHook(MemHook_t h) { + // Set custom unregister hook; called when a regulated object is to be + // untracked + unregisterHook = h; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.h b/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.h new file mode 100644 index 0000000000000..c7bc9cb666c2b --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.h @@ -0,0 +1,48 @@ +#ifndef CPYRT_MEMORYREGULATOR_H +#define CPYRT_MEMORYREGULATOR_H + +#include "Python.h" +#include "cppjit_interop.h" + +#include +#include + +namespace cppjit::cpyrt { + +class CPPInstance; + +typedef std::function(interop::TCppObject_t, + interop::TCppScope_t)> + MemHook_t; + +class MemoryRegulator { +private: + static MemHook_t registerHook, unregisterHook; + +public: + MemoryRegulator(); + + // callback from C++-side frameworks + static bool RecursiveRemove(interop::TCppObject_t cppobj, + interop::TCppScope_t klass); + + // called when a new python proxy object is created + static bool RegisterPyObject(CPPInstance* pyobj, + interop::TCppObject_t cppobj); + + // called when a the python proxy object is about to be garbage collected or + // when it is about to delete the proxied C++ object, if owned + static bool UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass); + + // new reference to python object matching cppobj, or 0 on failure + static PyObject* RetrievePyObject(interop::TCppObject_t cppobj, + PyObject* pyclass); + + // set hooks for custom memory regulation + static void SetRegisterHook(MemHook_t h); + static void SetUnregisterHook(MemHook_t h); +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_MEMORYREGULATOR_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx b/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx new file mode 100644 index 0000000000000..e1cc9e93344a0 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx @@ -0,0 +1,1066 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPConstructor.h" +#include "CPPDataMember.h" +#include "CPPExcInstance.h" +#include "CPPFunction.h" +#include "CPPGetSetItem.h" +#include "CPPInstance.h" +#include "CPPMethod.h" +#include "CPPOperator.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "Pythonize.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include + +//- data _______________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +extern PyObject* gPyTypeMap; +extern std::unordered_set gPinnedTypes; +} // namespace cppjit::cpyrt + +// to prevent having to walk scopes, track python classes by C++ class +typedef std::unordered_map PyClassMap_t; +static PyClassMap_t gPyClasses; + +//- helpers -------------------------------------------------------------------- + +namespace cppjit::cpyrt { + +typedef struct { + PyObject_HEAD PyObject* dict; +} proxyobject; + +// helper for creating new C++ proxy python types +static PyObject* CreateNewCppProxyClass(interop::TCppScope_t klass, + PyObject* pybases) { + // Create a new python shadow class with the required hierarchy and + // meta-classes. + PyObject* pymetabases = PyTuple_New(PyTuple_GET_SIZE(pybases)); + for (int i = 0; i < PyTuple_GET_SIZE(pybases); ++i) { + PyObject* btype = (PyObject*)Py_TYPE(PyTuple_GetItem(pybases, i)); + Py_INCREF(btype); + PyTuple_SET_ITEM(pymetabases, i, btype); + } + + std::string name = interop::GetFinalName(klass); + + // create meta-class, add a dummy __module__ to pre-empt the default setting + PyObject* args = + Py_BuildValue((char*)"sO{}", (name + "_meta").c_str(), pymetabases); + PyDict_SetItem(PyTuple_GET_ITEM(args, 2), PyStrings::gModule, Py_True); + Py_DECREF(pymetabases); + + PyObject* pymeta = (PyObject*)CPPScopeMeta_New(klass, args); + Py_DECREF(args); + if (!pymeta) + return nullptr; + + // alright, and now we really badly want to get rid of the dummy ... + PyObject* dictproxy = PyObject_GetAttr(pymeta, PyStrings::gDict); + PyDict_DelItem(((proxyobject*)dictproxy)->dict, PyStrings::gModule); + + // create actual class + args = Py_BuildValue((char*)"sO{}", name.c_str(), pybases); + PyObject* pyclass = + ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); + + Py_DECREF(args); + Py_DECREF(pymeta); + + return pyclass; +} + +static inline void AddPropertyToClass(PyObject* pyclass, + interop::TCppScope_t scope, + interop::TCppScope_t data) { + cpyrt::CPPDataMember* property = cpyrt::CPPDataMember_New(scope, data); + PyObject* pname = cpyrt_PyText_InternFromString( + const_cast(property->GetName().c_str())); + + // allow access at the instance level + PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); + if (PyErr_Occurred()) { + // CPPDataMember.tp_descr_set raises an error + // when all of the following conditions are met + // 1. data is static const + // 2. scope is a derived class + // 3. data is defined in both parent and base class (using + // parent::attribute;) + PyErr_Clear(); + } + + // allow access at the class level (always add after setting instance level) + if (interop::IsStaticDatamember(data) || interop::IsEnumConstant(data)) + PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, + (PyObject*)property); + + // cleanup + Py_DECREF(pname); + Py_DECREF(property); +} + +static inline void AddScopeToParent(PyObject* parent, const std::string& name, + PyObject* newscope) { + PyObject* pyname = cpyrt_PyText_InternFromString((char*)name.c_str()); + if (CPPScope_Check(parent)) + PyType_Type.tp_setattro(parent, pyname, newscope); + else + PyObject_SetAttr(parent, pyname, newscope); + Py_DECREF(pyname); +} + +static inline PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { + // get an attribute without causing getattr lookups + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + return attr; + } + return nullptr; +} + +} // namespace cppjit::cpyrt + +//- public functions --------------------------------------------------------- +namespace cppjit::cpyrt { + +static inline void sync_templates(PyObject* pyclass, + const std::string& mtCppName, + const std::string& mtName) { + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + PyObject* pyname = cpyrt_PyText_FromString(const_cast(mtName.c_str())); + PyObject* attr = PyObject_GetItem(dct, pyname); + if (!attr) + PyErr_Clear(); + Py_DECREF(dct); + if (!TemplateProxy_Check(attr)) { + TemplateProxy* pytmpl = TemplateProxy_New(mtCppName, mtName, pyclass); + if (CPPOverload_Check(attr)) + pytmpl->MergeOverload((CPPOverload*)attr); + PyType_Type.tp_setattro(pyclass, pyname, (PyObject*)pytmpl); + Py_DECREF(pytmpl); + } + Py_XDECREF(attr); + Py_DECREF(pyname); +} + +static int BuildScopeProxyDict(interop::TCppScope_t scope, PyObject* pyclass, + const unsigned int flags) { + // Collect methods and data for the given scope, and add them to the given + // python proxy object. + + // some properties that'll affect building the dictionary + bool isNamespace = interop::IsNamespace(scope); + bool isComplete = interop::IsComplete(scope); + bool hasConstructor = false; + interop::TCppMethod_t potGetItem = nullptr; + + // load all public methods and data members + typedef std::vector Callables_t; + typedef std::unordered_map CallableCache_t; + CallableCache_t cache; + + // bypass custom __getattr__ for efficiency + getattrofunc oldgetattro = Py_TYPE(pyclass)->tp_getattro; + Py_TYPE(pyclass)->tp_getattro = PyType_Type.tp_getattro; + + // functions in namespaces are properly found through lazy lookup, so do not + // create them until needed (the same is not true for data members) + std::vector methods; + if (isComplete) + interop::GetClassMethods(scope, methods); + + for (auto& method : methods) { + + // do not expose non-public methods as the Cling wrappers as those won't + // compile; exception: a non-public method whose declaring class is not this + // scope can only have entered the enumeration through a public + // using-declaration (the backend drops non-public shadows), so it is + // publicly callable through this class and its wrapper compiles (e.g. + // MSVC's std::shared_ptr re-exposes the protected std::_Ptr_base::get + // with `public: using _Mybase::get;`) + if (!interop::IsPublicMethod(method) && + interop::GetParentScope(interop::TCppScope_t(method.data)) == scope) + continue; + + // process the method based on its name + std::string mtCppName = interop::GetName(interop::TCppScope_t(method.data)); + + // special case trackers + bool setupSetItem = false; + bool isConstructor = interop::IsConstructor(method); + + // Here isTemplate means to ignore templated constructors, that is handled + // in the next loop. + bool isTemplate = interop::IsTemplatedMethod(method) && !isConstructor; + bool isStubbedOperator = false; + + // filter empty names (happens for namespaces, is bug?) + if (mtCppName == "") + continue; + + // filter C++ destructors + if (mtCppName[0] == '~') + continue; + + // translate operators + std::string mtName = Utility::MapOperatorName( + mtCppName, interop::GetMethodNumArgs(method), &isStubbedOperator); + if (mtName.empty()) + continue; + + // operator[]/() returning a reference type will be used for __setitem__ + bool isCall = mtName == "__call__"; + if (isCall || mtName == "__getitem__") { + const std::string& qual_return = + interop::GetMethodReturnTypeAsString(method); + const std::string& cpd = TypeManip::compound(qual_return); + if (!cpd.empty() && cpd[cpd.size() - 1] == '&' && + qual_return.find("const", 0, 5) == std::string::npos) { + if (isCall && !potGetItem) + potGetItem = method; + setupSetItem = true; // will add methods as overloads + } else if (isCall && 1 < interop::GetMethodNumArgs(method)) { + // not a non-const by-ref return, thus better __getitem__ candidate; the + // requirement for multiple arguments is that there is otherwise no + // benefit over the use of normal __getitem__ (this allows + // multi-indexing arguments, which is clean in Python, but not allowed + // in C++) + potGetItem = method; + } + } + + // template members; handled by adding a dispatcher to the class + bool storeOnTemplate = isTemplate; + if (storeOnTemplate) { + sync_templates(pyclass, mtCppName, mtName); + // continue processing to actually add the method so that the proxy can + // find it on the class when called explicitly + } + + // construct the holder + PyCallable* pycall = nullptr; + if (interop::IsStaticMethod(method)) // class method + pycall = new CPPClassMethod(scope, method); + else if (isNamespace) // free function + pycall = new CPPFunction(scope, method); + else if (isConstructor) { // ctor + mtName = "__init__"; + hasConstructor = true; + if (!interop::IsAbstract(scope)) { + if (flags & CPPScope::kIsMultiCross) { + pycall = new CPPMultiConstructor(scope, method); + } else + pycall = new CPPConstructor(scope, method); + } else + pycall = new CPPAbstractClassConstructor(scope, method); + } else if (isStubbedOperator) { + pycall = new CPPOperator(scope, method, mtName); + } else // member function + pycall = new CPPMethod(scope, method); + + if (storeOnTemplate) { + // template proxy was already created in sync_templates call above, so + // add only here, not to the cache of collected methods + PyObject* attr = + PyObject_GetAttrString(pyclass, const_cast(mtName.c_str())); + if (isTemplate) + ((TemplateProxy*)attr)->AdoptTemplate(pycall); + else + ((TemplateProxy*)attr)->AdoptMethod(pycall); + Py_DECREF(attr); + + // for operator[]/() that returns by ref, also add __setitem__ + if (setupSetItem) { + TemplateProxy* pysi = + (TemplateProxy*)GetAttrDirect(pyclass, PyStrings::gSetItem); + if (!TemplateProxy_Check(pysi)) { + CPPOverload* precursor = + (CPPOverload_Check(pysi)) ? (CPPOverload*)pysi : nullptr; + if (pysi && !precursor) + Py_DECREF(pysi); // something unknown, just drop it + pysi = TemplateProxy_New(mtCppName, "__setitem__", pyclass); + if (precursor) + pysi->MergeOverload(precursor); + Py_XDECREF(precursor); + PyObject_SetAttrString(pyclass, const_cast("__setitem__"), + (PyObject*)pysi); + } + if (isTemplate) + pysi->AdoptTemplate(new CPPSetItem(scope, method)); + else + pysi->AdoptMethod(new CPPSetItem(scope, method)); + Py_XDECREF(pysi); + } + + } else { + // lookup method dispatcher and store method + Callables_t& md = + (*(cache.insert(std::make_pair(mtName, Callables_t())).first)).second; + md.push_back(pycall); + + // special case for operator[]/() that returns by ref, use for + // getitem/call and setitem + if (setupSetItem) { + Callables_t& setitem = + (*(cache + .insert(std::make_pair(std::string("__setitem__"), + Callables_t())) + .first)) + .second; + setitem.push_back(new CPPSetItem(scope, method)); + } + } + } + + // add proxies for un-instantiated/non-overloaded templated methods + std::vector templ_methods; + interop::GetTemplatedMethods(scope, templ_methods); + for (auto& method : templ_methods) { + const std::string mtCppName = + interop::GetName(interop::TCppScope_t(method.data)); + // the number of arguments isn't known until instantiation and as far as C++ + // is concerned, all same-named operators are simply overloads; so will + // pre-emptively add both names if with and without arguments differ, + // letting the normal overload mechanism resolve on call + bool isConstructor = interop::IsConstructor(method); + + // first add with no arguments + std::string mtName0 = + isConstructor ? "__init__" : Utility::MapOperatorName(mtCppName, false); + sync_templates(pyclass, mtCppName, mtName0); + + // then add when taking arguments, if this method is different + if (!isConstructor) { + std::string mtName1 = Utility::MapOperatorName(mtCppName, true); + if (mtName0 != mtName1) + sync_templates(pyclass, mtCppName, mtName1); + } + } + + // add a pseudo-default ctor, if none defined + if (!hasConstructor) { + PyCallable* defctor = nullptr; + if (!isComplete) { + ((CPPScope*)pyclass)->fFlags |= CPPScope::kIsInComplete; + defctor = + new CPPIncompleteClassConstructor(scope, interop::TCppMethod_t{}); + } else if (interop::IsAbstract(scope)) { + defctor = new CPPAbstractClassConstructor(scope, interop::TCppMethod_t{}); + } else if (isNamespace) { + defctor = new CPPNamespaceConstructor(scope, interop::TCppMethod_t{}); + } else { + defctor = + new CPPAllPrivateClassConstructor(scope, interop::TCppMethod_t{}); + } + + if (defctor) + cache["__init__"].push_back(defctor); + } + + // map __call__ to __getitem__ if also mapped to __setitem__ + if (potGetItem) { + Callables_t& getitem = + (*(cache + .insert( + std::make_pair(std::string("__getitem__"), Callables_t())) + .first)) + .second; + getitem.push_back(new CPPGetItem(scope, potGetItem)); + } + + // add the methods to the class dictionary + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + for (CallableCache_t::iterator imd = cache.begin(); imd != cache.end(); + ++imd) { + // in order to prevent removing templated editions of this method (which + // were set earlier, above, as a different proxy object), we'll check and + // add this method flagged as a generic one (to be picked up by the + // templated one as appropriate) if a template exists + PyObject* pyname = + cpyrt_PyText_FromString(const_cast(imd->first.c_str())); + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(pyname); + if (TemplateProxy_Check(attr)) { + // template exists, supply it with the non-templated method overloads + for (auto cit : imd->second) + ((TemplateProxy*)attr)->AdoptMethod(cit); + } else { + if (!attr) + PyErr_Clear(); + // normal case, add a new method + CPPOverload* method = CPPOverload_New(imd->first, imd->second); + PyObject* pymname = cpyrt_PyText_InternFromString( + const_cast(method->GetName().c_str())); + PyType_Type.tp_setattro(pyclass, pymname, (PyObject*)method); + Py_DECREF(pymname); + Py_DECREF(method); + } + + Py_XDECREF(attr); // could have been found in base class or non-existent + } + Py_DECREF(dct); + + // collect data members (including enums) + std::vector datamembers; + interop::GetDatamembers(scope, datamembers); + for (auto& datamember : datamembers) { + // allow only public members + if (!interop::IsPublicData(datamember)) + continue; + + // enum datamembers (this in conjunction with previously collected enums + // above) + if (interop::IsEnumType(interop::GetDatamemberType(datamember)) && + interop::IsEnumConstant(datamember)) { + // two options: this is a static variable, or it is the enum value, the + // latter already exists, so check for it and move on if set + PyObject* eset = PyObject_GetAttrString( + pyclass, + const_cast(interop::GetFinalName(datamember).c_str())); + if (eset) { + Py_DECREF(eset); + continue; + } + + PyErr_Clear(); + + // it could still be that this is an anonymous enum, which is not in the + // list provided by the class + if (strstr(interop::GetDatamemberTypeAsString(datamember).c_str(), + "(anonymous)") != 0 || + strstr(interop::GetDatamemberTypeAsString(datamember).c_str(), + "(unnamed)") != 0) { + AddPropertyToClass(pyclass, scope, datamember); + continue; + } + } + + // properties (aka public (static) data members) + AddPropertyToClass(pyclass, scope, datamember); + } + + // restore custom __getattr__ + Py_TYPE(pyclass)->tp_getattro = oldgetattro; + + // all ok, done + return 0; +} + +//---------------------------------------------------------------------------- +static void CollectUniqueBases(interop::TCppScope_t klass, + std::deque& uqb) { + // collect bases in acceptable mro order, while removing duplicates (this may + // break the overload resolution in esoteric cases, but otherwise the class + // can not be used at all, as CPython will refuse the mro). + size_t nbases = interop::GetNumBases(klass); + + for (size_t ibase = 0; ibase < nbases; ++ibase) { + interop::TCppScope_t tp = interop::GetBaseScope(klass, ibase); + int decision = 2; + if (!tp) + continue; // means this base with not be available Python-side + for (size_t ibase2 = 0; ibase2 < uqb.size(); ++ibase2) { + if (uqb[ibase2] == tp) { // not unique ... skip + decision = 0; + break; + } + + if (interop::IsSubclass(tp, uqb[ibase2])) { + // mro requirement: sub-type has to follow base + decision = 1; + break; + } + } + + if (decision == 1) { + uqb.push_front(tp); + } else if (decision == 2) { + uqb.push_back(tp); + } + // skipped if decision == 0 (not unique) + } +} + +static PyObject* BuildCppClassBases(interop::TCppScope_t klass) { + // Build a tuple of python proxy classes of all the bases of the given + // 'klass'. + std::deque uqb; + CollectUniqueBases(klass, uqb); + + // allocate a tuple for the base classes, special case for first base + size_t nbases = uqb.size(); + + PyObject* pybases = PyTuple_New(nbases ? nbases : 1); + if (!pybases) + return nullptr; + + // build all the bases + if (nbases == 0) { + Py_INCREF((PyObject*)(void*)&CPPInstance_Type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPInstance_Type); + } else { + for (std::deque::size_type ibase = 0; ibase < nbases; + ++ibase) { + PyObject* pyclass = CreateScopeProxy(uqb[ibase]); + if (!pyclass) { + Py_DECREF(pybases); + return nullptr; + } + + PyTuple_SET_ITEM(pybases, ibase, pyclass); + } + + // special case, if true python types enter the hierarchy, make sure that + // the first base seen is still the CPPInstance_Type + if (!PyObject_IsSubclass(PyTuple_GET_ITEM(pybases, 0), + (PyObject*)&CPPInstance_Type)) { + PyObject* newpybases = PyTuple_New(nbases + 1); + Py_INCREF((PyObject*)(void*)&CPPInstance_Type); + PyTuple_SET_ITEM(newpybases, 0, (PyObject*)(void*)&CPPInstance_Type); + for (int ibase = 0; ibase < (int)nbases; ++ibase) { + PyObject* pyclass = PyTuple_GET_ITEM(pybases, ibase); + Py_INCREF(pyclass); + PyTuple_SET_ITEM(newpybases, ibase + 1, pyclass); + } + Py_DECREF(pybases); + pybases = newpybases; + } + } + + return pybases; +} + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +PyObject* cpyrt::GetScopeProxy(interop::TCppScope_t scope) { + // Retrieve scope proxy from the known ones. + PyClassMap_t::iterator pci = gPyClasses.find(scope); + if (pci != gPyClasses.end()) { + PyObject* pyclass = cpyrt_GetWeakRef(pci->second); + if (pyclass) + return pyclass; + } + + return nullptr; +} +namespace cppjit::cpyrt { +PyObject* CppType_To_PyObject(interop::TCppType_t type, std::string name, + interop::TCppScope_t parent_scope, + PyObject* parent) { + interop::TCppType_t resolved_type = interop::ResolveType(type); + if (gPyTypeMap) { + const std::string& resolved = interop::GetTypeAsString(resolved_type); + PyObject* tc = + PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed + if (tc && PyCallable_Check(tc)) { + const std::string& scName = interop::GetScopedFinalName(parent_scope); + PyObject* nt = + PyObject_CallFunction(tc, (char*)"ss", name.c_str(), + scName != "" ? scName.c_str() : ""); + if (nt) { + if (parent) { + AddScopeToParent(parent, name, nt); + Py_DECREF(parent); + } + return nt; + } + PyErr_Clear(); + } + } + return nullptr; +} +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(PyObject*, PyObject* args) { + // Build a python shadow class for the named C++ class. + std::string cname = cpyrt_PyText_AsString(PyTuple_GetItem(args, 0)); + if (PyErr_Occurred()) + return nullptr; + + return CreateScopeProxy(cname); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(const std::string& name, PyObject* parent, + const unsigned flags) { + // Build a python shadow class for the named C++ class or namespace. + + // determine complete scope name, if a python parent has been given + interop::TCppScope_t parent_scope; + if (parent) { + if (CPPScope_Check(parent)) + parent_scope = ((CPPScope*)parent)->fCppType; + else { + PyObject* parname = PyObject_GetAttr(parent, PyStrings::gName); + if (!parname) { + PyErr_Format(PyExc_SystemError, "given scope has no name for %s", + name.c_str()); + return nullptr; + } + + // should be a string + std::string scName = cpyrt_PyText_AsString(parname); + Py_DECREF(parname); + if (PyErr_Occurred()) + return nullptr; + parent_scope = interop::GetScope(scName); + } + + Py_INCREF(parent); + } + + // retrieve C++ class (this verifies name, and is therefore done first) + if (name == "") { + interop::TCppScope_t klass = interop::GetGlobalScope(); + Py_INCREF(gThisModule); + parent = gThisModule; + return CreateScopeProxy(klass, parent, flags); + } else if (interop::TCppScope_t klass = + interop::GetScope(name, parent_scope)) { + if (interop::IsTypedefed(klass) && + interop::IsPointerType(interop::GetTypeFromScope(klass)) && + interop::IsClass(interop::GetUnderlyingScope(klass))) + return nullptr; // this is handled by the caller; typedef to class pointer + return CreateScopeProxy(klass, parent, flags); + } else if (interop::IsBuiltin(name)) { + interop::TCppType_t type = interop::GetType(name); + PyObject* result = nullptr; + if (type) + result = CppType_To_PyObject(type, name, parent_scope, parent); + if (result) + return result; + } + // all options have been exhausted: it doesn't exist as such + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", + name.c_str()); + Py_XDECREF(parent); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(interop::TCppScope_t scope, PyObject* parent, + const unsigned flags) { + // Convenience function with a lookup first through the known existing + // proxies. + PyObject* pyclass = GetScopeProxy(scope); + if (pyclass) + return pyclass; + + interop::TCppScope_t parent_scope = nullptr; + if (CPPScope_Check(parent)) + parent_scope = ((CPPScope*)parent)->fCppType; + if (!parent_scope) + parent_scope = interop::GetParentScope(scope); + + if (!parent) { + if (parent_scope) + parent = CreateScopeProxy(parent_scope); + else { + Py_INCREF(gThisModule); + parent = gThisModule; + } + } + + std::string name = interop::GetFinalName(scope); + bool is_typedef = false; + std::string typedefed_name = ""; + if (interop::IsTypedefed(scope)) { + is_typedef = true; + typedefed_name = interop::GetFullName(scope); + interop::TCppScope_t underlying_scope = interop::GetUnderlyingScope(scope); + if ((underlying_scope) && (underlying_scope != scope)) { + scope = underlying_scope; + } else { + if (PyObject* result = CppType_To_PyObject( + interop::GetTypeFromScope(scope), name, parent_scope, parent)) + return result; + + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", + interop::GetScopedFinalName(scope).c_str()); + Py_XDECREF(parent); + return nullptr; + } + } + + if (interop::IsTemplate(scope)) { + // a "naked" templated class is requested: return callable proxy for + // instantiations + PyObject* pytcl = PyObject_GetAttr(gThisModule, PyStrings::gTemplate); + PyObject* cppscope = PyLong_FromVoidPtr(scope.data); + PyObject* pytemplate = PyObject_CallFunction( + pytcl, const_cast("sO"), + const_cast(interop::GetScopedFinalName(scope).c_str()), + cppscope); + Py_DECREF(pytcl); + + // cache the result + AddScopeToParent(parent, name, pytemplate); + + // done, next step should be a call into this template + Py_XDECREF(parent); + return pytemplate; + } + + if (interop::IsEnumScope(scope)) + return nullptr; + + // locate class by ID, if possible, to prevent parsing scopes/templates anew + PyObject* pyscope = GetScopeProxy(scope); + if (pyscope) { + if (parent) { + AddScopeToParent(parent, name, pyscope); + Py_DECREF(parent); + } + return pyscope; + } + + // if the scope was earlier found as actual, then we're done already, + // otherwise build a new scope proxy + if (!pyscope) { + // construct the base classes + PyObject* pybases = BuildCppClassBases(scope); + if (pybases != 0) { + // create a fresh Python class, given bases, name, and empty dictionary + pyscope = CreateNewCppProxyClass(scope, pybases); + Py_DECREF(pybases); + } + + // fill the dictionary, if successful + if (pyscope) { + if (BuildScopeProxyDict(scope, pyscope, flags)) { + // something failed in building the dictionary + Py_DECREF(pyscope); + pyscope = nullptr; + } + } + + // store a ref from cppjit scope id to new python class + if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { + gPyClasses[scope] = PyWeakref_NewRef(pyscope, nullptr); + + if (!(((CPPScope*)pyscope)->fFlags & CPPScope::kIsNamespace)) { + // add python-style features to classes only + if (!Pythonize(pyscope, scope)) { + Py_DECREF(pyscope); + pyscope = nullptr; + } + } else { + // add to sys.modules to allow importing from this namespace + PyObject* pyfullname = PyObject_GetAttr(pyscope, PyStrings::gModule); + cpyrt_PyText_AppendAndDel(&pyfullname, cpyrt_PyText_FromString(".")); + cpyrt_PyText_AppendAndDel(&pyfullname, + PyObject_GetAttr(pyscope, PyStrings::gName)); + PyObject* modules = PySys_GetObject(const_cast("modules")); + if (modules && PyDict_Check(modules)) + PyDict_SetItem(modules, pyfullname, pyscope); + Py_DECREF(pyfullname); + } + } + } + + // store on parent if found/created and complete + if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { + // FIXME: This is to mimic original behaviour. Still required? + if (interop::IsTemplateInstantiation(scope)) + name = interop::GetScopedFinalName(scope); + if (is_typedef && !typedefed_name.empty()) + AddScopeToParent(parent, typedefed_name, pyscope); + AddScopeToParent(parent, name, pyscope); + } + Py_DECREF(parent); + + // all done + return pyscope; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, + PyObject* parent) { + // To allow use of C++ exceptions in lieue of Python exceptions, they need to + // derive from BaseException, which can not mix with the normal CPPInstance + // and use of the meta-class. Instead, encapsulate them in a forwarding class + // that derives from Pythons Exception class + + // start with creation of CPPExcInstance type base classes + std::deque uqb; + CollectUniqueBases(((CPPScope*)pyscope)->fCppType, uqb); + size_t nbases = uqb.size(); + + // Support for multiple bases actually can not actually work as-is: the reason + // for deriving from BaseException is to guarantee the layout needed for + // storing traces. If some other base is std::exception (as e.g. + // boost::bad_any_cast) or also derives from std::exception, then there are + // two trace locations. OTOH, if the other class is a non-exception type, then + // the exception class does not need to derive from it because it can never be + // caught as that type forwarding to the proxy will work as expected, through, + // which is good enough). + // + // The code below restricts the hierarchy to a single base class, picking the + // "best" by filtering std::exception and non-exception bases. + + PyObject* pybases = PyTuple_New(1); + if (nbases == 0) { + Py_INCREF((PyObject*)(void*)&CPPExcInstance_Type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPExcInstance_Type); + } else { + PyObject* best_base = nullptr; + + for (std::deque::size_type ibase = 0; ibase < nbases; + ++ibase) { + // retrieve bases through their enclosing scope to guarantee treatment as + // exception classes and proper caching + interop::TCppScope_t parent_scope = interop::GetParentScope(uqb[ibase]); + PyObject* base_parent = CreateScopeProxy(parent_scope); + if (!base_parent) { + Py_DECREF(pybases); + return nullptr; + } + + PyObject* excbase = PyObject_GetAttrString( + base_parent, interop::GetFinalName(uqb[ibase]).c_str()); + Py_DECREF(base_parent); + if (!excbase) { + Py_DECREF(pybases); + return nullptr; + } + + if (PyType_IsSubtype((PyTypeObject*)excbase, &CPPExcInstance_Type)) { + Py_XDECREF(best_base); + best_base = excbase; + + if (interop::GetScopedFinalName(uqb[ibase]) != "std::exception") + break; + } else { + // just skip: there will be at least one exception derived base class + Py_DECREF(excbase); + } + } + + PyTuple_SET_ITEM(pybases, 0, best_base); + } + + PyObject* args = Py_BuildValue((char*)"OO{}", pyname, pybases); + + // meta-class attributes (__cpp_name__, etc.) can not be resolved lazily so + // add them directly instead in case they are needed + PyObject* dct = PyTuple_GET_ITEM(args, 2); + PyDict_SetItem(dct, PyStrings::gUnderlying, pyscope); + PyDict_SetItem(dct, PyStrings::gName, + PyObject_GetAttr(pyscope, PyStrings::gName)); + PyDict_SetItem(dct, PyStrings::gCppName, + PyObject_GetAttr(pyscope, PyStrings::gCppName)); + PyDict_SetItem(dct, PyStrings::gModule, + PyObject_GetAttr(pyscope, PyStrings::gModule)); + + // create the actual exception class + PyObject* exc_pyscope = PyType_Type.tp_new(&PyType_Type, args, nullptr); + Py_DECREF(args); + Py_DECREF(pybases); + + // cache the result for future lookups and return + PyType_Type.tp_setattro(parent, pyname, exc_pyscope); + return exc_pyscope; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObjectNoCast(interop::TCppObject_t address, + interop::TCppScope_t klass, + const unsigned flags) { + // only known or knowable objects will be bound (null object is ok) + if (!klass) { + PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); + return nullptr; + } + + // retrieve python class + PyObject* pyclass = CreateScopeProxy(klass); + if (!pyclass) + return nullptr; // error has been set in CreateScopeProxy + + bool noReg = flags & (CPPInstance::kNoMemReg | CPPInstance::kNoWrapConv); + bool isRef = flags & CPPInstance::kIsReference; + void* r_address = + isRef ? (address ? *(void**)address.data : nullptr) : address.data; + + // check whether the object to be bound is a smart pointer that needs + // embedding + PyObject* smart_type = (!(flags & CPPInstance::kNoWrapConv) && + (((CPPClass*)pyclass)->fFlags & CPPScope::kIsSmart)) + ? pyclass + : nullptr; + if (smart_type) { + interop::TCppScope_t underlying = + ((CPPSmartClass*)smart_type)->fUnderlyingType; + + // Downcast the underlying object to its actual (most derived) class, as + // BindCppObject does for raw pointers, but only when the cast is safe: + // * the actual class must really derive from the declared one; + // GetActualClass() can report a base (e.g. when the actual class has no + // dictionary of its own), and such an up-cast must be rejected. + // * the cast must need no pointer adjustment: the dereferencer always + // returns a pointer to the declared class, so a non-zero offset could + // not be applied consistently on later member access (multiple + // inheritance). + if (address && !isRef) { + if (void* deref = + interop::CallR(((CPPSmartClass*)smart_type)->fDereferencer, + address, 0, nullptr)) { + interop::TCppScope_t clActual = + interop::GetActualClass(underlying, deref); + if (clActual && clActual != underlying && + interop::IsSubclass(clActual, underlying) && + interop::GetBaseOffset(clActual, underlying, deref, + -1 /* down-cast */) == 0) + underlying = clActual; + } + } + + pyclass = CreateScopeProxy(underlying); + if (!pyclass) { + // simply restore and expose as the actual smart pointer class + pyclass = smart_type; + smart_type = nullptr; + } + } + + // TODO: make sure that a consistent address is used (may have to be done in + // BindCppObject) + if (address && !(flags & CPPInstance::kIsValue) /* always fresh */ && + !noReg) { + PyObject* oldPyObject = + MemoryRegulator::RetrievePyObject(r_address, pyclass); + + // embedded smart pointers are registered with the class of the underlying + // type, b/c there is no Python proxy for the smart pointer itself in that + // case; check whether the result found matches the smart-ness and if so, + // whether the smart pointer found is the same as the one requested (note: + // ptr-ptr requires old object to be a reference to enable re-use) + if (oldPyObject) { + CPPInstance* o_pyobj = ((CPPInstance*)oldPyObject); + + if ((bool)smart_type == o_pyobj->IsSmart() && + r_address == + (smart_type ? o_pyobj->GetObjectRaw() : o_pyobj->GetObject()) && + (!(flags & CPPInstance::kIsPtrPtr) || + (o_pyobj->fFlags & CPPInstance::kIsReference))) { + Py_DECREF(pyclass); + return oldPyObject; + } else { // reference or naked v.s. smart pointer mismatch + Py_DECREF(oldPyObject); + } + } + } + + // instantiate an object of this class + PyObject* args = PyTuple_New(0); + CPPInstance* pyobj = (CPPInstance*)((PyTypeObject*)pyclass) + ->tp_new((PyTypeObject*)pyclass, args, nullptr); + Py_DECREF(args); + + // bind, register and return if successful + if (pyobj != 0) { // fill proxy value? + unsigned objflags = + flags & (CPPInstance::kIsReference | CPPInstance::kIsPtrPtr | + CPPInstance::kIsValue | CPPInstance::kIsOwner | + CPPInstance::kIsActual); + pyobj->Set(address.data, (CPPInstance::EFlags)objflags); + + if (smart_type) + pyobj->SetSmart(smart_type); + + // do not register null pointers, references (regulated objects can be + // referenced, but referenced objects can not be regulated), or direct usage + // of smart pointers or iterators + if (address && !isRef && !noReg) + MemoryRegulator::RegisterPyObject( + pyobj, smart_type ? pyobj->GetObjectRaw() : pyobj->GetObject()); + } + + // successful completion; wrap exception options to make them raiseable, + // normal return otherwise + if (((CPPClass*)pyclass)->fFlags & CPPScope::kIsException) { + PyObject* exc_obj = + CPPExcInstance_Type.tp_new(&CPPExcInstance_Type, nullptr, nullptr); + ((CPPExcInstance*)exc_obj)->fCppInstance = (PyObject*)pyobj; + Py_DECREF(pyclass); + return exc_obj; + } + + Py_DECREF(pyclass); + return (PyObject*)pyobj; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObject(interop::TCppObject_t address, + interop::TCppScope_t klass, + const unsigned flags) { + // if the object is a null pointer, return a typed one (as needed for + // overloading) + if (!address) + return BindCppObjectNoCast(address, klass, flags); + + // only known or knowable objects will be bound + if (!klass) { + PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); + return nullptr; + } + + bool isRef = flags & CPPInstance::kIsReference; + + // downcast to real class for object returns, unless pinned + // TODO: should the memory regulator for klass be searched first, so that if + // successful, no down-casting is attempted? + // TODO: optimize for final classes + unsigned new_flags = flags; + if (!isRef && (gPinnedTypes.empty() || + gPinnedTypes.find(klass) == gPinnedTypes.end())) { + interop::TCppScope_t clActual = interop::GetActualClass(klass, address); + + if (clActual) { + if (clActual != klass) { + intptr_t offset = + interop::GetBaseOffset(clActual, klass, address, -1 /* down-cast */, + true /* report errors */); + if (offset != -1) { // may fail if clActual not fully defined + address = (void*)((intptr_t)address.data + offset); + klass = clActual; + } + } + new_flags |= CPPInstance::kIsActual; + } + } + + // actual binding (returned object may be zero w/ a python exception set) + return BindCppObjectNoCast(address, klass, new_flags); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObjectArray(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims) { + // TODO: this function exists for symmetry; need to figure out if it's useful + return TupleOfInstances_New(address, klass, dims); +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.h b/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.h new file mode 100644 index 0000000000000..ab4fec13e5db8 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.h @@ -0,0 +1,37 @@ +#ifndef CPYRT_PROXYWRAPPERS_H +#define CPYRT_PROXYWRAPPERS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +// construct a Python shadow class for the named C++ class +PyObject* GetScopeProxy(interop::TCppScope_t); +PyObject* CreateScopeProxy(PyObject*, PyObject* args); +PyObject* CreateScopeProxy(const std::string& scope_name, + PyObject* parent = nullptr, + const unsigned flags = 0); + +PyObject* CreateScopeProxy(interop::TCppScope_t scope, + PyObject* parent = nullptr, + const unsigned flags = 0); +// C++ exceptions form a special case b/c they have to derive from BaseException +PyObject* CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, + PyObject* parent); + +// bind a C++ object into a Python proxy object (flags are CPPInstance::Default) +PyObject* BindCppObjectNoCast(interop::TCppObject_t object, + interop::TCppScope_t klass, + const unsigned flags = 0); +PyObject* BindCppObject(interop::TCppObject_t object, + interop::TCppScope_t klass, const unsigned flags = 0); +PyObject* BindCppObjectArray(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PROXYWRAPPERS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/PyCallable.h b/bindings/pyroot/cppjit/src/cpyrt/PyCallable.h new file mode 100644 index 0000000000000..f9a64de44bd48 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyCallable.h @@ -0,0 +1,61 @@ +#ifndef CPYRT_PYCALLABLE_H +#define CPYRT_PYCALLABLE_H + +#include + +// Bindings +#include "CallContext.h" +#include "cppjit_interop.h" +#include "cpyrt/Reflex.h" + +namespace cppjit::cpyrt { + +class CPPInstance; + +class PyCallable { +public: + virtual ~PyCallable() {} + +public: + virtual PyObject* GetSignature(bool show_formalargs = true) = 0; + virtual PyObject* GetSignatureNames() = 0; + virtual PyObject* GetSignatureTypes() = 0; + virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; + virtual PyObject* GetTypeName() { return GetPrototype(false); } + virtual PyObject* GetDocString() { return GetPrototype(); } + virtual PyObject* + Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL) { + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; + }; + + virtual int GetPriority() = 0; + virtual bool IsGreedy() = 0; + + virtual int GetMaxArgs() = 0; + virtual PyObject* GetCoVarNames() = 0; + virtual PyObject* GetArgDefault(int /* iarg */, bool silent = true) = 0; + virtual bool IsConst() { return false; } + + virtual PyObject* GetScopeProxy() = 0; + virtual interop::TCppFuncAddr_t GetFunctionAddress() = 0; + + virtual PyCallable* Clone() = 0; + + virtual int GetArgMatchScore(PyObject* /* args_tuple */) { return INT_MAX; } + + virtual bool + IsSimilarFnType([[maybe_unused]] cppjit::interop::TCppType_t fn_type) { + return false; + } + +public: + virtual PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) = 0; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYCALLABLE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/PyException.cxx b/bindings/pyroot/cppjit/src/cpyrt/PyException.cxx new file mode 100644 index 0000000000000..5840faf1dc983 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyException.cxx @@ -0,0 +1,137 @@ +// Standard +#include + +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +#include "cpyrt/PyException.h" +#undef CPYRT_INTERNAL + +//______________________________________________________________________________ +// C++ exception for throwing python exceptions +// ============================================ +// Purpose: A C++ exception class for throwing python exceptions +// through C++ code. +// Created: Apr, 2004, Scott Snyder, from the version in D0's python_util. +// +// Note: Don't be tempted to declare the virtual functions defined here +// as inline. +// If you do, you may not be able to properly throw these +// exceptions across shared libraries. + +//- constructors/destructor -------------------------------------------------- +cppjit::cpyrt::PyException::PyException() { +#ifdef WITH_THREAD + PythonGILRAII python_gil_raii; +#endif + +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* pyvalue = PyErr_GetRaisedException(); + PyObject* pytype = pyvalue ? (PyObject*)Py_TYPE(pyvalue) : nullptr; + PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; +#else + PyObject *pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + PyObject* traceback = pytrace; // to keep the original unchanged + Py_XINCREF(traceback); +#endif + + if (pytype && pyvalue) { + const char* tname = PyExceptionClass_Name(pytype); + if (tname) { + char* dot = strrchr((char*)tname, '.'); + if (dot) + tname = dot + 1; + fMsg += tname; + fMsg += ": "; + } + + PyObject* msg = PyObject_Str(pyvalue); + if (msg) { + fMsg += cpyrt_PyText_AsString(msg); + Py_DECREF(msg); + } + } + + std::string locName; + std::string locFile; + int locLine = 0; + + while (traceback && traceback != Py_None) { + PyObject* frame = PyObject_GetAttrString(traceback, "tb_frame"); + PyObject* code = PyObject_GetAttrString(frame, "f_code"); + Py_DECREF(frame); + + PyObject* filename = PyObject_GetAttrString(code, "co_filename"); + Py_DECREF(code); + + PyObject* filenameStr = PyObject_Str(filename); + locFile = cpyrt_PyText_AsString(filenameStr); + Py_DECREF(filenameStr); + Py_DECREF(filename); + + PyObject* name = PyObject_GetAttrString(code, "co_name"); + PyObject* nameStr = PyObject_Str(name); + locName = cpyrt_PyText_AsString(nameStr); + Py_DECREF(nameStr); + Py_DECREF(name); + + PyObject* lineno = PyObject_GetAttrString(traceback, "tb_lineno"); + locLine = PyLong_AsLong(lineno); + Py_DECREF(lineno); + + if (locFile == "") { // these are not that useful, skipping + PyObject* nextTraceback = PyObject_GetAttrString(traceback, "tb_next"); + Py_DECREF(traceback); + traceback = nextTraceback; + continue; + } + + break; + } + + Py_XDECREF(traceback); + +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(pyvalue); +#else + PyErr_Restore(pytype, pyvalue, pytrace); +#endif + + if (fMsg.empty()) + fMsg = "python exception"; + + if (!locFile.empty()) { + + // only keeping the filename, not the full path + locFile = locFile.substr(locFile.find_last_of("/\\") + 1); + + fMsg += " (at " + locFile + ":" + std::to_string(locLine); + + if (locName != "") + fMsg += " in " + locName; + + fMsg += ")"; + } +} + +cppjit::cpyrt::PyException::~PyException() noexcept { + // destructor +} + +//- public members ----------------------------------------------------------- +const char* cppjit::cpyrt::PyException::what() const noexcept { + // Return reason for throwing this exception: a python exception was raised. + return fMsg.c_str(); +} + +void cppjit::cpyrt::PyException::clear() const noexcept { +#ifdef WITH_THREAD + PythonGILRAII python_gil_raii; +#endif + // clear Python error, to allow full error handling C++ side + PyErr_Clear(); +} diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h b/bindings/pyroot/cppjit/src/cpyrt/PyException.h similarity index 76% rename from bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h rename to bindings/pyroot/cppjit/src/cpyrt/PyException.h index 5c8133d89ca7b..e18e93ac499ee 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h +++ b/bindings/pyroot/cppjit/src/cpyrt/PyException.h @@ -1,5 +1,5 @@ -#ifndef CPYCPPYY_PYEXCEPTION_H -#define CPYCPPYY_PYEXCEPTION_H +#ifndef CPYRT_PYEXCEPTION_H +#define CPYRT_PYEXCEPTION_H ////////////////////////////////////////////////////////////////////////////// // // @@ -14,7 +14,7 @@ // - The C++ code can call back to python. // // - What to do then if the python callback throws an exception? // // // -// We need to get the control flow back to where CPyCppyy calls C++. // +// We need to get the control flow back to where cpyrt calls C++. // // To do that we throw a TPyException. // // We can then catch this exception when we do the C++ call. // // // @@ -31,26 +31,25 @@ #include // Bindings -#include "CPyCppyy/CommonDefs.h" +#include "cpyrt/CommonDefs.h" +namespace cppjit::cpyrt { -namespace CPyCppyy { - -class CPYCPPYY_CLASS_EXTERN PyException : public std::exception { +class CPYRT_CLASS_EXTERN PyException : public std::exception { public: - PyException(); - ~PyException() noexcept override; + PyException(); + ~PyException() noexcept override; -// give reason for raised exception - const char* what() const noexcept override; + // give reason for raised exception + const char* what() const noexcept override; -// clear Python error, to allow full error handling C++ side - void clear() const noexcept; + // clear Python error, to allow full error handling C++ side + void clear() const noexcept; private: - std::string fMsg; + std::string fMsg; }; -} // namespace CPyCppyy +} // namespace cppjit::cpyrt -#endif // !CPYCPPYY_PYEXCEPTION_H +#endif // !CPYRT_PYEXCEPTION_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc b/bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc similarity index 78% rename from bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc rename to bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc index 3274a7559983a..88f276215b3fd 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc +++ b/bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc @@ -111,57 +111,6 @@ static int merge_class_dict(PyObject* dict, PyObject* aclass) return 0; } -#if PY_VERSION_HEX < 0x03000000 - -/* Helper for PyObject_Dir. - If obj has an attr named attrname that's a list, merge its string - elements into keys of dict. - Return 0 on success, -1 on error. Errors due to not finding the attr, - or the attr not being a list, are suppressed. -*/ - -static int -merge_list_attr(PyObject* dict, PyObject* obj, const char *attrname) -{ - PyObject *list; - int result = 0; - - assert(PyDict_Check(dict)); - assert(obj); - assert(attrname); - - list = PyObject_GetAttrString(obj, attrname); - if (list == NULL) - PyErr_Clear(); - - else if (PyList_Check(list)) { - int i; - for (i = 0; i < PyList_GET_SIZE(list); ++i) { - PyObject *item = PyList_GET_ITEM(list, i); - if (PyString_Check(item)) { - result = PyDict_SetItem(dict, item, Py_None); - if (result < 0) - break; - } - } - if (Py_Py3kWarningFlag && - (strcmp(attrname, "__members__") == 0 || - strcmp(attrname, "__methods__") == 0)) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "__members__ and __methods__ not " - "supported in 3.x", 1) < 0) { - Py_XDECREF(list); - return -1; - } - } - } - - Py_XDECREF(list); - return result; -} - -#endif - /* Helper for PyObject_Dir of type objects: returns __dict__ and __bases__. We deliberately don't suck up its __class__, as methods belonging to the metaclass would probably be more confusing than helpful. @@ -208,15 +157,6 @@ static PyObject* _generic_dir(PyObject *obj) if (dict == NULL) goto error; -#if PY_VERSION_HEX < 0x03000000 - /* Merge in __members__ and __methods__ (if any). - * This is removed in Python 3000. */ - if (merge_list_attr(dict, obj, "__members__") < 0) - goto error; - if (merge_list_attr(dict, obj, "__methods__") < 0) - goto error; -#endif - /* Merge in attrs reachable from its class. */ itsclass = PyObject_GetAttrString(obj, "__class__"); if (itsclass == NULL) diff --git a/bindings/pyroot/cppjit/src/cpyrt/PyStrings.cxx b/bindings/pyroot/cppjit/src/cpyrt/PyStrings.cxx new file mode 100644 index 0000000000000..132878c7c5c1c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyStrings.cxx @@ -0,0 +1,302 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "PyStrings.h" + +//- data _____________________________________________________________________ +PyObject* cpyrt::PyStrings::gAssign = nullptr; +PyObject* cpyrt::PyStrings::gBases = nullptr; +PyObject* cpyrt::PyStrings::gBase = nullptr; +PyObject* cpyrt::PyStrings::gContains = nullptr; +PyObject* cpyrt::PyStrings::gCopy = nullptr; +PyObject* cpyrt::PyStrings::gCppBool = nullptr; +PyObject* cpyrt::PyStrings::gCppName = nullptr; +PyObject* cpyrt::PyStrings::gAnnotations = nullptr; +PyObject* cpyrt::PyStrings::gCastCpp = nullptr; +PyObject* cpyrt::PyStrings::gCType = nullptr; +PyObject* cpyrt::PyStrings::gDeref = nullptr; +PyObject* cpyrt::PyStrings::gPreInc = nullptr; +PyObject* cpyrt::PyStrings::gPostInc = nullptr; +PyObject* cpyrt::PyStrings::gDict = nullptr; +PyObject* cpyrt::PyStrings::gEmptyString = nullptr; +PyObject* cpyrt::PyStrings::gEq = nullptr; +PyObject* cpyrt::PyStrings::gFollow = nullptr; +PyObject* cpyrt::PyStrings::gHasValue = nullptr; +PyObject* cpyrt::PyStrings::gGetItem = nullptr; +PyObject* cpyrt::PyStrings::gGetNoCheck = nullptr; +PyObject* cpyrt::PyStrings::gSetItem = nullptr; +PyObject* cpyrt::PyStrings::gInit = nullptr; +PyObject* cpyrt::PyStrings::gIter = nullptr; +PyObject* cpyrt::PyStrings::gLen = nullptr; +PyObject* cpyrt::PyStrings::gLifeLine = nullptr; +PyObject* cpyrt::PyStrings::gModule = nullptr; +PyObject* cpyrt::PyStrings::gMRO = nullptr; +PyObject* cpyrt::PyStrings::gName = nullptr; +PyObject* cpyrt::PyStrings::gNe = nullptr; +PyObject* cpyrt::PyStrings::gRepr = nullptr; +PyObject* cpyrt::PyStrings::gCppRepr = nullptr; +PyObject* cpyrt::PyStrings::gStr = nullptr; +PyObject* cpyrt::PyStrings::gCppStr = nullptr; +PyObject* cpyrt::PyStrings::gTypeCode = nullptr; +PyObject* cpyrt::PyStrings::gCTypesType = nullptr; + +PyObject* cpyrt::PyStrings::gUnderlying = nullptr; +PyObject* cpyrt::PyStrings::gRealInit = nullptr; + +PyObject* cpyrt::PyStrings::gAdd = nullptr; +PyObject* cpyrt::PyStrings::gSub = nullptr; +PyObject* cpyrt::PyStrings::gMul = nullptr; +PyObject* cpyrt::PyStrings::gDiv = nullptr; + +PyObject* cpyrt::PyStrings::gLShift = nullptr; +PyObject* cpyrt::PyStrings::gLShiftC = nullptr; + +PyObject* cpyrt::PyStrings::gAt = nullptr; +PyObject* cpyrt::PyStrings::gBegin = nullptr; +PyObject* cpyrt::PyStrings::gEnd = nullptr; +PyObject* cpyrt::PyStrings::gFirst = nullptr; +PyObject* cpyrt::PyStrings::gSecond = nullptr; +PyObject* cpyrt::PyStrings::gSize = nullptr; +PyObject* cpyrt::PyStrings::gTemplate = nullptr; +PyObject* cpyrt::PyStrings::gVectorAt = nullptr; +PyObject* cpyrt::PyStrings::gInsert = nullptr; +PyObject* cpyrt::PyStrings::gValueType = nullptr; +PyObject* cpyrt::PyStrings::gValueTypePtr = nullptr; +PyObject* cpyrt::PyStrings::gValueSize = nullptr; + +PyObject* cpyrt::PyStrings::gCppReal = nullptr; +PyObject* cpyrt::PyStrings::gCppImag = nullptr; + +PyObject* cpyrt::PyStrings::gThisModule = nullptr; + +PyObject* cpyrt::PyStrings::gDispInit = nullptr; +PyObject* cpyrt::PyStrings::gDispGet = nullptr; + +PyObject* cpyrt::PyStrings::gExPythonize = nullptr; +PyObject* cpyrt::PyStrings::gPythonize = nullptr; + +PyObject* cpyrt::PyStrings::gArray = nullptr; +PyObject* cpyrt::PyStrings::gDType = nullptr; +PyObject* cpyrt::PyStrings::gFromBuffer = nullptr; + +//----------------------------------------------------------------------------- +#define CPPJIT_INITIALIZE_STRING(var, str) \ + if (!(PyStrings::var = cpyrt_PyText_InternFromString((char*)#str))) \ + return false + +bool cpyrt::CreatePyStrings() { + // Build cache of commonly used python strings (the cache is python intern, so + // all strings are shared python-wide, not just in cppjit). + CPPJIT_INITIALIZE_STRING(gAssign, __assign__); + CPPJIT_INITIALIZE_STRING(gBases, __bases__); + CPPJIT_INITIALIZE_STRING(gBase, __base__); + CPPJIT_INITIALIZE_STRING(gContains, contains); + CPPJIT_INITIALIZE_STRING(gCopy, copy); + CPPJIT_INITIALIZE_STRING(gCppBool, __cpp_bool__); + CPPJIT_INITIALIZE_STRING(gCppName, __cpp_name__); + CPPJIT_INITIALIZE_STRING(gAnnotations, __annotations__); + CPPJIT_INITIALIZE_STRING(gCastCpp, __cast_cpp__); + CPPJIT_INITIALIZE_STRING(gCType, __ctype__); + CPPJIT_INITIALIZE_STRING(gDeref, __deref__); + CPPJIT_INITIALIZE_STRING(gPreInc, __preinc__); + CPPJIT_INITIALIZE_STRING(gPostInc, __postinc__); + CPPJIT_INITIALIZE_STRING(gDict, __dict__); + if (!(PyStrings::gEmptyString = cpyrt_PyText_FromString((char*)""))) + return false; + CPPJIT_INITIALIZE_STRING(gEq, __eq__); + CPPJIT_INITIALIZE_STRING(gFollow, __follow__); + CPPJIT_INITIALIZE_STRING(gHasValue, has_value); + CPPJIT_INITIALIZE_STRING(gGetItem, __getitem__); + CPPJIT_INITIALIZE_STRING(gGetNoCheck, _getitem__unchecked); + CPPJIT_INITIALIZE_STRING(gSetItem, __setitem__); + CPPJIT_INITIALIZE_STRING(gInit, __init__); + CPPJIT_INITIALIZE_STRING(gIter, __iter__); + CPPJIT_INITIALIZE_STRING(gLen, __len__); + CPPJIT_INITIALIZE_STRING(gLifeLine, __lifeline); + CPPJIT_INITIALIZE_STRING(gModule, __module__); + CPPJIT_INITIALIZE_STRING(gMRO, __mro__); + CPPJIT_INITIALIZE_STRING(gName, __name__); + CPPJIT_INITIALIZE_STRING(gNe, __ne__); + CPPJIT_INITIALIZE_STRING(gRepr, __repr__); + CPPJIT_INITIALIZE_STRING(gCppRepr, __cpp_repr); + CPPJIT_INITIALIZE_STRING(gStr, __str__); + CPPJIT_INITIALIZE_STRING(gCppStr, __cpp_str); + CPPJIT_INITIALIZE_STRING(gTypeCode, typecode); + CPPJIT_INITIALIZE_STRING(gCTypesType, _type_); + + CPPJIT_INITIALIZE_STRING(gUnderlying, __underlying); + CPPJIT_INITIALIZE_STRING(gRealInit, __real_init); + + CPPJIT_INITIALIZE_STRING(gAdd, __add__); + CPPJIT_INITIALIZE_STRING(gSub, __sub__); + CPPJIT_INITIALIZE_STRING(gMul, __mul__); + CPPJIT_INITIALIZE_STRING(gDiv, CPPJIT__div__); + + CPPJIT_INITIALIZE_STRING(gLShift, __lshift__); + CPPJIT_INITIALIZE_STRING(gLShiftC, __lshiftc__); + + CPPJIT_INITIALIZE_STRING(gAt, at); + CPPJIT_INITIALIZE_STRING(gBegin, begin); + CPPJIT_INITIALIZE_STRING(gEnd, end); + CPPJIT_INITIALIZE_STRING(gFirst, first); + CPPJIT_INITIALIZE_STRING(gSecond, second); + CPPJIT_INITIALIZE_STRING(gSize, size); + CPPJIT_INITIALIZE_STRING(gTemplate, Template); + CPPJIT_INITIALIZE_STRING(gVectorAt, _vector__at); + CPPJIT_INITIALIZE_STRING(gInsert, insert); + CPPJIT_INITIALIZE_STRING(gValueType, value_type); + CPPJIT_INITIALIZE_STRING(gValueTypePtr, _value_type); + CPPJIT_INITIALIZE_STRING(gValueSize, value_size); + + CPPJIT_INITIALIZE_STRING(gCppReal, __cpp_real); + CPPJIT_INITIALIZE_STRING(gCppImag, __cpp_imag); + + CPPJIT_INITIALIZE_STRING(gThisModule, cppjit); + + CPPJIT_INITIALIZE_STRING(gDispInit, _init_dispatchptr); + CPPJIT_INITIALIZE_STRING(gDispGet, _get_dispatch); + + CPPJIT_INITIALIZE_STRING(gExPythonize, __cppjit_explicit_pythonize__); + CPPJIT_INITIALIZE_STRING(gPythonize, __cppjit_pythonize__); + + CPPJIT_INITIALIZE_STRING(gArray, __array__); + CPPJIT_INITIALIZE_STRING(gDType, dtype); + CPPJIT_INITIALIZE_STRING(gFromBuffer, frombuffer); + + return true; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::DestroyPyStrings() { + // Remove all cached python strings. + Py_DECREF(PyStrings::gBases); + PyStrings::gBases = nullptr; + Py_DECREF(PyStrings::gBase); + PyStrings::gBase = nullptr; + Py_DECREF(PyStrings::gContains); + PyStrings::gContains = nullptr; + Py_DECREF(PyStrings::gCopy); + PyStrings::gCopy = nullptr; + Py_DECREF(PyStrings::gCppBool); + PyStrings::gCppBool = nullptr; + Py_DECREF(PyStrings::gCppName); + PyStrings::gCppName = nullptr; + Py_DECREF(PyStrings::gAnnotations); + PyStrings::gAnnotations = nullptr; + Py_DECREF(PyStrings::gCType); + PyStrings::gCType = nullptr; + Py_DECREF(PyStrings::gDeref); + PyStrings::gDeref = nullptr; + Py_DECREF(PyStrings::gPreInc); + PyStrings::gPreInc = nullptr; + Py_DECREF(PyStrings::gPostInc); + PyStrings::gPostInc = nullptr; + Py_DECREF(PyStrings::gDict); + PyStrings::gDict = nullptr; + Py_DECREF(PyStrings::gEmptyString); + PyStrings::gEmptyString = nullptr; + Py_DECREF(PyStrings::gEq); + PyStrings::gEq = nullptr; + Py_DECREF(PyStrings::gFollow); + PyStrings::gFollow = nullptr; + Py_DECREF(PyStrings::gHasValue); + PyStrings::gHasValue = nullptr; + Py_DECREF(PyStrings::gGetItem); + PyStrings::gGetItem = nullptr; + Py_DECREF(PyStrings::gGetNoCheck); + PyStrings::gGetNoCheck = nullptr; + Py_DECREF(PyStrings::gSetItem); + PyStrings::gSetItem = nullptr; + Py_DECREF(PyStrings::gInit); + PyStrings::gInit = nullptr; + Py_DECREF(PyStrings::gIter); + PyStrings::gIter = nullptr; + Py_DECREF(PyStrings::gLen); + PyStrings::gLen = nullptr; + Py_DECREF(PyStrings::gLifeLine); + PyStrings::gLifeLine = nullptr; + Py_DECREF(PyStrings::gModule); + PyStrings::gModule = nullptr; + Py_DECREF(PyStrings::gMRO); + PyStrings::gMRO = nullptr; + Py_DECREF(PyStrings::gName); + PyStrings::gName = nullptr; + Py_DECREF(PyStrings::gNe); + PyStrings::gNe = nullptr; + Py_DECREF(PyStrings::gTypeCode); + PyStrings::gTypeCode = nullptr; + Py_DECREF(PyStrings::gCTypesType); + PyStrings::gCTypesType = nullptr; + + Py_DECREF(PyStrings::gUnderlying); + PyStrings::gUnderlying = nullptr; + Py_DECREF(PyStrings::gRealInit); + PyStrings::gRealInit = nullptr; + + Py_DECREF(PyStrings::gAdd); + PyStrings::gAdd = nullptr; + Py_DECREF(PyStrings::gSub); + PyStrings::gSub = nullptr; + Py_DECREF(PyStrings::gMul); + PyStrings::gMul = nullptr; + Py_DECREF(PyStrings::gDiv); + PyStrings::gDiv = nullptr; + + Py_DECREF(PyStrings::gLShift); + PyStrings::gLShift = nullptr; + Py_DECREF(PyStrings::gLShiftC); + PyStrings::gLShiftC = nullptr; + + Py_DECREF(PyStrings::gAt); + PyStrings::gAt = nullptr; + Py_DECREF(PyStrings::gBegin); + PyStrings::gBegin = nullptr; + Py_DECREF(PyStrings::gEnd); + PyStrings::gEnd = nullptr; + Py_DECREF(PyStrings::gFirst); + PyStrings::gFirst = nullptr; + Py_DECREF(PyStrings::gSecond); + PyStrings::gSecond = nullptr; + Py_DECREF(PyStrings::gSize); + PyStrings::gSize = nullptr; + Py_DECREF(PyStrings::gTemplate); + PyStrings::gTemplate = nullptr; + Py_DECREF(PyStrings::gVectorAt); + PyStrings::gVectorAt = nullptr; + Py_DECREF(PyStrings::gInsert); + PyStrings::gInsert = nullptr; + Py_DECREF(PyStrings::gValueType); + PyStrings::gValueType = nullptr; + Py_DECREF(PyStrings::gValueTypePtr); + PyStrings::gValueTypePtr = nullptr; + Py_DECREF(PyStrings::gValueSize); + PyStrings::gValueSize = nullptr; + + Py_DECREF(PyStrings::gCppReal); + PyStrings::gCppReal = nullptr; + Py_DECREF(PyStrings::gCppImag); + PyStrings::gCppImag = nullptr; + + Py_DECREF(PyStrings::gThisModule); + PyStrings::gThisModule = nullptr; + + Py_DECREF(PyStrings::gDispInit); + PyStrings::gDispInit = nullptr; + Py_DECREF(PyStrings::gDispGet); + PyStrings::gDispGet = nullptr; + + Py_DECREF(PyStrings::gExPythonize); + PyStrings::gExPythonize = nullptr; + Py_DECREF(PyStrings::gPythonize); + PyStrings::gPythonize = nullptr; + + Py_DECREF(PyStrings::gArray); + PyStrings::gArray = nullptr; + Py_DECREF(PyStrings::gDType); + PyStrings::gDType = nullptr; + Py_DECREF(PyStrings::gFromBuffer); + PyStrings::gFromBuffer = nullptr; + + Py_RETURN_NONE; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/PyStrings.h b/bindings/pyroot/cppjit/src/cpyrt/PyStrings.h new file mode 100644 index 0000000000000..64724f8b2420c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyStrings.h @@ -0,0 +1,92 @@ +#ifndef CPYRT_PYSTRINGS_H +#define CPYRT_PYSTRINGS_H + +namespace cppjit::cpyrt { + +// python strings kept for performance reasons + +namespace PyStrings { + +extern PyObject* gAssign; +extern PyObject* gBases; +extern PyObject* gBase; +extern PyObject* gContains; +extern PyObject* gCopy; +extern PyObject* gCppBool; +extern PyObject* gCppName; +extern PyObject* gAnnotations; +extern PyObject* gCastCpp; +extern PyObject* gCType; +extern PyObject* gDeref; +extern PyObject* gPreInc; +extern PyObject* gPostInc; +extern PyObject* gDict; +extern PyObject* gEmptyString; +extern PyObject* gEq; +extern PyObject* gFollow; +extern PyObject* gHasValue; +extern PyObject* gGetItem; +extern PyObject* gGetNoCheck; +extern PyObject* gSetItem; +extern PyObject* gInit; +extern PyObject* gIter; +extern PyObject* gLen; +extern PyObject* gLifeLine; +extern PyObject* gModule; +extern PyObject* gMRO; +extern PyObject* gName; +extern PyObject* gNe; +extern PyObject* gRepr; +extern PyObject* gCppRepr; +extern PyObject* gStr; +extern PyObject* gCppStr; +extern PyObject* gTypeCode; +extern PyObject* gCTypesType; + +extern PyObject* gUnderlying; +extern PyObject* gRealInit; + +extern PyObject* gAdd; +extern PyObject* gSub; +extern PyObject* gMul; +extern PyObject* gDiv; + +extern PyObject* gLShift; +extern PyObject* gLShiftC; + +extern PyObject* gAt; +extern PyObject* gBegin; +extern PyObject* gEnd; +extern PyObject* gFirst; +extern PyObject* gSecond; +extern PyObject* gSize; +extern PyObject* gTemplate; +extern PyObject* gVectorAt; +extern PyObject* gInsert; +extern PyObject* gValueType; +extern PyObject* gValueTypePtr; +extern PyObject* gValueSize; + +extern PyObject* gCppReal; +extern PyObject* gCppImag; + +extern PyObject* gThisModule; + +extern PyObject* gDispInit; +extern PyObject* gDispGet; + +extern PyObject* gExPythonize; +extern PyObject* gPythonize; + +extern PyObject* gArray; +extern PyObject* gDType; +extern PyObject* gFromBuffer; + +} // namespace PyStrings + +bool CreatePyStrings(); +PyObject* DestroyPyStrings(); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYSTRINGS_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Pythonize.cxx b/bindings/pyroot/cppjit/src/cpyrt/Pythonize.cxx new file mode 100644 index 0000000000000..cc1575bafce7e --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Pythonize.cxx @@ -0,0 +1,2391 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "Pythonize.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +//- data and local helpers --------------------------------------------------- +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +std::unordered_map>& pythonizations(); +} // namespace cppjit::cpyrt + +namespace { + +// for convenience +using namespace cppjit::cpyrt; + +//----------------------------------------------------------------------------- +bool HasAttrDirect(PyObject* pyclass, PyObject* pyname, + bool mustBecpyrt = false) { + // prevents calls to Py_TYPE(pyclass)->tp_getattr, which is unnecessary for + // our purposes here and could tickle problems w/ spurious lookups into ROOT + // meta + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + if (attr) { + bool ret = !mustBecpyrt || CPPOverload_Check(attr); + Py_DECREF(attr); + return ret; + } + } + PyErr_Clear(); + return false; +} + +PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { + // get an attribute without causing getattr lookups + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + return attr; + } + return nullptr; +} + +//----------------------------------------------------------------------------- +inline bool IsTemplatedSTLClass(const std::string& name, + const std::string& klass) { + // Scan the name of the class and determine whether it is a template + // instantiation. + auto pos = name.find(klass); + return pos == 5 && name.rfind("std::", 0, 5) == 0 && + name.find("::", name.rfind(">")) == std::string::npos; +} + +// to prevent compiler warnings about const char* -> char* +inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth) { + // Helper; call method with signature: obj->meth(). + Py_INCREF(obj); + PyObject* result = + PyObject_CallMethod(obj, const_cast(meth), const_cast("")); + Py_DECREF(obj); + return result; +} + +//----------------------------------------------------------------------------- +inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, + PyObject* arg1) { + // Helper; call method with signature: obj->meth(arg1). + Py_INCREF(obj); + PyObject* result = PyObject_CallMethod(obj, const_cast(meth), + const_cast("O"), arg1); + Py_DECREF(obj); + return result; +} + +//----------------------------------------------------------------------------- +PyObject* PyStyleIndex(PyObject* self, PyObject* index) { + // Helper; converts python index into straight C index. + Py_ssize_t idx = PyInt_AsSsize_t(index); + if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) + return nullptr; + + Py_ssize_t size = PySequence_Size(self); + if (idx >= size || (idx < 0 && idx < -size)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + + PyObject* pyindex = nullptr; + if (idx >= 0) { + Py_INCREF(index); + pyindex = index; + } else + pyindex = PyLong_FromSsize_t(size + idx); + + return pyindex; +} + +//----------------------------------------------------------------------------- +inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, + Py_ssize_t& stop, Py_ssize_t& step) { + // Helper; modify slice range to match the container. + if ((step > 0 && stop <= start) || (step < 0 && start <= stop)) + return false; + + if (start < 0) + start = 0; + if (start >= nlen) + start = nlen - 1; + if (step >= nlen) + step = nlen; + + stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1); + return true; +} + +//----------------------------------------------------------------------------- +inline PyObject* CallSelfIndex(CPPInstance* self, PyObject* idx, + PyObject* pymeth) { + // Helper; call method with signature: meth(pyindex). + Py_INCREF((PyObject*)self); + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) { + Py_DECREF((PyObject*)self); + return nullptr; + } + + PyObject* result = + PyObject_CallMethodOneArg((PyObject*)self, pymeth, pyindex); + Py_DECREF(pyindex); + Py_DECREF((PyObject*)self); + return result; +} + +//- "smart pointer" behavior --------------------------------------------------- +static PyObject* smart_follow(PyObject* self, PyObject* name, + PyObject* method) { + if (!cpyrt_PyText_Check(name)) + PyErr_SetString(PyExc_TypeError, + "getattr(): attribute name must be string"); + + PyObject* pyptr = PyObject_CallMethodNoArgs(self, method); + if (!pyptr) + return nullptr; + + // prevent a potential infinite loop + if (Py_TYPE(pyptr) == Py_TYPE(self)) { + PyObject* val1 = PyObject_Str((PyObject*)Py_TYPE(self)); + PyObject* val2 = PyObject_Str(name); + PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'", + cpyrt_PyText_AsString(val1), cpyrt_PyText_AsString(val2)); + Py_DECREF(val2); + Py_DECREF(val1); + + Py_DECREF(pyptr); + return nullptr; + } + + PyObject* result = PyObject_GetAttr(pyptr, name); + Py_DECREF(pyptr); + return result; +} + +PyObject* DeRefGetAttr(PyObject* self, PyObject* name) { + // Follow operator*() if present (available in python as __deref__), so that + // smart pointers behave as expected. + if (name == PyStrings::gTypeCode || name == PyStrings::gCTypesType) { + // TODO: these calls come from TemplateProxy and are unlikely to be needed + // in practice, whereas as-is, they can accidentally dereference the result + // of end() on some STL containers. Obviously, this is a dumb hack that + // should be resolved more fundamentally. + PyErr_SetString(PyExc_AttributeError, cpyrt_PyText_AsString(name)); + return nullptr; + } + + return smart_follow(self, name, PyStrings::gDeref); +} + +//----------------------------------------------------------------------------- +PyObject* ValueGetAttr(PyObject* self, PyObject* name) { + // std::optional and std::expected require has_value() before operator*(). + PyObject* has_value = PyObject_CallMethodNoArgs(self, PyStrings::gHasValue); + if (!has_value) + return nullptr; + + const int contains_value = PyObject_IsTrue(has_value); + Py_DECREF(has_value); + if (contains_value < 0) + return nullptr; + if (!contains_value) { + PyErr_SetObject(PyExc_AttributeError, name); + return nullptr; + } + + return DeRefGetAttr(self, name); +} + +//----------------------------------------------------------------------------- +PyObject* FollowGetAttr(PyObject* self, PyObject* name) { + // Follow operator->() if present (available in python as __follow__), so that + // smart pointers behave as expected. + return smart_follow(self, name, PyStrings::gFollow); +} + +//- pointer checking bool converter ------------------------------------------- +PyObject* NullCheckBool(PyObject* self) { + if (!CPPInstance_Check(self)) { + PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); + return nullptr; + } + + if (!((CPPInstance*)self)->GetObject()) + Py_RETURN_FALSE; + + return PyObject_CallMethodNoArgs(self, PyStrings::gCppBool); +} + +//- vector behavior as primitives ---------------------------------------------- + +// TODO: can probably use the below getters in the InitializerListConverter +struct ItemGetter { + ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); } + virtual ~ItemGetter() { Py_DECREF(fPyObject); } + virtual Py_ssize_t size() = 0; + virtual PyObject* get() = 0; + PyObject* fPyObject; +}; + +struct CountedItemGetter : public ItemGetter { + CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {} + Py_ssize_t fCur; +}; + +struct TupleItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); } + PyObject* get() override { + if (fCur < PyTuple_GET_SIZE(fPyObject)) { + PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++); + Py_INCREF(item); + return item; + } + PyErr_SetString(PyExc_StopIteration, "end of tuple"); + return nullptr; + } +}; + +struct ListItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); } + PyObject* get() override { + if (fCur < PyList_GET_SIZE(fPyObject)) { + PyObject* item = PyList_GET_ITEM(fPyObject, fCur++); + Py_INCREF(item); + return item; + } + PyErr_SetString(PyExc_StopIteration, "end of list"); + return nullptr; + } +}; + +struct SequenceItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { + Py_ssize_t sz = PySequence_Size(fPyObject); + if (sz < 0) { + PyErr_Clear(); + return PyObject_LengthHint(fPyObject, 8); + } + return sz; + } + PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); } +}; + +struct IterItemGetter : public ItemGetter { + using ItemGetter::ItemGetter; + Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); } + PyObject* get() override { + return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); + } +}; + +static ItemGetter* GetGetter(PyObject* args) { + // Create an ItemGetter to loop over the iterable argument, if any. + ItemGetter* getter = nullptr; + + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* fi = PyTuple_GET_ITEM(args, 0); + if (cpyrt_PyText_Check(fi) || PyBytes_Check(fi)) + return nullptr; // do not accept string to fill std::vector + + // TODO: this only tests for new-style buffers, which is too strict, but a + // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the + // main use case is numpy, which offers the new interface) + if (PyObject_CheckBuffer(fi)) + return nullptr; + + if (PyTuple_CheckExact(fi)) + getter = new TupleItemGetter(fi); + else if (PyList_CheckExact(fi)) + getter = new ListItemGetter(fi); + else if (PySequence_Check(fi)) + getter = new SequenceItemGetter(fi); + else { + PyObject* iter = PyObject_GetIter(fi); + if (iter) { + getter = new IterItemGetter{iter}; + Py_DECREF(iter); + } else + PyErr_Clear(); + } + } + + return getter; +} + +namespace { + +void compileSpanHelpers() { + static bool compiled = false; + + if (compiled) + return; + + compiled = true; + + auto code = R"( +namespace __cppjit_internal { + +template +struct ptr_iterator { + T *cur; + T *end; + + ptr_iterator(T *c, T *e) : cur(c), end(e) {} + + T &operator*() const { return *cur; } + ptr_iterator &operator++() + { + ++cur; + return *this; + } + bool operator==(const ptr_iterator &other) const { return cur == other.cur; } + bool operator!=(const ptr_iterator &other) const { return !(*this == other); } +}; + +template +ptr_iterator make_iter(T *begin, T *end) +{ + return {begin, end}; +} + +} // namespace __cppjit_internal + +// Note: for const span, T is const-qualified here +template +auto __cppjit_internal_begin(T &s) noexcept +{ + return __cppjit_internal::make_iter(s.data(), s.data() + s.size()); +} + +// Note: for const span, T is const-qualified here +template +auto __cppjit_internal_end(T &s) noexcept +{ + // end iterator = begin iterator with cur == end + return __cppjit_internal::make_iter(s.data() + s.size(), s.data() + s.size()); +} + )"; + interop::Compile(code, /*silent*/ true); +} + +PyObject* spanBegin() { + static PyObject* pyFunc = nullptr; + if (!pyFunc) { + compileSpanHelpers(); + PyObject* py_ns = cpyrt::GetScopeProxy(interop::GetGlobalScope()); + pyFunc = PyObject_GetAttrString(py_ns, "__cppjit_internal_begin"); + if (!pyFunc) { + PyErr_Format(PyExc_RuntimeError, + "cppyy internal error: failed to locate helper " + "'__cppjit_internal_begin' for std::span pythonization"); + } + } + return pyFunc; +} + +PyObject* spanEnd() { + static PyObject* pyFunc = nullptr; + if (!pyFunc) { + compileSpanHelpers(); + PyObject* py_ns = cpyrt::GetScopeProxy(interop::GetGlobalScope()); + pyFunc = PyObject_GetAttrString(py_ns, "__cppjit_internal_end"); + if (!pyFunc) { + PyErr_Format(PyExc_RuntimeError, + "cppyy internal error: failed to locate helper " + "'__cppjit_internal_end' for std::span pythonization"); + } + } + return pyFunc; +} + +} // namespace + +static PyObject* SpanBegin(PyObject* self, PyObject*) { + auto begin = spanBegin(); + if (!begin) + return nullptr; + return PyObject_CallOneArg(begin, self); +} + +static PyObject* SpanEnd(PyObject* self, PyObject*) { + auto end = spanEnd(); + if (!end) + return nullptr; + return PyObject_CallOneArg(end, self); +} + +static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) { + Py_ssize_t sz = getter->size(); + if (sz < 0) + return false; + + // reserve memory as applicable + if (0 < sz) { + PyObject* res = + PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz); + Py_DECREF(res); + } else // i.e. sz == 0, so empty container: done + return true; + + bool fill_ok = true; + + // two main options: a list of lists (or tuples), or a list of objects; the + // former are emplace_back'ed, the latter push_back'ed + PyObject* fi = PySequence_GetItem(PyTuple_GET_ITEM(args, 0), 0); + if (!fi) + PyErr_Clear(); + if (fi && (PyTuple_CheckExact(fi) || PyList_CheckExact(fi))) { + // use emplace_back to construct the vector entries one by one + PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back"); + PyObject* vtype = + GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueTypePtr); + bool value_is_vector = false; + if (vtype && PyLong_Check(vtype)) { + // if the value_type is a vector, then allow for initialization from + // sequences + if (interop::GetTypeAsString(PyLong_AsVoidPtr(vtype)) + .rfind("std::vector", 0) != std::string::npos) + value_is_vector = true; + } else + PyErr_Clear(); + Py_XDECREF(vtype); + + if (eb_call) { + PyObject* eb_args; + for (int i = 0; /* until break */; ++i) { + PyObject* item = getter->get(); + if (item) { + if (value_is_vector && PySequence_Check(item)) { + eb_args = PyTuple_New(1); + PyTuple_SET_ITEM(eb_args, 0, item); + } else if (PyTuple_CheckExact(item)) { + eb_args = item; + } else if (PyList_CheckExact(item)) { + Py_ssize_t isz = PyList_GET_SIZE(item); + eb_args = PyTuple_New(isz); + for (Py_ssize_t j = 0; j < isz; ++j) { + PyObject* iarg = PyList_GET_ITEM(item, j); + Py_INCREF(iarg); + PyTuple_SET_ITEM(eb_args, j, iarg); + } + Py_DECREF(item); + } else { + Py_DECREF(item); + PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", + i); + fill_ok = false; + break; + } + PyObject* ebres = PyObject_CallObject(eb_call, eb_args); + Py_DECREF(eb_args); + if (!ebres) { + fill_ok = false; + break; + } + Py_DECREF(ebres); + } else { + if (PyErr_Occurred()) { + if (!(PyErr_ExceptionMatches(PyExc_IndexError) || + PyErr_ExceptionMatches(PyExc_StopIteration))) + fill_ok = false; + else { + PyErr_Clear(); + } + } + break; + } + } + Py_DECREF(eb_call); + } + } else { + // use push_back to add the vector entries one by one + PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back"); + if (pb_call) { + for (;;) { + PyObject* item = getter->get(); + if (item) { + PyObject* pbres = + PyObject_CallFunctionObjArgs(pb_call, item, nullptr); + Py_DECREF(item); + if (!pbres) { + fill_ok = false; + break; + } + Py_DECREF(pbres); + } else { + if (PyErr_Occurred()) { + if (!(PyErr_ExceptionMatches(PyExc_IndexError) || + PyErr_ExceptionMatches(PyExc_StopIteration))) + fill_ok = false; + else { + PyErr_Clear(); + } + } + break; + } + } + Py_DECREF(pb_call); + } + } + Py_XDECREF(fi); + + return fill_ok; +} + +PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Implement fast __iadd__ on std::vector (generic __iadd__ is in Python) + ItemGetter* getter = GetGetter(args); + + if (getter) { + bool fill_ok = FillVector(self, args, getter); + delete getter; + + if (!fill_ok) + return nullptr; + + Py_INCREF(self); + return self; + } + + // if no getter, it could still be b/c we have a buffer (e.g. numpy); looping + // over a buffer here is slow, so use insert() instead + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* fi = PyTuple_GET_ITEM(args, 0); + if (PyObject_CheckBuffer(fi) && + !(cpyrt_PyText_Check(fi) || PyBytes_Check(fi))) { + PyObject* vend = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (vend) { + // when __iadd__ is overriden, the operation does not end with + // calling the __iadd__ method, but also assigns the result to the + // lhs of the iadd. For example, performing vec += arr, Python + // first calls our override, and then does vec = vec.iadd(arr). + PyObject* it = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, + vend, fi, nullptr); + Py_DECREF(vend); + + if (!it) + return nullptr; + + Py_DECREF(it); + // Assign the result of the __iadd__ override to the std::vector + Py_INCREF(self); + return self; + } + } + } + + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "argument is not iterable"); + return nullptr; // error already set +} + +PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized vector constructor to allow construction from containers; + // allowing such construction from initializer_list instead would possible, + // but can be error-prone. This use case is common enough for std::vector to + // implement it directly, except for arrays (which can be passed wholesale) + // and strings (which won't convert properly as they'll be seen as buffers) + + ItemGetter* getter = GetGetter(args); + + if (getter) { + // construct an empty vector, then back-fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) { + delete getter; + return nullptr; + } + + bool fill_ok = FillVector(self, args, getter); + delete getter; + + if (!fill_ok) { + Py_DECREF(result); + return nullptr; + } + + return result; + } + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//--------------------------------------------------------------------------- +PyObject* VectorData(PyObject* self, PyObject*) { + PyObject* pydata = CallPyObjMethod(self, "__real_data"); + if (!LowLevelView_Check(pydata) && !CPPInstance_Check(pydata)) + return pydata; + + PyObject* pylen = PyObject_CallMethodNoArgs(self, PyStrings::gSize); + if (!pylen) { + PyErr_Clear(); + return pydata; + } + + long clen = PyInt_AsLong(pylen); + Py_DECREF(pylen); + + if (CPPInstance_Check(pydata)) { + ((CPPInstance*)pydata)->CastToArray(clen); + return pydata; + } + + ((LowLevelView*)pydata)->resize((size_t)clen); + return pydata; +} + +//--------------------------------------------------------------------------- +PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* pydata = VectorData(self, nullptr); + PyObject* arrcall = PyObject_GetAttr(pydata, PyStrings::gArray); + PyObject* newarr = PyObject_Call(arrcall, args, kwargs); + Py_DECREF(arrcall); + Py_DECREF(pydata); + return newarr; +} + +//----------------------------------------------------------------------------- +static PyObject* vector_iter(PyObject* v) { + vectoriterobject* vi = PyObject_GC_New(vectoriterobject, &VectorIter_Type); + if (!vi) + return nullptr; + + Py_INCREF(v); + vi->ii_container = v; + + // tell the iterator code to set a life line if this container is a temporary + vi->vi_flags = vectoriterobject::kDefault; +#if PY_VERSION_HEX >= 0x030e0000 + if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || + (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#else + if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#endif + vi->vi_flags = vectoriterobject::kNeedLifeLine; + + Py_INCREF(v); + + PyObject* pyvalue_type = + PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueTypePtr); + if (pyvalue_type) { + PyObject* pyvalue_size = + GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); + if (pyvalue_size) { + vi->vi_stride = PyLong_AsLong(pyvalue_size); + Py_DECREF(pyvalue_size); + } else { + PyErr_Clear(); + vi->vi_stride = 0; + } + + if (PyLong_Check(pyvalue_type)) { + interop::TCppType_t value_type = PyLong_AsVoidPtr(pyvalue_type); + value_type = interop::ResolveType(value_type); + vi->vi_klass = interop::GetScopeFromType(value_type); + if (!vi->vi_klass) { + // look for a special case of pointer to a class type (which is a + // builtin, but it is more useful to treat it polymorphically by + // allowing auto-downcasts) + const std::string& clean_type = TypeManip::clean_type( + interop::GetTypeAsString(value_type), false, false); + interop::TCppScope_t c = interop::GetScope(clean_type); + if (c && + TypeManip::compound(interop::GetTypeAsString(value_type)) == "*") { + vi->vi_klass = c; + vi->vi_flags = vectoriterobject::kIsPolymorphic; + } + } + if (interop::IsPointerType(value_type)) + vi->vi_flags = vectoriterobject::kIsPolymorphic; + if (vi->vi_klass) { + vi->vi_converter = nullptr; + if (!vi->vi_flags) { + value_type = interop::ResolveType(value_type); + if (interop::GetTypeAsString(value_type).back() != + '*') // meaning, object stored by-value + vi->vi_flags = vectoriterobject::kNeedLifeLine; + } + } else + vi->vi_converter = CreateConverter(value_type); + if (!vi->vi_stride) + vi->vi_stride = interop::SizeOfType(value_type); + + } else if (CPPScope_Check(pyvalue_type)) { + vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType; + vi->vi_converter = nullptr; + if (!vi->vi_stride) + vi->vi_stride = interop::SizeOf(vi->vi_klass); + if (!vi->vi_flags) + vi->vi_flags = vectoriterobject::kNeedLifeLine; + } + + PyObject* pydata = CallPyObjMethod(v, "__real_data"); + if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0) + vi->vi_data = CPPInstance_Check(pydata) + ? ((CPPInstance*)pydata)->GetObjectRaw() + : nullptr; + Py_XDECREF(pydata); + + } else { + PyErr_Clear(); + vi->vi_data = nullptr; + vi->vi_stride = 0; + vi->vi_converter = nullptr; + vi->vi_klass = nullptr; + vi->vi_flags = 0; + } + + Py_XDECREF(pyvalue_type); + + vi->ii_pos = 0; + vi->ii_len = PySequence_Size(v); + + PyObject_GC_Track(vi); + return (PyObject*)vi; +} + +PyObject* VectorGetItem(CPPInstance* self, PySliceObject* index) { + // Implement python's __getitem__ for std::vector<>s. + if (PySlice_Check(index)) { + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); + PyObject* nseq = PyObject_CallObject(pyclass, nullptr); + + Py_ssize_t start, stop, step; + PySlice_GetIndices((cpyrt_PySliceCast)index, + PyObject_Length((PyObject*)self), &start, &stop, &step); + + const Py_ssize_t nlen = PySequence_Size((PyObject*)self); + if (!AdjustSlice(nlen, start, stop, step)) + return nseq; + + const Py_ssize_t sign = step < 0 ? -1 : 1; + for (Py_ssize_t i = start; i * sign < stop * sign; i += step) { + PyObject* pyidx = PyInt_FromSsize_t(i); + PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, + PyStrings::gGetNoCheck, pyidx); + CallPyObjMethod(nseq, "push_back", item); + Py_DECREF(item); + Py_DECREF(pyidx); + } + + return nseq; + } + + return CallSelfIndex(self, (PyObject*)index, PyStrings::gGetNoCheck); +} + +static interop::TCppScope_t sVectorBoolTypeID; + +PyObject* VectorBoolGetItem(CPPInstance* self, PyObject* idx) { + // std::vector is a special-case in C++, and its return type depends on + // the compiler: treat it special here as well + if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { + PyErr_Format(PyExc_TypeError, + "require object of type std::vector, but %s given", + interop::GetScopedFinalName(self->ObjectIsA()).c_str()); + return nullptr; + } + + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + if (PySlice_Check(idx)) { + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); + PyObject* nseq = PyObject_CallObject(pyclass, nullptr); + + Py_ssize_t start, stop, step; + PySlice_GetIndices((cpyrt_PySliceCast)idx, PyObject_Length((PyObject*)self), + &start, &stop, &step); + const Py_ssize_t nlen = PySequence_Size((PyObject*)self); + if (!AdjustSlice(nlen, start, stop, step)) + return nseq; + + const Py_ssize_t sign = step < 0 ? -1 : 1; + for (Py_ssize_t i = start; i * sign < stop * sign; i += step) { + PyObject* pyidx = PyInt_FromSsize_t(i); + PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, + PyStrings::gGetItem, pyidx); + CallPyObjMethod(nseq, "push_back", item); + Py_DECREF(item); + Py_DECREF(pyidx); + } + + return nseq; + } + + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) + return nullptr; + + int index = (int)PyLong_AsLong(pyindex); + Py_DECREF(pyindex); + + // get hold of the actual std::vector (no cast, as vector is never a + // base) + std::vector* vb = (std::vector*)self->GetObject(); + + // finally, return the value + if (bool((*vb)[index])) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +PyObject* VectorBoolSetItem(CPPInstance* self, PyObject* args) { + // std::vector is a special-case in C++, and its return type depends on + // the compiler: treat it special here as well + if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { + PyErr_Format(PyExc_TypeError, + "require object of type std::vector, but %s given", + interop::GetScopedFinalName(self->ObjectIsA()).c_str()); + return nullptr; + } + + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + int bval = 0; + PyObject* idx = nullptr; + if (!PyArg_ParseTuple(args, const_cast("Oi:__setitem__"), &idx, &bval)) + return nullptr; + + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) + return nullptr; + + int index = (int)PyLong_AsLong(pyindex); + Py_DECREF(pyindex); + + // get hold of the actual std::vector (no cast, as vector is never a + // base) + std::vector* vb = (std::vector*)self->GetObject(); + + // finally, set the value + (*vb)[index] = (bool)bval; + + Py_RETURN_NONE; +} + +//- array behavior as primitives ---------------------------------------------- +PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // std::array is normally only constructed using aggregate initialization, + // which is a concept that does not exist in python, so use this custom + // constructor to to fill the array using setitem + + if (args && PyTuple_GET_SIZE(args) == 1 && + PySequence_Check(PyTuple_GET_ITEM(args, 0))) { + // construct the empty array, then fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* items = PyTuple_GET_ITEM(args, 0); + Py_ssize_t fillsz = PySequence_Size(items); + if (PySequence_Size(self) != fillsz) { + PyErr_Format(PyExc_ValueError, + "received sequence of size %zd where %zd expected", fillsz, + PySequence_Size(self)); + Py_DECREF(result); + return nullptr; + } + + PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); + for (Py_ssize_t i = 0; i < fillsz; ++i) { + PyObject* item = PySequence_GetItem(items, i); + PyObject* index = PyInt_FromSsize_t(i); + PyObject* sires = + PyObject_CallFunctionObjArgs(si_call, index, item, nullptr); + Py_DECREF(index); + Py_DECREF(item); + if (!sires) { + Py_DECREF(si_call); + Py_DECREF(result); + return nullptr; + } else + Py_DECREF(sires); + } + Py_DECREF(si_call); + + return result; + } else + PyErr_Clear(); + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//- map behavior as primitives ------------------------------------------------ +static PyObject* MapFromPairs(PyObject* self, PyObject* pairs) { + // construct an empty map, then fill it with the key, value pairs + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); + for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) { + PyObject* pair = PySequence_GetItem(pairs, i); + PyObject* sires = nullptr; + if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) { + PyObject* key = PySequence_GetItem(pair, 0); + PyObject* value = PySequence_GetItem(pair, 1); + sires = PyObject_CallFunctionObjArgs(si_call, key, value, nullptr); + Py_DECREF(value); + Py_DECREF(key); + } + Py_DECREF(pair); + if (!sires) { + Py_DECREF(si_call); + Py_DECREF(result); + if (!PyErr_Occurred()) + PyErr_SetString( + PyExc_TypeError, + "Failed to fill map (argument not a dict or sequence of pairs)"); + return nullptr; + } else + Py_DECREF(sires); + } + Py_DECREF(si_call); + + return result; +} + +PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized map constructor to allow construction from mapping containers + // and from tuples of pairs ("initializer_list style"). + + // PyMapping_Check is not very discriminatory, as it basically only checks for + // the existence of __getitem__, hence the most common cases of tuple and + // list are dropped straight-of-the-bat (the PyMapping_Items call will fail on + // them). + if (PyTuple_GET_SIZE(args) == 1 && + PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && + !(PyTuple_Check(PyTuple_GET_ITEM(args, 0)) || + PyList_Check(PyTuple_GET_ITEM(args, 0)))) { + PyObject* assoc = PyTuple_GET_ITEM(args, 0); + // in p3, PyMapping_Items isn't a macro, but a function that short-circuits + // dict + PyObject* items = PyMapping_Items(assoc); + if (items && PySequence_Check(items)) { + PyObject* result = MapFromPairs(self, items); + Py_DECREF(items); + return result; + } + + Py_XDECREF(items); + PyErr_Clear(); + + // okay to fall through as long as 'self' has not been created (is done in + // MapFromPairs) + } + + // tuple of pairs case (some mapping types are sequences) + if (PyTuple_GET_SIZE(args) == 1 && + PySequence_Check(PyTuple_GET_ITEM(args, 0))) + return MapFromPairs(self, PyTuple_GET_ITEM(args, 0)); + + // The given argument wasn't a mapping or tuple of pairs: forward to regular + // constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +PyObject* STLContainsWithFind(PyObject* self, PyObject* obj) { + // Implement python's __contains__ for std::map/std::set + PyObject* result = nullptr; + + PyObject* iter = CallPyObjMethod(self, "find", obj); + if (CPPInstance_Check(iter)) { + PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (CPPInstance_Check(end)) { + if (!PyObject_RichCompareBool(iter, end, Py_EQ)) { + Py_INCREF(Py_True); + result = Py_True; + } + } + Py_XDECREF(end); + } + Py_XDECREF(iter); + + if (!result) { + PyErr_Clear(); // e.g. wrong argument type, which should always lead to + // False + Py_INCREF(Py_False); + result = Py_False; + } + + return result; +} + +//- set behavior as primitives ------------------------------------------------ +PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized set constructor to allow construction from Python sets. + if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) { + PyObject* pyset = PyTuple_GET_ITEM(args, 0); + + // construct an empty set, then fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* iter = PyObject_GetIter(pyset); + if (iter) { + PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert"); + + IterItemGetter getter{iter}; + Py_DECREF(iter); + + PyObject* item = getter.get(); + while (item) { + PyObject* insres = + PyObject_CallFunctionObjArgs(ins_call, item, nullptr); + Py_DECREF(item); + if (!insres) { + Py_DECREF(ins_call); + Py_DECREF(result); + return nullptr; + } else + Py_DECREF(insres); + item = getter.get(); + } + Py_DECREF(ins_call); + } + + return result; + } + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//- STL container iterator support -------------------------------------------- +static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash +static const ptrdiff_t PS_FLAG_ADDR = 11; // id. +static const ptrdiff_t PS_COLL_ADDR = 13; // id. + +PyObject* STLIterNext(PyObject* self); // defined below; used by STLSequenceIter + +PyObject* LLSequenceIter(PyObject* self) { + // Implement python's __iter__ for low level views used through STL-type + // begin()/end() + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); + + if (LowLevelView_Check(iter)) { + // builtin pointer iteration: can only succeed if a size is available + Py_ssize_t sz = PySequence_Size(self); + if (sz == -1) { + Py_DECREF(iter); + return nullptr; + } + PyObject* lliter = Py_TYPE(iter)->tp_iter(iter); + ((indexiterobject*)lliter)->ii_len = sz; + Py_DECREF(iter); + return lliter; + } + + if (iter) { + Py_DECREF(iter); + PyErr_SetString(PyExc_TypeError, + "unrecognized iterator type for low level views"); + } + + return nullptr; +} + +PyObject* STLSequenceIter(PyObject* self) { + // Implement python's __iter__ for std::iterator<>s + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); + if (iter) { + PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (end) { + if (CPPInstance_Check(iter)) { + // Guarantee the returned iterator implements Python's iterator + // protocol. The deferred, name-based __next__ install can miss when an + // iterator's canonical return-type spelling and its scoped-final-name + // diverge (e.g libc++'s std::__1::__wrap_iter on macOS) leaving the + // type without + // __next__, so iter() rejects it. begin()/end() resolving here proves + // this is an STL forward iterator, so install the protocol now if it is + // still absent + PyTypeObject* itype = Py_TYPE(iter); + if (!PyIter_Check(iter)) { // no tp_iternext, or the + // _PyObject_NextNotImplemented sentinel + itype->tp_iternext = (iternextfunc)STLIterNext; + Utility::AddToClass((PyObject*)itype, CPPJIT__next__, + (PyCFunction)STLIterNext, METH_NOARGS); + if (!itype->tp_iter) { + itype->tp_iter = (getiterfunc)PyObject_SelfIter; + Utility::AddToClass((PyObject*)itype, "__iter__", + (PyCFunction)PyObject_SelfIter, METH_NOARGS); + } + PyType_Modified(itype); + } + // use the data member cache to store extra state on the iterator + // object, without it being visible on the Python side + auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache(); + dmc.push_back(std::make_pair(PS_END_ADDR, end)); + + // set a flag, indicating first iteration (reset in __next__) + Py_INCREF(Py_False); + dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False)); + + // make sure the iterated over collection remains alive for the duration + Py_INCREF(self); + dmc.push_back(std::make_pair(PS_COLL_ADDR, self)); + } else { + // could store "end" on the object's dictionary anyway, but if end() + // returns a user-customized object, then its __next__ is probably + // custom, too + Py_DECREF(end); + } + } + } + return iter; +} + +//- generic iterator support over a sequence with operator[] and size --------- +//----------------------------------------------------------------------------- +static PyObject* index_iter(PyObject* c) { + indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); + if (!ii) + return nullptr; + + Py_INCREF(c); + ii->ii_container = c; + ii->ii_pos = 0; + ii->ii_len = PySequence_Size(c); + + PyObject_GC_Track(ii); + return (PyObject*)ii; +} + +//- safe indexing for STL-like vector w/o iterator dictionaries --------------- +/* replaced by indexiterobject iteration, but may still have some future use ... +PyObject* CheckedGetItem(PyObject* self, PyObject* obj) +{ +// Implement a generic python __getitem__ for STL-like classes that are missing +the +// reflection info for their iterators. This is then used for iteration by means +of +// consecutive indices, it such index is of integer type. + Py_ssize_t size = PySequence_Size(self); + Py_ssize_t idx = PyInt_AsSsize_t(obj); + if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) { + // argument conversion problem: let method itself resolve anew and report + PyErr_Clear(); + return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); + } + + bool inbounds = false; + if (idx < 0) idx += size; + if (0 <= idx && 0 <= size && idx < size) + inbounds = true; + + if (inbounds) + return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); + else + PyErr_SetString( PyExc_IndexError, "index out of range" ); + + return nullptr; +}*/ + +//- pair as sequence to allow tuple unpacking -------------------------------- +PyObject* PairUnpack(PyObject* self, PyObject* pyindex) { + // For std::map<> iteration, unpack std::pair<>s into tuples for the loop. + long idx = PyLong_AsLong(pyindex); + if (idx == -1 && PyErr_Occurred()) + return nullptr; + + if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + if ((int)idx == 0) + return PyObject_GetAttr(self, PyStrings::gFirst); + else if ((int)idx == 1) + return PyObject_GetAttr(self, PyStrings::gSecond); + + // still here? Trigger stop iteration + PyErr_SetString(PyExc_IndexError, "out of bounds"); + return nullptr; +} + +//- simplistic len() functions ----------------------------------------------- +PyObject* ReturnTwo(CPPInstance*, PyObject*) { return PyInt_FromLong(2); } + +//- shared/unique_ptr behavior ----------------------------------------------- +PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // since the shared/unique pointer will take ownership, we need to relinquish + // it + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + if (result && PyTuple_GET_SIZE(args) == 1 && + CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) { + CPPInstance* cppinst = (CPPInstance*)PyTuple_GET_ITEM(args, 0); + if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) + cppinst->CppOwns(); + } + return result; + } + return nullptr; +} + +//- string behavior as primitives -------------------------------------------- +// TODO: this is wrong, b/c it doesn't order +static int PyObject_Compare(PyObject* one, PyObject* other) { + return !PyObject_RichCompareBool(one, other, Py_EQ); +} +static inline PyObject* cpyrt_PyString_FromCppString(std::string_view s, + bool native = true) { + if (native) + return PyBytes_FromStringAndSize(s.data(), s.size()); + return cpyrt_PyText_FromStringAndSize(s.data(), s.size()); +} + +static inline PyObject* cpyrt_PyString_FromCppString(std::wstring_view s, + bool native = true) { + PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); + if (pyobj && native) { + PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); + Py_DECREF(pyobj); + pyobj = pybytes; + } + return pyobj; +} + +#define CPPJIT_IMPL_STRING_PYTHONIZATION(type, name) \ + static inline PyObject* name##StringGetData(PyObject* self, \ + bool native = true) { \ + if (cpyrt::CPPInstance_Check(self)) { \ + type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ + if (obj) \ + return cpyrt_PyString_FromCppString(*obj, native); \ + } \ + PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ + return nullptr; \ + } \ + \ + PyObject* name##StringStr(PyObject* self) { \ + PyObject* pyobj = name##StringGetData(self, false); \ + if (!pyobj) { \ + /* do a native conversion to make printing possible (debatable) */ \ + PyErr_Clear(); \ + PyObject* pybytes = name##StringGetData(self, true); \ + if (pybytes) { /* should not fail */ \ + pyobj = PyObject_Str(pybytes); \ + Py_DECREF(pybytes); \ + } \ + } \ + return pyobj; \ + } \ + \ + PyObject* name##StringBytes(PyObject* self) { \ + return name##StringGetData(self, true); \ + } \ + \ + PyObject* name##StringRepr(PyObject* self) { \ + PyObject* data = name##StringGetData(self, true); \ + if (data) { \ + PyObject* repr = PyObject_Repr(data); \ + Py_DECREF(data); \ + return repr; \ + } \ + return nullptr; \ + } \ + \ + PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + if (data) { \ + PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \ + Py_DECREF(data); \ + return result; \ + } \ + return nullptr; \ + } \ + \ + PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + if (data) { \ + PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \ + Py_DECREF(data); \ + return result; \ + } \ + return nullptr; \ + } + +// Only define STLStringCompare: +#define CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(type, name) \ + CPPJIT_IMPL_STRING_PYTHONIZATION(type, name) \ + PyObject* name##StringCompare(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + int result = 0; \ + if (data) { \ + result = PyObject_Compare(data, obj); \ + Py_DECREF(data); \ + } \ + if (PyErr_Occurred()) \ + return nullptr; \ + return PyInt_FromLong(result); \ + } + +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) + +static inline std::string* GetSTLString(CPPInstance* self) { + if (!CPPInstance_Check(self)) { + PyErr_SetString(PyExc_TypeError, "std::string object expected"); + return nullptr; + } + + std::string* obj = (std::string*)self->GetObject(); + if (!obj) + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + + return obj; +} + +PyObject* STLStringDecode(CPPInstance* self, PyObject* args, PyObject* kwds) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr}; + const char* encoding = nullptr; + const char* errors = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, kwds, const_cast("s|s"), + keywords, &encoding, &errors)) + return nullptr; + + return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors); +} + +PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + const char* needle = cpyrt_PyText_AsString(pyobj); + if (!needle) + return nullptr; + + if (obj->find(needle) != std::string::npos) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +PyObject* STLStringReplace(CPPInstance* self, PyObject* args, + PyObject* /*kwds*/) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + // both str and std::string have a method "replace", but the Python version + // only accepts strings and takes no keyword arguments, whereas the C++ + // version has no overload that takes a string + + if (2 <= PyTuple_GET_SIZE(args) && + cpyrt_PyText_Check(PyTuple_GET_ITEM(args, 0))) { + PyObject* pystr = cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); + PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace"); + Py_DECREF(pystr); + PyObject* result = PyObject_CallObject(meth, args); + Py_DECREF(meth); + return result; + } + + PyObject* cppreplace = + PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace"); + if (cppreplace) { + PyObject* result = PyObject_Call(cppreplace, args, nullptr); + Py_DECREF(cppreplace); + return result; + } + + PyErr_SetString(PyExc_AttributeError, + "\'std::string\' object has no attribute \'replace\'"); + return nullptr; +} + +#define CPYRT_STRING_FINDMETHOD(name, cppname, pyname) \ + PyObject* STLString##name(CPPInstance* self, PyObject* args, \ + PyObject* /*kwds*/) { \ + std::string* obj = GetSTLString(self); \ + if (!obj) \ + return nullptr; \ + \ + PyObject* cppmeth = \ + PyObject_GetAttrString((PyObject*)self, (char*)#cppname); \ + if (cppmeth) { \ + PyObject* result = PyObject_Call(cppmeth, args, nullptr); \ + Py_DECREF(cppmeth); \ + if (result) { \ + if (PyLongOrInt_AsULong64(result) == \ + (PY_ULONG_LONG)std::string::npos) { \ + Py_DECREF(result); \ + return PyInt_FromLong(-1); \ + } \ + return result; \ + } \ + PyErr_Clear(); \ + } \ + \ + PyObject* pystr = \ + cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); \ + PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \ + Py_DECREF(pystr); \ + PyObject* result = PyObject_CallObject(pymeth, args); \ + Py_DECREF(pymeth); \ + return result; \ + } + +// both str and std::string have method "find" and "rfin"; try the C++ version +// first and fall back on the Python one in case of failure +// clang-format off +CPYRT_STRING_FINDMETHOD( Find, __cpp_find, find) +CPYRT_STRING_FINDMETHOD(RFind, __cpp_rfind, rfind) +// clang-format on + +PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + PyObject* pystr = cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); + PyObject* attr = PyObject_GetAttr(pystr, attr_name); + Py_DECREF(pystr); + return attr; +} + +Py_hash_t STLStringHash(PyObject* self) { + // std::string objects hash to the same values as Python strings to allow + // matches in dictionaries etc. + PyObject* data = STLStringGetData(self, false); + Py_hash_t h = cpyrt_PyText_Type.tp_hash(data); + Py_DECREF(data); + return h; +} + +//- string_view behavior as primitive ---------------------------------------- +PyObject* StringViewInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // if constructed from a Python unicode object, the constructor will convert + // it to a temporary byte string, which is likely to go out of scope too soon; + // so buffer it as needed + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject *strbuf = nullptr, *newArgs = nullptr; + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + if (PyUnicode_Check(arg0)) { + // convert to the expected bytes array to control the temporary + strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict"); + newArgs = PyTuple_New(1); + Py_INCREF(strbuf); + PyTuple_SET_ITEM(newArgs, 0, strbuf); + } else if (PyBytes_Check(arg0)) { + // tie the life time of the provided string to the string_view + Py_INCREF(arg0); + strbuf = arg0; + } + } + + PyObject* result = + PyObject_Call(realInit, newArgs ? newArgs : args, nullptr); + + Py_XDECREF(newArgs); + Py_DECREF(realInit); + + // if construction was successful and a string buffer was used, add a + // life line to it from the string_view bound object + if (result && self && strbuf) + PyObject_SetAttr(self, PyStrings::gLifeLine, strbuf); + Py_XDECREF(strbuf); + + return result; + } + return nullptr; +} + +//- STL iterator behavior ---------------------------------------------------- +PyObject* STLIterNext(PyObject* self) { + // Python iterator protocol __next__ for STL forward iterators. + bool mustIncrement = true; + PyObject* last = nullptr; + if (CPPInstance_Check(self)) { + auto& dmc = ((CPPInstance*)self)->GetDatamemberCache(); + for (auto& p : dmc) { + if (p.first == PS_END_ADDR) { + last = p.second; + Py_INCREF(last); + } else if (p.first == PS_FLAG_ADDR) { + mustIncrement = p.second == Py_True; + if (!mustIncrement) { + Py_DECREF(p.second); + Py_INCREF(Py_True); + p.second = Py_True; + } + } + } + } + + PyObject* next = nullptr; + if (last) { + // handle special case of empty container (i.e. self is end) + if (!PyObject_RichCompareBool(last, self, Py_EQ)) { + bool iter_valid = true; + if (mustIncrement) { + // prefer preinc, but allow post-inc; in both cases, it is "self" that + // has the updated state to dereference + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gPreInc); + if (!iter) { + PyErr_Clear(); + static PyObject* dummy = PyInt_FromLong(1l); + iter = PyObject_CallMethodOneArg(self, PyStrings::gPostInc, dummy); + } + iter_valid = iter && PyObject_RichCompareBool(last, self, Py_NE); + Py_XDECREF(iter); + } + + if (iter_valid) { + next = PyObject_CallMethodNoArgs(self, PyStrings::gDeref); + if (!next) + PyErr_Clear(); + } + } + Py_DECREF(last); + } + + if (!next) + PyErr_SetString(PyExc_StopIteration, ""); + return next; +} + +//- STL complex behavior -------------------------------------------------- +#define COMPLEX_METH_GETSET(name, cppname) \ + static PyObject* name##ComplexGet(PyObject* self, void*) { \ + return PyObject_CallMethodNoArgs(self, cppname); \ + } \ + static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \ + PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \ + if (result) { \ + Py_DECREF(result); \ + return 0; \ + } \ + return -1; \ + } \ + PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, \ + (setter)name##ComplexSet, nullptr, nullptr}; + +COMPLEX_METH_GETSET(real, PyStrings::gCppReal) +COMPLEX_METH_GETSET(imag, PyStrings::gCppImag) + +static PyObject* ComplexComplex(PyObject* self) { + PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); + if (!real) + return nullptr; + double r = PyFloat_AsDouble(real); + Py_DECREF(real); + if (r == -1. && PyErr_Occurred()) + return nullptr; + + PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); + if (!imag) + return nullptr; + double i = PyFloat_AsDouble(imag); + Py_DECREF(imag); + if (i == -1. && PyErr_Occurred()) + return nullptr; + + return PyComplex_FromDoubles(r, i); +} + +static PyObject* ComplexRepr(PyObject* self) { + PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); + if (!real) + return nullptr; + double r = PyFloat_AsDouble(real); + Py_DECREF(real); + if (r == -1. && PyErr_Occurred()) + return nullptr; + + PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); + if (!imag) + return nullptr; + double i = PyFloat_AsDouble(imag); + Py_DECREF(imag); + if (i == -1. && PyErr_Occurred()) + return nullptr; + + std::ostringstream s; + s << '(' << r << '+' << i << "j)"; + return cpyrt_PyText_FromString(s.str().c_str()); +} + +static PyObject* ComplexDRealGet(CPPInstance* self, void*) { + return PyFloat_FromDouble(((std::complex*)self->GetObject())->real()); +} + +static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*) { + double d = PyFloat_AsDouble(value); + if (d == -1.0 && PyErr_Occurred()) + return -1; + ((std::complex*)self->GetObject())->real(d); + return 0; +} + +PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, + (setter)ComplexDRealSet, nullptr, nullptr}; + +static PyObject* ComplexDImagGet(CPPInstance* self, void*) { + return PyFloat_FromDouble(((std::complex*)self->GetObject())->imag()); +} + +static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*) { + double d = PyFloat_AsDouble(value); + if (d == -1.0 && PyErr_Occurred()) + return -1; + ((std::complex*)self->GetObject())->imag(d); + return 0; +} + +PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, + (setter)ComplexDImagSet, nullptr, nullptr}; + +static PyObject* ComplexDComplex(CPPInstance* self) { + double r = ((std::complex*)self->GetObject())->real(); + double i = ((std::complex*)self->GetObject())->imag(); + return PyComplex_FromDoubles(r, i); +} + +} // unnamed namespace + +//- public functions --------------------------------------------------------- +namespace cppjit::cpyrt { +std::set gIteratorTypes; +} + +static inline bool run_pythonizors(PyObject* pyclass, PyObject* pyname, + const std::vector& v) { + PyObject* args = PyTuple_New(2); + Py_INCREF(pyclass); + PyTuple_SET_ITEM(args, 0, pyclass); + Py_INCREF(pyname); + PyTuple_SET_ITEM(args, 1, pyname); + + bool pstatus = true; + for (auto pythonizor : v) { + PyObject* result = PyObject_CallObject(pythonizor, args); + if (!result) { + pstatus = false; // TODO: detail the error handling + break; + } + Py_DECREF(result); + } + Py_DECREF(args); + + return pstatus; +} + +bool cpyrt::Pythonize(PyObject* pyclass, interop::TCppScope_t scope) { + const std::string& name = interop::GetScopedFinalName(scope); + + // Add pre-defined pythonizations (for STL and ROOT) to classes based on their + // signature and/or class name. + if (!pyclass) + return false; + + CPPScope* klass = (CPPScope*)pyclass; + + //- method name based pythonization ------------------------------------------ + + // for smart pointer style classes that are otherwise not known as such; would + // prefer operator-> as that returns a pointer (which is simpler since it + // never has to deal with ref-assignment), but operator* plays better with STL + // iters and algorithms + // optional and expected use operator* to access a contained value, rather + // than to provide pointer-like access to an object. + const bool has_value_semantics = IsTemplatedSTLClass(name, "optional") || + IsTemplatedSTLClass(name, "expected"); + if (HasAttrDirect(pyclass, PyStrings::gDeref) && + !interop::IsSmartPtr(klass->fCppType)) { + if (has_value_semantics) + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)ValueGetAttr, + METH_O); + else + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)DeRefGetAttr, + METH_O); + } else if (HasAttrDirect(pyclass, PyStrings::gFollow) && + !interop::IsSmartPtr(klass->fCppType)) + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)FollowGetAttr, + METH_O); + + // for pre-check of nullptr for boolean types + if (HasAttrDirect(pyclass, PyStrings::gCppBool)) { + const char* pybool_name = "__bool__"; + Utility::AddToClass(pyclass, pybool_name, (PyCFunction)NullCheckBool, + METH_NOARGS); + } + + // for STL containers, and user classes modeled after them + // the attribute must be a cpyrt overload, otherwise the check gives false + // positives in the case where the class has a non-function attribute that is + // called "size". + { + // only pythonize as __len__ if the class actually models a container, + // i.e. it is iterable (begin/end) or indexable (operator[]), and size() + // returns an integral value; e.g. a size() returning a custom dimension + // object must not become __len__ (len() would fail on a valid object). + // The size method and the container interface may come from a base class + // (checked with PyObject_HasAttr), as long as at least one of them is + // freshly declared here (else the base's __len__ is inherited via the + // normal MRO and there is nothing to do). + bool sizeDirect = + HasAttrDirect(pyclass, PyStrings::gSize, /*mustBecpyrt=*/true); + bool ifaceDirect = (HasAttrDirect(pyclass, PyStrings::gBegin) && + HasAttrDirect(pyclass, PyStrings::gEnd)) || + HasAttrDirect(pyclass, PyStrings::gGetItem); + PyObject* size_attr = (sizeDirect || ifaceDirect) + ? PyObject_GetAttr(pyclass, PyStrings::gSize) + : nullptr; + if (!size_attr) + PyErr_Clear(); + else if (!CPPOverload_Check(size_attr)) { + // non-function attribute called "size": not pythonizable + Py_DECREF(size_attr); + size_attr = nullptr; + } + bool isContainer = size_attr && + PyObject_HasAttr(pyclass, PyStrings::gBegin) && + PyObject_HasAttr(pyclass, PyStrings::gEnd); + if (size_attr && !isContainer) { + // indexable via a real C++ operator[]? (every class "has" __getitem__ + // through the CPPInstance defaults, so require an actual overload) + PyObject* gi = PyObject_GetAttr(pyclass, PyStrings::gGetItem); + if (gi) { + isContainer = CPPOverload_Check(gi); + Py_DECREF(gi); + } else + PyErr_Clear(); + } + if (isContainer) { + auto isIntegral = [](const std::string& tn) { + static const char* const integrals[] = { + "char", "signed char", "unsigned char", "short", + "unsigned short", "int", "unsigned int", "long", + "unsigned long", "long long", "unsigned long long"}; + for (const char* i : integrals) + if (tn == i) + return true; + return false; + }; + // find the scope (this class or a base) that declares size() + std::vector sizes; + std::deque todo{klass->fCppType}; + while (sizes.empty() && !todo.empty()) { + interop::TCppScope_t s = todo.front(); + todo.pop_front(); + sizes = interop::GetMethodsFromName(s, "size"); + for (interop::TCppIndex_t i = 0; i < interop::GetNumBases(s); ++i) + todo.push_back(interop::GetBaseScope(s, i)); + } + for (interop::TCppMethod_t m : sizes) { + if (isIntegral(interop::GetMethodReturnTypeAsString(m))) { + Utility::AddToClass(pyclass, "__len__", "size"); + break; + } + } + } + Py_XDECREF(size_attr); + } + + if (HasAttrDirect(pyclass, PyStrings::gContains)) { + Utility::AddToClass(pyclass, "__contains__", "contains"); + } + + if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below + !((PyTypeObject*)pyclass)->tp_iter) { + if (HasAttrDirect(pyclass, PyStrings::gBegin) && + HasAttrDirect(pyclass, PyStrings::gEnd)) { + // obtain the name of the return type + const auto& methods = + interop::GetMethodsFromName(klass->fCppType, "begin"); + if (!methods.empty()) { + // check return type; if not explicitly an iterator, add it to the + // "known" return types to add the "next" method on use + interop::TCppMethod_t meth = methods[0]; + const std::string& resname = interop::GetMethodReturnTypeAsString(meth); + bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end(); + if (!isIterator && interop::GetScope(resname)) { + if (resname.find("iterator") == std::string::npos) + gIteratorTypes.insert(resname); + isIterator = true; + } + + if (isIterator) { + // install iterator protocol a la STL + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)STLSequenceIter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)STLSequenceIter, + METH_NOARGS); + } else { + // still okay if this is some pointer type of builtin persuasion + // (general class won't work: the return type needs to understand the + // iterator protocol) + std::string resolved = interop::ResolveName(resname); + if (resolved.back() == '*' && + interop::IsBuiltin(resolved.substr(0, resolved.size() - 1))) { + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)LLSequenceIter; + Utility::AddToClass(pyclass, "__iter__", + (PyCFunction)LLSequenceIter, METH_NOARGS); + } + } + } + } + if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved + HasAttrDirect(pyclass, PyStrings::gGetItem) && + PyObject_HasAttr(pyclass, PyStrings::gLen)) { + // Python will iterate over __getitem__ using integers, but C++ operator[] + // will never raise a StopIteration. A checked getitem (raising IndexError + // if beyond size()) works in some cases but would mess up if operator[] + // is meant to implement an associative container. So, this has to be + // implemented as an iterator protocol. + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)index_iter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)index_iter, + METH_NOARGS); + } + } + + // operator==/!= are used in op_richcompare of CPPInstance, which subsequently + // allows comparisons to None; if no operator is available, a hook is + // installed for lazy lookups in the global and/or class namespace + if (HasAttrDirect(pyclass, PyStrings::gEq, true) && + interop::GetMethodsFromName(klass->fCppType, "__eq__").empty()) { + PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gEq); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators(); + klass->fOperators->fEq = cppol; + // re-insert the forwarding __eq__ from the CPPInstance in case there was a + // Python-side override in the base class + static PyObject* top_eq = nullptr; + if (!top_eq) { + PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); + top_eq = PyObject_GetAttr(top_cls, PyStrings::gEq); + Py_DECREF(top_eq); // make it borrowed + Py_DECREF(top_cls); + } + PyObject_SetAttr(pyclass, PyStrings::gEq, top_eq); + } + + if (HasAttrDirect(pyclass, PyStrings::gNe, true) && + interop::GetMethodsFromName(klass->fCppType, "__ne__").empty()) { + PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gNe); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators(); + klass->fOperators->fNe = cppol; + // re-insert the forwarding __ne__ (same reason as above for __eq__) + static PyObject* top_ne = nullptr; + if (!top_ne) { + PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); + top_ne = PyObject_GetAttr(top_cls, PyStrings::gNe); + Py_DECREF(top_ne); // make it borrowed + Py_DECREF(top_cls); + } + PyObject_SetAttr(pyclass, PyStrings::gNe, top_ne); + } + + // The pseudo-constructor is not meant for standard-library aggregates; + // detect those by walking the enclosing scopes instead of testing the + // exposed name, which may or may not be qualified (e.g. std::tuple is + // reported without its namespace here). + bool stdclass = false; + for (interop::TCppScope_t p = + interop::GetParentScope(((CPPClass*)pyclass)->fCppType); + p; p = interop::GetParentScope(p)) { + if (interop::IsNamespace(p) && interop::GetName(p) == "std") { + stdclass = true; + break; + } + } + + if (interop::IsAggregate(((CPPClass*)pyclass)->fCppType) && !stdclass) { + // create a pseudo-constructor to allow initializer-style object creation + interop::TCppScope_t kls = ((CPPClass*)pyclass)->fCppType; + std::vector datamems; + interop::GetDatamembers(kls, datamems); + if (!datamems.empty()) { + std::string rname = name; + TypeManip::cppscope_to_legalname(rname); + + std::ostringstream initdef; + initdef << "namespace __cppjit_internal {\n" + << "void init_" << rname << "(" << name << "** self"; + bool codegen_ok = true; + std::vector arg_types, arg_names, arg_defaults; + const int ndata = datamems.size(); + arg_types.reserve(ndata); + arg_names.reserve(ndata); + arg_defaults.reserve(ndata); + for (auto data : datamems) { + if (interop::IsStaticDatamember(data) || !interop::IsPublicData(data)) + continue; + + interop::TCppType_t datammember_type = interop::GetDatamemberType(data); + const std::string& res = + interop::IsEnumType(datammember_type) + ? interop::GetScopedFinalName( + interop::GetScopeFromType(datammember_type)) + : interop::GetTypeAsString( + interop::ResolveType(datammember_type)); + const std::string& cpd = TypeManip::compound(res); + std::string res_clean = TypeManip::clean_type(res, false, true); + + if (res_clean == "internal_enum_type_t") + res_clean = res; // restore (properly scoped name) + + if (res.rfind(']') == std::string::npos && + res.rfind(')') == std::string::npos) { + if (!cpd.empty()) + arg_types.push_back(res_clean + cpd); + else + arg_types.push_back("const " + res_clean + "&"); + arg_names.push_back(interop::GetFinalName(data)); + if ((!cpd.empty() && cpd.back() == '*') || + interop::IsBuiltin(res_clean)) + arg_defaults.push_back("0"); + else { + interop::TCppScope_t klsid = interop::GetScope(res_clean); + if (interop::IsDefaultConstructable(klsid)) + arg_defaults.push_back(res_clean + "{}"); + } + } else { + codegen_ok = + false; // TODO: how to support arrays, anonymous enums, etc? + break; + } + } + + if (codegen_ok && !arg_types.empty()) { + bool defaults_ok = arg_defaults.size() == arg_types.size(); + for (std::vector::size_type i = 0; i < arg_types.size(); + ++i) { + initdef << ", " << arg_types[i] << " " << arg_names[i]; + if (defaults_ok) + initdef << " = " << arg_defaults[i]; + } + initdef << ") {\n *self = new " << name << "{"; + for (std::vector::size_type i = 0; i < arg_names.size(); + ++i) { + if (i != 0) + initdef << ", "; + initdef << arg_names[i]; + } + initdef << "};\n} }"; + + if (interop::Compile(initdef.str(), true /* silent */)) { + interop::TCppScope_t cis = interop::GetScope("__cppjit_internal"); + const auto& methods = + interop::GetMethodsFromName(cis, "init_" + rname); + if (methods.size()) { + if (!Utility::AddToClass(pyclass, "__init__", + new CPPFunction(cis, methods[0]))) + PyErr_Clear(); + } + } + } + } + } + + //- class name based pythonization ------------------------------------------- + + if (IsTemplatedSTLClass(name, "span")) { + // libstdc++ (GCC >= 15) implements std::span::iterator using a private + // nested tag type, which makes the iterator non-instantiable by + // CallFunc-generated wrappers (the return type cannot be named without + // violating access rules). + // + // To preserve correct Python iteration semantics, we replace begin()/end() + // for std::span to return a custom pointer-based iterator instead. + Utility::AddToClass(pyclass, "begin", (PyCFunction)SpanBegin, METH_NOARGS); + Utility::AddToClass(pyclass, "end", (PyCFunction)SpanEnd, METH_NOARGS); + } + + if (IsTemplatedSTLClass(name, "vector")) { + + // std::vector is a special case in C++ + if (!sVectorBoolTypeID) + sVectorBoolTypeID = interop::GetScope("std::vector"); + if (klass->fCppType == sVectorBoolTypeID) { + Utility::AddToClass(pyclass, "__getitem__", + (PyCFunction)VectorBoolGetItem, METH_O); + Utility::AddToClass(pyclass, "__setitem__", + (PyCFunction)VectorBoolSetItem); + } else { + // constructor that takes python collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)VectorInit, + METH_VARARGS | METH_KEYWORDS); + + // data with size + if (!Utility::AddToClass(pyclass, "__real_data", "data")) + PyErr_Clear(); // no 'data' method to alias + Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); + + // checked getitem + if (HasAttrDirect(pyclass, PyStrings::gLen)) { + Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__"); + Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorGetItem, + METH_O); + } + + // vector-optimized iterator protocol + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)vector_iter; + + // optimized __iadd__ + Utility::AddToClass(pyclass, "__iadd__", (PyCFunction)VectorIAdd, + METH_VARARGS | METH_KEYWORDS); + + // helpers for iteration + interop::TCppType_t value_type = + interop::GetTypeFromScope(interop::GetNamed("value_type", scope)); + interop::TCppType_t vtype = interop::ResolveType(value_type); + + // numpy array conversion; only for vectors of non-class types: + // data() on a vector of class instances hands back a proxy of the + // first element, so forwarding __array__ to it would yield that + // element instead of the full vector (numpy's generic sequence + // protocol handles such vectors correctly on its own) + if (vtype && !interop::GetScopeFromType(vtype)) + Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, + METH_VARARGS | METH_KEYWORDS /* unused */); + + if (vtype) { // actually resolved? + PyObject* pyvalue_type = PyLong_FromVoidPtr(vtype.data); + PyObject_SetAttr(pyclass, PyStrings::gValueTypePtr, pyvalue_type); + Py_DECREF(pyvalue_type); + pyvalue_type = + PyUnicode_FromString(interop::GetTypeAsString(vtype).c_str()); + PyObject_SetAttr(pyclass, PyStrings::gValueType, pyvalue_type); + Py_DECREF(pyvalue_type); + } + + size_t typesz = interop::SizeOfType(vtype); + if (typesz) { + PyObject* pyvalue_size = PyLong_FromSsize_t(typesz); + PyObject_SetAttr(pyclass, PyStrings::gValueSize, pyvalue_size); + Py_DECREF(pyvalue_size); + } + } + } + + else if (IsTemplatedSTLClass(name, "array")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)ArrayInit, + METH_VARARGS | METH_KEYWORDS); + } + + else if (IsTemplatedSTLClass(name, "map") || + IsTemplatedSTLClass(name, "unordered_map")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)MapInit, + METH_VARARGS | METH_KEYWORDS); + // From C++20, std::map/unordered_map have a native contains() that the + // generic contains->__contains__ mapping above will pick up. Strong-types + // reflection does not always expose it, so fall back to a find()-based + // __contains__ when none was reflected (still O(log n)/O(1), never + // iterating). + if (!HasAttrDirect(pyclass, PyStrings::gContains)) + Utility::AddToClass(pyclass, "__contains__", + (PyCFunction)STLContainsWithFind, METH_O); + } + + else if (IsTemplatedSTLClass(name, "set")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, + METH_VARARGS | METH_KEYWORDS); + // From C++20, std::set has a native contains() that the generic + // contains->__contains__ mapping above will pick up. Strong-types + // reflection does not always expose it, so fall back to a find()-based + // __contains__ when none was reflected (still O(log n), never iterating). + if (!HasAttrDirect(pyclass, PyStrings::gContains)) + Utility::AddToClass(pyclass, "__contains__", + (PyCFunction)STLContainsWithFind, METH_O); + } + + else if (IsTemplatedSTLClass(name, "pair")) { + Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)PairUnpack, + METH_O); + Utility::AddToClass(pyclass, "__len__", (PyCFunction)ReturnTwo, + METH_NOARGS); + } + + if (IsTemplatedSTLClass(name, "shared_ptr") || + IsTemplatedSTLClass(name, "unique_ptr")) { + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)SmartPtrInit, + METH_VARARGS | METH_KEYWORDS); + } + + else if (!((PyTypeObject*)pyclass)->tp_iter && + (name.find("iterator") != std::string::npos || + gIteratorTypes.find(name) != gIteratorTypes.end())) { + ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext; + Utility::AddToClass(pyclass, CPPJIT__next__, (PyCFunction)STLIterNext, + METH_NOARGS); + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)PyObject_SelfIter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, + METH_NOARGS); + } + + else if (name == "std::basic_string" || + name == "std::basic_string,std::" + "allocator >" || + name == "std::basic_string, " + "std::allocator >" || + name == "std::__1::basic_string" || // libc++ inline namespace + name == "std::string") { // typedef preserved by GetScopedFinalName + // on libc++ + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, + METH_O); + // Python's `in` operator needs __contains__ on the proxy regardless of the + // C++ standard cpyrt is built with; it never dispatches to C++'s + // std::string::contains (C++23+). The old `__cplusplus <= 202302L` guard + // wrongly dropped it when built with -std=c++2c (__cplusplus == 202400L). + Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, + METH_O); + Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_find", "find"); + Utility::AddToClass(pyclass, "find", (PyCFunction)STLStringFind, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_rfind", "rfind"); + Utility::AddToClass(pyclass, "rfind", (PyCFunction)STLStringRFind, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_replace", "replace"); + Utility::AddToClass(pyclass, "replace", (PyCFunction)STLStringReplace, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)STLStringGetAttr, + METH_O); + + // to allow use of std::string in dictionaries and findable with str + ((PyTypeObject*)pyclass)->tp_hash = (hashfunc)STLStringHash; + } + + else if (name == "std::basic_string_view" || + name == "std::basic_string_view >" || + name == "std::basic_string_view >" || + name == + "std::__1::basic_string_view" || // libc++ inline namespace + name == "std::string_view") { // typedef preserved by + // GetScopedFinalName on libc++ + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, + METH_O); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, + METH_NOARGS); + } + + // The first condition was already present in upstream cpyrt. The others + // are special to ROOT, because its reflection layer gives us the types + // without the "std::" namespace. On some platforms, that applies only to the + // template arguments, and on others also to the "basic_string". + else if (name == "std::basic_string,std::" + "allocator >" || + name == "std::basic_string, " + "std::allocator >" || + name == "basic_string,allocator >" || + name == "std::basic_string,allocator<" + "wchar_t> >" || + name == "std::__1::basic_string,std::__1::allocator >" || + name == "std::wstring") { + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLWStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLWStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLWStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLWStringIsNotEqual, + METH_O); + } + + else if (name == "complex" || name == "std::complex") { + Utility::AddToClass(pyclass, "__cpp_real", "real"); + PyObject_SetAttrString( + pyclass, "real", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDReal)); + Utility::AddToClass(pyclass, "__cpp_imag", "imag"); + PyObject_SetAttrString( + pyclass, "imag", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDImag)); + Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexDComplex, + METH_NOARGS); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, + METH_NOARGS); + } + + else if (IsTemplatedSTLClass(name, "complex")) { + Utility::AddToClass(pyclass, "__cpp_real", "real"); + PyObject_SetAttrString( + pyclass, "real", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &realComplex)); + Utility::AddToClass(pyclass, "__cpp_imag", "imag"); + PyObject_SetAttrString( + pyclass, "imag", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &imagComplex)); + Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexComplex, + METH_NOARGS); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, + METH_NOARGS); + } + + // direct user access; there are two calls here: + // - explicit pythonization: won't fall through to the base classes and is + // preferred if present + // - normal pythonization: only called if explicit isn't present, falls + // through to base classes + bool bUserOk = true; + PyObject* res = nullptr; + PyObject* pyname = cpyrt_PyText_FromString(name.c_str()); + if (HasAttrDirect(pyclass, PyStrings::gExPythonize)) { + res = PyObject_CallMethodObjArgs(pyclass, PyStrings::gExPythonize, pyclass, + pyname, nullptr); + bUserOk = (bool)res; + } else { + PyObject* func = PyObject_GetAttr(pyclass, PyStrings::gPythonize); + if (func) { + res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr); + Py_DECREF(func); + bUserOk = (bool)res; + } else + PyErr_Clear(); + } + if (!bUserOk) { + Py_DECREF(pyname); + return false; + } else { + Py_XDECREF(res); + // pyname handed to args tuple below + } + + // call registered pythonizors, if any: first run the namespace-specific + // pythonizors, then the global ones (the idea is to allow writing a + // pythonizor that see all classes) + bool pstatus = true; + std::string outer_scope = TypeManip::extract_namespace(name); + auto& pyzMap = pythonizations(); + if (!outer_scope.empty()) { + auto p = pyzMap.find(outer_scope); + if (p != pyzMap.end()) { + PyObject* subname = cpyrt_PyText_FromString( + name.substr(outer_scope.size() + 2, std::string::npos).c_str()); + pstatus = run_pythonizors(pyclass, subname, p->second); + Py_DECREF(subname); + } + } + + if (pstatus) { + auto p = pyzMap.find(""); + if (p != pyzMap.end()) + pstatus = run_pythonizors(pyclass, pyname, p->second); + } + + Py_DECREF(pyname); + + // phew! all done ... + return pstatus; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/Pythonize.h b/bindings/pyroot/cppjit/src/cpyrt/Pythonize.h new file mode 100644 index 0000000000000..3b9577c9497a1 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Pythonize.h @@ -0,0 +1,14 @@ +#ifndef CPYRT_PYTHONIZE_H +#define CPYRT_PYTHONIZE_H + +// Standard +#include + +namespace cppjit::cpyrt { + +// make the named C++ class more python-like +bool Pythonize(PyObject* pyclass, interop::TCppScope_t scope); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYTHONIZE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Reflex.h b/bindings/pyroot/cppjit/src/cpyrt/Reflex.h new file mode 100644 index 0000000000000..c23208766cb23 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Reflex.h @@ -0,0 +1,30 @@ +#ifndef CPYRT_REFLEX_H +#define CPYRT_REFLEX_H + +// +// Access to the C++ reflection information +// + +namespace cppjit::interop { + +namespace Reflex { + +typedef int RequestId_t; + +const RequestId_t IS_NAMESPACE = 1; +const RequestId_t IS_AGGREGATE = 2; + +const RequestId_t OFFSET = 16; +const RequestId_t RETURN_TYPE = 17; +const RequestId_t TYPE = 18; + +typedef int FormatId_t; +const FormatId_t OPTIMAL = 1; +const FormatId_t AS_TYPE = 2; +const FormatId_t AS_STRING = 3; + +} // namespace Reflex + +} // namespace cppjit::interop + +#endif // !CPYRT_REFLEX_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.cxx b/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.cxx new file mode 100644 index 0000000000000..1308c0e0885ab --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.cxx @@ -0,0 +1,1045 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPConstructor.h" +#include "CPPFunction.h" +#include "CPPMethod.h" +#include "CPPOverload.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +static inline std::string targs2str(TemplateProxy* pytmpl) { + if (!pytmpl || !pytmpl->fTemplateArgs) + return ""; + return cpyrt_PyText_AsString(pytmpl->fTemplateArgs); +} + +//---------------------------------------------------------------------------- +TemplateInfo::TemplateInfo() + : fPyClass(nullptr), fNonTemplated(nullptr), fTemplated(nullptr), + fLowPriority(nullptr), fDoc(nullptr) { + /* empty */ +} + +//---------------------------------------------------------------------------- +TemplateInfo::~TemplateInfo() { + Py_XDECREF(fPyClass); + + Py_XDECREF(fDoc); + Py_DECREF(fNonTemplated); + Py_DECREF(fTemplated); + Py_DECREF(fLowPriority); + + for (const auto& p : fDispatchMap) { + for (const auto& c : p.second) { + Py_DECREF(c.second); + } + } +} + +//---------------------------------------------------------------------------- +void TemplateProxy::MergeOverload(CPPOverload* mp) { + // Store overloads of this templated method. + bool isGreedy = false; + for (auto pc : mp->fMethodInfo->fMethods) { + if (pc->IsGreedy()) { + isGreedy = true; + break; + } + } + + CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated; + cppol->MergeOverload(mp); +} + +void TemplateProxy::AdoptMethod(PyCallable* pc) { + // Store overload of this templated method. + CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated; + cppol->AdoptMethod(pc); +} + +void TemplateProxy::AdoptTemplate(PyCallable* pc) { + // Store known template methods. + fTI->fTemplated->AdoptMethod(pc); +} + +//---------------------------------------------------------------------------- +PyObject* TemplateProxy::Instantiate(const std::string& fname, + cpyrt_PyArgs_t args, size_t nargsf, + Utility::ArgPreference pref, int* pcnt) { + // Instantiate (and cache) templated methods, return method if any + std::string proto = ""; + std::string protoPair = ""; + + // adjust arguments for self if this is a rebound global function + bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (!isNS && cpyrt_PyArgs_GET_SIZE(args, nargsf) && + (!fSelf || (fSelf == Py_None && + !interop::IsStaticTemplate( + ((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { + args += 1; + nargsf -= 1; + } + + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + if (argc != 0) { + PyObject* tpArgs = PyTuple_New(argc); + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i); + + bool bArgSet = false; + + // special case for arrays + if (TemplateProxy_CheckExact(itemi)) { + TemplateProxy* tn = (TemplateProxy*)itemi; + PyObject* f = PyUnicode_FromFormat("%s%s", tn->fTI->fCppName.c_str(), + targs2str(tn).c_str()); + PyTuple_SET_ITEM(tpArgs, i, f); + bArgSet = true; + } + PyObject* pytc = nullptr; + if (!bArgSet && (pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode))) { + Py_buffer bufinfo; + memset(&bufinfo, 0, sizeof(Py_buffer)); + std::string ptrdef; + if (PyObject_GetBuffer(itemi, &bufinfo, PyBUF_FORMAT) == 0) { + for (int j = 0; j < bufinfo.ndim; ++j) + ptrdef += "*"; + PyBuffer_Release(&bufinfo); + } else { + ptrdef += "*"; + PyErr_Clear(); + } + + PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true); + if (pyptrname) { + PyTuple_SET_ITEM(tpArgs, i, pyptrname); + bArgSet = true; + // string added, but not counted towards nStrings + } + Py_DECREF(pytc); + pytc = nullptr; + } else + PyErr_Clear(); + + // if not arg set, try special case for ctypes + if (!bArgSet) + pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType); + + if (!bArgSet && pytc) { + PyObject* pyactname = Utility::CT2CppName(pytc, "&", false); + if (!pyactname) { + // _type_ of a pointer to c_type is that type, which will have a type + PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType); + Py_DECREF(pytc); + pytc = newpytc; + if (pytc) { + pyactname = Utility::CT2CppName(pytc, "*", false); + } else + PyErr_Clear(); + } + Py_XDECREF(pytc); + pytc = nullptr; + if (pyactname) { + PyTuple_SET_ITEM(tpArgs, i, pyactname); + bArgSet = true; + // string added, but not counted towards nStrings + } + } else + PyErr_Clear(); + + if (!bArgSet) { + // normal case (may well fail) + PyErr_Clear(); + PyObject* tp = (PyObject*)Py_TYPE(itemi); + Py_INCREF(tp); + PyTuple_SET_ITEM(tpArgs, i, tp); + } + } + + PyObject* pyargs = PyTuple_New(argc); + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(args, i); + Py_INCREF(item); + PyTuple_SET_ITEM(pyargs, i, item); + } + const std::string& name_v1 = + Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt); + + // A Python 2-tuple is typed above as std::initializer_list, but it may + // equally represent a std::pair, to which an initializer_list can not + // convert; construct an alternative prototype to retry the lookup with if + // the first one fails + if (!PyErr_Occurred()) { + bool has2Tuple = false; + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i); + if (PyTuple_CheckExact(itemi) && PyTuple_GET_SIZE(itemi) == 2) { + has2Tuple = true; + break; + } + } + + if (has2Tuple) { + PyObject* tpArgsPair = PyTuple_New(argc); + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i); + PyObject* entry = nullptr; + if (PyTuple_CheckExact(itemi) && PyTuple_GET_SIZE(itemi) == 2) { + PyObject* subtypes = PyTuple_New(2); + for (Py_ssize_t j = 0; j < 2; ++j) { + PyObject* tp = (PyObject*)Py_TYPE(PyTuple_GET_ITEM(itemi, j)); + Py_INCREF(tp); + PyTuple_SET_ITEM(subtypes, j, tp); + } + const std::string& sub = Utility::ConstructTemplateArgs( + nullptr, subtypes, itemi, pref, 0, nullptr); + Py_DECREF(subtypes); + if (!PyErr_Occurred() && 2 < sub.size()) + entry = cpyrt_PyText_FromString(("std::pair" + sub).c_str()); + else + PyErr_Clear(); + } + if (!entry) { + entry = PyTuple_GET_ITEM(tpArgs, i); + Py_INCREF(entry); + } + PyTuple_SET_ITEM(tpArgsPair, i, entry); + } + + const std::string& name_v2 = Utility::ConstructTemplateArgs( + nullptr, tpArgsPair, pyargs, pref, 0, nullptr); + Py_DECREF(tpArgsPair); + if (!PyErr_Occurred() && name_v2.size()) + protoPair = name_v2.substr(1, name_v2.size() - 2); + else + PyErr_Clear(); + } + } + + Py_DECREF(pyargs); + Py_DECREF(tpArgs); + + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + + if (name_v1.size()) + proto = name_v1.substr(1, name_v1.size() - 2); + } + + // the following causes instantiation as necessary + interop::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType; + interop::TCppMethod_t cppmeth = + interop::GetMethodTemplate(scope, fname, proto); + if (!cppmeth && !protoPair.empty() && protoPair != proto) { + // retry with 2-tuples typed as std::pair instead of std::initializer_list + cppmeth = interop::GetMethodTemplate(scope, fname, protoPair); + if (cppmeth) + proto = protoPair; + } + if (cppmeth) { // overload stops here + // A successful instantiation needs to be cached to pre-empt future + // instantiations. There are two names involved, the original asked (which + // may be partial) and the received. + // + // Caching scheme: if the match is exact, simply add the overload to the + // pre-existing one, or create a new overload for later lookups. If the + // match is not exact, do the same, but also create an alias. Only add exact + // matches to the set of known template instantiations, to prevent piling on + // from different partial instantiations. + // + // TODO: this caches the lookup method before the call, meaning that failing + // overloads can add already existing overloads to the set of methods. + + std::string resname = + interop::GetFullName(interop::TCppScope_t(cppmeth.data)); + + // An initializer_list is preferred for the argument types, but should not + // leak into the argument types. If it did, replace with vector and lookup + // anew. + if (resname.find("initializer_list") != std::string::npos) { + auto pos = proto.find("initializer_list"); + while (pos != std::string::npos) { + proto.replace(pos, 16, "vector"); + pos = proto.find("initializer_list", pos + 6); + } + + interop::TCppMethod_t m2 = + interop::GetMethodTemplate(scope, fname, proto); + if (m2 && m2 != cppmeth) { + // replace if the new method with vector was found; otherwise just + // continue with the previously found method with initializer_list. + cppmeth = m2; + resname = interop::GetFullName(interop::TCppScope_t(cppmeth.data)); + } + } + + bool bExactMatch = fname == resname; + + // lookup on existing name in case this was an overload, not a caching, + // failure + PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict); + PyObject* pycachename = cpyrt_PyText_InternFromString(fname.c_str()); + PyObject* pyol = PyObject_GetItem(dct, pycachename); + if (!pyol) + PyErr_Clear(); + bool bIsCppOL = CPPOverload_Check(pyol); + bool bIsCppTP = TemplateProxy_Check(pyol); + + // find the full name if the requested one was partial + PyObject* exact = nullptr; + PyObject* pyresname = cpyrt_PyText_FromString(resname.c_str()); + if (!bExactMatch) { + exact = PyObject_GetItem(dct, pyresname); + if (!exact) + PyErr_Clear(); + } + Py_DECREF(dct); + + bool bIsConstructor = false, bNeedsRebind = true; + + PyCallable* meth = nullptr; + if (interop::IsNamespace(scope)) { + meth = new CPPFunction(scope, cppmeth); + bNeedsRebind = false; + } else if (interop::IsStaticMethod(cppmeth)) { + meth = new CPPClassMethod(scope, cppmeth); + bNeedsRebind = false; + } else if (interop::IsConstructor(cppmeth)) { + bIsConstructor = true; + meth = new CPPConstructor(scope, cppmeth); + } else + meth = new CPPMethod(scope, cppmeth); + + // Case 1/2: method simply did not exist before + if (!pyol) { + // actual overload to use (now owns meth) + pyol = (PyObject*)CPPOverload_New(fname, meth); + if (bIsConstructor) { + // TODO: this is an ugly hack :( + ((CPPOverload*)pyol)->fMethodInfo->fFlags |= + CallContext::kIsCreator | CallContext::kIsConstructor; + } + + // add to class dictionary + PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol); + } + + // Case 3/4: pre-existing method that was either not found b/c the full + // templated name was constructed in this call or it failed as overload + else if (bIsCppOL) { + // TODO: see above, since the call hasn't happened yet, this overload may + // already exist and fail again. + ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership + } + + // Case 5: must be a template proxy, meaning that current template name is + // not a template overload + else if (bIsCppTP) { + ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone()); + Py_DECREF(pyol); + pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership + } + // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy + // we do not cache it, as this might be a pythonization (monkey-patched + // func/method) + else { + Py_DECREF(pyol); + pyol = (PyObject*)CPPOverload_New(fname, meth); + } + + // Special Case if name was aliased (e.g. typedef in template instantiation) + if (!exact && !bExactMatch) { + PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol); + } + + // cleanup + Py_XDECREF(exact); + Py_DECREF(pyresname); + Py_DECREF(pycachename); + + // retrieve fresh (for boundedness) and call + PyObject* pymeth = CPPOverload_Type.tp_descr_get( + pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type); + Py_DECREF(pyol); + return pymeth; + } + + PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"", + fname.c_str(), proto.c_str()); + return nullptr; +} + +//= cpyrt template proxy construction/destruction ========================= +static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) { + // Create a new empty template method proxy. + TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type); + pytmpl->fSelf = nullptr; + pytmpl->fTemplateArgs = nullptr; + pytmpl->fWeakrefList = nullptr; + new (&pytmpl->fTI) TP_TInfo_t{}; + pytmpl->fTI = std::make_shared(); + + PyObject_GC_Track(pytmpl); + return pytmpl; +} + +//---------------------------------------------------------------------------- +static Py_hash_t tpp_hash(TemplateProxy* self) { return (Py_hash_t)self; } + +//---------------------------------------------------------------------------- +static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) { + if (op == Py_EQ || op == Py_NE) { + if (!TemplateProxy_CheckExact(other)) + Py_RETURN_FALSE; + + if (self->fTI == ((TemplateProxy*)other)->fTI) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; + } + + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +//---------------------------------------------------------------------------- +static int tpp_clear(TemplateProxy* pytmpl) { + // Garbage collector clear of held python member objects. + Py_CLEAR(pytmpl->fSelf); + Py_CLEAR(pytmpl->fTemplateArgs); + + return 0; +} + +//---------------------------------------------------------------------------- +static void tpp_dealloc(TemplateProxy* pytmpl) { + // Destroy the given template method proxy. + if (pytmpl->fWeakrefList) + PyObject_ClearWeakRefs((PyObject*)pytmpl); + PyObject_GC_UnTrack(pytmpl); + tpp_clear(pytmpl); + pytmpl->fTI.~TP_TInfo_t(); + PyObject_GC_Del(pytmpl); +} + +//---------------------------------------------------------------------------- +static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) { + // Garbage collector traverse of held python member objects. + Py_VISIT(pytmpl->fSelf); + Py_VISIT(pytmpl->fTemplateArgs); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) { + if (pytmpl->fTI->fDoc) { + Py_INCREF(pytmpl->fTI->fDoc); + return pytmpl->fTI->fDoc; + } + + // Forward to method proxies to doc all overloads + PyObject* doc = nullptr; + if (pytmpl->fTI->fNonTemplated->HasMethods()) + doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated, + "__doc__"); + if (pytmpl->fTI->fTemplated->HasMethods()) { + PyObject* doc2 = + PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__"); + if (doc && doc2) { + cpyrt_PyText_AppendAndDel(&doc, cpyrt_PyText_FromString("\n")); + cpyrt_PyText_AppendAndDel(&doc, doc2); + } else if (!doc && doc2) { + doc = doc2; + } + } + if (pytmpl->fTI->fLowPriority->HasMethods()) { + PyObject* doc2 = + PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__"); + if (doc && doc2) { + cpyrt_PyText_AppendAndDel(&doc, cpyrt_PyText_FromString("\n")); + cpyrt_PyText_AppendAndDel(&doc, doc2); + } else if (!doc && doc2) { + doc = doc2; + } + } + + if (doc) + return doc; + + return cpyrt_PyText_FromString(TemplateProxy_Type.tp_doc); +} + +static int tpp_doc_set(TemplateProxy* pytmpl, PyObject* val, void*) { + Py_XDECREF(pytmpl->fTI->fDoc); + Py_INCREF(val); + pytmpl->fTI->fDoc = val; + return 0; +} + +//---------------------------------------------------------------------------- + +//= cpyrt template proxy callable behavior ================================ + +#define TPPCALL_RETURN \ + { \ + errors.clear(); \ + return result; \ + } + +static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, + uint64_t sighash, CPPOverload* pymeth) { + // Memoize a method in the dispatch map after successful call; replace old if + // need be (may be with the same CPPOverload, just with more methods). + bool bInserted = false; + auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""]; + + Py_INCREF(pymeth); + for (auto& p : v) { + if (p.first == sighash) { + Py_DECREF(p.second); + p.second = pymeth; + bInserted = true; + } + } + if (!bInserted) + v.push_back(std::make_pair(sighash, pymeth)); +} + +static inline PyObject* +SelectAndForward(TemplateProxy* pytmpl, CPPOverload* pymeth, + cpyrt_PyArgs_t args, size_t nargsf, PyObject* kwds, + bool implicitOkay, bool use_targs, uint64_t sighash, + std::vector& errors) { + // Forward a call to known overloads, if any. + if (pymeth->HasMethods()) { + PyObject* pycall = CPPOverload_Type.tp_descr_get( + (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); + + if (!implicitOkay) + ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit; + + // now call the method with the arguments (loops internally) + PyObject* result = cpyrt_tp_call(pycall, args, nargsf, kwds); + Py_DECREF(pycall); + if (result) { + UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } + + return nullptr; +} + +static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth, + cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, bool impOK, + uint64_t sighash) { + // Actual call of a given overload: takes care of handlign of "self" and + // dereferences the overloaded method after use. + + PyObject* result; + if (!impOK && CPPOverload_Check(pymeth)) + ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit; + bool isNS = + (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) { + // this is a global method added a posteriori to the class + PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds}; + AdjustSelf(cargs); + result = cpyrt_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds); + } else { + if (!pytmpl->fSelf && CPPOverload_Check(pymeth)) + ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr; + result = cpyrt_tp_call(pymeth, args, nargsf, kwds); + } + + if (result) { + Py_XDECREF(((CPPOverload*)pymeth)->fSelf); + ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind + UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth); + } + + Py_DECREF(pymeth); + pymeth = nullptr; + return result; +} + +static PyObject* tpp_vectorcall(TemplateProxy* pytmpl, PyObject* const* args, + size_t nargsf, PyObject* kwds) { + // Dispatcher to the actual member method, several uses possible; in order: + // + // case 1: explicit template previously selected through subscript + // + // case 2: select known non-template overload + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) // non-template + // + // case 3: select known template overload + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) // all known templates + // + // case 4: auto-instantiation from types of arguments + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) + // + // Note: explicit instantiation needs to use [] syntax: + // + // obj.method[type, type, ...](a0, a1, ...) + // + // case 5: low priority methods, such as ones that take void* arguments + // + + // TODO: should previously instantiated templates be considered first? + + PyObject *pymeth = nullptr, *result = nullptr; + + // short-cut through memoization map + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + uint64_t sighash = HashSignature(args, argc); + + CPPOverload* ol = nullptr; + if (!pytmpl->fTemplateArgs) { + // look for known signatures ... + auto& v = pytmpl->fTI->fDispatchMap[""]; + for (const auto& p : v) { + if (p.first == sighash) { + ol = p.second; + break; + } + } + + if (ol != nullptr) { + if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) { + result = cpyrt_tp_call((PyObject*)ol, args, nargsf, kwds); + } else { + pymeth = CPPOverload_Type.tp_descr_get((PyObject*)ol, pytmpl->fSelf, + (PyObject*)&CPPOverload_Type); + result = cpyrt_tp_call(pymeth, args, nargsf, kwds); + Py_DECREF(pymeth); + pymeth = nullptr; + } + if (result) + return result; + } + } + + // container for collecting errors + std::vector errors; + if (ol) + Utility::FetchError(errors); + + // case 1: explicit template previously selected through subscript + if (pytmpl->fTemplateArgs) { + // instantiate explicitly + PyObject* pyfullname = + cpyrt_PyText_FromString(pytmpl->fTI->fCppName.c_str()); + cpyrt_PyText_Append(&pyfullname, pytmpl->fTemplateArgs); + + // first, lookup by full name, if previously stored + bool isNS = + (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS) + pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname); + else // by-passes custom scope getattr that searches into Cling + pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname); + + // attempt call if found (this may fail if there are specializations) + if (CPPOverload_Check(pymeth)) { + // since the template args are fully explicit, allow implicit conversion + // of arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } else if (pymeth && PyCallable_Check(pymeth)) { + // something different (user provided?) + result = cpyrt_PyObject_Call(pymeth, args, nargsf, kwds); + Py_DECREF(pymeth); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } else if (!pymeth) + PyErr_Clear(); + + // not cached or failed call; try instantiation + pymeth = pytmpl->Instantiate(cpyrt_PyText_AsString(pyfullname), args, + nargsf, Utility::kNone); + if (pymeth) { + // attempt actual call; same as above, allow implicit conversion of + // arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + } + + // no drop through if failed (if implicit was desired, don't provide + // template args) + Utility::FetchError(errors); + PyObject* topmsg = cpyrt_PyText_FromFormat( + "Could not find \"%s\" (set cppjit.set_debug() for C++ errors):", + cpyrt_PyText_AsString(pyfullname)); + Py_DECREF(pyfullname); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + + return nullptr; + } + + // case 2: select known non-template overload + result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf, + kwds, true /* implicitOkay */, + false /* use_targs */, sighash, errors); + if (result) + TPPCALL_RETURN; + + // case 3: select known template overload + result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds, + false /* implicitOkay */, true /* use_targs */, + sighash, errors); + if (result) + TPPCALL_RETURN; + + // case 4: auto-instantiation from types of arguments + for (auto pref : {Utility::kReference, Utility::kPointer, Utility::kValue}) { + // TODO: no need to loop if there are no non-instance arguments; also, + // should any failed lookup be removed? + int pcnt = 0; + pymeth = + pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt); + if (pymeth) { + // attempt actual call; even if argument based, allow implicit + // conversions, for example for non-template arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) + TPPCALL_RETURN; + } + Utility::FetchError(errors); + if (!pcnt) + break; // preference never used; no point trying others + } + + // case 5: low priority methods, such as ones that take void* arguments + result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf, + kwds, false /* implicitOkay */, + false /* use_targs */, sighash, errors); + if (result) + TPPCALL_RETURN; + + // error reporting is fraud, given the numerous steps taken, but more details + // seems better + if (!errors.empty()) { + PyObject* topmsg = + cpyrt_PyText_FromString("Template method resolution failed:"); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + } else { + PyErr_Format(PyExc_TypeError, + "cannot resolve method template call for \'%s\'", + pytmpl->fTI->fCppName.c_str()); + } + + return nullptr; +} + +//---------------------------------------------------------------------------- +static TemplateProxy* tpp_descr_get(TemplateProxy* pytmpl, PyObject* pyobj, + PyObject*) { + // create and use a new template proxy (language requirement) + TemplateProxy* newPyTmpl = + (TemplateProxy*)TemplateProxy_Type.tp_alloc(&TemplateProxy_Type, 0); + + // new method is to be bound to current object (may be nullptr) + if (pyobj) { + Py_INCREF(pyobj); + newPyTmpl->fSelf = pyobj; + } else { + Py_INCREF(Py_None); + newPyTmpl->fSelf = Py_None; + } + + Py_XINCREF(pytmpl->fTemplateArgs); + newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs; + + // copy name, class, etc. pointers + new (&newPyTmpl->fTI) std::shared_ptr{pytmpl->fTI}; + + newPyTmpl->fVectorCall = pytmpl->fVectorCall; + + return newPyTmpl; +} + +//---------------------------------------------------------------------------- +static PyObject* tpp_subscript(TemplateProxy* pytmpl, PyObject* args) { + // Explicit template member lookup/instantiation; works by re-bounding. This + // method can not cache overloads as instantiations need not be unique for the + // argument types due to template specializations. + TemplateProxy* typeBoundMethod = + tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr); + Py_XDECREF(typeBoundMethod->fTemplateArgs); + typeBoundMethod->fTemplateArgs = cpyrt_PyText_FromString( + Utility::ConstructTemplateArgs(nullptr, args).c_str()); + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + return (PyObject*)typeBoundMethod; +} + +//----------------------------------------------------------------------------- +static PyObject* tpp_getuseffi(CPPOverload*, void*) { + return PyInt_FromLong(0); // dummy (__useffi__ unused) +} + +//----------------------------------------------------------------------------- +static int tpp_setuseffi(CPPOverload*, PyObject*, void*) { + return 0; // dummy (__useffi__ unused) +} + +//----------------------------------------------------------------------------- +static PyObject* tpp_gettemplateargs(TemplateProxy* self, void*) { + if (!self->fTemplateArgs) { + Py_RETURN_NONE; + } + + Py_INCREF(self->fTemplateArgs); + return self->fTemplateArgs; +} + +//----------------------------------------------------------------------------- +static int tpp_settemplateargs(TemplateProxy*, PyObject*, void*) { + PyErr_SetString(PyExc_AttributeError, "__template_args__ is read-only"); + return -1; +} + +//---------------------------------------------------------------------------- +static PyMappingMethods tpp_as_mapping = {nullptr, (binaryfunc)tpp_subscript, + nullptr}; + +static PyGetSetDef tpp_getset[] = { + {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr}, + {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi, + (char*)"unused", nullptr}, + {(char*)"__template_args__", (getter)tpp_gettemplateargs, + (setter)tpp_settemplateargs, + (char*)"the template arguments for this method", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}, +}; + +//---------------------------------------------------------------------------- +void TemplateProxy::Set(const std::string& cppname, const std::string& pyname, + PyObject* pyclass) { + // Initialize the proxy for the given 'pyclass.' + fSelf = nullptr; + fTemplateArgs = nullptr; + + fTI->fCppName = cppname; + Py_XINCREF(pyclass); + fTI->fPyClass = pyclass; + + std::vector dummy; + fTI->fNonTemplated = CPPOverload_New(pyname, dummy); + fTI->fTemplated = CPPOverload_New(pyname, dummy); + fTI->fLowPriority = CPPOverload_New(pyname, dummy); + + fVectorCall = (vectorcallfunc)tpp_vectorcall; +} + +//= cpyrt method proxy access to internals ================================ +static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) { + // Select and call a specific C++ overload, based on its signature. + const char* sigarg = nullptr; + const char* tmplarg = nullptr; + PyObject* sigarg_tuple = nullptr; + int want_const = -1; + + interop::TCppScope_t scope = nullptr; + interop::TCppMethod_t cppmeth = nullptr; + std::string proto; + + if (PyArg_ParseTuple(args, const_cast("s|i:__overload__"), &sigarg, + &want_const)) { + want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; + + // check existing overloads in order + PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const); + if (ol) + return ol; + + proto = Utility::ConstructTemplateArgs(nullptr, args); + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + + scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName, + proto.substr(1, proto.size() - 2)); + } else if (PyArg_ParseTuple(args, const_cast("ss:__overload__"), + &sigarg, &tmplarg)) { + scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + std::string full_name = + std::string(pytmpl->fTI->fCppName) + "<" + tmplarg + ">"; + + cppmeth = interop::GetMethodTemplate(scope, full_name, sigarg); + } else if (PyArg_ParseTuple(args, const_cast("O|i:__overload__"), + &sigarg_tuple, &want_const)) { + PyErr_Clear(); + want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; + + // check existing overloads in order + PyObject* ol = + pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + + proto.reserve(128); + proto.push_back('<'); + Py_ssize_t n = PyTuple_Size(sigarg_tuple); + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(sigarg_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_Format(PyExc_LookupError, + "argument types should be in string format"); + return (PyObject*)nullptr; + } + proto.append(cpyrt_PyText_AsString(pItem)); + if (i < n - 1) + proto.push_back(','); + } + proto.push_back('>'); + + scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName, + proto.substr(1, proto.size() - 2)); + } else { + PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); + return nullptr; + } + + // else attempt instantiation + if (!cppmeth) { + return nullptr; + } + + PyErr_Clear(); + + // TODO: the next step should be consolidated with Instantiate() + PyCallable* meth = nullptr; + if (interop::IsNamespace(scope)) { + meth = new CPPFunction(scope, cppmeth); + } else if (interop::IsStaticMethod(cppmeth)) { + meth = new CPPClassMethod(scope, cppmeth); + } else if (interop::IsConstructor(cppmeth)) { + meth = new CPPConstructor(scope, cppmeth); + } else + meth = new CPPMethod(scope, cppmeth); + + return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName + proto, meth); +} + +static PyMethodDef tpp_methods[] = {{(char*)"__overload__", + (PyCFunction)tpp_overload, METH_VARARGS, + (char*)"select overload for dispatch"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt template proxy type ============================================= +PyTypeObject TemplateProxy_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.TemplateProxy", // tp_name + sizeof(TemplateProxy), // tp_basicsize + 0, // tp_itemsize + (destructor)tpp_dealloc, // tp_dealloc + offsetof(TemplateProxy, fVectorCall), + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + &tpp_as_mapping, // tp_as_mapping + (hashfunc)tpp_hash, // tp_hash + (ternaryfunc)PyVectorcall_Call, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL | + Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags + (char*)"cppjit template proxy (internal)", // tp_doc + (traverseproc)tpp_traverse, // tp_traverse + (inquiry)tpp_clear, // tp_clear + (richcmpfunc)tpp_richcompare, // tp_richcompare + offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + tpp_methods, // tp_methods + 0, // tp_members + tpp_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)tpp_descr_get, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)tpp_new, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.h b/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.h new file mode 100644 index 0000000000000..ff9ad99911ef7 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.h @@ -0,0 +1,101 @@ +#ifndef CPYRT_TEMPLATEPROXY_H +#define CPYRT_TEMPLATEPROXY_H + +// Bindings +#include "CPPScope.h" +#include "Utility.h" + +// Standard +#include +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +class PyCallable; +class CPPOverload; + +/** Template proxy object to return functions and methods + */ + +typedef std::pair TP_DispatchEntry_t; +typedef std::unordered_map> + TP_DispatchMap_t; + +class TemplateInfo { +public: + TemplateInfo(); + TemplateInfo(const TemplateInfo&) = delete; + TemplateInfo& operator=(const TemplateInfo&) = delete; + ~TemplateInfo(); + +public: + std::string fCppName; + PyObject* fPyClass; + CPPOverload* fNonTemplated; // holder for non-template overloads + CPPOverload* fTemplated; // holder for templated overloads + CPPOverload* fLowPriority; // low priority overloads such as void*/void** + + TP_DispatchMap_t fDispatchMap; + PyObject* fDoc; +}; + +typedef std::shared_ptr TP_TInfo_t; + +class TemplateProxy { +private: + friend TemplateProxy* TemplateProxy_New(const std::string& cppname, + const std::string& pyname, + PyObject* pyclass); + void Set(const std::string& cppname, const std::string& pyname, + PyObject* pyclass); + +public: // public, as the python C-API works with C structs + PyObject_HEAD PyObject* fSelf; // must be first (same layout as CPPOverload) + PyObject* fTemplateArgs; + PyObject* fWeakrefList; + vectorcallfunc fVectorCall; + TP_TInfo_t fTI; + +public: + void MergeOverload(CPPOverload* mp); + void AdoptMethod(PyCallable* pc); + void AdoptTemplate(PyCallable* pc); + PyObject* Instantiate(const std::string& fname, cpyrt_PyArgs_t tmplArgs, + size_t nargsf, Utility::ArgPreference, + int* pcnt = nullptr); + +private: // private, as the python C-API will handle creation + TemplateProxy() = delete; +}; + +//- template proxy type and type verification -------------------------------- +extern PyTypeObject TemplateProxy_Type; + +template inline bool TemplateProxy_Check(T* object) { + return object && PyObject_TypeCheck(object, &TemplateProxy_Type); +} + +template inline bool TemplateProxy_CheckExact(T* object) { + return object && Py_TYPE(object) == &TemplateProxy_Type; +} + +//- creation ----------------------------------------------------------------- +inline TemplateProxy* TemplateProxy_New(const std::string& cppname, + const std::string& pyname, + PyObject* pyclass) { + // Create and initialize a new template method proxy for the class. + if (!CPPScope_Check(pyclass)) + return nullptr; + + TemplateProxy* pytmpl = (TemplateProxy*)TemplateProxy_Type.tp_new( + &TemplateProxy_Type, nullptr, nullptr); + pytmpl->Set(cppname, pyname, pyclass); + return pytmpl; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TEMPLATEPROXY_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.cxx b/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.cxx new file mode 100644 index 0000000000000..624c4497d3912 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.cxx @@ -0,0 +1,266 @@ +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "ProxyWrappers.h" +#include "TupleOfInstances.h" + +namespace { + +typedef struct { + PyObject_HEAD interop::TCppScope_t ia_klass; + void* ia_array_start; + Py_ssize_t ia_pos; + Py_ssize_t ia_len; + Py_ssize_t ia_stride; +} ia_iterobject; + +static PyObject* ia_iternext(ia_iterobject* ia) { + if (ia->ia_len != (Py_ssize_t)-1 && ia->ia_pos >= ia->ia_len) { + ia->ia_pos = 0; // debatable, but since the iterator is cached, this + return nullptr; // allows for multiple conversions to e.g. a tuple + } else if (ia->ia_stride == 0 && ia->ia_pos != 0) { + PyErr_SetString(PyExc_ReferenceError, "no stride available for indexing"); + return nullptr; + } + PyObject* result = cpyrt::BindCppObjectNoCast( + (char*)ia->ia_array_start + ia->ia_pos * ia->ia_stride, ia->ia_klass); + ia->ia_pos += 1; + return result; +} + +static int ia_traverse(ia_iterobject*, visitproc, void*) { return 0; } + +static PyObject* ia_getsize(ia_iterobject* ia, void*) { + return PyInt_FromSsize_t(ia->ia_len); +} + +static int ia_setsize(ia_iterobject* ia, PyObject* pysize, void*) { + Py_ssize_t size = PyInt_AsSsize_t(pysize); + if (size == (Py_ssize_t)-1 && PyErr_Occurred()) + return -1; + ia->ia_len = size; + return 0; +} + +static PyGetSetDef ia_getset[] = { + {(char*)"size", (getter)ia_getsize, (setter)ia_setsize, + (char*)"set size of array to which this iterator refers", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +static Py_ssize_t ia_length(ia_iterobject* ia) { return ia->ia_len; } + +static PyObject* ia_subscript(ia_iterobject* ia, PyObject* pyidx) { + // Subscripting the iterator allows direct access through indexing on arrays + // that do not have a defined length. This way, the return from accessing such + // an array as a data member can both be used in a loop and directly. + Py_ssize_t idx = PyInt_AsSsize_t(pyidx); + if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) + return nullptr; + + if (ia->ia_len != (Py_ssize_t)-1 && (idx < 0 || ia->ia_len <= idx)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + + return cpyrt::BindCppObjectNoCast( + (char*)ia->ia_array_start + ia->ia_pos * ia->ia_stride, ia->ia_klass); +} + +static PyMappingMethods ia_as_mapping = { + (lenfunc)ia_length, // mp_length + (binaryfunc)ia_subscript, // mp_subscript + (objobjargproc) nullptr, // mp_ass_subscript +}; + +} // unnamed namespace + +namespace cppjit::cpyrt { + +PyTypeObject InstanceArrayIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.instancearrayiter", // tp_name + sizeof(ia_iterobject), // tp_basicsize + 0, + (destructor)PyObject_GC_Del, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &ia_as_mapping, // tp_as_mapping + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)ia_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)ia_iternext, // tp_iternext + 0, + 0, + ia_getset, // tp_getset + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +//= support for C-style arrays of objects ==================================== +PyObject* TupleOfInstances_New(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims) { + // recursively set up tuples of instances on all dimensions + if (dims.ndim() == UNKNOWN_SIZE || + dims[0] == UNKNOWN_SIZE /* unknown shape or size */) { + // no known length ... return an iterable object and let the user figure it + // out + ia_iterobject* ia = PyObject_GC_New(ia_iterobject, &InstanceArrayIter_Type); + if (!ia) + return nullptr; + + ia->ia_klass = klass; + ia->ia_array_start = address.data; + ia->ia_pos = 0; + ia->ia_len = -1; + ia->ia_stride = interop::SizeOf(klass); + + PyObject_GC_Track(ia); + return (PyObject*)ia; + } else if (1 < dims.ndim()) { + // not the innermost dimension, descend one level + size_t block_size = 1; + for (Py_ssize_t i = 1; i < dims.ndim(); ++i) { + if (dims[i] != 0) + block_size *= (size_t)dims[i]; + } + block_size *= interop::SizeOf(klass); + + Py_ssize_t nelems = dims[0]; + PyObject* tup = PyTuple_New(nelems); + for (Py_ssize_t i = 0; i < nelems; ++i) { + PyTuple_SetItem( + tup, i, + TupleOfInstances_New(((char*)address.data) + i * block_size, klass, + dims.sub())); + } + return tup; + } else { + // innermost dimension: construct tuple + int nelems = (int)dims[0]; + size_t block_size = interop::SizeOf(klass); + if (block_size == 0) { + PyErr_Format(PyExc_TypeError, + "can not determine size of type \"%s\" for array indexing", + interop::GetScopedFinalName(klass).c_str()); + return nullptr; + } + + // TODO: the extra copy is inefficient, but it appears that the only way to + // initialize a subclass of a tuple is through a sequence + PyObject* tup = PyTuple_New(nelems); + for (int i = 0; i < nelems; ++i) { + // TODO: there's an assumption here that there is no padding, which is + // bound to be incorrect in certain cases + PyTuple_SetItem( + tup, i, + BindCppObjectNoCast(((char*)address.data) + i * block_size, klass)); + // Note: objects are bound as pointers, yet since the pointer value stays + // in place, updates propagate just as if they were bound by-reference + } + + PyObject* args = PyTuple_New(1); + Py_INCREF(tup); + PyTuple_SET_ITEM(args, 0, tup); + PyObject* arr = PyTuple_Type.tp_new(&TupleOfInstances_Type, args, nullptr); + + Py_DECREF(args); + // tup ref eaten by SET_ITEM on args + + return arr; + } + + // never get here + return nullptr; +} + +//= cpyrt custom tuple-like array type ==================================== +PyTypeObject TupleOfInstances_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.InstancesArray", // tp_name + 0, // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"array of C++ instances", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + 0, // tp_methods + 0, // tp_members + 0, // tp_getset + &PyTuple_Type, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + 0, // tp_new + 0, // tp_free + 0, // tp_is_gc + 0, // tp_bases + 0, // tp_mro + 0, // tp_cache + 0, // tp_subclasses + 0, // tp_weaklist + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.h b/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.h new file mode 100644 index 0000000000000..9be79f213f15f --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.h @@ -0,0 +1,32 @@ +#ifndef CPYRT_TUPLEOFINSTANCES_H +#define CPYRT_TUPLEOFINSTANCES_H + +// Bindings +#include "Dimensions.h" + +namespace cppjit::cpyrt { + +/** Representation of C-style array of instances + @author WLAV + @date 02/10/2014 + @version 1.0 + */ + +//- custom tuple type that can pass through C-style arrays ------------------- +extern PyTypeObject TupleOfInstances_Type; +extern PyTypeObject InstanceArrayIter_Type; + +template inline bool TupleOfInstances_Check(T* object) { + return object && PyObject_TypeCheck(object, &TupleOfInstances_Type); +} + +template inline bool TupleOfInstances_CheckExact(T* object) { + return object && Py_TYPE(object) == &TupleOfInstances_Type; +} + +PyObject* TupleOfInstances_New(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TUPLEOFINSTANCES_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/TypeManip.cxx b/bindings/pyroot/cppjit/src/cpyrt/TypeManip.cxx new file mode 100644 index 0000000000000..887899b739f7c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TypeManip.cxx @@ -0,0 +1,279 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "TypeManip.h" + +// Standard +#include +#include +#include + +//- helpers ------------------------------------------------------------------ +static inline bool is_varchar(char c) { + return isalnum((int)c) || c == '_' || c == ')' || + c == '(' /* for (anonymous)/(unnamed) */; +} + +static inline std::string::size_type +find_qualifier_index(const std::string& name) { + // Find the first location that is not part of the class name proper. + std::string::size_type i = name.size() - 1; + bool arr_open = false; + for (; 0 < i; --i) { + std::string::value_type c = name[i]; + if (!arr_open && (is_varchar(c) || c == '>')) { + if (c == 't' && 6 < i && !is_varchar(name[i - 5]) && + name.substr(i - 4, 5) == "const") + i -= 4; // this skips 'const' on a pointer type + else + break; + } else if (c == ']') { + arr_open = true; + } else if (c == '[') { + arr_open = false; + } + } + + return i + 1; +} + +static inline void erase_const(std::string& name) { + // Find and remove all occurrence of 'const'. + if (name.empty()) + return; + + std::string::size_type spos = std::string::npos; + std::string::size_type start = 0; + while ((spos = name.find("const", start)) != std::string::npos) { + // make sure not to erase 'const' as part of the name: if it is + // connected, before or after, to a variable name, then keep it + std::string::size_type after = spos + 5; + if (after < name.size() && is_varchar(name[after])) { + start = after; + continue; + } else if (after == name.size()) { + if (spos > 0 && is_varchar(name[spos - 1])) + break; + } + + std::string::size_type i = 5; + while (name[spos + i] == ' ') + ++i; + name.swap(name.erase(spos, i)); + } +} + +static inline void rstrip(std::string& name) { + // Remove space from the right side of name. + std::string::size_type i = name.size(); + for (; 0 < i; --i) { + if (!isspace(name[i - 1])) + break; + } + + if (i != name.size()) + name = name.substr(0, i); +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::remove_const(const std::string& cppname) { + // Remove 'const' qualifiers from the given C++ name. + std::string::size_type tmplt_start = cppname.find('<'); + std::string::size_type type_stop = cppname.rfind('>'); + if (cppname.find("::", type_stop + 1) != + std::string::npos) // e.g. klass::some_typedef + type_stop = cppname.find(' ', type_stop + 1); + if (tmplt_start != std::string::npos && cppname[tmplt_start + 1] != '<') { + // only replace const qualifying cppname, not in template parameters + std::string pre = cppname.substr(0, tmplt_start); + erase_const(pre); + std::string post = ""; + if (type_stop != std::string::npos && type_stop != cppname.size() - 1) { + post = cppname.substr(type_stop + 1, std::string::npos); + erase_const(post); + } + + return pre + cppname.substr(tmplt_start, type_stop + 1 - tmplt_start) + + post; + } + + std::string clean_name = cppname; + erase_const(clean_name); + return clean_name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::clean_type(const std::string& cppname, + bool template_strip, + bool const_strip) { + // Strip C++ name from all qualifiers and compounds. + std::string::size_type i = find_qualifier_index(cppname); + std::string name = cppname.substr(0, i); + rstrip(name); + + if (name.back() == ']') { // array type? + // TODO: this fails templates instantiated on arrays (not common) + name = name.substr(0, name.find('[')); + } else if (template_strip && name.back() == '>') { + name = name.substr(0, name.find('<')); + } + + if (const_strip) { + if (template_strip) + erase_const(name); + else + name = remove_const(name); + } + + return name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::template_base(const std::string& cppname) { + // If this is a template, return the underlying template name w/o arguments + if (cppname.empty() || cppname.back() != '>') + return cppname; + + int tpl_open = 0; + for (std::string::size_type pos = cppname.size() - 1; 0 < pos; --pos) { + std::string::value_type c = cppname[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && cppname[pos + 1] != '<') + ++tpl_open; + + if (tpl_open == 0) + return cppname.substr(0, pos); + } + + return cppname; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::compound(const std::string& name) { + // FIXME: temporary fix for translation unit decl being passed in + // CreateExecutor from InitExecutor_ (run test10_object_identity), remove + // later + if (name.empty()) + return ""; + // Break down the compound of a fully qualified type name. + std::string cleanName = remove_const(name); + auto idx = find_qualifier_index(cleanName); + + std::string cpd = cleanName.substr(idx, std::string::npos); + + // for easy identification of fixed size arrays + if (!cpd.empty() && cpd.back() == ']') { + if (cpd.front() == '[') + return "[]"; // fixed array any; dimensions handled separately + + std::ostringstream scpd; + scpd << cpd.substr(0, cpd.find('[')) << "[]"; + cpd = scpd.str(); + } + + // XXX: remove this hack + if (!cpd.empty() && cpd[0] == ' ') + return cpd.substr(1, cpd.length() - 1); + + return cpd; +} + +//---------------------------------------------------------------------------- +void cpyrt::TypeManip::cppscope_to_pyscope(std::string& cppscope) { + // Change '::' in C++ scope into '.' as in a Python scope. + std::string::size_type pos = 0; + while ((pos = cppscope.find("::", pos)) != std::string::npos) { + cppscope.replace(pos, 2, "."); + pos += 1; + } +} + +//---------------------------------------------------------------------------- +void cpyrt::TypeManip::cppscope_to_legalname(std::string& cppscope) { + // Change characters illegal in a variable name into '_' to form a legal name. + for (char& c : cppscope) { + for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*', '-'}) + if (c == needle) + c = '_'; + } +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::extract_namespace(const std::string& name) { + // Find the namespace the named class lives in, take care of templates + if (name.empty()) + return name; + + int tpl_open = 0; + for (std::string::size_type pos = name.size() - 1; 0 < pos; --pos) { + std::string::value_type c = name[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && name[pos + 1] != '<') + ++tpl_open; + + // collect name up to "::" + else if (tpl_open == 0 && c == ':' && name[pos - 1] == ':') { + // found the extend of the scope ... done + return name.substr(0, pos - 1); + } + } + + // no namespace; assume outer scope + return ""; +} + +//---------------------------------------------------------------------------- +std::vector +cpyrt::TypeManip::extract_arg_types(const std::string& sig) { + // break out the argument types from the signature string + std::vector result; + + if (sig.empty() || sig == "()") + return result; + + int tpl_open = 0; + std::string::size_type start = 1; + for (std::string::size_type pos = 1; pos < sig.size() - 1; ++pos) { + std::string::value_type c = sig[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && sig[pos + 1] != '<') + ++tpl_open; + + // collect type name up to ',' or end ')' + else if (tpl_open == 0 && c == ',') { + // found the extend of the scope ... done + result.push_back(sig.substr(start, pos - start)); + start = pos + 1; + } + } + + // add last type + result.push_back(sig.substr(start, sig.rfind(")") - start)); + + return result; +} + +//---------------------------------------------------------------------------- +Py_ssize_t cpyrt::TypeManip::array_size(const std::string& name) { + // Extract the array size from a given type name (assumes 1D arrays) + std::string cleanName = remove_const(name); + if (cleanName[cleanName.size() - 1] == ']') { + std::string::size_type idx = cleanName.rfind('['); + if (idx != std::string::npos) { + const std::string asize = cleanName.substr(idx + 1, cleanName.size() - 2); + return strtoul(asize.c_str(), nullptr, 0); + } + } + + return -1; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/TypeManip.h b/bindings/pyroot/cppjit/src/cpyrt/TypeManip.h new file mode 100644 index 0000000000000..49c3b4fa3a50b --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TypeManip.h @@ -0,0 +1,28 @@ +#ifndef CPYRT_TYPEMANIP_H +#define CPYRT_TYPEMANIP_H + +#include +#include + +namespace cppjit::cpyrt { + +namespace TypeManip { + +std::string remove_const(const std::string& cppname); +std::string clean_type(const std::string& cppname, bool template_strip = true, + bool const_strip = true); +std::string template_base(const std::string& cppname); +std::string compound(const std::string& name); + +void cppscope_to_pyscope(std::string& cppscope); +void cppscope_to_legalname(std::string& cppscope); +std::string extract_namespace(const std::string& name); + +std::vector extract_arg_types(const std::string& sig); +Py_ssize_t array_size(const std::string& name); + +} // namespace TypeManip + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TYPEMANIP_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx new file mode 100644 index 0000000000000..f83d39c27fcd7 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx @@ -0,0 +1,1608 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CustomPyTypes.h" +#include "ProxyWrappers.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +//- data _____________________________________________________________________ +typedef std::unordered_map TC2POperatorMapping_t; +static TC2POperatorMapping_t gC2POperatorMapping; +static std::set gOpSkip; +static std::set gOpRemove; + +namespace cppjit::cpyrt { +// special objects +extern PyObject* gNullPtrObject; +extern PyObject* gDefaultObject; +} // namespace cppjit::cpyrt + +namespace { + +using namespace cpyrt::Utility; + +struct InitOperatorMapping_t { +public: + InitOperatorMapping_t() { + // Initialize the global map of operator names C++ -> python. + + gOpSkip.insert("[]"); // __s/getitem__, depends on return type + gOpSkip.insert("+"); // __add__, depends on # of args (see __pos__) + gOpSkip.insert("-"); // __sub__, id. (eq. __neg__) + gOpSkip.insert("*"); // __mul__ or __deref__ + gOpSkip.insert("++"); // __postinc__ or __preinc__ + gOpSkip.insert("--"); // __postdec__ or __predec__ + + gOpRemove.insert("new"); // this and the following not handled at all + gOpRemove.insert("new[]"); + gOpRemove.insert("delete"); + gOpRemove.insert("delete[]"); + + gC2POperatorMapping["[]"] = "__getitem__"; + gC2POperatorMapping["()"] = "__call__"; + gC2POperatorMapping["%"] = "__mod__"; + gC2POperatorMapping["**"] = "__pow__"; + gC2POperatorMapping["<<"] = "__lshift__"; + gC2POperatorMapping[">>"] = "__rshift__"; + gC2POperatorMapping["&"] = "__and__"; + gC2POperatorMapping["&&"] = "__dand__"; + gC2POperatorMapping["|"] = "__or__"; + gC2POperatorMapping["||"] = "__dor__"; + gC2POperatorMapping["^"] = "__xor__"; + gC2POperatorMapping["~"] = "__invert__"; + gC2POperatorMapping[","] = "__comma__"; + gC2POperatorMapping["+="] = "__iadd__"; + gC2POperatorMapping["-="] = "__isub__"; + gC2POperatorMapping["*="] = "__imul__"; + gC2POperatorMapping["/="] = CPPJIT__idiv__; + gC2POperatorMapping["%="] = "__imod__"; + gC2POperatorMapping["**="] = "__ipow__"; + gC2POperatorMapping["<<="] = "__ilshift__"; + gC2POperatorMapping[">>="] = "__irshift__"; + gC2POperatorMapping["&="] = "__iand__"; + gC2POperatorMapping["|="] = "__ior__"; + gC2POperatorMapping["^="] = "__ixor__"; + gC2POperatorMapping["=="] = "__eq__"; + gC2POperatorMapping["!="] = "__ne__"; + gC2POperatorMapping[">"] = "__gt__"; + gC2POperatorMapping["<"] = "__lt__"; + gC2POperatorMapping[">="] = "__ge__"; + gC2POperatorMapping["<="] = "__le__"; + + // the following type mappings are "exact" + gC2POperatorMapping["const char*"] = "__str__"; + gC2POperatorMapping["char*"] = "__str__"; + gC2POperatorMapping["const char *"] = gC2POperatorMapping["const char*"]; + gC2POperatorMapping["char *"] = gC2POperatorMapping["char*"]; + gC2POperatorMapping["int"] = "__int__"; + gC2POperatorMapping["long"] = CPPJIT__long__; + gC2POperatorMapping["double"] = "__float__"; + + // the following type mappings are "okay"; the assumption is that they + // are not mixed up with the ones above or between themselves (and if + // they are, that it is done consistently) + gC2POperatorMapping["short"] = "__int__"; + gC2POperatorMapping["unsigned short"] = "__int__"; + gC2POperatorMapping["unsigned int"] = CPPJIT__long__; + gC2POperatorMapping["unsigned long"] = CPPJIT__long__; + gC2POperatorMapping["long long"] = CPPJIT__long__; + gC2POperatorMapping["unsigned long long"] = CPPJIT__long__; + gC2POperatorMapping["float"] = "__float__"; + + gC2POperatorMapping["->"] = "__follow__"; // not an actual python operator + gC2POperatorMapping["="] = "__assign__"; // id. + + gC2POperatorMapping["bool"] = "__cpp_bool__"; + } +} initOperatorMapping_; + +inline std::string full_scope(const std::string& tpname) { + return tpname[0] == ':' ? tpname : "::" + tpname; +} + +} // unnamed namespace + +//- public functions --------------------------------------------------------- +unsigned long cpyrt::PyLongOrInt_AsULong(PyObject* pyobject) { + // Convert to C++ unsigned long, with bounds checking, allow int -> + // ulong. + if (PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long"); + return (unsigned long)-1; + } else if (pyobject == cpyrt::gDefaultObject) { + return (unsigned long)0; + } + + unsigned long ul = PyLong_AsUnsignedLong(pyobject); + if (ul == (unsigned long)-1 && PyErr_Occurred() && PyInt_Check(pyobject)) { + PyErr_Clear(); + long i = PyInt_AS_LONG(pyobject); + if (0 <= i) { + ul = (unsigned long)i; + } else { + PyErr_SetString(PyExc_ValueError, + "can\'t convert negative value to unsigned long"); + return (unsigned long)-1; + } + } + + return ul; +} + +//---------------------------------------------------------------------------- +PY_ULONG_LONG cpyrt::PyLongOrInt_AsULong64(PyObject* pyobject) { + // Convert to C++ unsigned long long, with bounds checking. + if (PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, + "can\'t convert float to unsigned long long"); + return -1; + } else if (pyobject == cpyrt::gDefaultObject) { + return (unsigned long)0; + } + + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(pyobject); + if (PyErr_Occurred() && PyInt_Check(pyobject)) { + PyErr_Clear(); + long i = PyInt_AS_LONG(pyobject); + if (0 <= i) { + ull = (PY_ULONG_LONG)i; + } else { + PyErr_SetString(PyExc_ValueError, + "can\'t convert negative value to unsigned long long"); + } + } + + return ull; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + PyCFunction cfunc, int flags) { + // Add the given function to the class under name 'label'. + + // use list for clean-up (.so's are unloaded only at interpreter shutdown) + static std::list s_pymeths; + + s_pymeths.push_back(PyMethodDef()); + PyMethodDef* pdef = &s_pymeths.back(); + pdef->ml_name = const_cast(label); + pdef->ml_meth = cfunc; + pdef->ml_flags = flags; + pdef->ml_doc = nullptr; + + PyObject* func = PyCFunction_New(pdef, nullptr); + PyObject* name = cpyrt_PyText_InternFromString(pdef->ml_name); + PyObject* method = CustomInstanceMethod_New(func, nullptr, pyclass); + PyObject *pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + bool isOk = PyType_Type.tp_setattro(pyclass, name, method) == 0; + PyErr_Restore(pytype, pyvalue, pytrace); + Py_DECREF(method); + Py_DECREF(name); + Py_DECREF(func); + + if (PyErr_Occurred()) + return false; + + if (!isOk) { + PyErr_Format(PyExc_TypeError, "could not add method %s", label); + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + const char* func) { + // Add the given function to the class under name 'label'. + PyObject* pyfunc = PyObject_GetAttrString(pyclass, const_cast(func)); + if (!pyfunc) + return false; + + PyObject* pylabel = cpyrt_PyText_InternFromString(const_cast(label)); + bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, pyfunc) == 0; + Py_DECREF(pylabel); + + Py_DECREF(pyfunc); + return isOk; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + PyCallable* pyfunc) { + // Add the given function to the class under name 'label'. + CPPOverload* method = + (CPPOverload*)PyObject_GetAttrString(pyclass, const_cast(label)); + + if (!method || !CPPOverload_Check(method)) { + // not adding to existing CPPOverload; add callable directly to the class + if (PyErr_Occurred()) + PyErr_Clear(); + Py_XDECREF((PyObject*)method); + method = CPPOverload_New(label, pyfunc); + PyObject* pylabel = cpyrt_PyText_InternFromString(const_cast(label)); + bool isOk = + PyType_Type.tp_setattro(pyclass, pylabel, (PyObject*)method) == 0; + Py_DECREF(pylabel); + Py_DECREF(method); + return isOk; + } + + method->AdoptMethod(pyfunc); + + Py_DECREF(method); + return true; +} + +//---------------------------------------------------------------------------- +static inline cpyrt::PyCallable* BuildOperator(const std::string& lcname, + const std::string& rcname, + const char* op, + interop::TCppScope_t scope, + bool reverse = false) { + // Helper to find a function with matching signature in 'funcs'. + + interop::TCppMethod_t meth = + interop::GetGlobalOperator(scope, lcname, rcname, op); + if (!meth) + return nullptr; + + if (!reverse) + return new cpyrt::CPPFunction(scope, meth); + return new cpyrt::CPPReverseBinary(scope, meth); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* cpyrt::Utility::FindUnaryOperator(PyObject* pyclass, + const char* op) { + // Find a callable matching named operator (op) and klass arguments in the + // global namespace or the klass' namespace. + + if (!CPPScope_Check(pyclass)) + return nullptr; + + CPPClass* klass = (CPPClass*)pyclass; + const std::string& lcname = interop::GetScopedFinalName(klass->fCppType); + interop::TCppScope_t scope = + interop::GetScope(TypeManip::extract_namespace(lcname)); + return FindBinaryOperator(lcname, "", op, scope, false); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* +cpyrt::Utility::FindBinaryOperator(PyObject* left, PyObject* right, + const char* op, interop::TCppScope_t scope) { + // Find a callable matching the named operator (op) and the (left, right) + // arguments in the global or these objects' namespaces. + + bool reverse = false; + if (!CPPInstance_Check(left)) { + if (CPPInstance_Check(right)) + reverse = true; + else + return nullptr; + } + + // retrieve the class names to match the signature of any found global + // functions + const std::string& lcname = ClassName(left); + const std::string& rcname = ClassName(right); + return FindBinaryOperator(lcname, rcname, op, scope, reverse); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* +cpyrt::Utility::FindBinaryOperator(const std::string& lcname, + const std::string& rcname, const char* op, + interop::TCppScope_t scope, bool reverse) { + // Find a global function with a matching signature; search __gnu_cxx, + // std::__1, and __cppjit_internal pro-actively (as there's AFAICS no way to + // unearth 'using' information). + + if (rcname == "" || lcname == "") + return nullptr; + + PyCallable* pyfunc = 0; + + if (!scope) { + // TODO: the following should remain sync with what clingwrapper does in its + // type remapper; there must be a better way? + if (lcname == "str" || lcname == "unicode" || lcname == "complex" || + lcname.find("std::") == 0) + scope = interop::GetScope("std"); + } + if (scope) + pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); + + // search the global scope before the namespace of the left operand: a user + // operator for these exact types defined at global scope is what C++ overload + // resolution would select over a generic template found through ADL. MSVC's + // std::ostream rvalue-stream-inserter template (ostream&&, const _Ty&) is + // greedy enough to match any (ostream, T) pair, and searching std first made + // it shadow global-scope operator<< overloads (advancedcpp test25). + if (!pyfunc && scope != interop::GetGlobalScope()) + pyfunc = + BuildOperator(lcname, rcname, op, interop::GetGlobalScope(), reverse); + + if (!pyfunc) { + interop::TCppScope_t lcscope = + interop::GetScope(TypeManip::extract_namespace(lcname)); + if (lcscope && lcscope != scope && lcscope != interop::GetGlobalScope()) + pyfunc = BuildOperator(lcname, rcname, op, lcscope, reverse); + } + + if (!pyfunc) { + // For GNU on clang, search the internal __gnu_cxx namespace for binary + // operators (is typically the case for STL iterators operator==/!=. + // TODO: only look in __gnu_cxx for iterators (and more generally: do + // lookups in the + // namespace where the class is defined + static interop::TCppScope_t gnucxx = interop::GetScope("__gnu_cxx"); + if (gnucxx) + pyfunc = BuildOperator(lcname, rcname, op, gnucxx, reverse); + } + + if (!pyfunc) { + // Same for clang (on Mac only?). TODO: find proper pre-processor magic to + // only use those specific namespaces that are actually around; although to + // be sure, this isn't expensive. + static interop::TCppScope_t std__1 = interop::GetFullScope("std::__1"); + + if (std__1 +#ifdef __APPLE__ + && lcname.find("__wrap_iter") == + std::string::npos // wrapper call does not compile +#endif + ) { + pyfunc = BuildOperator(lcname, rcname, op, std__1, reverse); + } + } + + if (!pyfunc) { + // One more, mostly for Mac, but again not sure whether this is not a + // general issue. Some operators are declared as friends only in classes, so + // then they're not found in the global namespace, so this helper let's the + // compiler resolve the operator. + static interop::TCppScope_t s_intern = + interop::GetScope("__cppjit_internal"); + if (s_intern) { + std::stringstream fname, proto; + if (strncmp(op, "==", 2) == 0) { + fname << "is_equal<"; + } else if (strncmp(op, "!=", 2) == 0) { + fname << "is_not_equal<"; + } else { + fname << "not_implemented<"; + } + fname << lcname << ", " << rcname << ">"; + proto << "const " << lcname << "&, const " << rcname; + interop::TCppMethod_t method = + interop::GetMethodTemplate(s_intern, fname.str(), proto.str()); + if (method) + pyfunc = new CPPFunction(s_intern, method); + } + } + + return pyfunc; +} + +//---------------------------------------------------------------------------- +static inline std::string AnnotationAsText(PyObject* pyobj) { + if (!cpyrt_PyText_Check(pyobj)) { + PyObject* pystr = PyObject_GetAttr(pyobj, cpyrt::PyStrings::gName); + if (!pystr) { + PyErr_Clear(); + pystr = PyObject_Str(pyobj); + } + + std::string str = cpyrt_PyText_AsString(pystr); + Py_DECREF(pystr); + return str; + } + return cpyrt_PyText_AsString(pyobj); +} + +static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, + cpyrt::Utility::ArgPreference pref, + int* pcnt = nullptr) { + // Determine the appropriate C++ type for a given Python type; this is a + // helper because it can recurse if the type is list or tuple and needs + // matching on std::vector. + using namespace cppjit::cpyrt; + using namespace cpyrt::Utility; + + if (tn == (PyObject*)&PyInt_Type) { + if (arg) { + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + tmpl_name.append("int"); // still out of range, will fail later + } else + tmpl_name.append( + "unsigned long long"); // since already failed long long + } else + tmpl_name.append( + (ll < INT_MIN || INT_MAX < ll) + ? ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") + : "int"); + } else + tmpl_name.append("int"); + + return true; + } + + if (tn == (PyObject*)&PyFloat_Type) { + // special case for floats (Python-speak for double) if from argument (only) + tmpl_name.append(arg ? "double" : "float"); + return true; + } + + if (tn == (PyObject*)&PyComplex_Type) { + tmpl_name.append("std::complex"); + return true; + } + + if (tn == (PyObject*)&PyUnicode_Type) { + tmpl_name.append("std::string"); + return true; + } + + if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { + if (arg && PySequence_Size(arg)) { + std::string subtype{"std::initializer_list<"}; + PyObject* item = PySequence_GetItem(arg, 0); + ArgPreference subpref = pref == kValue ? kValue : kPointer; + bool ret = AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref); + if (ret) { + tmpl_name.append(subtype); + tmpl_name.append(">"); + } + Py_DECREF(item); + // Error occurred in inner call to AddTypeName, which means it has + // also set a TypeError. We return and let the error propagate. + if (!ret) { + return false; + } + } + + return true; + } + + if (CPPScope_Check(tn)) { + auto cpp_type = interop::GetScopedFinalName(((CPPClass*)tn)->fCppType); + tmpl_name.append(full_scope(cpp_type)); + if (arg) { + // try to specialize the type match for the given object + CPPInstance* pyobj = (CPPInstance*)arg; + if (CPPInstance_Check(pyobj)) { + if (pyobj->fFlags & CPPInstance::kIsRValue) + tmpl_name.append("&&"); + else { + if (pcnt) + *pcnt += 1; + if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) + tmpl_name.push_back('*'); + else if (pref != kValue) + tmpl_name.push_back('&'); + } + } + } + + return true; + } + + if (tn == (PyObject*)&CPPOverload_Type) { + PyObject* tpName = arg ? PyObject_GetAttr(arg, PyStrings::gCppName) + : cpyrt_PyText_FromString("void* (*)(...)"); + tmpl_name.append(cpyrt_PyText_AsString(tpName)); + Py_DECREF(tpName); + + return true; + } + + if (arg && PyCallable_Check(arg)) { + // annotated/typed Python function + PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); + if (annot) { + if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { + PyObject* ret = PyDict_GetItemString(annot, "return"); + if (ret) { + // dict is ordered, with the last value being the return type + std::ostringstream tpn; + tpn << (CPPScope_Check(ret) ? ClassName(ret) : AnnotationAsText(ret)) + << " (*)("; + + PyObject* values = PyDict_Values(annot); + for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values) - 1); ++i) { + if (i) + tpn << ", "; + PyObject* item = PyList_GET_ITEM(values, i); + tpn << (CPPScope_Check(item) ? full_scope(ClassName(item)) + : AnnotationAsText(item)); + } + Py_DECREF(values); + + tpn << ')'; + tmpl_name.append(tpn.str()); + + Py_DECREF(annot); + return true; + + } else + PyErr_Clear(); + } + Py_DECREF(annot); + } else + PyErr_Clear(); + + // ctypes function pointer + PyObject* argtypes = nullptr; + PyObject* ret = nullptr; + if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) && + (ret = PyObject_GetAttrString(arg, "restype"))) { + std::ostringstream tpn; + PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); + tpn << CT2CppNameS(pytc, false) << " (*)("; + Py_DECREF(pytc); + + for (Py_ssize_t i = 0; i < PySequence_Length(argtypes); ++i) { + if (i) + tpn << ", "; + PyObject* item = PySequence_GetItem(argtypes, i); + pytc = PyObject_GetAttr(item, PyStrings::gCTypesType); + tpn << CT2CppNameS(pytc, false); + Py_DECREF(pytc); + Py_DECREF(item); + } + + tpn << ')'; + tmpl_name.append(tpn.str()); + + Py_DECREF(ret); + Py_DECREF(argtypes); + + return true; + + } else { + PyErr_Clear(); + Py_XDECREF(ret); + Py_XDECREF(argtypes); + } + + // callable C++ type (e.g. std::function) + PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); + if (tpName) { + const char* cname = cpyrt_PyText_AsString(tpName); + tmpl_name.append(CPPScope_Check(arg) ? full_scope(cname) : cname); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { + PyObject* tpName = PyObject_GetAttr(tn, nn); + if (tpName) { + tmpl_name.append(cpyrt_PyText_AsString(tpName)); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { + // last ditch attempt, works for things like int values; since this is a + // source of errors otherwise, it is limited to specific types and not + // generally used (str(obj) can print anything ...) + PyObject* pystr = PyObject_Str(tn); + tmpl_name.append(cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + return true; + } + + // Give up with a TypeError + + // Try to get a readable representation of the argument + PyObject* repr = PyObject_Repr(tn); + const char* repr_cstr = repr ? PyUnicode_AsUTF8(repr) : ""; + + PyErr_Format(PyExc_TypeError, + "could not construct C++ name from template argument %s", + repr_cstr); + + Py_XDECREF(repr); + return false; +} + +std::string cpyrt::Utility::ConstructTemplateArgs(PyObject* pyname, + PyObject* tpArgs, + PyObject* args, + ArgPreference pref, + int argoff, int* pcnt) { + // Helper to construct the "" part of a templated name + // (either for a class or method lookup + bool justOne = !PyTuple_CheckExact(tpArgs); + + // Note: directly appending to string is a lot faster than stringstream + std::string tmpl_name; + tmpl_name.reserve(128); + if (pyname) + tmpl_name.append(cpyrt_PyText_AsString(pyname)); + tmpl_name.push_back('<'); + + if (pcnt) + *pcnt = 0; // count number of times 'pref' is used + + Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); + for (int i = argoff; i < nArgs; ++i) { + // add type as string to name + PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); + if (cpyrt_PyText_Check(tn)) { + tmpl_name.append(cpyrt_PyText_AsString(tn)); + // some common numeric types (separated out for performance: checking for + // __cpp_name__ and/or __name__ is rather expensive) + } else { + if (!AddTypeName(tmpl_name, tn, + (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, + pcnt)) { + PyErr_SetString( + PyExc_TypeError, + "could not construct C++ name from provided template argument."); + return ""; + } + } + + // add a comma, as needed (no space as internally, final names don't have + // them) + if (i != nArgs - 1) + tmpl_name.push_back(','); + } + + // close template name + tmpl_name.push_back('>'); + + return tmpl_name; +} + +//---------------------------------------------------------------------------- +static bool AddTypeName(std::vector& types, PyObject* tn, + PyObject* arg, cpyrt::Utility::ArgPreference pref, + int* pcnt = nullptr) { + // Determine the appropriate C++ type for a given Python type; this is a + // helper because it can recurse if the type is list or tuple and needs + // matching on std::vector. + using namespace cppjit::cpyrt; + using namespace cpyrt::Utility; + + if (tn == (PyObject*)&PyInt_Type) { + if (arg) { + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + types.push_back(interop::GetType("int") + .data); // still out of range, will fail later + } else + types.push_back(interop::GetType("unsigned long long") + .data); // since already failed long long + } else + types.push_back( + interop::GetType( + (ll < INT_MIN || INT_MAX < ll) + ? ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") + : "int") + .data); + } else { + types.push_back(interop::GetType("int").data); + } + + return true; + } + + if (tn == (PyObject*)&PyFloat_Type) { + // special case for floats (Python-speak for double) if from argument (only) + types.push_back(interop::GetType(arg ? "double" : "float").data); + return true; + } + + if (tn == (PyObject*)&PyComplex_Type) { + types.push_back( + interop::GetType("std::complex", /* enable_slow_lookup */ true) + .data); + return true; + } + + if (tn == (PyObject*)&PyUnicode_Type) { + types.push_back( + interop::GetType("std::string", /* enable_slow_lookup */ true).data); + return true; + } + + if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { + if (arg && PySequence_Size(arg)) { + std::string subtype{"std::initializer_list<"}; + PyObject* item = PySequence_GetItem(arg, 0); + ArgPreference subpref = pref == kValue ? kValue : kPointer; + if (AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref)) { + subtype.append(">"); + types.push_back(interop::GetType(subtype).data); + } + Py_DECREF(item); + } + + return true; + } + + if (CPPScope_Check(tn)) { + auto cpp_type = interop::GetTypeFromScope(((CPPClass*)tn)->fCppType); + if (arg) { + // try to specialize the type match for the given object + CPPInstance* pyobj = (CPPInstance*)arg; + if (CPPInstance_Check(pyobj)) { + if (pyobj->fFlags & CPPInstance::kIsRValue) + cpp_type = interop::GetReferencedType(cpp_type, /*rvalue=*/true); + else { + if (pcnt) + *pcnt += 1; + if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) + cpp_type = interop::GetPointerType(cpp_type); + else if (pref != kValue) + cpp_type = interop::GetReferencedType(cpp_type, /*rvalue=*/false); + } + } + } + types.push_back(cpp_type.data); + return true; + } + + if (tn == (PyObject*)&CPPOverload_Type) { + PyObject* tpName = arg ? PyObject_GetAttr(arg, PyStrings::gCppName) + : cpyrt_PyText_FromString("void* (*)(...)"); + types.push_back(interop::GetType(cpyrt_PyText_AsString(tpName), + /* enable_slow_lookup */ true) + .data); + Py_DECREF(tpName); + + return true; + } + + if (arg && PyCallable_Check(arg)) { + PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); + if (annot) { + if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { + PyObject* ret = PyDict_GetItemString(annot, "return"); + if (ret) { + // dict is ordered, with the last value being the return type + std::ostringstream tpn; + tpn << (CPPScope_Check(ret) ? ClassName(ret) + : cpyrt_PyText_AsString(ret)) + << " (*)("; + + PyObject* values = PyDict_Values(annot); + for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values) - 1); ++i) { + if (i) + tpn << ", "; + PyObject* item = PyList_GET_ITEM(values, i); + tpn << (CPPScope_Check(item) ? ClassName(item) + : cpyrt_PyText_AsString(item)); + } + Py_DECREF(values); + + tpn << ')'; + // tmpl_name.append(tpn.str()); + // FIXME: find a way to add it to types + throw std::runtime_error( + "This path is not yet implemented (AddTypeName) \n"); + + return true; + + } else + PyErr_Clear(); + } + Py_DECREF(annot); + } else + PyErr_Clear(); + + PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); + if (tpName) { + types.push_back(interop::GetType(cpyrt_PyText_AsString(tpName), + /* enable_slow_lookup */ true) + .data); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { + PyObject* tpName = PyObject_GetAttr(tn, nn); + if (tpName) { + interop::TCppType_t type = interop::GetType( + cpyrt_PyText_AsString(tpName), /* enable_slow_lookup */ true); + if (!type) { + // any Python object has a __name__; one that does not name a C++ + // type contributes no argument + Py_DECREF(tpName); + continue; + } + if (interop::IsEnumType(type)) { + PyObject* value_int = PyNumber_Index(tn); + if (!value_int) { + types.push_back(type.data); + PyErr_Clear(); + } else { + PyObject* pystr = PyObject_Str(tn); + std::string num = cpyrt_PyText_AsString(pystr); + types.push_back({type.data, strdup(num.c_str())}); + Py_DECREF(pystr); + Py_DECREF(value_int); + } + } else { + types.push_back(type.data); + } + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { + // last ditch attempt, works for things like int values; since this is a + // source of errors otherwise, it is limited to specific types and not + // generally used (str(obj) can print anything ...) + PyObject* pystr = PyObject_Str(tn); + std::string num = cpyrt_PyText_AsString(pystr); + if (num == "True") + num = "1"; + else if (num == "False") + num = "0"; + types.push_back({interop::GetType("int").data, strdup(num.c_str())}); + Py_DECREF(pystr); + return true; + } + + return false; +} + +std::vector +cpyrt::Utility::GetTemplateArgsTypes(PyObject* /*scope*/, PyObject* tpArgs, + PyObject* args, ArgPreference pref, + int argoff, int* pcnt) { + // Helper to construct the "" part of a templated name + // (either for a class or method lookup + bool justOne = !PyTuple_CheckExact(tpArgs); + + // Note: directly appending to string is a lot faster than stringstream + std::vector types; + types.reserve(8); + + if (pcnt) + *pcnt = 0; // count number of times 'pref' is used + + Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); + for (int i = argoff; i < nArgs; ++i) { + // add type as string to name + PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); + if (cpyrt_PyText_Check(tn)) { + const char* tn_string = cpyrt_PyText_AsString(tn); + + if (interop::AppendTypesSlow(tn_string, types)) { + PyErr_Format(PyExc_TypeError, "Cannot find Templated Arg: %s", + tn_string); + return {}; + } + + // some commmon numeric types (separated out for performance: checking for + // __cpp_name__ and/or __name__ is rather expensive) + } else { + if (!AddTypeName(types, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), + pref, pcnt)) { + PyErr_SetString( + PyExc_TypeError, + "could not construct C++ name from provided template argument."); + return {}; + } + } + } + + return types; +} + +std::string cpyrt::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp) { + // helper to convert ctypes' `_type_` info to the equivalent C++ name + const char* name = ""; + if (cpyrt_PyText_Check(pytc)) { + char tc = ((char*)cpyrt_PyText_AsString(pytc))[0]; + switch (tc) { + case '?': + name = "bool"; + break; + case 'c': + name = "char"; + break; + case 'b': + name = "char"; + break; + case 'B': + name = "unsigned char"; + break; + case 'h': + name = "short"; + break; + case 'H': + name = "unsigned short"; + break; + case 'i': + name = "int"; + break; + case 'I': + name = "unsigned int"; + break; + case 'l': + name = "long"; + break; + case 'L': + name = "unsigned long"; + break; + case 'q': + name = "long long"; + break; + case 'Q': + name = "unsigned long long"; + break; + case 'f': + name = "float"; + break; + case 'd': + name = "double"; + break; + case 'g': + name = "long double"; + break; + case 'z': + name = "const char*"; + break; + default: + if (allow_voidp) + name = "void*"; + break; + } + } + + return name; +} + +//---------------------------------------------------------------------------- +static inline bool check_scope(const std::string& name) { + return (bool)interop::GetScope(cpyrt::TypeManip::clean_type(name, false)); +} + +void cpyrt::Utility::ConstructCallbackPreamble( + const std::string& retType, const std::vector& argtypes, + std::ostringstream& code) { + // Generate function setup to be used in callbacks (wrappers and overrides). + int nArgs = (int)argtypes.size(); + + // return value and argument type converters + bool isVoid = retType.find("void") == 0; // might contain trailing space + if (!isVoid) + code << " CPYRT_STATIC std::unique_ptr> " + "retconv{cppjit::cpyrt::CreateConverter(\"" + << retType << "\"), cppjit::cpyrt::DestroyConverter};\n"; + std::vector arg_is_ptr; + if (nArgs) { + arg_is_ptr.resize(nArgs); + code << " CPYRT_STATIC " + "std::vector>> argcvs;\n" + << " if (argcvs.empty()) {\n" + << " argcvs.reserve(" << nArgs << ");\n"; + for (int i = 0; i < nArgs; ++i) { + arg_is_ptr[i] = false; + code << " argcvs.emplace_back(cppjit::cpyrt::CreateConverter(\""; + const std::string& at = argtypes[i]; + const std::string& res_at = interop::ResolveName(at); + const std::string& cpd = TypeManip::compound(res_at); + if (!cpd.empty() && check_scope(res_at)) { + // in case of a pointer, the original argument needs to be used to + // ensure the pointer-value remains comparable + // + // in case of a reference, there is no extra indirection on the C++ side + // as would be when converting a data member, so adjust the converter + arg_is_ptr[i] = cpd.back() == '*'; + if (arg_is_ptr[i] || cpd.back() == '&') { + code << res_at.substr(0, res_at.size() - 1); + } else + code << at; + } else + code << at; + code << "\"), cppjit::cpyrt::DestroyConverter);\n"; + } + code << " }\n"; + } + + // declare return value (TODO: this does not work for most non-builtin values) + if (!isVoid) + code << " " << retType << " ret{};\n"; + + // build argument tuple if needed + if (nArgs) { + code << " std::vector pyargs;\n"; + code << " pyargs.reserve(" << nArgs << ");\n" + << " try {\n"; + for (int i = 0; i < nArgs; ++i) { + code << " pyargs.emplace_back(argcvs[" << i + << "]->FromMemory((void*)"; + if (!arg_is_ptr[i]) + code << '&'; + code << "arg" << i << "));\n" + << " if (!pyargs.back()) throw " << i << ";\n"; + } + code << " } catch(int) {\n" + << " for (auto pyarg : pyargs) Py_XDECREF(pyarg);\n" + << " cppjit::cpyrt::PyException pyexc; throw pyexc;\n" + << " }\n"; + } +} + +void cpyrt::Utility::ConstructCallbackReturn(const std::string& retType, + int nArgs, + std::ostringstream& code) { + // Generate code for return value conversion and error handling. + bool isVoid = retType.find("void") == 0; // might contain trailing space + bool isPtr = interop::ResolveName(retType).back() == '*'; + + if (nArgs) + code << " for (auto pyarg : pyargs) Py_DECREF(pyarg);\n"; + code << " bool cOk = (bool)pyresult;\n" + " if (pyresult) {\n"; + if (isPtr) { + // If the return type is a CPPInstance, owned by Python, and the ref-count + // down to 1, the return will hold a dangling pointer, so set it to nullptr + // instead. + code << " if (!cppjit::cpyrt::Instance_IsLively(pyresult))\n" + " ret = nullptr;\n" + " else {\n"; + } + code << (isVoid ? "" + : " cOk = retconv->ToMemory(pyresult, (void*)&ret);\n") + << " Py_DECREF(pyresult);\n }\n"; + if (isPtr) + code << " }\n"; + code << " if (!cOk) {" // assume error set when converter failed +// TODO: On Windows, throwing a C++ exception here makes the code hang; leave +// the error be which allows at least one layer of propagation +#ifdef _WIN32 + " /* do nothing */ }\n" +#else + " cppjit::cpyrt::PyException pyexc; throw pyexc; }\n" +#endif + " return"; + code << (isVoid ? ";\n }\n" : " ret;\n }\n"); +} + +//---------------------------------------------------------------------------- +static std::unordered_map sStdFuncLookup; +static std::unordered_map sStdFuncMakerLookup; +PyObject* cpyrt::Utility::FuncPtr2StdFunction(const std::string& retType, + const std::string& signature, + void* address) { + // Convert a function pointer to an equivalent std::function<> object. + static int maker_count = 0; + + auto pf = sStdFuncLookup.find(address); + if (pf != sStdFuncLookup.end()) { + Py_INCREF(pf->second); + return pf->second; + } + + PyObject* maker = nullptr; + + auto pm = sStdFuncMakerLookup.find(retType + signature); + if (pm == sStdFuncMakerLookup.end()) { + std::ostringstream fname; + fname << "ptr2func" << ++maker_count; + + std::ostringstream code; + code << "namespace __cppjit_internal { std::function<" << retType + << signature << "> " << fname.str() << "(intptr_t faddr) { return (" + << retType << "(*)" << signature << ")faddr;} }"; + + if (!interop::Compile(code.str())) { + PyErr_SetString(PyExc_TypeError, "conversion to std::function failed"); + return nullptr; + } + + PyObject* pyscope = CreateScopeProxy("__cppjit_internal"); + maker = PyObject_GetAttrString(pyscope, fname.str().c_str()); + Py_DECREF(pyscope); + if (!maker) + return nullptr; + + // cache the new maker (TODO: does it make sense to use weakrefs?) + sStdFuncMakerLookup[retType + signature] = maker; + } else + maker = pm->second; + + PyObject* args = PyTuple_New(1); + PyTuple_SET_ITEM(args, 0, PyLong_FromLongLong((intptr_t)address)); + PyObject* func = PyObject_Call(maker, args, NULL); + Py_DECREF(args); + + if (func) { // prevent moving this func object, since then it can not be + // reused + ((CPPInstance*)func)->fFlags |= CPPInstance::kIsLValue; + Py_INCREF(func); // TODO: use weak? The C++ maker doesn't go away either + sStdFuncLookup[address] = func; + } + + return func; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::InitProxy(PyObject* module, PyTypeObject* pytype, + const char* name) { + // Initialize a proxy class for use by python, and add it to the module. + + // finalize proxy type + if (PyType_Ready(pytype) < 0) + return false; + + // add proxy type to the given module + Py_INCREF(pytype); // PyModule_AddObject steals reference + if (PyModule_AddObject(module, (char*)name, (PyObject*)pytype) < 0) { + Py_DECREF(pytype); + return false; + } + + // declare success + return true; +} + +//---------------------------------------------------------------------------- +std::unordered_map const& cpyrt::Utility::TypecodeMap() { + // See https://docs.python.org/3/library/array.html#array.array + static std::unordered_map typecodeMap{ + {"char", 'b'}, {"unsigned char", 'B'}, +#if PY_VERSION_HEX >= 0x030d0000 + {"Py_UCS4", 'w'}, +#endif + {"short", 'h'}, {"unsigned short", 'H'}, + {"int", 'i'}, {"unsigned int", 'I'}, + {"long", 'l'}, {"unsigned long", 'L'}, + {"long long", 'q'}, {"unsigned long long", 'Q'}, + {"float", 'f'}, {"double", 'd'}}; + return typecodeMap; +} + +//---------------------------------------------------------------------------- +Py_ssize_t cpyrt::Utility::GetBuffer(PyObject* pyobject, char tc, int size, + void*& buf, bool check) { + // Retrieve a linear buffer pointer from the given pyobject. + + // special case: don't handle character strings here (yes, they're buffers, + // but not quite) + if (PyBytes_Check(pyobject) || PyUnicode_Check(pyobject)) + return 0; + + // special case: bytes array + if ((!check || tc == '*' || tc == 'B') && PyByteArray_CheckExact(pyobject)) { + buf = PyByteArray_AS_STRING(pyobject); + return PyByteArray_GET_SIZE(pyobject); + } + + // new-style buffer interface + if (PyObject_CheckBuffer(pyobject)) { + if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) + return 0; // PyObject_GetBuffer() crashes on some platforms for some + // zero-sized seqeunces + if (PyErr_Occurred()) { + // PySequence_Size errored with + // TypeError: object of type 'LP_c_type' has no len() + PyErr_Clear(); + } + + Py_buffer bufinfo; + memset(&bufinfo, 0, sizeof(Py_buffer)); + if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { + if (tc == '*' || + strchr(bufinfo.format, tc) + // if `long int` and `int` are the same size (on Windows and 32bit + // Linux, for example), `ctypes` isn't too picky about the type + // format, so make sure both integer types pass the type check + || (sizeof(long int) == sizeof(int) && + ((tc == 'I' && strchr(bufinfo.format, 'L')) || + (tc == 'i' && strchr(bufinfo.format, 'l')))) + // complex float is 'Zf' in bufinfo.format, but 'z' in single char + || (tc == 'z' && strstr(bufinfo.format, "Zf")) + // allow 'signed char' ('b') from array to pass through '?' (bool as + // from struct) + || (tc == '?' && strchr(bufinfo.format, 'b'))) { + buf = bufinfo.buf; + + if (check && bufinfo.itemsize != size) { + PyErr_Format( + PyExc_TypeError, + "buffer itemsize (%ld) does not match expected size (%d)", + bufinfo.itemsize, size); + PyBuffer_Release(&bufinfo); + return 0; + } + + Py_ssize_t buflen = 0; + if (buf && bufinfo.ndim == 0) + buflen = bufinfo.len / bufinfo.itemsize; + else if (buf && bufinfo.ndim == 1) + buflen = + bufinfo.shape ? bufinfo.shape[0] : bufinfo.len / bufinfo.itemsize; + PyBuffer_Release(&bufinfo); + if (buflen) + return buflen; + } else { + // have buf, but format mismatch: bail out now, otherwise the old + // code will return based on itemsize match + PyBuffer_Release(&bufinfo); + return 0; + } + } else if (bufinfo.obj) + PyBuffer_Release(&bufinfo); + PyErr_Clear(); + } + + // attempt to retrieve pointer through old-style buffer interface + PyBufferProcs* bufprocs = Py_TYPE(pyobject)->tp_as_buffer; + + PySequenceMethods* seqmeths = Py_TYPE(pyobject)->tp_as_sequence; + if (seqmeths != 0 && bufprocs != 0 && bufprocs->bf_getbuffer != 0) { + + // get the buffer + Py_buffer bufinfo; + (*(bufprocs->bf_getbuffer))(pyobject, &bufinfo, PyBUF_WRITABLE); + buf = (char*)bufinfo.buf; + Py_ssize_t buflen = bufinfo.len; + PyBuffer_Release(&bufinfo); + + if (buf && check == true) { + // determine buffer compatibility (use "buf" as a status flag) + PyObject* pytc = tc != '*' + ? PyObject_GetAttr(pyobject, PyStrings::gTypeCode) + : nullptr; + if (pytc != 0) { // for array objects + char cpytc = cpyrt_PyText_AsString(pytc)[0]; + if (!(cpytc == tc || (tc == '?' && cpytc == 'b'))) + buf = 0; // no match + Py_DECREF(pytc); + } else if (seqmeths->sq_length && + (int)(buflen / (*(seqmeths->sq_length))(pyobject)) == size) { + // this is a gamble ... may or may not be ok, but that's for the user + PyErr_Clear(); + } else if (buflen == size) { + // also a gamble, but at least 1 item will fit into the buffer, so very + // likely ok ... + PyErr_Clear(); + } else { + buf = 0; // not compatible + + // clarify error message + auto error = FetchPyError(); + PyObject* pyvalue2 = cpyrt_PyText_FromFormat( + (char*)"%s and given element size (%ld) do not match needed (%d)", + cpyrt_PyText_AsString(error.fValue.get()), + seqmeths->sq_length + ? (long)(buflen / (*(seqmeths->sq_length))(pyobject)) + : (long)buflen, + size); + error.fValue.reset(pyvalue2); + RestorePyError(error); + } + } + + if (!buf) + return 0; + return buflen / (size ? size : 1); + } + + return 0; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::Utility::MapOperatorName(const std::string& name, + bool bTakesParams, bool* stubbed) { + // Map the given C++ operator name on the python equivalent. + if (8 < name.size() && name.substr(0, 8) == "operator") { + std::string op = name.substr(8, std::string::npos); + + // stripping ... + std::string::size_type start = 0, end = op.size(); + while (start < end && isspace(op[start])) + ++start; + while (start < end && isspace(op[end - 1])) + --end; + op = op.substr(start, end - start); + + // certain operators should be removed completely (e.g. operator delete & + // friends) + if (gOpRemove.find(op) != gOpRemove.end()) + return ""; + + TC2POperatorMapping_t::iterator pop = gC2POperatorMapping.find(op); + // map C++ operator to python equivalent, or made up name if no equivalent + // exists + if (pop != gC2POperatorMapping.end()) { + return pop->second; + + } else if (op == "*") { + // dereference v.s. multiplication of two instances + if (!bTakesParams) + return "__deref__"; + if (stubbed) + *stubbed = true; + return "__mul__"; + + } else if (op == "/") { + // no unary, but is stubbed + return CPPJIT__div__; + + } else if (op == "+") { + // unary positive v.s. addition of two instances + if (!bTakesParams) + return "__pos__"; + if (stubbed) + *stubbed = true; + return "__add__"; + + } else if (op == "-") { + // unary negative v.s. subtraction of two instances + if (!bTakesParams) + return "__neg__"; + if (stubbed) + *stubbed = true; + return "__sub__"; + + } else if (op == "++") { + // prefix v.s. postfix increment + return bTakesParams ? "__postinc__" : "__preinc__"; + + } else if (op == "--") { + // prefix v.s. postfix decrement + return bTakesParams ? "__postdec__" : "__predec__"; + } + } + + // might get here, as not all operator methods are handled (new, delete, etc.) + return name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::Utility::ClassName(PyObject* pyobj) { + // Retrieve the class name from the given Python instance. + std::string clname = ""; + PyObject* pyclass = (PyObject*)Py_TYPE(pyobj); + PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gCppName); + if (!pyname) { + PyErr_Clear(); + pyname = PyObject_GetAttr(pyclass, PyStrings::gName); + } + + if (pyname) { + clname = cpyrt_PyText_AsString(pyname); + Py_DECREF(pyname); + } else + PyErr_Clear(); + return clname; +} + +//---------------------------------------------------------------------------- +static std::set sIteratorTypes; +bool cpyrt::Utility::IsSTLIterator(const std::string& classname) { + // attempt to recognize STL iterators (TODO: probably belongs in the backend), + // using a couple of common container classes with different iterator + // protocols (note that mapping iterators are handled separately in the + // pythonizations) as exemplars (the actual, resolved, names will be + // compiler-specific) that are picked b/c they are baked into the CoreLegacy + // dictionary + if (sIteratorTypes.empty()) { + std::string tt = "::"; + for (auto c : {"std::vector", "std::list", "std::deque"}) { + for (auto i : {"iterator", "const_iterator"}) { + const std::string& itname = interop::ResolveName(c + tt + i); + auto pos = itname.find('<'); + if (pos != std::string::npos) + sIteratorTypes.insert(itname.substr(0, pos)); + } + } + } + + auto pos = classname.find('<'); + if (pos != std::string::npos) + return sIteratorTypes.find(classname.substr(0, pos)) != + sIteratorTypes.end(); + return false; +} + +//---------------------------------------------------------------------------- +cpyrt::Utility::PyOperators::~PyOperators() { + Py_XDECREF(fEq); + Py_XDECREF(fNe); + Py_XDECREF(fLAdd); + Py_XDECREF(fRAdd); + Py_XDECREF(fSub); + Py_XDECREF(fLMul); + Py_XDECREF(fRMul); + Py_XDECREF(fDiv); + Py_XDECREF(fHash); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Utility::PyErr_Occurred_WithGIL() { + // Re-acquire the GIL before calling PyErr_Occurred() in case it has been + // released; note that the p2.2 code assumes that there are no callbacks in + // C++ to python (or at least none returning errors). + PythonGILRAII python_gil_raii; + PyObject* e = PyErr_Occurred(); + + return e; +} + +//---------------------------------------------------------------------------- +cpyrt::Utility::PyError_t cpyrt::Utility::FetchPyError() { + // create a PyError_t RAII object that will capture and store the exception + // data + cpyrt::Utility::PyError_t error{}; +#if PY_VERSION_HEX >= 0x030c0000 + error.fValue.reset(PyErr_GetRaisedException()); +#else + PyObject* pytype = nullptr; + PyObject* pyvalue = nullptr; + PyObject* pytrace = nullptr; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + error.fType.reset(pytype); + error.fValue.reset(pyvalue); + error.fTrace.reset(pytrace); +#endif + return error; +} + +//---------------------------------------------------------------------------- +void cpyrt::Utility::RestorePyError(cpyrt::Utility::PyError_t& error) { +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(error.fValue.release()); +#else + PyErr_Restore(error.fType.release(), error.fValue.release(), + error.fTrace.release()); +#endif +} + +//---------------------------------------------------------------------------- +size_t cpyrt::Utility::FetchError(std::vector& errors, bool is_cpp) { + // Fetch the current python error, if any, and store it for future use. + if (PyErr_Occurred()) { + errors.emplace_back(FetchPyError()); + errors.back().fIsCpp = is_cpp; + } + return errors.size(); +} + +//---------------------------------------------------------------------------- +void cpyrt::Utility::SetDetailedException(std::vector&& errors, + PyObject* topmsg, PyObject* defexc) { + // Use the collected exceptions to build up a detailed error log. + if (errors.empty()) { + // should not happen ... + PyErr_SetString(defexc, cpyrt_PyText_AsString(topmsg)); + Py_DECREF(topmsg); + return; + } + + // if a _single_ exception was thrown from C++, assume it has priority (see + // below) + PyError_t* unique_from_cpp = nullptr; + for (auto& e : errors) { + if (e.fIsCpp) { + if (!unique_from_cpp) + unique_from_cpp = &e; + else { + // two C++ exceptions, resort to default behavior + unique_from_cpp = nullptr; + break; + } + } + } + + if (unique_from_cpp) { + // report only this error; the idea here is that all other errors come from + // the bindings (e.g. argument conversion errors), while the exception from + // C++ means that it originated from an otherwise successful call + + // bind the original C++ object, rather than constructing from topmsg, as it + // is expected to have informative state + RestorePyError(*unique_from_cpp); + } else { + // try to consolidate Python exceptions, otherwise select default + PyObject* exc_type = nullptr; + for (auto& e : errors) { +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); +#else + PyObject* pytype = e.fType.get(); +#endif + if (!exc_type) + exc_type = pytype; + else if (exc_type != pytype) { + exc_type = defexc; + break; + } + } + + // add the details to the topmsg + PyObject* separator = cpyrt_PyText_FromString("\n "); + for (auto& e : errors) { + PyObject* pyvalue = e.fValue.get(); + cpyrt_PyText_Append(&topmsg, separator); + if (cpyrt_PyText_Check(pyvalue)) { + cpyrt_PyText_Append(&topmsg, pyvalue); + } else if (pyvalue) { + PyObject* excstr = PyObject_Str(pyvalue); + if (!excstr) { + PyErr_Clear(); + excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); + } + cpyrt_PyText_AppendAndDel(&topmsg, excstr); + } else { + cpyrt_PyText_AppendAndDel(&topmsg, + cpyrt_PyText_FromString("unknown exception")); + } + } + + Py_DECREF(separator); + + // set the python exception + PyErr_SetString(exc_type, cpyrt_PyText_AsString(topmsg)); + } + + Py_DECREF(topmsg); +} + +//---------------------------------------------------------------------------- +static bool includesDone = false; +bool cpyrt::Utility::IncludePython() { + // setup Python API for callbacks + if (!includesDone) { + bool okay = interop::Compile( + // basic API (converters etc.) + "#include \"cpyrt/API.h\"\n" + + // utilities from the cpyrt public API + "#include \"cpyrt/DispatchPtr.h\"\n" + "#include \"cpyrt/PyException.h\"\n"); + includesDone = okay; + } + + return includesDone; +} diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.h b/bindings/pyroot/cppjit/src/cpyrt/Utility.h new file mode 100644 index 0000000000000..acd6936ce7880 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.h @@ -0,0 +1,139 @@ +#ifndef CPYRT_UTILITY_H +#define CPYRT_UTILITY_H + +// Standard +#include +#include +#include +#include + +#include "Python.h" +#include "cppjit_interop.h" + +namespace cppjit::cpyrt { + +class PyCallable; + +// additional converter functions +unsigned long PyLongOrInt_AsULong(PyObject* pyobject); +PY_ULONG_LONG PyLongOrInt_AsULong64(PyObject* pyobject); + +namespace Utility { + +// convenience functions for adding methods to classes +bool AddToClass(PyObject* pyclass, const char* label, PyCFunction cfunc, + int flags = METH_VARARGS); +bool AddToClass(PyObject* pyclass, const char* label, const char* func); +bool AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc); + +// helpers for dynamically constructing operators +PyCallable* FindUnaryOperator(PyObject* pyclass, const char* op); +PyCallable* +FindBinaryOperator(PyObject* left, PyObject* right, const char* op, + interop::TCppScope_t scope = interop::TCppScope_t{}); +PyCallable* FindBinaryOperator( + const std::string& lcname, const std::string& rcname, const char* op, + interop::TCppScope_t scope = interop::TCppScope_t{}, bool reverse = false); + +// helper for template classes and methods +enum ArgPreference { kNone, kPointer, kReference, kValue }; +std::string ConstructTemplateArgs(PyObject* pyname, PyObject* tpArgs, + PyObject* args = nullptr, + ArgPreference = kNone, int argoff = 0, + int* pcnt = nullptr); +std::vector +GetTemplateArgsTypes(PyObject* scope, PyObject* tpArgs, + PyObject* args = nullptr, ArgPreference = kNone, + int argoff = 0, int* pcnt = nullptr); + +std::string CT2CppNameS(PyObject* pytc, bool allow_voidp); +inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp) { + const std::string& name = CT2CppNameS(pytc, allow_voidp); + if (!name.empty()) { + if (name == "const char*") + cpd = ""; + return PyUnicode_FromString((std::string{name} + cpd).c_str()); + } + return nullptr; +} + +// helper for generating callbacks +void ConstructCallbackPreamble(const std::string& retType, + const std::vector& argtypes, + std::ostringstream& code); +void ConstructCallbackReturn(const std::string& retType, int nArgs, + std::ostringstream& code); + +// helper for function pointer conversions +PyObject* FuncPtr2StdFunction(const std::string& retType, + const std::string& signature, void* address); + +// initialize proxy type objects +bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); + +std::unordered_map const& TypecodeMap(); + +// retrieve the memory buffer from pyobject, return buflength, tc (optional) is +// python array.array type code, size is type size, buf will point to buffer, +// and if check is true, some heuristics will be applied to check buffer +// compatibility with the type +Py_ssize_t GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, + bool check = true); + +// data/operator mappings +std::string MapOperatorName(const std::string& name, bool bTakesParames, + bool* stubbed = nullptr); + +struct PyOperators { + PyOperators() + : fEq(nullptr), fNe(nullptr), fLt(nullptr), fLe(nullptr), fGt(nullptr), + fGe(nullptr), fLAdd(nullptr), fRAdd(nullptr), fSub(nullptr), + fLMul(nullptr), fRMul(nullptr), fDiv(nullptr), fHash(nullptr) {} + ~PyOperators(); + + PyObject* fEq; + PyObject* fNe; + PyObject *fLt, *fLe; + PyObject *fGt, *fGe; + PyObject *fLAdd, *fRAdd; + PyObject* fSub; + PyObject *fLMul, *fRMul; + PyObject* fDiv; + PyObject* fHash; +}; + +// meta information +std::string ClassName(PyObject* pyobj); +bool IsSTLIterator(const std::string& classname); + +// for threading: save call to PyErr_Occurred() +PyObject* PyErr_Occurred_WithGIL(); + +// helpers for collecting/maintaining python exception data +struct PyError_t { + struct PyObjectDeleter { + void operator()(PyObject* obj) { Py_XDECREF(obj); } + }; +#if PY_VERSION_HEX < 0x030c0000 + std::unique_ptr fType; + std::unique_ptr fTrace; +#endif + std::unique_ptr fValue; + bool fIsCpp = false; +}; + +PyError_t FetchPyError(); +void RestorePyError(PyError_t& error); + +size_t FetchError(std::vector&, bool is_cpp = false); +void SetDetailedException(std::vector&& errors /* clears */, + PyObject* topmsg /* steals ref */, PyObject* defexc); + +// setup Python API for callbacks +bool IncludePython(); + +} // namespace Utility + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_UTILITY_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/cpyrt.h b/bindings/pyroot/cppjit/src/cpyrt/cpyrt.h new file mode 100644 index 0000000000000..0e15b3e8de618 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrt.h @@ -0,0 +1,221 @@ +#ifndef CPYRT_CPYRT_H +#define CPYRT_CPYRT_H + +#ifdef _WIN32 +// Disable warning C4275: non dll-interface class +#pragma warning(disable : 4275) +// Disable warning C4251: needs to have dll-interface to be used by clients +#pragma warning(disable : 4251) +// Disable warning C4800: 'int' : forcing value to bool +#pragma warning(disable : 4800) +// Avoid that pyconfig.h decides using a #pragma what library python library to +// use +// #define MS_NO_COREDLL 1 +#endif + +// to prevent problems with fpos_t and redefinition warnings +#if defined(linux) + +#include + +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif + +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif + +#endif // linux + +#define PY_SSIZE_T_CLEAN +#include "Python.h" +#include + +namespace cppjit::cpyrt { +typedef Py_ssize_t dim_t; +} // namespace cppjit::cpyrt + +typedef Py_ssize_t (*dict_lookup_func)(PyDictObject*, PyObject*, Py_hash_t, + PyObject**); + +// for 3.0 support (backwards compatibility, really) +#define cpyrt_PyText_Check PyUnicode_Check +#define cpyrt_PyText_CheckExact PyUnicode_CheckExact +#define cpyrt_PyText_AsString PyUnicode_AsUTF8 +#define cpyrt_PyText_AsStringChecked PyUnicode_AsUTF8 +#define cpyrt_PyText_GetSize PyUnicode_GetSize +#define cpyrt_PyText_GET_SIZE PyUnicode_GET_LENGTH +#define cpyrt_PyUnicode_GET_SIZE PyUnicode_GET_LENGTH +#define cpyrt_PyText_FromFormat PyUnicode_FromFormat +#define cpyrt_PyText_FromString PyUnicode_FromString +#define cpyrt_PyText_InternFromString PyUnicode_InternFromString +#define cpyrt_PyText_Append PyUnicode_Append +#define cpyrt_PyText_AppendAndDel PyUnicode_AppendAndDel +#define cpyrt_PyText_FromStringAndSize PyUnicode_FromStringAndSize + +#define _cpyrt_PyText_AsStringAndSize PyUnicode_AsUTF8AndSize + +static inline const char* cpyrt_PyText_AsStringAndSize(PyObject* pystr, + Py_ssize_t* size) { + const char* cstr = _cpyrt_PyText_AsStringAndSize(pystr, size); + if (!cstr && PyBytes_CheckExact(pystr)) { + PyErr_Clear(); + PyBytes_AsStringAndSize(pystr, (char**)&cstr, size); + } + return cstr; +} + +#define cpyrt_PyText_Type PyUnicode_Type + +#define PyIntObject PyLongObject +#define PyInt_Check PyLong_Check +#define PyInt_AsLong PyLong_AsLong +#define PyInt_AS_LONG PyLong_AsLong +#define PyInt_AsSsize_t PyLong_AsSsize_t +#define PyInt_CheckExact PyLong_CheckExact +#define PyInt_FromLong PyLong_FromLong +#define PyInt_FromSsize_t PyLong_FromSsize_t + +#define PyInt_Type PyLong_Type + +#define cpyrt_PyCapsule_New PyCapsule_New +#define cpyrt_PyCapsule_CheckExact PyCapsule_CheckExact +#define cpyrt_PyCapsule_GetPointer PyCapsule_GetPointer + +#define CPPJIT__long__ "__int__" +#define CPPJIT__idiv__ "__itruediv__" +#define CPPJIT__div__ "__truediv__" +#define CPPJIT__next__ "__next__" + +#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 +#define Py_TPFLAGS_CHECKTYPES 0 + +#define PyClass_Check PyType_Check + +#define PyBuffer_Type PyMemoryView_Type + +#define cpyrt_PySliceCast PyObject* +#define PyUnicode_GetSize PyUnicode_GetLength + +#define cpyrt_PyUnicode_AsWideChar PyUnicode_AsWideChar + +#ifdef R__MACOSX +#if SIZEOF_SIZE_T == SIZEOF_INT +#if defined(MAC_OS_X_VERSION_10_4) +#define PY_SSIZE_T_FORMAT "%ld" +#else +#define PY_SSIZE_T_FORMAT "%d" +#endif +#elif SIZEOF_SIZE_T == SIZEOF_LONG +#define PY_SSIZE_T_FORMAT "%ld" +#endif +#else +#define PY_SSIZE_T_FORMAT "%zd" +#endif + +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +// vector call support +#define cpyrt_PyCFunction_Call PyObject_Call + +// vector call support +typedef PyObject* const* cpyrt_PyArgs_t; +static inline PyObject* cpyrt_PyArgs_GET_ITEM(cpyrt_PyArgs_t args, + Py_ssize_t i) { + return args[i]; +} +static inline PyObject* cpyrt_PyArgs_SET_ITEM(cpyrt_PyArgs_t args, Py_ssize_t i, + PyObject* item) { + return ((PyObject**)args)[i] = item; +} +static inline Py_ssize_t cpyrt_PyArgs_GET_SIZE(cpyrt_PyArgs_t, size_t nargsf) { + return PyVectorcall_NARGS(nargsf); +} +static inline cpyrt_PyArgs_t cpyrt_PyArgs_New(Py_ssize_t N) { + return (cpyrt_PyArgs_t)PyMem_Malloc(N * sizeof(PyObject*)); +} +static inline void cpyrt_PyArgs_DEL(cpyrt_PyArgs_t args) { + PyMem_Free((void*)args); +} +#define cpyrt_PyObject_Call PyObject_Vectorcall +inline PyObject* cpyrt_tp_call(PyObject* cb, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds) { + Py_ssize_t offset = Py_TYPE(cb)->tp_vectorcall_offset; + vectorcallfunc func = *(vectorcallfunc*)(((char*)cb) + offset); + return func(cb, args, nargsf, kwds); +} + +#ifndef Py_TPFLAGS_HAVE_VECTORCALL +#define Py_TPFLAGS_HAVE_VECTORCALL _Py_TPFLAGS_HAVE_VECTORCALL +#endif + +// weakref forced strong reference +#if PY_VERSION_HEX < 0x30d0000 +static inline PyObject* cpyrt_GetWeakRef(PyObject* ref) { + PyObject* pyobject = PyWeakref_GetObject(ref); + if (!pyobject || pyobject == Py_None) + return nullptr; + Py_INCREF(pyobject); + return pyobject; +} +#else +static inline PyObject* cpyrt_GetWeakRef(PyObject* ref) { + PyObject* pyobject = nullptr; + if (PyWeakref_GetRef(ref, &pyobject) != -1) + return pyobject; + return nullptr; +} +#endif + +// C++ version of the cppjit API +#include "cppjit_interop.h" + +// export macros for our own API +#include "cpyrt/CommonDefs.h" + +// --- reusable PyTypeObject initializer tail ------------------------------- +// Members appended to PyTypeObject in newer CPython releases. Every cpyrt +// type leaves all of these zero/null-initialized, so they share one tail. +// To support a future Python version, add one block below and one line to +// CPYRT_PYTYPE_TAIL. + +#if PY_VERSION_HEX >= 0x030c0000 +#define CPYRT_TP_WATCHED , 0 /* tp_watched (>= 3.12) */ +#else +#define CPYRT_TP_WATCHED +#endif + +#if PY_VERSION_HEX >= 0x030d0000 +#define CPYRT_TP_VERSIONS_USED , 0 /* tp_versions_used(>= 3.13) */ +#else +#define CPYRT_TP_VERSIONS_USED +#endif + +#if PY_VERSION_HEX >= 0x030f0000 +#define CPYRT_TP_ITERITEM , nullptr /* _tp_iteritem (>= 3.15) */ +#else +#define CPYRT_TP_ITERITEM +#endif + +#define CPYRT_PYTYPE_TAIL \ + CPYRT_TP_WATCHED \ + CPYRT_TP_VERSIONS_USED \ + CPYRT_TP_ITERITEM +// -------------------------------------------------------------------------- + +#endif // !CPYRT_CPYRT_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx new file mode 100644 index 0000000000000..ac88c10c270de --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx @@ -0,0 +1,1029 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "CallContext.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "Utility.h" +#include "cppjit_interop.h" +#include + +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +namespace cppjit::cpyrt { +void* Instance_AsVoidPtr(PyObject* pyobject); +PyObject* Instance_FromVoidPtr(void* addr, const std::string& classname, + bool python_owns = false); +} // namespace cppjit::cpyrt +#undef CPYRT_INTERNAL + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +std::unordered_map TypeReductionMap; + +//- data ----------------------------------------------------------------------- +static PyObject* nullptr_repr(PyObject*) { + return cpyrt_PyText_FromString("nullptr"); +} + +static void nullptr_dealloc(PyObject*) { + Py_FatalError("deallocating nullptr"); +} + +static int nullptr_nonzero(PyObject*) { return 0; } + +static PyNumberMethods nullptr_as_number = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (inquiry)nullptr_nonzero, // tp_nonzero (nb_bool in p3) + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // nb_floor_divide + 0, // nb_true_divide + 0, + 0, + 0, // nb_index + 0, // nb_matrix_multiply + 0 // nb_inplace_matrix_multiply +}; + +static PyTypeObject PyNullPtr_t_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) "nullptr_t", // tp_name + sizeof(PyObject), // tp_basicsize + 0, // tp_itemsize + nullptr_dealloc, // tp_dealloc (never called) + 0, + 0, + 0, + 0, + nullptr_repr, // tp_repr + &nullptr_as_number, // tp_as_number + 0, + 0, +#if PY_VERSION_HEX >= 0x030d0000 + (hashfunc)Py_HashPointer, // tp_hash +#else + (hashfunc)_Py_HashPointer, // tp_hash +#endif + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +static PyObject* default_repr(PyObject*) { + return cpyrt_PyText_FromString("type default"); +} + +static void default_dealloc(PyObject*) { + Py_FatalError("deallocating default"); +} + +static PyTypeObject PyDefault_t_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) "default_t", // tp_name + sizeof(PyObject), // tp_basicsize + 0, // tp_itemsize + default_dealloc, // tp_dealloc (never called) + 0, + 0, + 0, + 0, + default_repr, // tp_repr + 0, + 0, + 0, +#if PY_VERSION_HEX >= 0x030d0000 + (hashfunc)Py_HashPointer, // tp_hash +#else + (hashfunc)_Py_HashPointer, // tp_hash +#endif + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +namespace { + +struct { + PyObject_HEAD +} _cpyrt_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; + +struct { + PyObject_HEAD +} _cpyrt_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; + +// TODO: refactor with Converters.cxx +struct cpyrt_tagCDataObject { // non-public (but stable) + PyObject_HEAD char* b_ptr; + int b_needsfree; +}; + +} // unnamed namespace + +namespace cppjit::cpyrt { +PyObject* gThisModule = nullptr; +PyObject* gPyTypeMap = nullptr; +PyObject* gNullPtrObject = nullptr; +PyObject* gDefaultObject = nullptr; +PyObject* gBusException = nullptr; +PyObject* gSegvException = nullptr; +PyObject* gIllException = nullptr; +PyObject* gAbrtException = nullptr; +std::unordered_set gPinnedTypes; +std::ostringstream gCapturedError; +std::streambuf* gOldErrorBuffer = nullptr; + +std::unordered_map>& pythonizations() { + static std::unordered_map> pyzMap; + return pyzMap; +} +} // namespace cppjit::cpyrt + +//- private helpers ------------------------------------------------------------ +namespace { + +using namespace cppjit::cpyrt; + +//---------------------------------------------------------------------------- +static PyObject* MakeCppTemplateClass(PyObject* /* self */, PyObject* args) { + // Create a binding for a templated class instantiation. + + // args is class name + template arguments; build full instantiation + Py_ssize_t nArgs = PyTuple_GET_SIZE(args); + if (nArgs < 2) { + PyErr_Format(PyExc_TypeError, + "too few arguments for template instantiation"); + return nullptr; + } + PyObject* cppscope = PyTuple_GET_ITEM(args, 0); + void* tmpl = PyLong_AsVoidPtr(cppscope); + + // build "< type, type, ... >" part of class name (modifies pyname) + std::vector types = + Utility::GetTemplateArgsTypes(cppscope, args, nullptr, Utility::kNone, 1); + if (PyErr_Occurred()) + return nullptr; + + interop::TCppScope_t scope = + interop::InstantiateTemplate(tmpl, types.data(), types.size()); + for (Cpp::TemplateArgInfo i : types) { + if (i.m_IntegralValue) + std::free((void*)i.m_IntegralValue); + } + + if (!scope) { + PyErr_Format(PyExc_TypeError, + "Template instantiation failed: '%s' with args: '%s\n'", + interop::GetScopedFinalName(tmpl).c_str(), + cpyrt_PyText_AsString(PyObject_Repr(args))); + return nullptr; + } + + return CreateScopeProxy(scope); +} + +//---------------------------------------------------------------------------- +static char* GCIA_kwlist[] = {(char*)"instance", (char*)"field", (char*)"byref", + NULL}; +static void* GetCPPInstanceAddress(const char* fname, PyObject* args, + PyObject* kwds) { + // Helper to get the address (address-of-address) of various object proxy + // types. + CPPInstance* pyobj = 0; + PyObject* pyname = 0; + int byref = 0; + if (PyArg_ParseTupleAndKeywords(args, kwds, const_cast("O|O!b"), + GCIA_kwlist, &pyobj, &cpyrt_PyText_Type, + &pyname, &byref)) { + + if (CPPInstance_Check(pyobj)) { + if (pyname != 0) { + // locate property proxy for offset info + CPPDataMember* pyprop = nullptr; + + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)pyobj); + PyObject* dict = PyObject_GetAttr(pyclass, PyStrings::gDict); + pyprop = (CPPDataMember*)PyObject_GetItem(dict, pyname); + Py_DECREF(dict); + + if (CPPDataMember_Check(pyprop)) { + // this is an address of a value (i.e. &myobj->prop) + void* addr = (void*)pyprop->GetAddress(pyobj); + Py_DECREF(pyprop); + return addr; + } + + Py_XDECREF(pyprop); + + PyErr_Format(PyExc_TypeError, "%s is not a valid data member", + cpyrt_PyText_AsString(pyname)); + return nullptr; + } + + // this is an address of an address (i.e. &myobj, with myobj of type + // MyObj*) note that the return result may be null + if (!byref) + return ((CPPInstance*)pyobj)->GetObject(); + return &((CPPInstance*)pyobj)->GetObjectRaw(); + + } else if (cpyrt_PyText_Check(pyobj)) { + // special cases for access to the cpyrt API + std::string req = cpyrt_PyText_AsString((PyObject*)pyobj); + if (req == "Instance_AsVoidPtr") + return (void*)&Instance_AsVoidPtr; + else if (req == "Instance_FromVoidPtr") + return (void*)&Instance_FromVoidPtr; + } + } + + if (!PyErr_Occurred()) + PyErr_Format(PyExc_ValueError, "invalid argument for %s", fname); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* addressof(PyObject* /* dummy */, PyObject* args, + PyObject* kwds) { + // Return object proxy address as a value (cppjit-style), or the same for an + // array. + void* addr = GetCPPInstanceAddress("addressof", args, kwds); + if (addr) + return PyLong_FromLongLong((intptr_t)addr); + else if (!PyErr_Occurred()) { + return PyLong_FromLong(0); + } else if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args) == 1) { + PyErr_Clear(); + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + + // nullptr special case + if (arg0 == gNullPtrObject || + (PyInt_Check(arg0) && PyInt_AsLong(arg0) == 0)) + return PyLong_FromLong(0); + + // overload if unambiguous + if (CPPOverload_CheckExact(arg0)) { + const auto& methods = ((CPPOverload*)arg0)->fMethodInfo->fMethods; + if (methods.size() != 1) { + PyErr_SetString(PyExc_TypeError, "overload is not unambiguous"); + return nullptr; + } + + interop::TCppFuncAddr_t caddr = methods[0]->GetFunctionAddress(); + return PyLong_FromLongLong((intptr_t)caddr); + } + + // C functions (incl. ourselves) + if (PyCFunction_Check(arg0)) { + void* caddr = (void*)PyCFunction_GetFunction(arg0); + return PyLong_FromLongLong((intptr_t)caddr); + } + + // LowLevelViews + if (LowLevelView_CheckExact(arg0)) { + auto* llv = (LowLevelView*)arg0; + return PyLong_FromLongLong((intptr_t)llv->get_buf()); + } + + // final attempt: any type of buffer + Utility::GetBuffer(arg0, '*', 1, addr, false); + if (addr) + return PyLong_FromLongLong((intptr_t)addr); + } + + // error message if not already set + if (!PyErr_Occurred()) { + if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args)) { + PyObject* str = PyObject_Str(PyTuple_GET_ITEM(args, 0)); + if (str && cpyrt_PyText_Check(str)) + PyErr_Format(PyExc_TypeError, "unknown object %s", + cpyrt_PyText_AsString(str)); + else + PyErr_Format(PyExc_TypeError, "unknown object at %p", + (void*)PyTuple_GET_ITEM(args, 0)); + Py_XDECREF(str); + } + } + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCObject(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as an opaque CObject. + void* addr = GetCPPInstanceAddress("as_cobject", args, kwds); + if (addr) + return cpyrt_PyCapsule_New((void*)addr, nullptr, nullptr); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCapsule(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as an opaque PyCapsule. + void* addr = GetCPPInstanceAddress("as_capsule", args, kwds); + if (addr) + return PyCapsule_New(addr, nullptr, nullptr); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCTypes(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as a ctypes c_void_p + void* addr = GetCPPInstanceAddress("as_ctypes", args, kwds); + if (!addr) + return nullptr; + + // TODO: refactor code below with converters code + static PyTypeObject* ct_cvoidp = nullptr; + if (!ct_cvoidp) { + PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) + return nullptr; + + ct_cvoidp = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_void_p"); + Py_DECREF(ctmod); + if (!ct_cvoidp) + return nullptr; + Py_DECREF(ct_cvoidp); // module keeps a reference + } + + PyObject* ref = ct_cvoidp->tp_new(ct_cvoidp, nullptr, nullptr); + *(void**)((cpyrt_tagCDataObject*)ref)->b_ptr = addr; + ((cpyrt_tagCDataObject*)ref)->b_needsfree = 0; + return ref; +} + +//---------------------------------------------------------------------------- +static PyObject* AsMemoryView(PyObject* /* unused */, PyObject* pyobject) { + // Return a raw memory view on arrays of PODs. + if (!CPPInstance_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); + return nullptr; + } + + CPPInstance* pyobj = (CPPInstance*)pyobject; + interop::TCppScope_t klass = ((CPPClass*)Py_TYPE(pyobject))->fCppType; + + Py_ssize_t array_len = pyobj->ArrayLength(); + + if (array_len < 0 || !interop::IsAggregate(klass)) { + PyErr_SetString(PyExc_TypeError, + "object is not a proxy to an array of PODs of known size"); + return nullptr; + } + + Py_buffer view; + + view.obj = pyobject; + view.buf = pyobj->GetObject(); + view.itemsize = interop::SizeOf(klass); + view.len = view.itemsize * array_len; + view.readonly = 0; + view.format = NULL; // i.e. "B" assumed + view.ndim = 1; + view.shape = NULL; + view.strides = NULL; + view.suboffsets = NULL; + view.internal = NULL; + + return PyMemoryView_FromBuffer(&view); +} + +//---------------------------------------------------------------------------- +static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) { + // From a long representing an address or a PyCapsule/CObject, bind to a + // class. + Py_ssize_t argc = PyTuple_GET_SIZE(args); + if (argc != 2) { + PyErr_Format( + PyExc_TypeError, + "bind_object takes 2 positional arguments but (" PY_SSIZE_T_FORMAT + " were given)", + argc); + return nullptr; + } + + // convert 2nd argument first (used for both pointer value and instance cases) + interop::TCppScope_t cast_type = nullptr; + PyObject* arg1 = PyTuple_GET_ITEM(args, 1); + if (!cpyrt_PyText_Check(arg1)) { // not string, then class + if (CPPScope_Check(arg1)) + cast_type = ((CPPClass*)arg1)->fCppType; + else + arg1 = PyObject_GetAttr(arg1, PyStrings::gName); + } else + Py_INCREF(arg1); + + if (!cast_type && arg1) { + cast_type = + (interop::TCppScope_t)interop::GetScope(cpyrt_PyText_AsString(arg1)); + Py_DECREF(arg1); + } + + if (!cast_type) { + PyErr_SetString( + PyExc_TypeError, + "bind_object expects a valid class or class name as an argument"); + return nullptr; + } + + // next, convert the first argument, some pointer value or a pre-existing + // instance + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + + if (CPPInstance_Check(arg0)) { + // if this instance's class has a relation to the requested one, calculate + // the offset, erase if from any caches, and update the pointer and type + CPPInstance* arg0_pyobj = (CPPInstance*)arg0; + interop::TCppScope_t cur_type = + arg0_pyobj->ObjectIsA(false /* check_smart */); + + bool isPython = CPPScope_Check(arg1) && + (((CPPClass*)arg1)->fFlags & CPPScope::kIsPython); + + if (cur_type == cast_type && !isPython) { + Py_INCREF(arg0); // nothing to do + return arg0; + } + + int direction = 0; + interop::TCppScope_t base = nullptr, derived = nullptr; + if (interop::IsSubclass(cast_type, cur_type)) { + derived = cast_type; + base = cur_type; + direction = -1; // down-cast + } else if (interop::IsSubclass(cur_type, cast_type)) { + base = cast_type; + derived = cur_type; + direction = 1; // up-cast + } else { + PyErr_SetString( + PyExc_TypeError, + "provided instance and provided target type are unrelated"); + return nullptr; + } + + interop::TCppObject_t address = + (interop::TCppObject_t)arg0_pyobj->GetObject(); + ptrdiff_t offset = + interop::GetBaseOffset(derived, base, address, direction); + + // it's debatable whether a new proxy should be created rather than updating + // the old, but changing the old object would be changing the behavior of + // all code that has a reference to it, which may not be the intention if + // the cast is on a C++ data member; this probably is the "least surprise" + // option + + // ownership is taken over as needed, again following the principle of + // "least surprise" as most likely only the cast object will be retained + bool owns = arg0_pyobj->fFlags & CPPInstance::kIsOwner; + + if (!isPython) { + // ordinary C++ class + PyObject* pyobj = BindCppObjectNoCast( + interop::TCppObject_t((void*)((intptr_t)address.data + offset)), + cast_type, owns ? CPPInstance::kIsOwner : 0); + if (owns && pyobj) + arg0_pyobj->CppOwns(); + return pyobj; + + } else { + // rebinding to a Python-side class, create a fresh instance first to be + // able to perform a lookup of the original dispatch object and if found, + // return original + void* cast_address = (void*)((intptr_t)address.data + offset); + PyObject* pyobj = + ((PyTypeObject*)arg1)->tp_new((PyTypeObject*)arg1, nullptr, nullptr); + ((CPPInstance*)pyobj)->GetObjectRaw() = cast_address; + + PyObject* dispproxy = cpyrt::GetScopeProxy(cast_type); + PyObject* res = + PyObject_CallMethodOneArg(dispproxy, PyStrings::gDispGet, pyobj); + /* Note: the resultant object is borrowed */ + if (CPPInstance_Check(res) && + ((CPPInstance*)res)->GetObject() == cast_address) { + ((CPPInstance*)pyobj)->CppOwns(); // make sure C++ object isn't deleted + Py_DECREF(pyobj); // on DECREF (is default, but still) + pyobj = res; + } else { + if (res) + Py_DECREF(res); // most likely Py_None + else + PyErr_Clear(); // should not happen + } + Py_DECREF(dispproxy); + + if (pyobj && owns) { + arg0_pyobj->CppOwns(); + ((CPPInstance*)pyobj)->PythonOwns(); + } + + return pyobj; + } + } + + // not a pre-existing object; get the address and bind + void* addr = nullptr; + if (arg0 != gNullPtrObject) { + addr = cpyrt_PyCapsule_GetPointer(arg0, nullptr); + if (PyErr_Occurred()) { + PyErr_Clear(); + + addr = PyLong_AsVoidPtr(arg0); + if (PyErr_Occurred()) { + PyErr_Clear(); + + // last chance, perhaps it's a buffer/array (return from void*) + Py_ssize_t buflen = + Utility::GetBuffer(PyTuple_GetItem(args, 0), '*', 1, addr, false); + if (!addr || !buflen) { + PyErr_SetString(PyExc_TypeError, + "bind_object requires a CObject/Capsule, long " + "integer, buffer, or instance as first argument"); + return nullptr; + } + } + } + } + + bool do_cast = false; + if (kwds) { + PyObject* cast = PyDict_GetItemString(kwds, "cast"); + do_cast = cast && PyObject_IsTrue(cast); + } + + if (do_cast) + return BindCppObject(addr, cast_type); + + return BindCppObjectNoCast(addr, cast_type); +} + +//---------------------------------------------------------------------------- +static PyObject* Move(PyObject*, PyObject* pyobject) { + // Prepare the given C++ object for moving. + if (!CPPInstance_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "C++ object expected"); + return nullptr; + } + + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + Py_INCREF(pyobject); + return pyobject; +} + +//---------------------------------------------------------------------------- +static PyObject* AddPythonization(PyObject*, PyObject* args) { + // Remove a previously registered pythonizor from the given scope. + PyObject* pythonizor = nullptr; + const char* scope; + if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) + return nullptr; + + if (!PyCallable_Check(pythonizor)) { + PyObject* pystr = PyObject_Str(pythonizor); + PyErr_Format(PyExc_TypeError, "given \'%s\' object is not callable", + cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + return nullptr; + } + + Py_INCREF(pythonizor); + pythonizations()[scope].push_back(pythonizor); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* RemovePythonization(PyObject*, PyObject* args) { + // Remove a previously registered pythonizor from the given scope. + PyObject* pythonizor = nullptr; + const char* scope; + if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) + return nullptr; + + auto& pyzMap = pythonizations(); + auto p1 = pyzMap.find(scope); + if (p1 != pyzMap.end()) { + auto p2 = std::find(p1->second.begin(), p1->second.end(), pythonizor); + if (p2 != p1->second.end()) { + p1->second.erase(p2); + Py_RETURN_TRUE; + } + } + + Py_RETURN_FALSE; +} + +//---------------------------------------------------------------------------- +static PyObject* PinType(PyObject*, PyObject* pyclass) { + // Add a pinning so that objects of type `derived' are interpreted as + // objects of type `base'. + if (!CPPScope_Check(pyclass)) { + PyErr_SetString(PyExc_TypeError, "C++ class expected"); + return nullptr; + } + + gPinnedTypes.insert(((CPPClass*)pyclass)->fCppType); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* AddTypeReducer(PyObject*, PyObject* args) { + // Add a type reducer to map type2 to type2 on function returns. + const char *reducable, *reduced; + if (!PyArg_ParseTuple(args, const_cast("ss"), &reducable, &reduced)) + return nullptr; + + interop::TCppType_t reducable_type = + interop::GetTypeFromScope(interop::GetScope(reducable)); + interop::TCppType_t reduced_type = + interop::GetTypeFromScope(interop::GetScope(reduced)); + TypeReductionMap[reducable_type] = reduced_type; + + Py_RETURN_NONE; +} + +#define DEFINE_CALL_POLICY_TOGGLE(name, flagname) \ + static PyObject* name(PyObject*, PyObject* args) { \ + PyObject* enabled = 0; \ + if (!PyArg_ParseTuple(args, const_cast("O"), &enabled)) \ + return nullptr; \ + \ + if (CallContext::SetGlobalPolicy(CallContext::flagname, \ + PyObject_IsTrue(enabled))) { \ + Py_RETURN_TRUE; \ + } \ + \ + Py_RETURN_FALSE; \ + } + +DEFINE_CALL_POLICY_TOGGLE(SetHeuristicMemoryPolicy, kUseHeuristics); +DEFINE_CALL_POLICY_TOGGLE(SetImplicitSmartPointerConversion, + kImplicitSmartPtrConversion); +DEFINE_CALL_POLICY_TOGGLE(SetGlobalSignalPolicy, kProtected); + +//---------------------------------------------------------------------------- +static PyObject* SetOwnership(PyObject*, PyObject* args) { + // Set the ownership (True is python-owns) for the given object. + CPPInstance* pyobj = nullptr; + PyObject* pykeep = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!O!"), &CPPInstance_Type, + (void*)&pyobj, &PyInt_Type, &pykeep)) + return nullptr; + + (bool)PyLong_AsLong(pykeep) ? pyobj->PythonOwns() : pyobj->CppOwns(); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* AddSmartPtrType(PyObject*, PyObject* args) { + // Add a smart pointer to the list of known smart pointer types. + const char* type_name; + if (!PyArg_ParseTuple(args, const_cast("s"), &type_name)) + return nullptr; + + // interop::AddSmartPtrType(type_name); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* BeginCaptureStderr(PyObject*, PyObject*) { + gOldErrorBuffer = std::cerr.rdbuf(); + std::cerr.rdbuf(gCapturedError.rdbuf()); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* EndCaptureStderr(PyObject*, PyObject*) { + // restore old rdbuf and return captured result + std::cerr.rdbuf(gOldErrorBuffer); + gOldErrorBuffer = nullptr; + + std::string capturedError = std::move(gCapturedError).str(); + + gCapturedError.str(""); + gCapturedError.clear(); + + return Py_BuildValue("s", capturedError.c_str()); +} +} // unnamed namespace + +//- data ----------------------------------------------------------------------- +//---------------------------------------------------------------------------- +static PyObject* LockInterpreter(PyObject*, PyObject*) { + // release the GIL while waiting: the thread holding the interpreter lock + // may need Python to finish + Py_BEGIN_ALLOW_THREADS + interop::LockInterpreter(); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* UnlockInterpreter(PyObject*, PyObject*) { + interop::UnlockInterpreter(); + Py_RETURN_NONE; +} + +static PyMethodDef gcpyrtMethods[] = { + {(char*)"CreateScopeProxy", (PyCFunction)cpyrt::CreateScopeProxy, + METH_VARARGS, (char*)"cppjit internal function"}, + {(char*)"MakeCppTemplateClass", (PyCFunction)MakeCppTemplateClass, + METH_VARARGS, (char*)"cppjit internal function"}, + {(char*)"_DestroyPyStrings", (PyCFunction)cpyrt::DestroyPyStrings, + METH_NOARGS, (char*)"cppjit internal function"}, + {(char*)"addressof", (PyCFunction)addressof, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field as a value."}, + {(char*)"as_cobject", (PyCFunction)AsCObject, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a CObject."}, + {(char*)"as_capsule", (PyCFunction)AsCapsule, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a PyCapsule."}, + {(char*)"as_ctypes", (PyCFunction)AsCTypes, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a ctypes " + "c_void_p."}, + {(char*)"as_memoryview", (PyCFunction)AsMemoryView, METH_O, + (char*)"Represent an array of objects as raw memory."}, + {(char*)"bind_object", (PyCFunction)BindObject, + METH_VARARGS | METH_KEYWORDS, + (char*)"Create an object of given type, from given address."}, + {(char*)"move", (PyCFunction)Move, METH_O, + (char*)"Cast the C++ object to become movable."}, + {(char*)"add_pythonization", (PyCFunction)AddPythonization, METH_VARARGS, + (char*)"Add a pythonizor."}, + {(char*)"remove_pythonization", (PyCFunction)RemovePythonization, + METH_VARARGS, (char*)"Remove a pythonizor."}, + {(char*)"_pin_type", (PyCFunction)PinType, METH_O, + (char*)"Install a type pinning."}, + {(char*)"_add_type_reducer", (PyCFunction)AddTypeReducer, METH_VARARGS, + (char*)"Add a type reducer."}, + {(char*)"SetHeuristicMemoryPolicy", (PyCFunction)SetHeuristicMemoryPolicy, + METH_VARARGS, + (char*)"Set the global memory policy, which affects object ownership when " + "objects are passed as function arguments."}, + {(char*)"SetImplicitSmartPointerConversion", + (PyCFunction)SetImplicitSmartPointerConversion, METH_VARARGS, + (char*)"Enable or disable the implicit conversion to smart pointers in " + "function calls (on by default)."}, + {(char*)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, + METH_VARARGS, + (char*)"Set the global signal policy, which determines whether a jmp " + "address should be saved to return to after a " + "C++ segfault. In practical terms: trap signals in safe mode to " + "prevent interpreter abort."}, + {(char*)"SetOwnership", (PyCFunction)SetOwnership, METH_VARARGS, + (char*)"Modify held C++ object ownership."}, + {(char*)"AddSmartPtrType", (PyCFunction)AddSmartPtrType, METH_VARARGS, + (char*)"Add a smart pointer to the list of known smart pointer types."}, + {(char*)"_begin_capture_stderr", (PyCFunction)BeginCaptureStderr, + METH_NOARGS, (char*)"Begin capturing stderr to a in memory buffer."}, + {(char*)"_end_capture_stderr", (PyCFunction)EndCaptureStderr, METH_NOARGS, + (char*)"End capturing stderr and returns the captured buffer."}, + {(char*)"_lock_interpreter", (PyCFunction)LockInterpreter, METH_NOARGS, + (char*)"Take the interpreter lock; pair with _unlock_interpreter."}, + {(char*)"_unlock_interpreter", (PyCFunction)UnlockInterpreter, + METH_NOARGS, (char*)"Release the lock taken by _lock_interpreter."}, + {nullptr, nullptr, 0, nullptr}}; + +struct module_state { + PyObject* error; +}; + +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) + +static int cpycppjitmodule_traverse(PyObject* m, visitproc visit, void* arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int cpycppjitmodule_clear(PyObject* m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, "libcppjit", nullptr, + sizeof(struct module_state), gcpyrtMethods, nullptr, + cpycppjitmodule_traverse, cpycppjitmodule_clear, nullptr}; + +namespace cppjit::cpyrt { + +//---------------------------------------------------------------------------- +PyObject* Init() { + // Initialization of extension module libcppjit. + + // load commonly used python strings + if (!cpyrt::CreatePyStrings()) + return nullptr; + + // setup this module + gThisModule = PyModule_Create(&moduledef); + if (!gThisModule) + return nullptr; + + // keep gThisModule, but do not increase its reference count even as it is + // borrowed, or a self-referencing cycle would be created + + // external types + gPyTypeMap = PyDict_New(); + PyModule_AddObject(gThisModule, "type_map", gPyTypeMap); // steals reference + + // Pythonizations ... + PyModule_AddObject(gThisModule, "UserExceptions", PyDict_New()); + + // inject meta type + if (!Utility::InitProxy(gThisModule, &CPPScope_Type, "CPPScope")) + return nullptr; + + // inject object proxy type + if (!Utility::InitProxy(gThisModule, &CPPInstance_Type, "CPPInstance")) + return nullptr; + + // inject exception object proxy type + if (!Utility::InitProxy(gThisModule, &CPPExcInstance_Type, "CPPExcInstance")) + return nullptr; + + // inject method proxy type + if (!Utility::InitProxy(gThisModule, &CPPOverload_Type, "CPPOverload")) + return nullptr; + + // inject template proxy type + if (!Utility::InitProxy(gThisModule, &TemplateProxy_Type, "TemplateProxy")) + return nullptr; + + // inject property proxy type + if (!Utility::InitProxy(gThisModule, &CPPDataMember_Type, "CPPDataMember")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &CustomInstanceMethod_Type, + "InstanceMethod")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &TupleOfInstances_Type, "InstanceArray")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &LowLevelView_Type, "LowLevelView")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &PyNullPtr_t_Type, "nullptr_t")) + return nullptr; + + // custom iterators + if (PyType_Ready(&InstanceArrayIter_Type) < 0) + return nullptr; + + if (PyType_Ready(&IndexIter_Type) < 0) + return nullptr; + + if (PyType_Ready(&VectorIter_Type) < 0) + return nullptr; + + // inject identifiable nullptr and default + gNullPtrObject = (PyObject*)&_cpyrt_NullPtrStruct; + Py_INCREF(gNullPtrObject); + PyModule_AddObject(gThisModule, (char*)"nullptr", gNullPtrObject); + + gDefaultObject = (PyObject*)&_cpyrt_DefaultStruct; + Py_INCREF(gDefaultObject); + PyModule_AddObject(gThisModule, (char*)"default", gDefaultObject); + + // C++-specific exceptions + PyObject* cppfatal = + PyErr_NewException((char*)"cppjit.ll.FatalError", nullptr, nullptr); + PyModule_AddObject(gThisModule, (char*)"FatalError", cppfatal); + + gBusException = + PyErr_NewException((char*)"cppjit.ll.BusError", cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"BusError", gBusException); + gSegvException = PyErr_NewException((char*)"cppjit.ll.SegmentationViolation", + cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"SegmentationViolation", + gSegvException); + gIllException = PyErr_NewException((char*)"cppjit.ll.IllegalInstruction", + cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"IllegalInstruction", gIllException); + gAbrtException = + PyErr_NewException((char*)"cppjit.ll.AbortSignal", cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException); + + // gbl namespace is injected in cppjit.py + + // create the memory regulator + static MemoryRegulator s_memory_regulator; + + Py_INCREF(gThisModule); + return gThisModule; +} + +} // namespace cppjit::cpyrt diff --git a/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.h b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.h new file mode 100644 index 0000000000000..3e0cf59aeab2a --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.h @@ -0,0 +1,12 @@ +#ifndef CPYRT_CPYRTMODULE_H +#define CPYRT_CPYRTMODULE_H + +#include "Python.h" + +namespace cppjit::cpyrt { + +PyObject* Init(); + +} + +#endif // !CPYRT_CPYRTMODULE_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/cpyrtPyModule.cxx b/bindings/pyroot/cppjit/src/cpyrt/cpyrtPyModule.cxx new file mode 100644 index 0000000000000..24eb80305c48c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtPyModule.cxx @@ -0,0 +1,4 @@ +#include "cpyrtModule.h" + +//---------------------------------------------------------------------------- +extern "C" PyObject* PyInit_libcppjit() { return cppjit::cpyrt::Init(); } diff --git a/bindings/pyroot/cppjit/src/interop/callcontext.h b/bindings/pyroot/cppjit/src/interop/callcontext.h new file mode 100644 index 0000000000000..c95ac71604d7f --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/callcontext.h @@ -0,0 +1,54 @@ +#ifndef CPPJIT_INTEROP_CALLCONTEXT_H +#define CPPJIT_INTEROP_CALLCONTEXT_H + +// Standard +#include +#include + +// convention to pass flag for direct calls (similar to Python's vector calls) +#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) + +namespace cppjit::cpyrt { + +// small number that allows use of stack for argument passing +const int SMALL_ARGS_N = 8; + +// The shipped cpyrt/API.h carries an identical Parameter for JIT-side +// code, which cannot see this in-tree header; the shared CPYRT_PARAMETER +// guard keeps one definition per TU. Keep both copies identical. +#ifndef CPYRT_PARAMETER +#define CPYRT_PARAMETER +// general place holder for function parameters +struct Parameter { + union Value { + bool fBool; + int8_t fInt8; + int16_t fInt16; + int32_t fInt32; + uint8_t fUInt8; + uint16_t fUInt16; + uint32_t fUInt32; + short fShort; + unsigned short fUShort; + int fInt; + unsigned int fUInt; + long fLong; + intptr_t fIntPtr; + unsigned long fULong; + long long fLLong; + unsigned long long fULLong; + int64_t fInt64; + uint64_t fUInt64; + float fFloat; + double fDouble; + long double fLDouble; + void* fVoidp; + } fValue; + void* fRef; + char fTypeCode; +}; +#endif // CPYRT_PARAMETER + +} // namespace cppjit::cpyrt + +#endif // !CPPJIT_INTEROP_CALLCONTEXT_H diff --git a/bindings/pyroot/cppjit/src/interop/cppinterop_dispatch.cxx b/bindings/pyroot/cppjit/src/interop/cppinterop_dispatch.cxx new file mode 100644 index 0000000000000..bd4452c58ba6d --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/cppinterop_dispatch.cxx @@ -0,0 +1,11 @@ +#include +#if __has_include("CppInterOp/CppInterOpAPI.inc") +using namespace Cpp; +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + Ret(*CppInternal::DispatchRaw::DN) RawTypes = nullptr; +#include "CppInterOp/CppInterOpAPI.inc" +#else +#define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +#endif diff --git a/bindings/pyroot/cppjit/src/interop/cppjit_interop.h b/bindings/pyroot/cppjit/src/interop/cppjit_interop.h new file mode 100644 index 0000000000000..db2ccf0b51dc4 --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/cppjit_interop.h @@ -0,0 +1,404 @@ +#ifndef CPPJIT_INTEROP_H +#define CPPJIT_INTEROP_H + +#include +#include + +// Standard +#include +#include +#include +#include +#include +#include + +#include "callcontext.h" +#include "precommondefs.h" + +// some more types; assumes cppjit_interop.h follows Python.h +#ifndef PY_LONG_LONG +#ifdef _WIN32 +typedef __int64 PY_LONG_LONG; +#else +typedef long long PY_LONG_LONG; +#endif +#endif + +#ifndef PY_ULONG_LONG +#ifdef _WIN32 +typedef unsigned __int64 PY_ULONG_LONG; +#else +typedef unsigned long long PY_ULONG_LONG; +#endif +#endif + +#ifndef PY_LONG_DOUBLE +typedef long double PY_LONG_DOUBLE; +#endif + +namespace cppjit::interop { +typedef Cpp::DeclRef TCppScope_t; +typedef Cpp::TypeRef TCppType_t; +typedef Cpp::ObjectRef TCppObject_t; +typedef Cpp::FuncRef TCppMethod_t; +typedef Cpp::InterpRef TInterp_t; +typedef size_t TCppIndex_t; +typedef void* TCppFuncAddr_t; + +// direct interpreter access ------------------------------------------------- +RPY_EXPORTED +bool Compile(const std::string& code, bool silent = false); +// the interpreter lock (ROOT's gInterpreterMutex when thread safety is on) +// for Cpp:: calls made directly from Python; recursive, pair the calls +RPY_EXPORTED +void LockInterpreter(); +RPY_EXPORTED +void UnlockInterpreter(); +RPY_EXPORTED +std::string ToString(TCppScope_t klass, TCppObject_t obj); + +// name to opaque C++ scope representation ----------------------------------- +RPY_EXPORTED +std::string ResolveName(const std::string& cppitem_name); +RPY_EXPORTED +TCppType_t ResolveType(TCppType_t cppitem_name); +RPY_EXPORTED +TCppType_t ResolveEnumReferenceType(TCppType_t type); +RPY_EXPORTED +TCppType_t ResolveEnumPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetRealType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); +RPY_EXPORTED +std::string ResolveEnum(TCppScope_t enum_scope); +RPY_EXPORTED +bool IsLValueReferenceType(TCppType_t type); +RPY_EXPORTED +bool IsMutablePtrRefType(TCppType_t type); +RPY_EXPORTED +bool IsRValueReferenceType(TCppType_t type); +RPY_EXPORTED +bool IsClassType(TCppType_t type); +RPY_EXPORTED +bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); +RPY_EXPORTED +bool IsPointerType(TCppType_t type); +RPY_EXPORTED +bool IsFunctionPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetType(const std::string& name, bool enable_slow_lookup = false); +RPY_EXPORTED +bool AppendTypesSlow(const std::string& name, + std::vector& types, + TCppScope_t parent = nullptr); +RPY_EXPORTED +TCppType_t GetComplexType(const std::string& element_type); +RPY_EXPORTED +TCppScope_t GetScope(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +RPY_EXPORTED +TCppScope_t GetUnderlyingScope(TCppScope_t scope); +RPY_EXPORTED +TCppScope_t GetFullScope(const std::string& scope_name); +RPY_EXPORTED +TCppScope_t GetTypeScope(TCppScope_t klass); +RPY_EXPORTED +TCppScope_t GetNamed(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +RPY_EXPORTED +TCppScope_t GetParentScope(TCppScope_t scope); +RPY_EXPORTED +TCppScope_t GetScopeFromType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetTypeFromScope(TCppScope_t klass); +RPY_EXPORTED +TCppScope_t GetGlobalScope(); +RPY_EXPORTED +TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); +RPY_EXPORTED +size_t SizeOf(TCppScope_t klass); +RPY_EXPORTED +size_t SizeOfType(TCppType_t type); + +RPY_EXPORTED +bool IsBuiltin(const std::string& type_name); + +RPY_EXPORTED +bool IsBuiltin(TCppType_t type); + +RPY_EXPORTED +bool IsComplete(TCppScope_t type); + +// memory management --------------------------------------------------------- +RPY_EXPORTED +TCppObject_t Allocate(TCppScope_t scope); +RPY_EXPORTED +void Deallocate(TCppScope_t scope, TCppObject_t instance); +RPY_EXPORTED +TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); +RPY_EXPORTED +void Destruct(TCppScope_t scope, TCppObject_t instance); + +// method/function dispatching ----------------------------------------------- +RPY_EXPORTED +void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +RPY_EXPORTED +char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +RPY_EXPORTED +float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); + +RPY_EXPORTED +void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, + size_t* length); +RPY_EXPORTED +TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, + size_t nargs, void* args); +RPY_EXPORTED +void CallDestructor(TCppScope_t type, TCppObject_t self); +RPY_EXPORTED +TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args, TCppType_t result_type); + +RPY_EXPORTED +TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, + bool check_enabled = true); + +// handling of function argument buffer -------------------------------------- +RPY_EXPORTED +void* AllocateFunctionArgs(size_t nargs); +RPY_EXPORTED +void DeallocateFunctionArgs(void* args); +RPY_EXPORTED +size_t GetFunctionArgSizeof(); +RPY_EXPORTED +size_t GetFunctionArgTypeoffset(); + +// scope reflection information ---------------------------------------------- +RPY_EXPORTED +bool IsNamespace(TCppScope_t scope); +RPY_EXPORTED +bool IsClass(TCppScope_t scope); +RPY_EXPORTED +bool IsTemplate(TCppScope_t scope); +RPY_EXPORTED +bool IsTemplateInstantiation(TCppScope_t scope); +RPY_EXPORTED +bool IsTypedefed(TCppScope_t scope); +RPY_EXPORTED +bool IsAbstract(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumScope(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumConstant(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumType(TCppType_t type); +RPY_EXPORTED +bool IsAggregate(TCppScope_t type); +RPY_EXPORTED +bool IsDefaultConstructable(TCppScope_t scope); +RPY_EXPORTED +bool IsVariable(TCppScope_t scope); + +RPY_EXPORTED +void GetAllCppNames(TCppScope_t scope, std::set& cppnames); + +// namespace reflection information ------------------------------------------ +RPY_EXPORTED +std::vector GetUsingNamespaces(TCppScope_t); + +// class reflection information ---------------------------------------------- +RPY_EXPORTED +std::string GetFinalName(TCppScope_t type); +RPY_EXPORTED +std::string GetScopedFinalName(TCppScope_t type); +RPY_EXPORTED +bool HasVirtualDestructor(TCppScope_t type); +RPY_EXPORTED +TCppIndex_t GetNumBases(TCppScope_t klass); +RPY_EXPORTED +TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); +RPY_EXPORTED +std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); +RPY_EXPORTED +TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); +RPY_EXPORTED +bool IsSubclass(TCppScope_t derived, TCppScope_t base); +RPY_EXPORTED +bool IsSmartPtr(TCppScope_t klass); +RPY_EXPORTED +bool GetSmartPtrInfo(const std::string&, TCppScope_t* raw, TCppMethod_t* deref); +// calculate offsets between declared and actual type, up-cast: direction > 0; +// down-cast: direction < 0 +RPY_EXPORTED +ptrdiff_t GetBaseOffset(TCppScope_t derived, TCppScope_t base, + TCppObject_t address, int direction, + bool rerror = false); + +// method/function reflection information ------------------------------------ +RPY_EXPORTED +void GetClassMethods(TCppScope_t scope, std::vector& methods); +RPY_EXPORTED +std::vector GetMethodsFromName(TCppScope_t scope, + const std::string& name); +RPY_EXPORTED +std::string GetName(TCppScope_t); +RPY_EXPORTED +std::string GetFullName(TCppScope_t); +RPY_EXPORTED +TCppType_t GetMethodReturnType(TCppMethod_t); +RPY_EXPORTED +std::string GetMethodReturnTypeAsString(TCppMethod_t); +RPY_EXPORTED +TCppIndex_t GetMethodNumArgs(TCppMethod_t); +RPY_EXPORTED +TCppIndex_t GetMethodReqArgs(TCppMethod_t); +RPY_EXPORTED +std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, + const std::string& req_type); +RPY_EXPORTED +std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, + TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, + TCppIndex_t max_args = (TCppIndex_t)-1); +RPY_EXPORTED +bool IsFunctionType(TCppType_t typ); +RPY_EXPORTED +TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type); +RPY_EXPORTED +void GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types); +RPY_EXPORTED +bool IsSameType(TCppType_t typ1, TCppType_t typ2); +RPY_EXPORTED +bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2); +RPY_EXPORTED +std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); +RPY_EXPORTED +bool IsConstMethod(TCppMethod_t); +// Templated method/function reflection information +// ------------------------------------ +RPY_EXPORTED +void GetTemplatedMethods(TCppScope_t scope, std::vector& methods); +RPY_EXPORTED +TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, + bool accept_namespace = false); +RPY_EXPORTED +std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); +RPY_EXPORTED +bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); +RPY_EXPORTED +bool IsTemplatedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsStaticTemplate(TCppScope_t scope, const std::string& name); +RPY_EXPORTED +TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string& name, + const std::string& proto); +RPY_EXPORTED +void GetClassOperators(TCppScope_t klass, const std::string& opname, + std::vector& operators); +RPY_EXPORTED +TCppMethod_t GetGlobalOperator(TCppScope_t scope, const std::string& lc, + const std::string& rc, const std::string& op); + +// method properties --------------------------------------------------------- +RPY_EXPORTED +bool IsDeletedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsPublicMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsProtectedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsPrivateMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsConstructor(TCppMethod_t method); +RPY_EXPORTED +bool IsDestructor(TCppMethod_t method); +RPY_EXPORTED +bool IsStaticMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsExplicit(TCppMethod_t method); + +// data member reflection information ---------------------------------------- +RPY_EXPORTED +void GetDatamembers(TCppScope_t scope, std::vector& datamembers); +RPY_EXPORTED +bool IsLambdaClass(TCppType_t type); +RPY_EXPORTED +TCppScope_t WrapLambdaFromVariable(TCppScope_t var); +RPY_EXPORTED +TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); +RPY_EXPORTED +TCppType_t GetDatamemberType(TCppScope_t data); +RPY_EXPORTED +std::string GetDatamemberTypeAsString(TCppScope_t var); +RPY_EXPORTED +std::string GetTypeAsString(TCppType_t type); +RPY_EXPORTED +intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); +RPY_EXPORTED +bool CheckDatamember(TCppScope_t scope, const std::string& name); + +// // data member properties +// ---------------------------------------------------- +RPY_EXPORTED +bool IsPublicData(TCppScope_t var); +RPY_EXPORTED +bool IsProtectedData(TCppScope_t var); +RPY_EXPORTED +bool IsPrivateData(TCppScope_t var); +RPY_EXPORTED +bool IsStaticDatamember(TCppScope_t var); +RPY_EXPORTED +bool IsConstVar(TCppScope_t var); +RPY_EXPORTED +TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); +RPY_EXPORTED +std::vector GetDimensions(TCppType_t type); + +// enum properties ----------------------------------------------------------- +RPY_EXPORTED +std::vector GetEnumConstants(TCppScope_t scope); +RPY_EXPORTED +TCppType_t GetEnumConstantType(TCppScope_t scope); +RPY_EXPORTED +long long GetEnumDataValue(TCppScope_t scope); + +RPY_EXPORTED +TCppScope_t InstantiateTemplate(TCppScope_t tmpl, Cpp::TemplateArgInfo* args, + size_t args_size); + +RPY_EXPORTED +void DumpScope(TCppScope_t scope); +} // namespace cppjit::interop + +#endif // !CPPJIT_INTEROP_H diff --git a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx new file mode 100644 index 0000000000000..0358941f9fe78 --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx @@ -0,0 +1,2078 @@ +#ifndef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +// silence warnings about getenv, strncpy, etc. +#define _CRT_SECURE_NO_WARNINGS +#endif +#endif + +#include "precommondefs.h" // This defines several system feature macros and should be included before any system header. + +// Bindings +#include "cppjit_interop.h" + +using namespace cppjit; +#include "callcontext.h" + +// ROOT +#include "TClass.h" +#include "TInterpreter.h" +#include "TROOT.h" +#include "TSystem.h" + +typedef cppjit::cpyrt::Parameter Parameter; + +static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } + +// Standard +#include // for std::count, std::remove +#include +#include +#include +#include // for getenv +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Take ROOT's interpreter lock (when it exists) around every CppInterOp +// entry point, so Python-side reflection serializes against TCling users on +// other threads (I/O, RDataFrame workers). +// The private recursive mutex still serializes concurrent Python-side callers +// when ROOT thread safety is not enabled. Lock order is global-then-local; no +// deadlock is possible since threads coming through TCling only ever take the +// global mutex. +class RInterOpMutex { +public: + void lock() { + // gInterpreterMutex may get created at any point (by + // ROOT::EnableThreadSafety), so remember for each acquisition what was + // actually locked, to release exactly that in unlock(). + TVirtualMutex* global = gInterpreterMutex; + if (global) + global->Lock(); + fLocal.lock(); + fGlobalLocked.push_back(global); + } + + void unlock() { + TVirtualMutex* global = fGlobalLocked.back(); + fGlobalLocked.pop_back(); + fLocal.unlock(); + if (global) + global->UnLock(); + } + +private: + std::recursive_mutex fLocal; + // Lock/unlock pairs are properly nested per thread, so a per-thread stack + // suffices to match each unlock() to its lock(). + static thread_local std::vector fGlobalLocked; +}; + +thread_local std::vector RInterOpMutex::fGlobalLocked; + +RInterOpMutex InterOpMutex; + +// builtin types +static std::set g_builtins = {"bool", + "char", + "signed char", + "unsigned char", + "wchar_t", + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", + "long double", + "void"}; + +// configuration +static bool gEnableFastPath = true; + +// global initialization ----------------------------------------------------- +namespace { + +static inline bool is_integral(std::string& s) { + if (s == "false") { + s = "0"; + return true; + } else if (s == "true") { + s = "1"; + return true; + } + return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { + return !std::isdigit(c); + }) == s.end(); +} + +struct InterOpPaths { + std::string Library; + std::vector IncludeDirs; + std::string ClangIncludeDir; // empty when no usable resource dir is known +}; + +// In ROOT the dispatch API comes from libCling (loadDispatchAPI) and the +// CppInterOp headers are staged under ROOT's etc directory, etc/cppinterop +// in the build tree and in the install tree alike, so TROOT::GetEtcDir() +// locates them in both. +static InterOpPaths cppinterop_paths() { + InterOpPaths Paths; + std::string dir = TROOT::GetEtcDir().Data(); + dir += "/cppinterop"; + if (gSystem->AccessPathName(dir.c_str())) + std::cerr << "[cppjit] CppInterOp headers not found in " << dir + << std::endl; + Paths.IncludeDirs.push_back(dir); + return Paths; +} + +// The one place the dispatch source is dlopen'd. In ROOT, CppInterOp is +// compiled into libCling, so the dispatch API is loaded from there instead of +// from a standalone libclangCppInterOp. +static bool loadDispatchAPI(const InterOpPaths& /*Paths*/) { + (void)gROOT; + char* libcling = gSystem->DynamicPathName("libCling"); + + if (!libcling) { + std::cerr << "[cppjit] Failed to find libCling" << std::endl; + return false; + } + if (!Cpp::LoadDispatchAPI(libcling)) { + std::cerr << "[cppjit] Failed to load CppInterOp" << std::endl; + return false; + } + return true; +} + +// CppInterOp itself appends CPPINTEROP_EXTRA_INTERPRETER_ARGS inside +// CreateInterpreter, so nothing needs to be forwarded from here. +static interop::TInterp_t +acquireOrCreateInterpreter(const InterOpPaths& Paths) { + if (auto existingInterp = Cpp::GetInterpreter()) + return existingInterp; + + std::vector args = {"-std=c++17"}; +#if !(defined(__arm64__) && defined(__APPLE__)) + // apple silicon clang rejects -march=native + args.push_back("-march=native"); +#endif + // Without clang's builtin headers the interpreter fails at its first + // #include. Prefer the bundled copy: it matches the build clang and + // needs no LLVM on the host. DetectResourceDir refuses version + // mismatches, and CppInterOp itself probes only bare `clang`. + std::string resourceDir = Paths.ClangIncludeDir; + if (resourceDir.empty()) + resourceDir = Cpp::DetectResourceDir("clang-" CPPJIT_CLANG_MAJOR); + if (!resourceDir.empty()) { + args.push_back("-resource-dir"); + args.push_back(resourceDir.c_str()); + } + return Cpp::CreateInterpreter(args, /*GpuArgs=*/{}); +} + +static void configureInterpreter(const InterOpPaths& Paths) { + std::set bi{g_builtins}; + for (const auto& name : bi) { + for (const char* a : {"*", "&", "*&", "[]", "*[]"}) + g_builtins.insert(name + a); + } + + if (getenv("CPPJIT_DISABLE_FASTPATH")) + gEnableFastPath = false; + + // set opt level (default to 2 if not given; Cling itself defaults to 0) + int optLevel = 2; + + if (getenv("CPPJIT_OPT_LEVEL")) + optLevel = atoi(getenv("CPPJIT_OPT_LEVEL")); + + if (optLevel != 0) { + std::ostringstream s; + s << "#pragma cling optimize " << optLevel; + Cpp::Process(s.str().c_str()); + } + + for (const std::string& dir : Paths.IncludeDirs) + Cpp::AddIncludePath(dir.c_str()); + Cpp::LoadLibrary("libstdc++", /* lookup= */ true); +} + +static void preloadHeaders() { + const char* code = "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for strcpy + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for the dispatcher code to + // use std::function + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#if __has_include()\n" + "#include \n" + "#endif\n" + "#include \n"; + Cpp::Process(code); +} + +static void defineRuntimeHelpers() { + Cpp::Declare("namespace __cppjit_internal { template" + " bool is_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 == c2); } }", + /*silent=*/false); + Cpp::Declare("namespace __cppjit_internal { template" + " bool is_not_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 != c2); } }", + /*silent=*/false); + + // helper for multiple inheritance + Cpp::Declare("namespace __cppjit_internal { struct Sep; }", + /*silent=*/false); +} + +} // unnamed namespace + +// Load CppInterOp and set up the interpreter. A dlopen during static +// initialization is unsafe, so _cpython_cppjit.py calls this explicitly +// before the first libcppjit use. Thread-safe and idempotent; returns 1 on +// success. +extern "C" { +RPY_EXPORTED int LoadCppInterOp(); +} + +extern "C" int LoadCppInterOp() { + static std::once_flag Once; + static int Loaded = 0; + std::call_once(Once, [] { + std::lock_guard Lock(InterOpMutex); + const InterOpPaths Paths = cppinterop_paths(); + if (!loadDispatchAPI(Paths)) + return; + + acquireOrCreateInterpreter(Paths); + configureInterpreter(Paths); + preloadHeaders(); + defineRuntimeHelpers(); + + Loaded = 1; + }); + return Loaded; +} + +// local helpers ------------------------------------------------------------- +static inline char* cppstring_to_cstring(const std::string& cppstr) { + char* cstr = (char*)malloc(cppstr.size() + 1); + memcpy(cstr, cppstr.c_str(), cppstr.size() + 1); + return cstr; +} + +// direct interpreter access ------------------------------------------------- +// Returns false on failure and true on success +bool interop::Compile(const std::string& code, bool silent) { + std::lock_guard Lock(InterOpMutex); + // Declare returns an enum which equals 0 on success + return !Cpp::Declare(code.c_str(), silent); +} + +void interop::LockInterpreter() { InterOpMutex.lock(); } + +void interop::UnlockInterpreter() { InterOpMutex.unlock(); } + +std::string interop::ToString(TCppScope_t klass, TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); + if (klass && obj && !Cpp::IsNamespace(klass)) + return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), + obj.data); + return ""; +} + +// // name to opaque C++ scope representation +// ----------------------------------- +std::string interop::ResolveName(const std::string& name) { + if (!name.empty()) { + if (interop::TCppType_t type = + interop::GetType(name, /*enable_slow_lookup=*/true)) + return interop::GetTypeAsString(interop::ResolveType(type)); + return name; + } + return ""; +} + +interop::TCppType_t interop::ResolveEnumReferenceType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) + return type; + + TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type); + if (Cpp::IsEnumType(nonReferenceType)) { + TCppType_t underlying_type = + Cpp::GetIntegerTypeFromEnumType(nonReferenceType); + return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false); + } + return type; +} + +interop::TCppType_t interop::ResolveEnumPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + if (!Cpp::IsPointerType(type)) + return type; + + TCppType_t PointeeType = Cpp::GetPointeeType(type); + if (Cpp::IsEnumType(PointeeType)) { + TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType); + return Cpp::GetPointerType(underlying_type); + } + return type; +} + +interop::TCppType_t int_like_type(interop::TCppType_t type) { + interop::TCppType_t check_int_typedefs = type; + if (Cpp::IsPointerType(check_int_typedefs)) + check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs); + if (Cpp::IsReferenceType(check_int_typedefs)) + check_int_typedefs = + Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false); + + if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" || + Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t") + return check_int_typedefs; + return nullptr; +} + +interop::TCppType_t interop::ResolveType(TCppType_t type) { + if (!type) + return type; + + std::lock_guard Lock(InterOpMutex); + + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return type; + + interop::TCppType_t canonType = Cpp::GetCanonicalType(type); + + if (Cpp::IsEnumType(canonType)) { + if (Cpp::GetTypeAsString(type) != "std::byte") + return Cpp::GetIntegerTypeFromEnumType(canonType); + } + if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { + return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict); + } + + return canonType; +} + +interop::TCppType_t interop::GetRealType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return check_int_typedefs; + return Cpp::GetUnderlyingType(type); +} + +interop::TCppType_t interop::GetPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetPointerType(type); +} + +interop::TCppType_t interop::GetReferencedType(TCppType_t type, bool rvalue) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetReferencedType(type, rvalue); +} + +bool interop::IsRValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue; +} + +bool interop::IsLValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue; +} + +// Whether the callee can rebind a pointer through this reference (T*&); +// false for T* const& and for references to non-pointers. +bool interop::IsMutablePtrRefType(TCppType_t type) { + if (!Cpp::IsReferenceType(type)) + return false; + TCppType_t nonref = Cpp::GetNonReferenceType(type); + return Cpp::IsPointerType(nonref) && + !Cpp::HasTypeQualifier(nonref, Cpp::QualKind::Const); +} + +bool interop::IsClassType(TCppType_t type) { return Cpp::IsRecordType(type); } + +bool interop::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) { + if (is_signed) { + Cpp::Signedness sign; + bool res = Cpp::IsIntegerType(type, &sign); + *is_signed = (sign == Cpp::Signedness::kSigned); + return res; + } + return Cpp::IsIntegerType(type, nullptr); +} + +bool interop::IsPointerType(TCppType_t type) { + return Cpp::IsPointerType(type); +} + +bool interop::IsFunctionPointerType(TCppType_t type) { + return Cpp::IsFunctionPointerType(type); +} + +std::string trim(const std::string& line) { + if (line.empty()) + return ""; + const char* WhiteSpace = " \t\v\r\n"; + std::size_t start = line.find_first_not_of(WhiteSpace); + std::size_t end = line.find_last_not_of(WhiteSpace); + return line.substr(start, end - start + 1); +} + +// returns false of angular brackets dont match, else true +bool split_comma_saparated_types(const std::string& name, + std::vector& types) { + std::string trimed_name = trim(name); + size_t start_pos = 0; + size_t end_pos = 0; + int matching_angular_brackets = 0; + while (end_pos < trimed_name.size()) { + switch (trimed_name[end_pos]) { + case ',': { + if (!matching_angular_brackets) { + if (end_pos > start_pos) + types.push_back( + trim(trimed_name.substr(start_pos, end_pos - start_pos))); + start_pos = end_pos + 1; + } + break; + } + case '<': { + matching_angular_brackets++; + break; + } + case '>': { + matching_angular_brackets--; + break; + } + } + end_pos++; + } + if (start_pos < trimed_name.size()) + types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos))); + return true; +} + +interop::TCppScope_t GetEnumFromCompleteName(const std::string& name) { + std::string delim = "::"; + size_t start = 0; + size_t end = name.find(delim); + interop::TCppScope_t curr_scope; + while (end != std::string::npos) { + curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope); + start = end + delim.length(); + end = name.find(delim, start); + } + return Cpp::GetNamed(name.substr(start, end), curr_scope); +} +static bool is_identifier(std::string_view s) { + if (s.empty()) + return false; + auto is_valid_start = [](unsigned char c) { + return std::isalpha(c) || c == '_'; + }; + auto is_valid_body = [](unsigned char c) { + return std::isalnum(c) || c == '_'; + }; + return is_valid_start(s[0]) && + std::all_of(s.begin() + 1, s.end(), is_valid_body); +}; + +// returns true if no new type was added. +bool interop::AppendTypesSlow(const std::string& name, + std::vector& types, + interop::TCppScope_t parent) { + + // Add no new type if string is empty + if (name.empty()) + return true; + + // The ast printer gave us garbage. + if (name == "") + return true; + + // A type inside an anonymous namespace cannot be spelled in injected + // code; attempting it crashes codegen on the ill-formed recovery (e.g. + // auto-downcasting to an anonymous FuncExporter<...> instantiation). + if (name.find("(anonymous namespace)") != std::string::npos) + return true; + + auto replace_all = [](std::string& str, const std::string& from, + const std::string& to) { + if (from.empty()) + return; + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + }; + + std::string resolved_name = name; + replace_all(resolved_name, "std::initializer_list<", + "std::vector<"); // replace initializer_list with vector + + // If we have a single identifier, we don't need anything complicated. + // Try scoped lookup first (catches type aliases / nested types declared + // inside `parent`), then fall back to TU (catches typedefs declared + // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted + // from a method on Foo). + if (is_identifier(name)) { + TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr; + if (!type) + type = Cpp::GetType(name); + if (type) { + types.emplace_back(type.data); + return false; + } + if (!parent || parent == Cpp::GetGlobalScope()) + return true; + // Fall through: the name may live in a scope enclosing `parent` (e.g. + // "RVecF" from within ROOT::RDF::RInterface<...> resolves to + // ROOT::RVecF); the trampoline below applies real unqualified lookup. + } + + std::lock_guard Lock(InterOpMutex); + + // We might have an entire expression such as int, double. + static unsigned long long struct_count = 0; + std::string code = + "template struct __cppjit_interop_AppendTypesSlow {};\n"; + if (!struct_count) + Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline + + // Perform the lookup from within the innermost (reopenable) namespace + // enclosing `parent` -- `parent` itself if it is a namespace, else the + // namespace its class chain lives in -- by reopening that namespace around + // the trampoline declaration: the compiler then applies real C++ name + // lookup, walking outward with proper shadowing of outer names, which a + // global declaration with a parent-qualified fallback cannot emulate (e.g. + // "RVecF" from within ROOT::RDF::RInterface<...> resolves to ROOT::RVecF). + std::string lookup_ns; + TCppScope_t lookup_scope = nullptr; + for (TCppScope_t s = parent; s && s != Cpp::GetGlobalScope(); + s = Cpp::GetParentScope(s)) { + if (!Cpp::IsNamespace(s)) + continue; + std::string qname = Cpp::GetQualifiedName(s); + if (!qname.empty() && qname.find('(') == std::string::npos) { // unnamed? + lookup_ns = qname; + lookup_scope = s; + } + break; + } + + // The reopened namespace doesn't see names nested in a class parent, so a + // name written relative to it (e.g. a nested type) won't resolve. Try the + // name as given, then qualified by the parent. + std::vector candidates = {resolved_name}; + if (parent && parent != Cpp::GetGlobalScope() && parent != lookup_scope && + (interop::IsNamespace(parent) || interop::IsClass(parent))) + candidates.push_back(Cpp::GetQualifiedCompleteName(parent) + + "::" + resolved_name); + + for (const std::string& candidate : candidates) { + std::string var = "__cppjit_interop_s" + std::to_string(struct_count++); + // nodebug: with -g the variable's debug info would carry the full DIE + // tree of every template argument (all member declarations included) -- + // a large, uncacheable per-lookup cost on heavyweight types. + std::string decl = "__cppjit_interop_AppendTypesSlow<" + candidate + + "> __attribute__((nodebug)) " + var + ";\n"; + if (!lookup_ns.empty()) + decl = "namespace " + lookup_ns + " { " + decl + "}\n"; + if (!Cpp::Declare(decl.c_str(), /*silent=*/true)) { + TCppType_t varN = Cpp::GetVariableType(Cpp::GetNamed( + var.c_str(), lookup_ns.empty() ? nullptr : lookup_scope)); + TCppScope_t instance_class = Cpp::GetScopeFromType(varN); + if (!instance_class) + continue; // recovered-but-broken decl; try next candidate or split path + size_t oldSize = types.size(); + Cpp::GetClassTemplateInstantiationArgs(instance_class, types); + return oldSize == types.size(); + } + } + + // We split each individual types based on , and resolve it + // FIXME: see discussion on should we support template instantiation with + // string: + // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491 + // We should consider eliminating the `split_comma_saparated_types` and + // `is_integral` string parsing. + std::vector individual_types; + if (!split_comma_saparated_types(resolved_name, individual_types)) + return true; + + for (std::string& i : individual_types) { + // Try going via interop::GetType first. + const char* integral_value = nullptr; + interop::TCppType_t type = nullptr; + + if (!lookup_ns.empty()) { + // resolve from within the parent namespace, honouring C++ name lookup + // and shadowing (see the trampoline declaration above) + std::string id = "__cppjit_interop_s" + std::to_string(struct_count++); + if (!Cpp::Declare(("namespace " + lookup_ns + " { using " + id + + " = __typeof__(" + i + "); }\n") + .c_str(), + /*silent=*/true)) + type = Cpp::GetCanonicalType( + Cpp::GetTypeFromScope(Cpp::GetNamed(id, lookup_scope))); + } else { + type = GetType(i, /*enable_slow_lookup=*/true); + if (!type && parent && + (interop::IsNamespace(parent) || interop::IsClass(parent))) { + type = + interop::GetTypeFromScope(interop::GetNamed(resolved_name, parent)); + } + } + + if (!type) { + types.clear(); + return true; + } + + if (is_integral(i)) + integral_value = strdup(i.c_str()); + if (TCppScope_t scope = GetEnumFromCompleteName(i)) + if (Cpp::IsEnumConstant(scope)) + integral_value = + strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str()); + types.emplace_back(type.data, integral_value); + } + return false; +} + +interop::TCppType_t interop::GetType(const std::string& name, + bool enable_slow_lookup /* = false */) { + // The ast printer gave us garbage. + if (name == "") + return nullptr; + std::lock_guard Lock(InterOpMutex); + + if (auto type = Cpp::GetType(name)) + return type; + + // Plain identifiers don't need the heavy __typeof__ trampoline: + // Cpp::GetType above already covers builtin types and named + // scopes. Exception: the three identifier-shaped C++ value- + // literals -- `true`, `false`, `nullptr` -- aren't reachable by + // name (no type called "false") but appear as non-type template + // args in libstdc++ types like _Node_iterator<..., false, false>; + // map them to their underlying type directly so the per-chunk + // fallback in AppendTypesSlow gets a real type without paying + // the trampoline cost. + if (is_identifier(name)) { + if (name == "true" || name == "false") + return Cpp::GetType("bool"); + if (name == "nullptr") + return Cpp::GetType("nullptr_t", Cpp::GetNamed("std")); + return nullptr; + } + + if (!enable_slow_lookup) { + if (name.find("::") != std::string::npos) + throw std::runtime_error( + "Calling interop::GetType with qualified name '" + name + "'\n"); + return nullptr; + } + + // Here we might need to deal with integral types such as 3.14. + + // Declaring the trampoline parses and compiles code, and each one adds a + // new alias to the AST, so memoize the resolved types by name. A name that + // fails to resolve may succeed once more declarations are available, so + // failures are retried. + static std::map s_slow_type_cache; + auto cached = s_slow_type_cache.find(name); + if (cached != s_slow_type_cache.end()) + return cached->second; + + static unsigned long long var_count = 0; + std::string id = "__cppjit_interop_GetType_" + std::to_string(var_count++); + std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n"; + + if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) { + TCppScope_t lookup = Cpp::GetNamed(id); + TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup); + TCppType_t result = Cpp::GetCanonicalType(lookup_ty); + if (result) + s_slow_type_cache.emplace(name, result); + return result; + } + return nullptr; +} + +interop::TCppType_t interop::GetComplexType(const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetComplexType(Cpp::GetType(name)); +} + +std::string interop::ResolveEnum(TCppScope_t handle) { + std::lock_guard Lock(InterOpMutex); + std::string type = + Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(handle)); + if (type == "signed char") + return "char"; + return type; +} + +interop::TCppScope_t interop::GetUnderlyingScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetUnderlyingScope(scope); +} + +interop::TCppScope_t interop::GetScope(const std::string& name, + TCppScope_t parent_scope) { + std::unique_lock Lock(InterOpMutex); + // CppInterOp directly looks at the AST which is not enough. + // We require lazy module loading that ROOT relies on, so we do it here + // first. Use TClass::GetClass to trigger auto-loading of dictionaries and + // modules. + if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) + TClass::GetClass(name.c_str(), true /* load */, true /* silent */); + + if (interop::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) + return scope; + if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) { + if (interop::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name)) + return scope; + } else if (name.find('<') == std::string::npos) { + // Cpp::GetScope handles single identifiers only, so walk the components + // of a qualified name. Templated names take the branch below: "::" inside + // a template argument list cannot be split naively. + TCppScope_t curr = parent_scope; + size_t start = 0, end; + while (curr && (end = name.find("::", start)) != std::string::npos) { + curr = Cpp::GetScope(name.substr(start, end - start), curr); + start = end + 2; + } + if (curr && curr != parent_scope) + if (interop::TCppScope_t scope = Cpp::GetScope(name.substr(start), curr)) + return scope; + } + + // FIXME: avoid string parsing here + if (name.find('<') != std::string::npos) { + // Templated type; may need instantiation. Resolve the whole type + // expression (e.g. "std::array") and read back its scope. + // Splitting off the argument list and resolving it directly cannot + // represent non-type arguments such as the `3` in std::array. + std::vector types; + Lock.unlock(); // unlock to allow AppendTypesSlow + bool added_new_type = + !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope); + Lock.lock(); + if (added_new_type && types.size() == 1) { + // A pointer or reference spelling (e.g. "std::chrono::nanoseconds *", + // the return type of std::array::begin()) does not + // name a scope; GetScopeFromType would silently strip the pointer and + // return the pointee's scope, misclassifying the name. + if (Cpp::IsPointerType(types[0].m_Type) || + Cpp::IsReferenceType(types[0].m_Type)) + return nullptr; + TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type); + // Naming the type as a template argument above does not instantiate + // it, so the specialization may still be declared-but-undefined. + // Force its definition: callers expect a complete scope, e.g. to + // walk its base classes. + if (scope) + Cpp::IsComplete(scope); + return scope; + } + } + return nullptr; +} + +interop::TCppScope_t interop::GetFullScope(const std::string& name) { + return interop::GetScope(name); +} + +interop::TCppScope_t interop::GetTypeScope(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType(Cpp::GetVariableType(var)); +} + +interop::TCppScope_t interop::GetNamed(const std::string& name, + TCppScope_t parent_scope) { + std::lock_guard Lock(InterOpMutex); + if (TCppScope_t named = Cpp::GetNamed(name, parent_scope)) + return named; + + // "gROOT" is a macro expanding to ROOT::GetROOT(); ROOT master exposed it + // to cppyy as a TGlobalMappedFunction, a mechanism this backend does not + // consult. Resolve it to the underlying variable instead (making sure it + // has been initialized first). + if (name == "gROOT" && + (!parent_scope || parent_scope == Cpp::GetGlobalScope())) { + ROOT::GetROOT(); + return Cpp::GetNamed( + "gROOTLocal", + Cpp::GetNamed("Internal", Cpp::GetNamed("ROOT", nullptr))); + } + + return nullptr; +} + +interop::TCppScope_t interop::GetParentScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetParentScope(scope); +} + +interop::TCppScope_t interop::GetScopeFromType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType(type); +} + +interop::TCppType_t interop::GetTypeFromScope(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeFromScope(klass); +} + +interop::TCppScope_t interop::GetGlobalScope() { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetGlobalScope(); +} + +bool interop::IsTemplate(TCppScope_t handle) { return Cpp::IsTemplate(handle); } + +bool interop::IsTemplateInstantiation(TCppScope_t handle) { + return Cpp::IsTemplateSpecialization(handle); +} + +bool interop::IsTypedefed(TCppScope_t handle) { + return Cpp::IsTypedefed(handle); +} + +namespace { +class AutoCastRTTI { +public: + virtual ~AutoCastRTTI() {} +}; +} // namespace + +interop::TCppScope_t interop::GetActualClass(TCppScope_t klass, + TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); + + if (!obj || !Cpp::IsClassPolymorphic(klass)) + return klass; + + // Skip the std stream hierarchy: autocasting it is not useful, and on MSVC + // its virtual inheritance puts the vbptr, not a vfptr, at offset 0, so the + // RTTI probe below follows a garbage pointer. + static const std::array stream_bases = { + interop::GetScope("std::ios_base"), interop::GetScope("std::streambuf"), + interop::GetScope("std::wstreambuf")}; + for (TCppScope_t base : stream_bases) + if (base && interop::IsSubclass(klass, base)) + return klass; + + // This is hit for every polymorphic object handed to Python, and the name + // lookups below cost microseconds, so their outcomes are memoized. Keyed on + // (static class, dynamic type). Only outcomes that cannot change later are + // cached; a failed lookup may succeed once more declarations are available, + // so failures are retried. + static std::map, TCppScope_t> + s_actual_class_cache; + const std::type_info* typ = &typeid(*(AutoCastRTTI*)obj.data); + if (!typ) + return klass; + const auto cacheKey = std::make_pair((const void*)klass.data, typ); + auto cached = s_actual_class_cache.find(cacheKey); + if (cached != s_actual_class_cache.end()) + return cached->second; + +#ifdef _WIN32 + // MSVC's type_info::name() is already human-readable, but prefixed with + // the tag kind ("class TWinNTSystem"), which plain name lookup does not + // accept. Strip the prefix by hand; respelling through type resolution + // would JIT an uncached declaration per downcast. Template arguments + // keep their tags, which is harmless as templated names go through type + // resolution rather than plain lookup. + std::string demangled_name = typ->name(); + for (const char* prefix : {"class ", "struct ", "union ", "enum "}) { + if (demangled_name.compare(0, strlen(prefix), prefix) == 0) { + demangled_name = demangled_name.substr(strlen(prefix)); + break; + } + } +#else + std::string mangled_name = typ->name(); + std::string demangled_name = Cpp::Demangle(mangled_name); +#endif + + // A type in an anonymous namespace cannot be named in injected code and + // has no dictionary, so looking it up would fail; worse, its unspellable + // name crashes the interpreter ("(anonymous namespace)" on Itanium, + // "`anonymous namespace'" on MSVC). Keep the base type. + if (demangled_name.find("anonymous namespace") != std::string::npos) + return klass; + + if (TCppScope_t scope = interop::GetScope(demangled_name)) { + // A type inside an anonymous namespace cannot be spelled in injected + // code (e.g. the dispatcher's), so it is unusable as a cast target; + // keep the static type. Anonymous namespaces are the ones without a + // name of their own. + for (TCppScope_t p = Cpp::GetParentScope(scope); p; + p = Cpp::GetParentScope(p)) + if (Cpp::IsNamespace(p) && Cpp::GetName(p).empty()) + return klass; + // Only return the derived type once it has a complete definition. Under + // runtime_cxxmodules=OFF, autoloading the dictionary registers only a + // forward declaration, so force the definition into the AST here. Internal + // classes like TCling have no header and cannot be completed (their + // CXXRecordDecl has no DefinitionData); GetOrForceDefinition returns null + // for them and we fall back to the base type, avoiding a crash when + // querying offsets. + if (Cpp::GetOrForceDefinition(scope)) { + s_actual_class_cache.emplace(cacheKey, scope); + return scope; + } + } + + return klass; +} + +size_t interop::SizeOf(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::SizeOf(klass); +} + +size_t interop::SizeOfType(TCppType_t klass) { + if (!klass) + return 0; + std::lock_guard Lock(InterOpMutex); + return Cpp::GetSizeOfType(klass); +} + +bool interop::IsBuiltin(const std::string& type_name) { + static std::set s_builtins = {"bool", + "char", + "signed char", + "unsigned char", + "wchar_t", + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", + "long double", + "void"}; + if (s_builtins.find(trim(type_name)) != s_builtins.end()) + return true; + + if (strstr(type_name.c_str(), "std::complex")) + return true; + + return false; +} + +bool interop::IsBuiltin(TCppType_t type) { return Cpp::IsBuiltin(type); } + +bool interop::IsComplete(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::IsComplete(scope); +} + +// // memory management +// --------------------------------------------------------- +interop::TCppObject_t interop::Allocate(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::Allocate(scope, /*count=*/1); +} + +void interop::Deallocate(TCppScope_t scope, TCppObject_t instance) { + std::lock_guard Lock(InterOpMutex); + Cpp::Deallocate(scope, instance, /*count=*/1); +} + +// Constructors and destructors are resolved (and JIT-compiled) under the +// lock and run without it, as WrapperCall does: their bodies are user code. +interop::TCppObject_t interop::Construct(TCppScope_t scope, + void* arena /*=nullptr*/) { + std::unique_lock Lock(InterOpMutex); + if (!Cpp::HasDefaultConstructor(scope)) + return nullptr; + Cpp::JitCall JC = + Cpp::MakeFunctionCallable(Cpp::GetDefaultConstructor(scope)); + Lock.unlock(); + if (!JC) + return nullptr; + void* result = arena; + JC.InvokeConstructor(&result, /*nary=*/1, /*args=*/{}, + /*is_arena=*/arena ? reinterpret_cast(1) + : nullptr); + return result; +} + +static Cpp::JitCall destructor_callable(interop::TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::MakeFunctionCallable(Cpp::GetDestructor(scope)); +} + +void interop::Destruct(TCppScope_t scope, TCppObject_t instance) { + if (Cpp::JitCall JC = destructor_callable(scope)) + JC.InvokeDestructor(instance.data, /*nary=*/0, /*withFree=*/true); +} + +static inline bool copy_args(Parameter* args, size_t nargs, void** vargs) { + bool runRelease = false; + for (size_t i = 0; i < nargs; ++i) { + switch (args[i].fTypeCode) { + case 'X': /* (void*)type& with free */ + runRelease = true; + case 'V': /* (void*)type& */ + vargs[i] = args[i].fValue.fVoidp; + break; + case 'r': /* const type& */ + vargs[i] = args[i].fRef; + break; + default: /* all other types in union */ + vargs[i] = (void*)&args[i].fValue.fVoidp; + break; + } + } + return runRelease; +} + +static inline void release_args(Parameter* args, size_t nargs) { + for (size_t i = 0; i < nargs; ++i) { + if (args[i].fTypeCode == 'X') + free(args[i].fValue.fVoidp); + } +} + +static inline bool WrapperCall(interop::TCppMethod_t method, size_t nargs, + void* args_, void* self, void* result) { + Parameter* args = (Parameter*)args_; + // bool is_direct = nargs & DIRECT_CALL; + nargs = CALL_NARGS(nargs); + + // if (!is_ready(wrap, is_direct)) + // return false; // happens with compilation error + InterOpMutex.lock(); + if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) { + InterOpMutex.unlock(); + bool runRelease = false; + // const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; + if (nargs <= cpyrt::SMALL_ARGS_N) { + void* smallbuf[cpyrt::SMALL_ARGS_N]; + if (nargs) + runRelease = copy_args(args, nargs, smallbuf); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {smallbuf, nargs}, self); + // _CLING_CATCH_UNCAUGHT + } else { + std::vector buf(nargs); + runRelease = copy_args(args, nargs, buf.data()); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {buf.data(), nargs}, self); + // _CLING_CATCH_UNCAUGHT + } + if (runRelease) + release_args(args, nargs); + return true; + } + InterOpMutex.unlock(); + return false; +} + +template +static inline T CallT(interop::TCppMethod_t method, interop::TCppObject_t self, + size_t nargs, void* args) { + T t{}; + if (WrapperCall(method, nargs, args, self.data, &t)) + return t; + throw std::runtime_error("failed to resolve function"); + return (T)-1; +} + +#ifdef PRINT_DEBUG +#define _IMP_CALL_PRINT_STMT(type) printf("IMP CALL with type: %s\n", #type); +#else +#define _IMP_CALL_PRINT_STMT(type) +#endif + +#define CPPJIT_IMP_CALL(typecode, rtype) \ + rtype interop::Call##typecode(TCppMethod_t method, TCppObject_t self, \ + size_t nargs, void* args) { \ + _IMP_CALL_PRINT_STMT(rtype) \ + return CallT(method, self, nargs, args); \ + } + +void interop::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args) { + if (!WrapperCall(method, nargs, args, self.data, nullptr)) + return /* TODO ... report error */; +} + +// clang-format off +CPPJIT_IMP_CALL(B, unsigned char) +CPPJIT_IMP_CALL(C, char ) +CPPJIT_IMP_CALL(H, short ) +CPPJIT_IMP_CALL(I, int ) +CPPJIT_IMP_CALL(L, long ) +CPPJIT_IMP_CALL(LL, long long ) +CPPJIT_IMP_CALL(F, float ) +CPPJIT_IMP_CALL(D, double ) +CPPJIT_IMP_CALL(LD, long double ) +// clang-format on + +void* interop::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args) { + void* r = nullptr; + if (WrapperCall(method, nargs, args, self.data, &r)) + return r; + return nullptr; +} + +char* interop::CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args, size_t* length) { + char* cstr = nullptr; + // TClassRef cr("std::string"); // TODO: Why is this required? + std::string* cppresult = (std::string*)malloc(sizeof(std::string)); + if (WrapperCall(method, nargs, args, self.data, (void*)cppresult)) { + cstr = cppstring_to_cstring(*cppresult); + *length = cppresult->size(); + cppresult->std::string::~basic_string(); + } else + *length = 0; + free((void*)cppresult); + return cstr; +} + +interop::TCppObject_t interop::CallConstructor(TCppMethod_t method, + TCppScope_t /*klass*/, + size_t nargs, void* args) { + void* obj = nullptr; + WrapperCall(method, nargs, args, nullptr, &obj); + return (TCppObject_t)obj; +} + +void interop::CallDestructor(TCppScope_t scope, TCppObject_t self) { + if (Cpp::JitCall JC = destructor_callable(scope)) + JC.InvokeDestructor(self.data, /*nary=*/0, /*withFree=*/false); +} + +interop::TCppObject_t interop::CallO(TCppMethod_t method, TCppObject_t self, + size_t nargs, void* args, + TCppType_t result_type) { + size_t size = interop::SizeOfType(result_type); + if (size == 0) + return TCppObject_t{}; // unsizable return type; the caller reports + void* obj = ::operator new(size); + if (WrapperCall(method, nargs, args, self.data, obj)) + return (TCppObject_t)obj; + ::operator delete(obj); + return TCppObject_t{}; +} + +interop::TCppFuncAddr_t interop::GetFunctionAddress(TCppMethod_t method, + bool /*check_enabled*/) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionAddress(method); +} + +// handling of function argument buffer -------------------------------------- +void* interop::AllocateFunctionArgs(size_t nargs) { + return new Parameter[nargs]; +} + +void interop::DeallocateFunctionArgs(void* args) { delete[] (Parameter*)args; } + +size_t interop::GetFunctionArgSizeof() { return sizeof(Parameter); } + +size_t interop::GetFunctionArgTypeoffset() { + return offsetof(Parameter, fTypeCode); +} + +// scope reflection information ---------------------------------------------- +bool interop::IsNamespace(TCppScope_t scope) { + if (!scope) + return false; + + // Test if this scope represents a namespace. + std::lock_guard Lock(InterOpMutex); + return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; +} + +bool interop::IsClass(TCppScope_t scope) { + // Test if this scope represents a namespace. + return Cpp::IsClass(scope); +} +// +bool interop::IsAbstract(TCppScope_t scope) { + // Test if this type may not be instantiated. + return Cpp::IsAbstract(scope); +} + +bool interop::IsEnumScope(TCppScope_t scope) { return Cpp::IsEnumScope(scope); } + +bool interop::IsEnumConstant(TCppScope_t scope) { + return Cpp::IsEnumConstant(interop::GetUnderlyingScope(scope)); +} + +bool interop::IsEnumType(TCppType_t type) { return Cpp::IsEnumType(type); } + +bool interop::IsAggregate(TCppScope_t type) { + // Test if this type is a "plain old data" type + return Cpp::IsAggregate(type); +} + +bool interop::IsDefaultConstructable(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + // Test if this type has a default constructor or is a "plain old data" type + return Cpp::HasDefaultConstructor(scope); +} + +bool interop::IsVariable(TCppScope_t scope) { return Cpp::IsVariable(scope); } + +void interop::GetAllCppNames(TCppScope_t scope, + std::set& cppnames) { + // Collect all known names of C++ entities under scope. This is useful for + // IDEs employing tab-completion, for example. Note that functions names need + // not be unique as they can be overloaded. + std::lock_guard Lock(InterOpMutex); + Cpp::GetAllCppNames(scope, cppnames); +} + +// class reflection information ---------------------------------------------- +std::vector +interop::GetUsingNamespaces(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetUsingNamespaces(scope); +} + +// Normalize a type or scope name to cppyy's canonical form: no space after +// the commas separating template arguments, and pointers/references attached +// to the type. This is the form cpyrt itself constructs (e.g. when +// looking up cached template instantiations by name, see +// Utility::ConstructTemplateArgs) and the convention that user code and the +// test suite inherited from upstream cppyy; clang's printer instead emits +// "a, b", "T *" and "T &". +static std::string cppyy_normalize_name(std::string name) { + std::string::size_type pos = 0; + while ((pos = name.find(", ", pos)) != std::string::npos) + name.erase(pos + 1, 1); + pos = 0; + while ((pos = name.find(" *", pos)) != std::string::npos) + name.erase(pos, 1); + pos = 0; + while ((pos = name.find(" &", pos)) != std::string::npos) + name.erase(pos, 1); + return name; +} + +// class reflection information ---------------------------------------------- +std::string interop::GetFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return cppyy_normalize_name( + Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass))); +} + +std::string interop::GetScopedFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return cppyy_normalize_name(Cpp::GetQualifiedCompleteName(klass)); +} + +bool interop::HasVirtualDestructor(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + TCppMethod_t func = Cpp::GetDestructor(scope); + return Cpp::IsVirtualMethod(func); +} + +interop::TCppIndex_t interop::GetNumBases(TCppScope_t klass) { + // Get the total number of base classes that this class has. + std::lock_guard Lock(InterOpMutex); + return Cpp::GetNumBases(klass); +} + +//////////////////////////////////////////////////////////////////////////////// +/// \fn interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t +/// klass) \brief Retrieve number of base classes in the longest branch of the +/// inheritance tree of the input class. +/// \param[in] klass The class to start the retrieval process from. +/// +/// This is a helper function for interop::GetNumBasesLongestBranch. +/// Given an inheritance tree, the function assigns weight 1 to each class that +/// has at least one base. Starting from the input class, the function is +/// called recursively on all the bases. For each base the return value is one +/// (the weight of the base itself) plus the maximum value retrieved for their +/// bases in turn. For example, given the following inheritance tree: +/// +/// ~~~{.cpp} +/// class A {}; class B: public A {}; +/// class X {}; class Y: public X {}; class Z: public Y {}; +/// class C: public B, Z {}; +/// ~~~ +/// +/// calling this function on an instance of `C` will return 3, the steps +/// required to go from C to X. +interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t klass) { + std::vector num; + for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase) + num.push_back( + GetNumBasesLongestBranch(interop::GetBaseScope(klass, ibase))); + if (num.empty()) + return 0; + return *std::max_element(num.begin(), num.end()) + 1; +} + +std::string interop::GetBaseName(TCppScope_t klass, TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetName(Cpp::GetBaseClass(klass, ibase)); +} + +interop::TCppScope_t interop::GetBaseScope(TCppScope_t klass, + TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetBaseClass(klass, ibase); +} + +bool interop::IsSubclass(TCppScope_t derived, TCppScope_t base) { + std::lock_guard Lock(InterOpMutex); + // Checked on every method call that receives 'self' as its first argument + // (e.g. from pythonizations and protocol slots), so memoize per class + // pair. A class that is still incomplete can gain bases once its + // definition is loaded, so a negative answer is only cached for complete + // classes. + static std::map, bool> s_subclass_cache; + const auto cacheKey = std::make_pair(derived.data, base.data); + auto cached = s_subclass_cache.find(cacheKey); + if (cached != s_subclass_cache.end()) + return cached->second; + bool result = Cpp::IsSubclass(derived, base); + if (result || (Cpp::IsComplete(derived) && Cpp::IsComplete(base))) + s_subclass_cache.emplace(cacheKey, result); + return result; +} + +static std::set gSmartPtrTypes = { + "std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"}; + +bool interop::IsSmartPtr(TCppScope_t klass) { + const std::string& rn = interop::GetScopedFinalName(klass); + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) + return true; + return false; +} + +bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw, + TCppMethod_t* deref) { + // TODO: We can directly accept scope instead of name + const std::string& rn = ResolveName(tname); + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end()) + return false; + + if (!raw && !deref) + return true; + + TCppScope_t scope = interop::GetScope(rn); + if (!scope) + return false; + + std::vector ops; + { + std::lock_guard Lock(InterOpMutex); + Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, + /*kind=*/Cpp::OperatorArity::kBoth); + if (ops.size() != 1) + return false; + + // The dereference operator can be a member template: MSVC's + // std::shared_ptr::operator-> is SFINAE-constrained on the element + // type not being an array. Its return type is dependent, so it has + // to be instantiated (it takes no arguments) before the pointee + // type can be determined - and before it can be called. + if (Cpp::IsTemplatedFunction(ops[0])) + ops[0] = Cpp::BestOverloadFunctionMatch(ops, {}, {}); + if (!ops[0]) + return false; + } + + if (deref) + *deref = ops[0]; + if (raw) + *raw = interop::GetScopeFromType(interop::GetMethodReturnType(ops[0])); + return (!deref || *deref) && (!raw || *raw); +} + +// type offsets -------------------------------------------------------------- +ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, + TCppObject_t /*address*/, int direction, + bool rerror) { + std::lock_guard Lock(InterOpMutex); + + // Queried for every downcast object handed to Python; computing it walks + // the inheritance paths, so memoize per class pair. The object address is + // not used, so the offset is fixed per pair once both records are complete. + static std::map, intptr_t> + s_base_offset_cache; + const auto cacheKey = std::make_pair(derived.data, base.data); + auto cached = s_base_offset_cache.find(cacheKey); + intptr_t offset = 0; + if (cached != s_base_offset_cache.end()) { + offset = cached->second; + } else { + // An incomplete record has no walkable bases (GetBaseClassOffset would + // read an empty base path), and the answer may change once the + // definition is loaded, so treat it like the error case below and retry + // next time instead of caching. + if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base)) + return rerror ? (ptrdiff_t)-1 : 0; + + offset = Cpp::GetBaseClassOffset(derived, base); + + if (offset == -1) // Cling error, treat silently + return rerror ? (ptrdiff_t)offset : 0; + + s_base_offset_cache.emplace(cacheKey, offset); + } + + return (ptrdiff_t)(direction < 0 ? -offset : offset); +} + +// method/function reflection information ------------------------------------ +// A deleted overload is not callable, and leaving it in the set adds a +// spurious conversion error to every failed-call report. +static void +remove_deleted_methods(std::vector& methods) { + methods.erase(std::remove_if(methods.begin(), methods.end(), + [](interop::TCppMethod_t m) { + return Cpp::IsFunctionDeleted(m); + }), + methods.end()); +} + +void interop::GetClassMethods(TCppScope_t scope, + std::vector& methods) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetClassMethods(scope, methods); + remove_deleted_methods(methods); +} + +std::vector +interop::GetMethodsFromName(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + std::vector methods = + Cpp::GetFunctionsUsingName(scope, name); + remove_deleted_methods(methods); + return methods; +} + +std::string interop::GetName(TCppScope_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetName(method); +} + +std::string interop::GetFullName(TCppScope_t method) { + std::lock_guard Lock(InterOpMutex); + return cppyy_normalize_name(Cpp::GetCompleteName(method)); +} + +interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionReturnType(method); +} + +std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + TCppType_t ret = Cpp::GetCanonicalType(Cpp::GetFunctionReturnType(method)); + // C++ deletes top-level cv-qualifiers on non-class return types from the + // function type ([dcl.fct]); mirror that so name matching sees the plain + // type, e.g. "unsigned long" for `static const size_t size()`. Class + // types keep the qualifier: it stays part of the function type there + // (e.g. for override matching in the dispatcher). + if (ret && !Cpp::IsRecordType(ret)) + ret = Cpp::RemoveTypeQualifier(ret, + Cpp::QualKind::Const | Cpp::QualKind::Volatile); + return Cpp::GetTypeAsString(ret); +} + +interop::TCppIndex_t interop::GetMethodNumArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionNumArgs(method); +} + +interop::TCppIndex_t interop::GetMethodReqArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionRequiredArgs(method); +} + +std::string interop::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { + if (!method) + return ""; + + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgName(method, iarg); +} + +interop::TCppType_t interop::GetMethodArgType(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgType(method, iarg); +} + +std::string interop::GetMethodArgTypeAsString(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( + Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); +} + +std::string interop::GetMethodArgCanonTypeAsString(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( + Cpp::GetCanonicalType(Cpp::GetFunctionArgType(method, iarg))); +} + +std::string interop::GetMethodArgDefault(TCppMethod_t method, + TCppIndex_t iarg) { + if (!method) + return ""; + + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgDefault(method, iarg); +} + +interop::TCppIndex_t +interop::CompareMethodArgType(TCppMethod_t /*method*/, TCppIndex_t /*iarg*/, + const std::string& /*req_type*/) { + // if (method) { + // TFunction* f = m2f(method); + // TMethodArg* arg = (TMethodArg + // *)f->GetListOfMethodArgs()->At((int)iarg); void *argqtp = + // gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); + + // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); + // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); + + // if (ArgSimilarityScore(argqtp, reqqtp) < 10) { + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // else { // Match using underlying types + // if(gInterpreter->IsPointerType(argqtp)) + // argqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp)); + + // // Handles reference types and strips qualifiers + // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp); + // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp); + // argqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul))); + // reqqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul))); + + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // } + return 0; // Method is not valid +} + +std::string interop::GetMethodSignature(TCppMethod_t method, + bool show_formal_args, + TCppIndex_t max_args) { + std::ostringstream sig; + sig << "("; + int nArgs = GetMethodNumArgs(method); + if (max_args != (TCppIndex_t)-1) + nArgs = std::min(nArgs, (int)max_args); + for (int iarg = 0; iarg < nArgs; ++iarg) { + sig << cppyy_normalize_name( + interop::GetMethodArgTypeAsString(method, iarg)); + if (show_formal_args) { + std::string argname = interop::GetMethodArgName(method, iarg); + if (!argname.empty()) + sig << " " << argname; + std::string defvalue = interop::GetMethodArgDefault(method, iarg); + if (!defvalue.empty()) + sig << " = " << defvalue; + } + if (iarg != nArgs - 1) + sig << ", "; + } + sig << ")"; + return sig.str(); +} + +interop::TCppType_t interop::GetFnTypeFromStdFn(TCppType_t fn_type) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) + : fn_type; + fn_type = + Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + TCppScope_t scope = Cpp::GetScopeFromType(fn_type); + std::vector args; + Cpp::GetClassTemplateArgs(scope, args); + assert(args.size() == 1); + if (args.size() == 1) + return args[0].m_Type; + return nullptr; +} + +void interop::GetFnTypeSig(TCppType_t fn_type, + std::vector& arg_types) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) + : fn_type; + fn_type = + Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + Cpp::GetFnTypeSignature(fn_type, arg_types); +} + +bool interop::IsSameType(TCppType_t typ1, TCppType_t typ2) { + return Cpp::IsSameType(typ1, typ2); +} + +bool interop::IsFunctionType(TCppType_t typ) { + typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ; + typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ; + return Cpp::IsFunctionProtoType(typ); +} + +bool interop::IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2) { + typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1; + typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2; + typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1; + typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2; + return Cpp::IsSameType(typ1, typ2); +} + +std::string interop::GetDoxygenComment(TCppScope_t scope, bool strip_markers) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetDoxygenComment(scope, strip_markers); +} + +bool interop::IsConstMethod(TCppMethod_t method) { + if (!method) + return false; + std::lock_guard Lock(InterOpMutex); + return Cpp::IsConstMethod(method); +} + +void interop::GetTemplatedMethods(TCppScope_t scope, + std::vector& methods) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetFunctionTemplatedDecls(scope, methods); +} + +interop::TCppIndex_t +interop::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) { + std::lock_guard Lock(InterOpMutex); + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); + return mc.size(); +} + +std::string interop::GetTemplatedMethodName(TCppScope_t scope, + TCppIndex_t imeth) { + std::lock_guard Lock(InterOpMutex); + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); + + if (imeth < mc.size()) + return Cpp::GetName(TCppScope_t(mc[imeth].data)); + + return ""; +} + +bool interop::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return Cpp::ExistsFunctionTemplate(name, scope); +} + +bool interop::IsTemplatedMethod(TCppMethod_t method) { + return Cpp::IsTemplatedFunction(method); +} + +bool interop::IsStaticTemplate(TCppScope_t scope, const std::string& name) { + std::vector candidate_methods; + Cpp::GetClassTemplatedMethods(name, scope, candidate_methods); + bool is_static = true; + for (auto i : candidate_methods) { + if (!Cpp::IsStaticMethod(i)) { + is_static = false; + break; + } + } + return is_static; +} + +interop::TCppMethod_t interop::GetMethodTemplate(TCppScope_t scope, + const std::string& name, + const std::string& proto) { + std::string pureName; + std::string explicit_params; + + if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) { + pureName = name.substr(0, name.find('<')); + size_t start = name.find('<'); + size_t end = name.rfind('>'); + explicit_params = name.substr(start + 1, end - start - 1); + } else { + pureName = name; + } + + std::lock_guard Lock(InterOpMutex); + + std::vector unresolved_candidate_methods; + Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); + if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { + // try operators + interop::GetClassOperators(scope, pureName, unresolved_candidate_methods); + } + + // cpyrt assumes that we attempt instantiation here + std::vector arg_types; + std::vector templ_params; + interop::AppendTypesSlow(proto, arg_types, scope); + interop::AppendTypesSlow(explicit_params, templ_params, scope); + interop::TCppMethod_t cppmeth = nullptr; + cppmeth = Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods, + templ_params, arg_types); + + // If overload resolution failed but explicit template arguments were + // supplied, fall back to direct template-argument substitution: ask Sema + // to instantiate each candidate with the explicit args. Sema's SFINAE + // rejects overloads whose substitution fails (e.g. the initializer_list + // form of std::make_any with non-init-list explicit args), so iterating + // gives back exactly the viable specialisation. The wrapper-side argument + // conversion then handles e.g. taking the address of an instance when the + // substituted parameter is a pointer. + if (!cppmeth && !templ_params.empty()) { + for (const auto& cand : unresolved_candidate_methods) { + if (Cpp::DeclRef spec = Cpp::InstantiateTemplate( + TCppScope_t(cand.data), templ_params.data(), templ_params.size(), + /*instantiate_body=*/false)) { + cppmeth = spec.data; + break; + } + } + } + + return TCppMethod_t(cppmeth.data); + // if it fails, use Sema to propogate info about why it failed (DeductionInfo) +} + +static inline bool is_basic_string_of(const std::string& n, const char* charT) { + // Match "std::basic_string' so that e.g. "char" does not match + // "char16_t". + std::string prefix = "std::basic_string<"; + prefix += charT; + if (n.compare(0, prefix.size(), prefix) != 0) + return false; + return n.size() > prefix.size() && + (n[prefix.size()] == ',' || n[prefix.size()] == '>'); +} + +static inline std::string type_remap(const std::string& n1, + const std::string& n2) { + // Operator lookups of (C++ string, Python str) should succeed for the + // combos of string/str, wstring/str, string/unicode and wstring/unicode; + // since C++ does not have a operator+(std::string, std::wstring), we'll + // have to look up the same type and rely on the converters in + // cpyrt/_cppjit. + if (n1 == "str" || n1 == "unicode" || is_basic_string_of(n1, "char")) { + if (is_basic_string_of(n2, "wchar_t")) + return "std::basic_string&"; // match like for like + return "std::basic_string&"; // probably best bet + } else if (is_basic_string_of(n1, "wchar_t")) { + return "std::basic_string&"; + } else if (n1 == "complex") { + return "std::complex"; + } + return n1; +} + +void interop::GetClassOperators(interop::TCppScope_t klass, + const std::string& opname, + std::vector& operators) { + std::lock_guard Lock(InterOpMutex); + std::string op = opname.substr(8); + Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, + /*kind=*/Cpp::OperatorArity::kBoth); +} + +interop::TCppMethod_t interop::GetGlobalOperator(TCppScope_t scope, + const std::string& lc, + const std::string& rc, + const std::string& opname) { + std::string rc_type = type_remap(rc, lc); + std::string lc_type = type_remap(lc, rc); + + std::vector overloads; + Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, + /*kind=*/Cpp::OperatorArity::kBoth); + + // Avoid pushing nullptr into arg_types which would crash + // BestOverloadFunctionMatch when it dereferences each entry's QualType. + auto resolve_arg_type = [](const std::string& name) -> interop::TCppType_t { + if (auto s = interop::GetScope(name)) + if (auto t = interop::GetTypeFromScope(s)) + return interop::GetReferencedType(t); + return interop::GetType(name, /*enable_slow_lookup=*/true); + }; + + std::vector arg_types; + if (auto l = resolve_arg_type(lc_type)) + arg_types.emplace_back(l.data); + else + return nullptr; + + if (!rc_type.empty()) { + if (auto r = resolve_arg_type(rc_type)) + arg_types.emplace_back(r.data); + else + return nullptr; + } + interop::TCppMethod_t cppmeth = + Cpp::BestOverloadFunctionMatch(overloads, {}, arg_types); + if (cppmeth) + return cppmeth; + return nullptr; +} + +// method properties --------------------------------------------------------- +bool interop::IsDeletedMethod(TCppMethod_t method) { + return Cpp::IsFunctionDeleted(method); +} + +bool interop::IsPublicMethod(TCppMethod_t method) { + return Cpp::IsPublicMethod(method); +} + +bool interop::IsProtectedMethod(TCppMethod_t method) { + return Cpp::IsProtectedMethod(method); +} + +bool interop::IsPrivateMethod(TCppMethod_t method) { + return Cpp::IsPrivateMethod(method); +} + +bool interop::IsConstructor(TCppMethod_t method) { + return Cpp::IsConstructor(method); +} + +bool interop::IsDestructor(TCppMethod_t method) { + return Cpp::IsDestructor(method); +} + +bool interop::IsStaticMethod(TCppMethod_t method) { + return Cpp::IsStaticMethod(method); +} + +bool interop::IsExplicit(TCppMethod_t method) { + return Cpp::IsExplicit(method); +} + +// data member reflection information ---------------------------------------- +void interop::GetDatamembers(TCppScope_t scope, + std::vector& datamembers) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetDatamembers(scope, datamembers); + Cpp::GetStaticDatamembers(scope, datamembers); + Cpp::GetEnumConstantDatamembers(scope, datamembers, false); +} + +bool interop::CheckDatamember(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return (bool)Cpp::LookupDatamember(name, scope); +} + +bool interop::IsLambdaClass(TCppType_t type) { + return Cpp::IsLambdaClass(type); +} + +interop::TCppScope_t interop::WrapLambdaFromVariable(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + std::ostringstream code; + std::string name = interop::GetFinalName(var); + code << "namespace __cppjit_internal_wrap_g {\n" + << " " << "std::function " << name + << " = ::" << Cpp::GetQualifiedName(var) << ";\n" + << "}\n"; + + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return res; + } + return var; +} + +interop::TCppMethod_t +interop::AdaptFunctionForLambdaReturn(interop::TCppMethod_t fn) { + std::lock_guard Lock(InterOpMutex); + + std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); + std::string signature = interop::GetMethodSignature(fn, true); + + std::ostringstream call; + call << "("; + for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) { + call << interop::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; + } + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "lambda_return_convert_" + std::to_string(++i); + code << "namespace __cppjit_internal_wrap_g {\n" + << "auto " << name << signature << "{" << "return std::function(" + << fn_name << call.str() << "); }\n" + << "}\n"; + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return TCppMethod_t(res.data); + } + return fn; +} + +interop::TCppType_t interop::GetDatamemberType(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); +} + +std::string interop::GetDatamemberTypeAsString(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( + Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope))); +} + +std::string interop::GetTypeAsString(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString(type); +} + +intptr_t interop::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); +} + +// data member properties ---------------------------------------------------- +bool interop::IsPublicData(TCppScope_t datamem) { + return Cpp::IsPublicVariable(datamem); +} + +bool interop::IsProtectedData(TCppScope_t datamem) { + return Cpp::IsProtectedVariable(datamem); +} + +bool interop::IsPrivateData(TCppScope_t datamem) { + return Cpp::IsPrivateVariable(datamem); +} + +bool interop::IsStaticDatamember(TCppScope_t var) { + return Cpp::IsStaticVariable(interop::GetUnderlyingScope(var)); +} + +bool interop::IsConstVar(TCppScope_t var) { return Cpp::IsConstVariable(var); } + +interop::TCppMethod_t interop::ReduceReturnType(TCppMethod_t fn, + TCppType_t reduce) { + std::lock_guard Lock(InterOpMutex); + + std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); + std::string signature = interop::GetMethodSignature(fn, true); + std::string result_type = interop::GetTypeAsString(reduce); + + std::ostringstream call; + call << "("; + for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) { + call << interop::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; + } + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "reduced_function_" + std::to_string(++i); + code << "namespace __cppjit_internal_wrap_g {\n" + << result_type << " " << name << signature << "{" << "return (" + << result_type << ")::" << fn_name << call.str() << "; }\n" + << "}\n"; + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return TCppMethod_t(res.data); + } + return fn; +} + +std::vector interop::GetDimensions(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetDimensions(type); +} + +// enum properties ----------------------------------------------------------- +std::vector interop::GetEnumConstants(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstants(scope); +} + +interop::TCppType_t interop::GetEnumConstantType(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); +} + +long long interop::GetEnumDataValue(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantValue(scope); +} + +interop::TCppScope_t interop::InstantiateTemplate(TCppScope_t tmpl, + Cpp::TemplateArgInfo* args, + size_t args_size) { + std::lock_guard Lock(InterOpMutex); + return Cpp::InstantiateTemplate(tmpl, args, args_size, + /*instantiate_body=*/false); +} + +void interop::DumpScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + Cpp::DumpScope(scope); +} diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h b/bindings/pyroot/cppjit/src/interop/precommondefs.h similarity index 79% rename from bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h rename to bindings/pyroot/cppjit/src/interop/precommondefs.h index 62710b6e41b3e..3cb57df58e87c 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h +++ b/bindings/pyroot/cppjit/src/interop/precommondefs.h @@ -7,7 +7,6 @@ #ifndef __PYPY_PRECOMMONDEFS_H #define __PYPY_PRECOMMONDEFS_H - /* Define on Darwin to activate all library features */ #define _DARWIN_C_SOURCE 1 /* This must be set to 64 on some systems to enable large file support. */ @@ -20,7 +19,7 @@ #define _NETBSD_SOURCE 1 /* Define to activate features from IEEE Stds 1003.1-2001 */ #ifndef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200112L #endif /* Define on FreeBSD to activate all library features */ #define __BSD_VISIBLE 1 @@ -28,20 +27,19 @@ /* Windows: winsock/winsock2 mess */ #define WIN32_LEAN_AND_MEAN #ifdef _WIN64 - typedef __int64 Signed; - typedef unsigned __int64 Unsigned; -# define SIGNED_MIN LLONG_MIN +typedef __int64 Signed; +typedef unsigned __int64 Unsigned; +#define SIGNED_MIN LLONG_MIN #else - typedef long Signed; - typedef unsigned long Unsigned; -# define SIGNED_MIN LONG_MIN +typedef long Signed; +typedef unsigned long Unsigned; +#define SIGNED_MIN LONG_MIN #endif #if !defined(RPY_ASSERT) && !defined(RPY_LL_ASSERT) && !defined(NDEBUG) -# define NDEBUG +#define NDEBUG #endif - /* All functions and global variables declared anywhere should use one of the following attributes: @@ -61,17 +59,16 @@ a bug; please report or fix it. */ #ifdef __GNUC__ -# define RPY_EXPORTED extern __attribute__((visibility("default"))) -# define _RPY_HIDDEN __attribute__((visibility("hidden"))) +#define RPY_EXPORTED extern __attribute__((visibility("default"))) +#define _RPY_HIDDEN __attribute__((visibility("hidden"))) #else -# define RPY_EXPORTED extern __declspec(dllexport) -# define _RPY_HIDDEN /* nothing */ +#define RPY_EXPORTED extern __declspec(dllexport) +#define _RPY_HIDDEN /* nothing */ #endif #ifndef RPY_EXTERN -# define RPY_EXTERN extern +#define RPY_EXTERN extern #endif - #endif /* __PYPY_PRECOMMONDEFS_H */ /***** End of precommondefs.h *****/ diff --git a/bindings/pyroot/cppjit/test/CMakeLists.txt b/bindings/pyroot/cppjit/test/CMakeLists.txt new file mode 100644 index 0000000000000..482c2abb89102 --- /dev/null +++ b/bindings/pyroot/cppjit/test/CMakeLists.txt @@ -0,0 +1,127 @@ +# 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. + +# The suite is pytest-based with no unittest-compatible entry points, so the +# tests run through `python -m pytest` directly. Dictionaries are compiled by +# ACLiC into the test working directory (the suite's own Makefile is disabled +# via CPPJIT_TEST_SKIP_MAKE). + +set(pythonpaths ${localruntimedir} $ENV{PYTHONPATH}) +cmake_path(CONVERT "${pythonpaths}" TO_NATIVE_PATH_LIST pythonpaths_native) + +set(common_env PYTHONPATH=${pythonpaths_native} CPPJIT_TEST_SKIP_MAKE=1) +if(NOT MSVC) + list(APPEND common_env ${ld_library_path}=${CMAKE_CURRENT_BINARY_DIR}) +endif() + +# Some dictionary sources include Python.h (e.g. pythonizables.cxx), which +# ACLiC does not find on its own. +set(root_cmd $ -b -q + -e "gSystem->AddIncludePath(\"-I${Python3_INCLUDE_DIRS}\")") +if(APPLE) + # ACLiC links dictionaries as -dynamiclib; CPython symbols in them resolve + # from the hosting interpreter process, as for any extension module. + list(APPEND root_cmd + -e "gSystem->AddLinkedLibs(\"-Wl,-undefined,dynamic_lookup\")") +elseif(MSVC) + # Python.h auto-links pythonNN.lib via #pragma comment(lib, ...); ACLiC's + # def/import-lib step honors the directive but has no library search path, + # so suppress it and hand the link step the library explicitly. + list(APPEND root_cmd + -e "gSystem->AddIncludePath(\"-DPy_NO_LINK_LIB\")" + -e "gSystem->AddLinkedLibs(\"${Python3_LIBRARIES}\")") +endif() + +function(CPPJIT_ADD_PYTEST name) + cmake_parse_arguments(ARG + "DROP_ROOTMAP" "" "DICTS;FIXTURES_SETUP;FIXTURES_REQUIRED" ${ARGN}) + + set(extra) + if(ARG_DICTS) + set(copy_files) + set(precmd_args) + foreach(dict ${ARG_DICTS}) + list(APPEND copy_files + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/${dict}.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/${dict}.h) + list(APPEND precmd_args -e ".L ${dict}.cxx++") + endforeach() + if(ARG_DROP_ROOTMAP AND MSVC) + foreach(dict ${ARG_DICTS}) + list(APPEND precmd_args + -e "(void)gSystem->Unlink(\"${dict}_cxx.rootmap\")") + endforeach() + endif() + list(APPEND extra COPY_TO_BUILDDIR ${copy_files} + PRECMD ${root_cmd} ${precmd_args}) + endif() + + string(REGEX REPLACE "[_]" "-" good_name "${name}") + set(test_name pyunittests-bindings-cppjit-${good_name}) + + ROOT_ADD_TEST(${test_name} + # Upstream marks platform xfails without explicit strict=; the in-tree + # patches fix many of them on the ROOT stack, so an xpass must not fail + # the suite (pyproject.toml sets xfail_strict=true for upstream CI). + COMMAND ${Python3_EXECUTABLE} -B -m pytest -v -p no:cacheprovider + -o xfail_strict=false + ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py + WORKING_DIR ${CMAKE_CURRENT_BINARY_DIR} + ENVIRONMENT ${common_env} + LABELS python + PYTHON_DEPS pytest + ${extra}) + + if(ARG_FIXTURES_SETUP) + set_property(TEST ${test_name} PROPERTY FIXTURES_SETUP ${ARG_FIXTURES_SETUP}) + endif() + if(ARG_FIXTURES_REQUIRED) + set_property(TEST ${test_name} PROPERTY FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED}) + endif() +endfunction() + +CPPJIT_ADD_PYTEST(test_conversions DICTS conversions) +if(NOT MSVC) + # Windows ACLiC emits a .rootmap whose forward declaration of the + # std::hash specialization conflicts with the just-parsed + # header; the failed load makes ACLiC relink over its own locked DLL. + CPPJIT_ADD_PYTEST(test_cpp11features DICTS cpp11features) +endif() +CPPJIT_ADD_PYTEST(test_crossinheritance DICTS crossinheritance) +CPPJIT_ADD_PYTEST(test_doc_features DICTS doc_helper) +CPPJIT_ADD_PYTEST(test_fragile DICTS fragile) +CPPJIT_ADD_PYTEST(test_operators DICTS operators) +CPPJIT_ADD_PYTEST(test_overloads DICTS overloads) +CPPJIT_ADD_PYTEST(test_pythonization DICTS pythonizables) +CPPJIT_ADD_PYTEST(test_stltypes DICTS stltypes) +CPPJIT_ADD_PYTEST(test_streams DICTS std_streams) +# The rootmap Windows ACLiC emits forward-declares the variadic matryoshka +# template without its parameter pack; parsing it fails the whole dictionary +# payload. The tests load the library by name and never need the rootmap. +CPPJIT_ADD_PYTEST(test_templates DICTS templates DROP_ROOTMAP) + +CPPJIT_ADD_PYTEST(test_pythonify DICTS example01 + FIXTURES_SETUP cppjit_example01_dict_fixture) +CPPJIT_ADD_PYTEST(test_advancedcpp DICTS advancedcpp advancedcpp2) +CPPJIT_ADD_PYTEST(test_lowlevel DICTS datatypes + FIXTURES_SETUP cppjit_datatypes_dict_fixture) + +CPPJIT_ADD_PYTEST(test_aclassloader + FIXTURES_REQUIRED cppjit_example01_dict_fixture) +CPPJIT_ADD_PYTEST(test_basic_api + FIXTURES_REQUIRED cppjit_example01_dict_fixture) +CPPJIT_ADD_PYTEST(test_datatypes + FIXTURES_REQUIRED cppjit_datatypes_dict_fixture) + +CPPJIT_ADD_PYTEST(test_api) +CPPJIT_ADD_PYTEST(test_boost) +CPPJIT_ADD_PYTEST(test_concurrent) +CPPJIT_ADD_PYTEST(test_cpp23features) +CPPJIT_ADD_PYTEST(test_eigen) +CPPJIT_ADD_PYTEST(test_leakcheck) +CPPJIT_ADD_PYTEST(test_numba) +CPPJIT_ADD_PYTEST(test_regression) +CPPJIT_ADD_PYTEST(test_root_interpreter_lock) diff --git a/bindings/pyroot/cppjit/test/Makefile b/bindings/pyroot/cppjit/test/Makefile new file mode 100644 index 0000000000000..7c89130434b28 --- /dev/null +++ b/bindings/pyroot/cppjit/test/Makefile @@ -0,0 +1,50 @@ +# the C++ sides of the tests live under cpp/ and build into shared +# libraries next to their sources (load_reflection_info resolves the +# header as a sibling of the loaded Dict library) +soext = .so +ifeq ($(OS),Windows_NT) + soext = .dll +endif + +dictnames = advancedcpp \ + advancedcpp2 \ + conversions \ + cpp11features \ + crossinheritance \ + datatypes \ + doc_helper \ + example01 \ + fragile \ + operators \ + overloads \ + pythonizables \ + std_streams \ + stltypes \ + templates + +dicts = $(addprefix cpp/,$(addsuffix Dict$(soext),$(dictnames))) + +all : $(dicts) + +PYTHON ?= python3 +cppflags= -std=c++17 -O3 -fPIC -I$(shell $(PYTHON) -c 'import sysconfig as sc; print(sc.get_config_var("INCLUDEPY"))') -Wno-register + +PLATFORM := $(shell uname -s) +ifeq ($(PLATFORM),Darwin) + cppflags+=-dynamiclib -single_module -undefined dynamic_lookup -Wno-delete-non-virtual-dtor +endif + +# a worker can load the library while another rebuilds it, so publish it whole +cpp/%Dict$(soext): cpp/%.cxx cpp/%.h + $(CXX) $(cppflags) -shared -o $@.$$$$.tmp $< && mv -f $@.$$$$.tmp $@ + +# convenience: `make datatypesDict$(soext)` builds cpp/datatypesDict$(soext) +%Dict$(soext): cpp/%Dict$(soext) ; + +.PHONY: test clean + +test: + pytest test_*.py + +clean: + -rm -f $(dicts) cpp/*.tmp cpp/*.lock diff --git a/bindings/pyroot/cppjit/test/conftest.py b/bindings/pyroot/cppjit/test/conftest.py new file mode 100644 index 0000000000000..525f07149fa38 --- /dev/null +++ b/bindings/pyroot/cppjit/test/conftest.py @@ -0,0 +1,66 @@ +"""Suite-wide pytest infrastructure. + +Tests within a file share interpreter state (cppdefs, loaded dictionaries, +pythonizations), so distributed runs must keep whole files on one worker. +""" + +import pytest + + +def pytest_addoption(parser): + parser.addoption( + "--run-crashing-xfails", + action="store_true", + default=False, + help="run xfail(run=False) crash-class tests; a pass is a strict xpass", + ) + + +def _applies_here(mark): + """Whether a mark's conditions hold; pytest evaluates string ones itself.""" + + conditions = list(mark.args[:1]) + if "condition" in mark.kwargs: + conditions.append(mark.kwargs["condition"]) + return all(True if isinstance(c, str) else bool(c) for c in conditions) + + +def pytest_collection_modifyitems(config, items): + if not config.getoption("--run-crashing-xfails"): + return + # Keep only the crash markers that claim this platform, and let them run: + # the marker stays, so one that stopped crashing reports as a strict + # xpass. The rest are deselected; they would only add state the real + # suite never has. + selected, deselected = [], [] + for item in items: + crashing = [ + m + for m in item.own_markers + if m.name == "xfail" and m.kwargs.get("run") is False and _applies_here(m) + ] + if not crashing: + deselected.append(item) + continue + item.own_markers = [ + pytest.mark.xfail(*m.args, **{**m.kwargs, "run": True}).mark + if m in crashing + else m + for m in item.own_markers + ] + selected.append(item) + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = selected + + +def pytest_configure(config): + # -n implies --dist load; every mode finer than per-file is remapped + # ("each" and "no" already keep files whole). + if config.getoption("numprocesses", None) and config.getoption("dist", "no") in ( + "load", + "worksteal", + "loadscope", + "loadgroup", + ): + config.option.dist = "loadfile" diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx b/bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx similarity index 53% rename from bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx rename to bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx index 23f4c041f69c2..b15eabaeb3d94 100644 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx +++ b/bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx @@ -2,18 +2,22 @@ #include - // for testing of default arguments -#define IMPLEMENT_DEFAULTERS(type, tname) \ -tname##_defaulter::tname##_defaulter(type a, type b, type c) { \ - m_a = a; m_b = b; m_c = c; \ -} \ -type tname##_defaulter_func(int idx, type a, type b, type c) { \ - if (idx == 0) return a; \ - if (idx == 1) return b; \ - if (idx == 2) return c; \ - return (type)idx; \ -} +#define IMPLEMENT_DEFAULTERS(type, tname) \ + tname##_defaulter::tname##_defaulter(type a, type b, type c) { \ + m_a = a; \ + m_b = b; \ + m_c = c; \ + } \ + type tname##_defaulter_func(int idx, type a, type b, type c) { \ + if (idx == 0) \ + return a; \ + if (idx == 1) \ + return b; \ + if (idx == 2) \ + return c; \ + return (type)idx; \ + } IMPLEMENT_DEFAULTERS(short, short) IMPLEMENT_DEFAULTERS(unsigned short, ushort) IMPLEMENT_DEFAULTERS(int, int) @@ -25,65 +29,60 @@ IMPLEMENT_DEFAULTERS(unsigned long long, ullong) IMPLEMENT_DEFAULTERS(float, float) IMPLEMENT_DEFAULTERS(double, double) -std::string string_defaulter_func(int idx, const std::string& name1, std::string name2) { - if (idx == 0) return name1; - if (idx == 1) return name2; - return "mies"; +std::string string_defaulter_func(int idx, const std::string& name1, + std::string name2) { + if (idx == 0) + return name1; + if (idx == 1) + return name2; + return "mies"; } - // for esoteric inheritance testing a_class* create_c1() { return new c_class_1; } a_class* create_c2() { return new c_class_2; } -int get_a( a_class& a ) { return a.m_a; } -int get_b( b_class& b ) { return b.m_b; } -int get_c( c_class& c ) { return c.m_c; } -int get_d( d_class& d ) { return d.m_d; } - +int get_a(a_class& a) { return a.m_a; } +int get_b(b_class& b) { return b.m_b; } +int get_c(c_class& c) { return c.m_c; } +int get_d(d_class& d) { return d.m_d; } // for namespace testing -int a_ns::g_a = 11; -int a_ns::b_class::s_b = 22; -int a_ns::b_class::c_class::s_c = 33; -int a_ns::d_ns::g_d = 44; -int a_ns::d_ns::e_class::s_e = 55; +int a_ns::g_a = 11; +int a_ns::b_class::s_b = 22; +int a_ns::b_class::c_class::s_c = 33; +int a_ns::d_ns::g_d = 44; +int a_ns::d_ns::e_class::s_e = 55; int a_ns::d_ns::e_class::f_class::s_f = 66; int a_ns::get_g_a() { return g_a; } int a_ns::d_ns::get_g_d() { return g_d; } - // for template testing template class T1; -template class T2 >; +template class T2>; template class T3; -template class T3, T2 > >; +template class T3, T2>>; template class a_ns::T4; -template class a_ns::T4 > >; - +template class a_ns::T4>>; // helpers for checking pass-by-ref -void set_int_through_ref(int& i, int val) { i = val; } -int pass_int_through_const_ref(const int& i) { return i; } -void set_long_through_ref(long& l, long val) { l = val; } -long pass_long_through_const_ref(const long& l) { return l; } -void set_double_through_ref(double& d, double val) { d = val; } +void set_int_through_ref(int& i, int val) { i = val; } +int pass_int_through_const_ref(const int& i) { return i; } +void set_long_through_ref(long& l, long val) { l = val; } +long pass_long_through_const_ref(const long& l) { return l; } +void set_double_through_ref(double& d, double val) { d = val; } double pass_double_through_const_ref(const double& d) { return d; } - // for math conversions testing -bool operator==(const some_comparable& c1, const some_comparable& c2) -{ - return &c1 != &c2; // the opposite of a pointer comparison +bool operator==(const some_comparable& c1, const some_comparable& c2) { + return &c1 != &c2; // the opposite of a pointer comparison } -bool operator!=(const some_comparable& c1, const some_comparable& c2) -{ - return &c1 == &c2; // the opposite of a pointer comparison +bool operator!=(const some_comparable& c1, const some_comparable& c2) { + return &c1 == &c2; // the opposite of a pointer comparison } - // a couple of globals for access testing double my_global_double = 12.; double my_global_array[500]; @@ -93,41 +92,38 @@ const char my_global_string2[] = "zus jet teun"; const char* my_global_string3[3] = {"aap", "noot", "mies"}; some_int_holder my_global_int_holders[5] = { some_int_holder(13), some_int_holder(42), some_int_holder(88), - some_int_holder(-1), some_int_holder(17) }; + some_int_holder(-1), some_int_holder(17)}; some_abstract_class* g_abstract_ptr = nullptr; - // for life-line and identity testing int some_class_with_data::some_data::s_num_data = 0; - // for testing multiple inheritance multi1::~multi1() {} multi2::~multi2() {} multi::~multi() {} - // for testing calls to overloaded new int new_overloader::s_instances = 0; void* new_overloader::operator new(std::size_t size) { - ++s_instances; - return ::operator new(size); + ++s_instances; + return ::operator new(size); } void* new_overloader::operator new(std::size_t, void* p) throw() { - // no ++s_instances, as no memory is allocated - return p; + // no ++s_instances, as no memory is allocated + return p; } void new_overloader::operator delete(void* p, std::size_t) { - if (p == 0) return; - --s_instances; - ::operator delete(p); + if (p == 0) + return; + --s_instances; + ::operator delete(p); } - // overload order testing int overload_one_way::gime() const { return 1; } std::string overload_one_way::gime() { return "aap"; } @@ -135,52 +131,43 @@ std::string overload_one_way::gime() { return "aap"; } std::string overload_the_other_way::gime() { return "aap"; } int overload_the_other_way::gime() const { return 1; } - // exception handling testing -void Thrower::throw_anything() { - throw 1; -} +void Thrower::throw_anything() { throw 1; } void Thrower::throw_exception() { - throw std::runtime_error("C++ function failed"); + throw std::runtime_error("C++ function failed"); } - // operator to __str__ mapping std::ostream& Cpp2PyPrinting::Printable1::operator<<(std::ostream& os) { - os << "Printable1::operator<<"; - return os; + os << "Printable1::operator<<"; + return os; } std::ostream& Cpp2PyPrinting::operator<<(std::ostream& os, const Printable2&) { - os << "Cpp2PyPrinting::operator<<"; - return os; + os << "Cpp2PyPrinting::operator<<"; + return os; } std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&) { - os << "::operator<<(3)"; - return os; + os << "::operator<<(3)"; + return os; } std::ostream& operator<<(std::ostream& os, const Printable4&) { - os << "::operator<<(4)"; - return os; + os << "::operator<<(4)"; + return os; } Printable6& Printable6::operator<<(int) { return *this; } -std::ostream& operator<<(std::ostream& os, const Printable6& y) { return os << "Printable6"; } - +std::ostream& operator<<(std::ostream& os, const Printable6& y) { + return os << "Printable6"; +} // for using directives testing -int UsedSpace1::foo1() { - return 13; -} +int UsedSpace1::foo1() { return 13; } -int UsedSpace2::bar() { - return 42; -} +int UsedSpace2::bar() { return 42; } -int UsedSpace1::inner::foo2() { - return 27; -} +int UsedSpace1::inner::foo2() { return 27; } diff --git a/bindings/pyroot/cppjit/test/cpp/advancedcpp.h b/bindings/pyroot/cppjit/test/cpp/advancedcpp.h new file mode 100644 index 0000000000000..fba4afcf57476 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/advancedcpp.h @@ -0,0 +1,522 @@ +#include +#include +#include +#include +#include + +//=========================================================================== +#define DECLARE_DEFAULTERS(type, tname) \ + class tname##_defaulter { \ + public: \ + tname##_defaulter(type a = 11, type b = 22, type c = 33); \ + \ + public: \ + type m_a, m_b, m_c; \ + }; \ + type tname##_defaulter_func(int idx = 0, type a = 11, type b = 22, \ + type c = 33); +DECLARE_DEFAULTERS(short, short) // for testing of default arguments +DECLARE_DEFAULTERS(unsigned short, ushort) +DECLARE_DEFAULTERS(int, int) +DECLARE_DEFAULTERS(unsigned, uint) +DECLARE_DEFAULTERS(long, long) +DECLARE_DEFAULTERS(unsigned long, ulong) +DECLARE_DEFAULTERS(long long, llong) +DECLARE_DEFAULTERS(unsigned long long, ullong) +DECLARE_DEFAULTERS(float, float) +DECLARE_DEFAULTERS(double, double) + +std::string string_defaulter_func(int idx, const std::string& name1 = "aap", + std::string name2 = "noot"); + +//=========================================================================== +class base_class { // for simple inheritance testing +public: + base_class() { + m_b = 1; + m_db = 1.1; + } + virtual ~base_class() {} + virtual int get_value() { return m_b; } + double get_base_value() { return m_db; } + + virtual base_class* cycle(base_class* b) { return b; } + virtual base_class* clone() { return new base_class; } + + virtual void* mask(void* p) { return p; } + +public: + int m_b; + double m_db; +}; + +class derived_class : public base_class { +public: + derived_class() { + m_d = 2; + m_dd = 2.2; + } + virtual int get_value() { return m_d; } + double get_derived_value() { return m_dd; } + virtual base_class* clone() { return new derived_class; } + +public: + int m_d; + double m_dd; +}; + +//=========================================================================== +class a_class { // for esoteric inheritance testing +public: + a_class() { + m_a = 1; + m_da = 1.1; + } + virtual ~a_class() {} + virtual int get_value() = 0; + +public: + int m_a; + double m_da; +}; + +class b_class : public virtual a_class { +public: + b_class() { + m_b = 2; + m_db = 2.2; + } + virtual int get_value() { return m_b; } + +public: + int m_b; + double m_db; +}; + +class c_class_1 : public virtual a_class, public virtual b_class { +public: + c_class_1() { m_c = 3; } + virtual int get_value() { return m_c; } + +public: + int m_c; +}; + +class c_class_2 : public virtual b_class, public virtual a_class { +public: + c_class_2() { m_c = 3; } + virtual int get_value() { return m_c; } + +public: + int m_c; +}; + +typedef c_class_2 c_class; + +class d_class : public virtual c_class, public virtual a_class { +public: + d_class() { m_d = 4; } + virtual int get_value() { return m_d; } + +public: + int m_d; +}; + +a_class* create_c1(); +a_class* create_c2(); + +int get_a(a_class& a); +int get_b(b_class& b); +int get_c(c_class& c); +int get_d(d_class& d); + +//=========================================================================== +namespace a_ns { // for namespace testing +extern int g_a; +int get_g_a(); + +struct b_class { + b_class() { m_b = -2; } + int m_b; + static int s_b; + + struct c_class { + c_class() { m_c = -3; } + int m_c; + static int s_c; + }; +}; + +namespace d_ns { +extern int g_d; +int get_g_d(); + +struct e_class { + e_class() { m_e = -5; } + int m_e; + static int s_e; + + struct f_class { + f_class() { m_f = -6; } + int m_f; + static int s_f; + }; +}; + +} // namespace d_ns + +} // namespace a_ns + +//=========================================================================== +template // for template testing +class T1 { +public: + T1(T t = T(1)) : m_t1(t) {} + T get_value() { return m_t1; } + +public: + T m_t1; +}; + +template class T2 { +public: + T2(T t = T(2)) : m_t2(t) {} + T get_value() { return m_t2; } + +public: + T m_t2; +}; + +template class T3 { +public: + T3(T t = T(3), U u = U(33)) : m_t3(t), m_u3(u) {} + T get_value_t() { return m_t3; } + U get_value_u() { return m_u3; } + +public: + T m_t3; + U m_u3; +}; + +namespace a_ns { + +template class T4 { +public: + T4(T t = T(4)) : m_t4(t) {} + T get_value() { return m_t4; } + +public: + T m_t4; +}; + +} // namespace a_ns + +extern template class T1; +extern template class T2>; +extern template class T3; +extern template class T3, T2>>; +extern template class a_ns::T4; +extern template class a_ns::T4>>; + +//=========================================================================== +// for checking pass-by-reference of builtin types +void set_int_through_ref(int& i, int val); +int pass_int_through_const_ref(const int& i); +void set_long_through_ref(long& l, long val); +long pass_long_through_const_ref(const long& l); +void set_double_through_ref(double& d, double val); +double pass_double_through_const_ref(const double& d); + +//=========================================================================== +class some_abstract_class { // to test abstract class handling +public: + virtual ~some_abstract_class() {} + virtual void a_virtual_method() = 0; +}; + +class some_concrete_class : public some_abstract_class { +public: + virtual void a_virtual_method() {} +}; + +//=========================================================================== +class ref_tester { // for assignment by-ref testing +public: + ref_tester() : m_i(-99) {} + ref_tester(int i) : m_i(i) {} + ref_tester(const ref_tester& s) : m_i(s.m_i) {} + ref_tester& operator=(const ref_tester& s) { + if (&s != this) + m_i = s.m_i; + return *this; + } + ~ref_tester() {} + +public: + int m_i; +}; + +//=========================================================================== +class some_convertible { // for math conversions testing +public: + some_convertible() : m_i(-99), m_d(-99.) {} + + operator int() { return m_i; } + operator long() { return m_i; } + operator double() { return m_d; } + +public: + int m_i; + double m_d; +}; + +class some_comparable {}; + +bool operator==(const some_comparable& c1, const some_comparable& c2); +bool operator!=(const some_comparable& c1, const some_comparable& c2); + +//=========================================================================== +extern double my_global_double; // a couple of globals for access testing +extern double my_global_array[500]; +extern double* my_global_ptr; +static const char my_global_string1[] = "aap " + " noot " + " mies"; +extern const char my_global_string2[]; +extern const char* my_global_string3[3]; + +class some_int_holder { +public: + some_int_holder(int val) : m_val(val) {} + +public: + int m_val; + char gap[7]; +}; +extern some_int_holder my_global_int_holders[5]; + +extern some_abstract_class* g_abstract_ptr; + +//=========================================================================== +class some_class_with_data { // for life-line and identity testing +public: + class some_data { + public: + some_data() { ++s_num_data; } + some_data(const some_data&) { ++s_num_data; } + ~some_data() { --s_num_data; } + + static int s_num_data; + }; + + some_class_with_data gime_copy() { return *this; } + + const some_data& gime_data() { /* TODO: methptrgetter const support */ + return m_data; + } + + int m_padding; + some_data m_data; +}; + +class refers_to_self { // for data member reuse testing +public: + refers_to_self* m_other = nullptr; +}; + +//=========================================================================== +class pointer_pass { // for testing passing of void*'s +public: + intptr_t gime_address_ptr(void* obj) { return (intptr_t)obj; } + + intptr_t gime_address_ptr_ptr(void** obj) { + return (intptr_t) * ((long**)obj); + } + + intptr_t gime_address_ptr_ref(const void*& obj) { return (intptr_t)obj; } + + static intptr_t set_address_ptr_ptr(void** obj) { + (*(intptr_t**)obj) = (intptr_t*)0x4321; + return 42; + } + + static intptr_t set_address_ptr_ref(const void*& obj) { + obj = (void*)0x1234; + return 21; + } +}; + +//=========================================================================== +class multi1 { // for testing multiple inheritance +public: + multi1(int val) : m_int(val) {} + virtual ~multi1(); + int get_multi1_int() { return m_int; } + +private: + int m_int; +}; + +class multi2 { +public: + multi2(int val) : m_int(val) {} + virtual ~multi2(); + int get_multi2_int() { return m_int; } + +private: + int m_int; +}; + +class multi : public multi1, public multi2 { +public: + multi(int val1, int val2, int val3) + : multi1(val1), multi2(val2), m_int(val3) {} + virtual ~multi(); + int get_my_own_int() { return m_int; } + +private: + int m_int; +}; + +//=========================================================================== +class new_overloader { // for testing calls to overloaded new +public: + static int s_instances; + +public: + void* operator new(std::size_t size); + void* operator new(std::size_t, void* p) throw(); + void operator delete(void* p, std::size_t size); +}; + +//=========================================================================== +template // more template testing +class my_templated_class { +public: + T m_b; +}; + +template T my_templated_function(T t) { return t; } + +template class my_templated_class>; +template char my_templated_function(char); +template double my_templated_function(double); + +//=========================================================================== +class overload_one_way { // overload order testing +public: + int gime() const; + std::string gime(); +}; + +class overload_the_other_way { +public: + std::string gime(); + int gime() const; +}; + +//=========================================================================== +class Thrower { // exception handling testing +public: + void throw_anything(); + void throw_exception(); +}; + +//=========================================================================== +class UsingBase1 { // using declaration testing +public: + UsingBase1(int n = 13) : m_int(n) {} + virtual ~UsingBase1() {} + virtual char vcheck() { return 'A'; } + int m_int; +}; + +class UsingDerived1 : public UsingBase1 { +public: + using UsingBase1::UsingBase1; + virtual char vcheck() { return 'B'; } + int m_int2 = 42; +}; + +class UsingBase2 { +public: + virtual ~UsingBase2() {} + virtual char vcheck() { return 'A'; } +}; + +class UsingDerived2 : public UsingBase2 { +public: + using UsingBase2::vcheck; + virtual char vcheck(int) { return 'B'; } +}; + +//=========================================================================== +class TypedefToPrivateClass { // typedef resolution testing +private: + class PC { + public: + PC(int i) : m_val(i) {} + int m_val; + }; + +public: + typedef PC PP; + PP f() { return PC(42); } +}; + +//=========================================================================== +namespace Cpp2PyPrinting { // operator to __str__ mapping + +class Printable1 { +public: + std::ostream& operator<<(std::ostream& os); +}; + +class Printable2 { /* empty */ +}; +class Printable3 { /* empty */ +}; + +std::ostream& operator<<(std::ostream& os, const Printable2&); + +} // namespace Cpp2PyPrinting + +class Printable4 { /* empty */ +}; + +std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&); +std::ostream& operator<<(std::ostream& os, const Printable4&); + +template class PrintableBase1 {}; +class Printable5 : public PrintableBase1 {}; + +template +std::ostream& operator<<(std::ostream& os, const PrintableBase1& y) { + return os << "Ok."; +} + +class Printable6 { +public: + Printable6& operator<<(int); +}; + +std::ostream& operator<<(std::ostream& os, const Printable6& y); + +//=========================================================================== +namespace UsedSpace1 { // for using directives testing +int foo1(); + +namespace inner { +int foo2(); +} +} // namespace UsedSpace1 + +namespace UsedSpace2 { +int bar(); +} + +namespace UserDirs { +using namespace UsedSpace1; +using namespace UsedSpace2; +using namespace UsedSpace1::inner; +} // namespace UserDirs diff --git a/bindings/pyroot/cppjit/test/cpp/advancedcpp2.cxx b/bindings/pyroot/cppjit/test/cpp/advancedcpp2.cxx new file mode 100644 index 0000000000000..b0965055e88ef --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/advancedcpp2.cxx @@ -0,0 +1,12 @@ +#include "advancedcpp2.h" + +// for namespace testing +int a_ns::g_g = 77; +int a_ns::g_class::s_g = 88; +int a_ns::g_class::h_class::s_h = 99; +int a_ns::d_ns::g_i = 111; +int a_ns::d_ns::i_class::s_i = 222; +int a_ns::d_ns::i_class::j_class::s_j = 333; + +int a_ns::get_g_g() { return g_g; } +int a_ns::d_ns::get_g_i() { return g_i; } diff --git a/bindings/pyroot/cppjit/test/cpp/advancedcpp2.h b/bindings/pyroot/cppjit/test/cpp/advancedcpp2.h new file mode 100644 index 0000000000000..eec725951c5ca --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/advancedcpp2.h @@ -0,0 +1,36 @@ +//=========================================================================== +namespace a_ns { // for namespace testing +extern int g_g; +int get_g_g(); + +struct g_class { + g_class() { m_g = -7; } + int m_g; + static int s_g; + + struct h_class { + h_class() { m_h = -8; } + int m_h; + static int s_h; + }; +}; + +namespace d_ns { +extern int g_i; +int get_g_i(); + +struct i_class { + i_class() { m_i = -9; } + int m_i; + static int s_i; + + struct j_class { + j_class() { m_j = -10; } + int m_j; + static int s_j; + }; +}; + +} // namespace d_ns + +} // namespace a_ns diff --git a/bindings/pyroot/cppjit/test/cpp/conversions.cxx b/bindings/pyroot/cppjit/test/cpp/conversions.cxx new file mode 100644 index 0000000000000..c092fa596186f --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/conversions.cxx @@ -0,0 +1,44 @@ +#include "conversions.h" + +#include + +//=========================================================================== +double CNS::sumit(const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.); +} + +double CNS::sumit(const std::vector& v1, + const std::vector& v2) { + return std::accumulate(v1.begin(), v1.end(), + std::accumulate(v2.begin(), v2.end(), 0.)); +} + +int CNS::Counter::s_count; + +CNS::Counter::Counter() { ++s_count; } + +CNS::Counter::Counter(const Counter&) { ++s_count; } + +CNS::Counter& CNS::Counter::operator=(const Counter&) { return *this; } + +CNS::Counter::~Counter() { --s_count; } + +double CNS::myhowmany(const std::vector& v) { return v.size(); } + +double CNS::myhowmany(const std::vector& v1, + const std::vector& v2) { + return v1.size() + v2.size(); +} + +int CNS::sumints(const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0); +} + +int CNS::sumints(const std::vector& v1, const std::vector& v2) { + return std::accumulate(v1.begin(), v1.end(), + std::accumulate(v2.begin(), v2.end(), 0)); +} + +double notallowed(std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.); +} diff --git a/bindings/pyroot/cppyy/cppyy/test/conversions.h b/bindings/pyroot/cppjit/test/cpp/conversions.h similarity index 81% rename from bindings/pyroot/cppyy/cppyy/test/conversions.h rename to bindings/pyroot/cppjit/test/cpp/conversions.h index 9f1401b50fb0e..c0f8f7950e383 100644 --- a/bindings/pyroot/cppyy/cppyy/test/conversions.h +++ b/bindings/pyroot/cppjit/test/cpp/conversions.h @@ -1,6 +1,5 @@ #include - namespace CNS { //=========================================================================== @@ -9,12 +8,12 @@ double sumit(const std::vector&, const std::vector&); class Counter { public: - Counter(); - Counter(const Counter&); - Counter& operator=(const Counter&); - ~Counter(); + Counter(); + Counter(const Counter&); + Counter& operator=(const Counter&); + ~Counter(); - static int s_count; + static int s_count; }; double myhowmany(const std::vector&); diff --git a/bindings/pyroot/cppjit/test/cpp/cpp11features.cxx b/bindings/pyroot/cppjit/test/cpp/cpp11features.cxx new file mode 100644 index 0000000000000..72a2153cebab3 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/cpp11features.cxx @@ -0,0 +1,61 @@ +#include "cpp11features.h" + +// for std::shared/unique_ptr<> testing +int TestSmartPtr::s_counter = 0; + +std::shared_ptr create_shared_ptr_instance() { + return std::shared_ptr(new TestSmartPtr); +} + +std::unique_ptr create_unique_ptr_instance() { + return std::unique_ptr(new TestSmartPtr); +} + +int TestSmartPtr::get_value() { return 17; } + +int DerivedTestSmartPtr::get_value() { return m_int + 76; } + +int pass_shared_ptr(std::shared_ptr p) { return p->get_value(); } + +int move_shared_ptr(std::shared_ptr&& p) { + return p->get_value(); +} + +int move_unique_ptr(std::unique_ptr&& p) { + return p->get_value(); +} + +int move_unique_ptr_derived(std::unique_ptr&& p) { + return p->get_value(); +} + +TestSmartPtr create_TestSmartPtr_by_value() { return TestSmartPtr{}; } + +std::shared_ptr create_shared_ptr_to_derived() { + return std::shared_ptr(new PubDerivedTestSmartPtr); +} + +std::unique_ptr create_unique_ptr_to_derived() { + return std::unique_ptr(new PubDerivedTestSmartPtr); +} + +std::unique_ptr create_unique_ptr_to_offset_derived() { + return std::unique_ptr(new MultiDerivedTestSmartPtr); +} + +// for move ctors etc. +int TestMoving1::s_move_counter = 0; +int TestMoving1::s_instance_counter = 0; +int TestMoving2::s_move_counter = 0; +int TestMoving2::s_instance_counter = 0; + +void implicit_converion_move(TestMoving2&&) { /* empty */ } + +// for std::function testing +std::function FNCreateTestStructFunc() { + return [](const FNTestStruct& t) { return t.t; }; +} +std::function +FunctionNS::FNCreateTestStructFunc() { + return [](const FNTestStruct& t) { return t.t; }; +} diff --git a/bindings/pyroot/cppjit/test/cpp/cpp11features.h b/bindings/pyroot/cppjit/test/cpp/cpp11features.h new file mode 100644 index 0000000000000..5102651c911c8 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/cpp11features.h @@ -0,0 +1,182 @@ +#ifndef CPP11FEATURES_H +#define CPP11FEATURES_H + +#include +#include +#include +#include + +//=========================================================================== +class TestSmartPtr { // for std::shared/unique_ptr<> testing +public: + static int s_counter; + +public: + TestSmartPtr() { ++s_counter; } + TestSmartPtr(const TestSmartPtr&) { ++s_counter; } + virtual ~TestSmartPtr() { --s_counter; } + +public: + virtual int get_value(); +}; + +std::shared_ptr create_shared_ptr_instance(); +std::unique_ptr create_unique_ptr_instance(); + +class DerivedTestSmartPtr : TestSmartPtr { +public: + DerivedTestSmartPtr(int i) : m_int(i) {} + virtual int get_value(); + +public: + int m_int; +}; + +int pass_shared_ptr(std::shared_ptr p); +int move_shared_ptr(std::shared_ptr&& p); +int move_unique_ptr(std::unique_ptr&& p); +int move_unique_ptr_derived(std::unique_ptr&& p); + +TestSmartPtr create_TestSmartPtr_by_value(); + +// for auto-downcast of objects returned through a smart pointer +class PubDerivedTestSmartPtr : public TestSmartPtr { +public: + int only_in_derived() { return 27; } +}; + +// second base so that the cross-cast to the most derived type needs a +// non-zero pointer adjustment, which the smart pointer's dereferencer can +// not apply consistently (so no down-cast should happen in that case) +class TestSmartPtrIface { +public: + virtual ~TestSmartPtrIface() {} + long m_pad = 0; + int only_in_iface() { return 37; } +}; + +class MultiDerivedTestSmartPtr : public PubDerivedTestSmartPtr, + public TestSmartPtrIface {}; + +std::shared_ptr create_shared_ptr_to_derived(); +std::unique_ptr create_unique_ptr_to_derived(); +std::unique_ptr create_unique_ptr_to_offset_derived(); + +// sinks expecting a smart pointer to the *derived* type; a base-class smart +// pointer (even when its object was auto-down-cast) must not be accepted here +int pass_unique_ptr_to_derived(std::unique_ptr p) { + return p->only_in_derived(); +} + +int pass_shared_ptr_to_derived(std::shared_ptr p) { + return p->only_in_derived(); +} + +// Overloaded function to check if automatic downcasting is consistently +// applied for regular proxy objects and smart pointer proxies. +std::string pass_ptr_overloaded(TestSmartPtr*) { return "TestSmartPtr"; } +std::string pass_ptr_overloaded(PubDerivedTestSmartPtr*) { + return "PubDerivedTestSmartPtr"; +} +std::string pass_ref_overloaded(TestSmartPtr&) { return "TestSmartPtr"; } +std::string pass_ref_overloaded(PubDerivedTestSmartPtr&) { + return "PubDerivedTestSmartPtr"; +} +std::string pass_val_overloaded(TestSmartPtr) { return "TestSmartPtr"; } +std::string pass_val_overloaded(PubDerivedTestSmartPtr) { + return "PubDerivedTestSmartPtr"; +} + +//=========================================================================== +class TestMoving1 { // for move ctors etc. +public: + static int s_move_counter; + static int s_instance_counter; + +public: + TestMoving1() { ++s_instance_counter; } + TestMoving1(TestMoving1&&) { + ++s_move_counter; + ++s_instance_counter; + } + TestMoving1(const TestMoving1&) { ++s_instance_counter; } + TestMoving1& operator=(TestMoving1&&) { + ++s_move_counter; + return *this; + } + TestMoving1& operator=(TestMoving1&) { return *this; } + ~TestMoving1() { --s_instance_counter; } +}; + +class TestMoving2 { // note opposite method order from TestMoving1 +public: + static int s_move_counter; + static int s_instance_counter; + +public: + TestMoving2() { ++s_instance_counter; } + TestMoving2(const TestMoving1&) { ++s_instance_counter; } + TestMoving2(const TestMoving2&) { ++s_instance_counter; } + TestMoving2(TestMoving2&& other) { + ++s_move_counter; + ++s_instance_counter; + } + TestMoving2& operator=(TestMoving2&) { return *this; } + TestMoving2& operator=(TestMoving2&&) { + ++s_move_counter; + return *this; + } + ~TestMoving2() { --s_instance_counter; } +}; + +void implicit_converion_move(TestMoving2&&); + +//=========================================================================== +struct TestData { // for initializer list construction + TestData(int i = 0) : m_int(i) {} + int m_int; +}; + +struct TestData2 { + TestData2(int i = 0) : m_int(i) {} + virtual ~TestData2() {} + int m_int; +}; + +template class WithInitList { +public: + WithInitList(std::initializer_list ll) : m_data(ll) {} + const T& operator[](int i) { return m_data[i]; } + + int size() { return m_data.size(); } + +private: + std::vector m_data; +}; + +//=========================================================================== +struct FNTestStruct { // for std::function<> testing + FNTestStruct(int i) : t(i) {} + int t; +}; +std::function FNCreateTestStructFunc(); + +namespace FunctionNS { +struct FNTestStruct { + FNTestStruct(int i) : t(i) {} + int t; +}; +std::function FNCreateTestStructFunc(); +} // namespace FunctionNS + +//=========================================================================== +struct StructWithHash {}; // for std::hash<> testing +struct StructWithoutHash {}; + +namespace std { +template <> struct hash { + size_t operator()(const StructWithHash&) const { return 17; } +}; +} // namespace std + +#endif // CPP11FEATURES_H diff --git a/bindings/pyroot/cppjit/test/cpp/crossinheritance.cxx b/bindings/pyroot/cppjit/test/cpp/crossinheritance.cxx new file mode 100644 index 0000000000000..88f8e9e3b4295 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/crossinheritance.cxx @@ -0,0 +1,100 @@ +#include "crossinheritance.h" + +// for overridden method checking +CrossInheritance::Base1::~Base1() {} + +int CrossInheritance::Base1::call_get_value(Base1* b) { return b->get_value(); } + +int CrossInheritance::Base1::call_sum_value(Base1* b, int i) { + return b->sum_value(i); +} + +int CrossInheritance::Base1::call_sum_all(Base1* b, int i) { + return b->sum_all(i); +} + +int CrossInheritance::Base1::call_sum_all(Base1* b, int i, int j) { + return b->sum_all(i, j); +} + +int CrossInheritance::Base1::sum_pass_value(Base1* b) { + int a = 0; + a += b->pass_value1(1); + int i = 2; + a += b->pass_value2(i); + a += b->pass_value3(3); + a += b->pass_value4(*b); + a += b->pass_value5(*b); + return a; +} + +int CrossInheritance::IBase2::call_get_value(IBase2* b) { + return b->get_value(); +} + +CrossInheritance::IBase3::IBase3(int i) { m_int = i; } + +int CrossInheritance::CBase2::get_value() { return 42; } + +int CrossInheritance::IBase4::call_get_value(IBase4* b) { + return b->get_value(); +} + +int CrossInheritance::CBase4::get_value() const { return 27; } + +int CrossInheritance::TDerived1::get_value() { return 27; } + +int CrossInheritance::CountableBase::s_count = 0; + +CrossInheritance::CountableBase::CountableBase() { ++s_count; } + +CrossInheritance::CountableBase::CountableBase(const CountableBase&) { + ++s_count; +} + +CrossInheritance::CountableBase& +CrossInheritance::CountableBase::operator=(const CountableBase&) { + return *this; +} + +CrossInheritance::CountableBase::~CountableBase() { --s_count; } + +int CrossInheritance::CountableBase::call() { return -1; } + +CrossInheritance::Component::Component() { ++s_count; } + +CrossInheritance::Component::~Component() { --s_count; } + +int CrossInheritance::Component::get_count() { return s_count; } + +int CrossInheritance::Component::s_count = 0; + +namespace { + +class ComponentWithValue : public CrossInheritance::Component { +public: + ComponentWithValue(int value) : m_value(value) {} + int getValue() { return m_value; } + +protected: + int m_value; +}; + +} // unnamed namespace + +CrossInheritance::Component* CrossInheritance::build_component(int value) { + return new ComponentWithValue(value); +} + +CrossInheritance::Component* CrossInheritance::cycle_component(Component* c) { + return c; +} + +// for protected member testing +AccessProtected::MyBase::MyBase() : my_data(101) { /* empty */ } + +AccessProtected::MyBase::~MyBase() { /* empty */ } + +int AccessProtected::MyBase::get_data_v() { return my_data; } + +int AccessProtected::MyBase::get_data() { return my_data; } diff --git a/bindings/pyroot/cppjit/test/cpp/crossinheritance.h b/bindings/pyroot/cppjit/test/cpp/crossinheritance.h new file mode 100644 index 0000000000000..e7ed4f6f70d58 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/crossinheritance.h @@ -0,0 +1,130 @@ +#ifndef CPPJIT_TEST_CROSSINHERITANCE_H +#define CPPJIT_TEST_CROSSINHERITANCE_H + +#include + +//=========================================================================== +namespace CrossInheritance { + +class Base1 { // for overridden method checking +public: + Base1() : m_int(42) {} + Base1(int i) : m_int(i) {} + virtual ~Base1(); + + virtual int get_value() { return m_int; } + static int call_get_value(Base1* b); + + virtual int sum_value(int i) { return m_int + i; } + static int call_sum_value(Base1* b, int); + + virtual int sum_all(int i) { return m_int + i; } + virtual int sum_all(int i, int j) { return m_int + i + j; } + static int call_sum_all(Base1* b, int); + static int call_sum_all(Base1* b, int, int); + + virtual int pass_value1(int a) { return a; } + virtual int pass_value2(int& a) { return a; } + virtual int pass_value3(const int& a) { return a; } + virtual int pass_value4(const Base1& b) { return b.m_int; } + virtual int pass_value5(Base1& b) { return b.m_int; } + static int sum_pass_value(Base1* b); + +public: + int m_int; +}; + +class IBase2 { +public: + IBase2() {} + virtual ~IBase2() {} + virtual int get_value() = 0; + static int call_get_value(IBase2* b); +}; + +class IBase3 : IBase2 { +public: + IBase3(int); + int m_int; +}; + +class CBase2 : public IBase2 { +public: + int get_value(); +}; + +class IBase4 { +public: + IBase4() {} + virtual ~IBase4() {} + virtual int get_value() const = 0; // <- const, as opposed to IBase2 + static int call_get_value(IBase4* b); +}; + +class CBase4 : public IBase4 { +public: + int get_value() const; +}; + +template class TBase1 { +public: + virtual ~TBase1() {} + virtual int get_value() { return 42; } +}; + +class TDerived1 : public TBase1 { +public: + int get_value(); +}; + +using TBase1_I = TBase1; + +class CountableBase { +public: + CountableBase(); + CountableBase(const CountableBase&); + CountableBase& operator=(const CountableBase&); + virtual ~CountableBase(); + + virtual int call(); + + static int s_count; +}; + +class Component { +public: + Component(); + Component(const Component&) = delete; + Component& operator=(const Component&) = delete; + virtual ~Component(); + + static int get_count(); + +private: + static int s_count; +}; + +Component* build_component(int value); +Component* cycle_component(Component* c); + +} // namespace CrossInheritance + +//=========================================================================== +namespace AccessProtected { // for protected member testing + +class MyBase { +public: + MyBase(); + virtual ~MyBase(); + +protected: + virtual int get_data_v(); + int get_data(); + +protected: + int my_data; +}; + +} // namespace AccessProtected + +#endif // !CPPJIT_TEST_CROSSINHERITANCE_H diff --git a/bindings/pyroot/cppjit/test/cpp/datatypes.cxx b/bindings/pyroot/cppjit/test/cpp/datatypes.cxx new file mode 100644 index 0000000000000..babc876eca83a --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/datatypes.cxx @@ -0,0 +1,1042 @@ +#include "datatypes.h" + +//=========================================================================== +std::vector vecFruits{kCitrus, kApple}; + +//=========================================================================== +CppjitTestData::CppjitTestData() : m_const_int(17), m_owns_arrays(false) { + m_bool = false; + m_char = 'a'; + m_schar = 'b'; + m_uchar = 'c'; + m_wchar = L'D'; + m_char16 = u'\u00df'; + m_char32 = U'\u00df'; + m_byte = (std::byte)'d'; + m_int8 = -9; + m_uint8 = 9; + m_short = -11; + m_ushort = 11u; + m_int = -22; + m_uint = 22u; + m_long = -33l; + m_ulong = 33ul; + m_llong = -44ll; + m_ullong = 44ull; + m_long64 = -55ll; + m_ulong64 = 55ull; + m_float = -66.f; + m_double = -77.; + m_ldouble = -88.l; + m_complex = {99., 101.}; + m_icomplex = {121, 141}; + m_ccomplex = {151., 161.}; + m_enum = kNothing; + m_voidp = (void*)0; + + m_bool_array2 = new bool[N]; + m_schar_array2 = new signed char[N]; + m_uchar_array2 = new unsigned char[N]; + m_byte_array2 = new std::byte[N]; + m_int8_array2 = new int8_t[N]; + m_uint8_array2 = new uint8_t[N]; + m_short_array2 = new short[N]; + m_ushort_array2 = new unsigned short[N]; + m_int_array2 = new int[N]; + m_uint_array2 = new unsigned int[N]; + m_long_array2 = new long[N]; + m_ulong_array2 = new unsigned long[N]; + + m_float_array2 = new float[N]; + m_double_array2 = new double[N]; + m_complex_array2 = new complex_t[N]; + m_ccomplex_array2 = new ccomplex_t[N]; + + for (int i = 0; i < N; ++i) { + m_bool_array[i] = bool(i % 2); + m_bool_array2[i] = bool((i + 1) % 2); + m_schar_array[i] = 1 * i; + m_schar_array2[i] = 2 * i; + m_uchar_array[i] = 1u * i; + m_uchar_array2[i] = 2u * i; + m_byte_array[i] = (std::byte)(3u * i); + m_byte_array2[i] = (std::byte)(4u * i); + m_int8_array[i] = -1 * i; + m_int8_array2[i] = -2 * i; + m_uint8_array[i] = 3u * i; + m_uint8_array2[i] = 4u * i; + m_short_array[i] = -5 * i; + m_short_array2[i] = -6 * i; + m_ushort_array[i] = 7u * i; + m_ushort_array2[i] = 8u * i; + m_int_array[i] = -9 * i; + m_int_array2[i] = -10 * i; + m_uint_array[i] = 11u * i; + m_uint_array2[i] = 23u * i; + m_long_array[i] = -13l * i; + m_long_array2[i] = -14l * i; + m_ulong_array[i] = 15ul * i; + m_ulong_array2[i] = 18ul * i; + + m_float_array[i] = -13.f * i; + m_float_array2[i] = -14.f * i; + m_double_array[i] = -15. * i; + m_double_array2[i] = -16. * i; + m_complex_array2[i] = {17. * i, 18. * i}; + m_ccomplex_array2[i] = {19. * i, 20. * i}; + } + + m_owns_arrays = true; + + m_pod.m_int = 888; + m_pod.m_double = 3.14; + + m_ppod = &m_pod; +}; + +CppjitTestData::~CppjitTestData() { destroy_arrays(); } + +void CppjitTestData::destroy_arrays() { + if (m_owns_arrays == true) { + delete[] m_bool_array2; + delete[] m_schar_array2; + delete[] m_uchar_array2; + delete[] m_byte_array2; + delete[] m_int8_array2; + delete[] m_uint8_array2; + delete[] m_short_array2; + delete[] m_ushort_array2; + delete[] m_int_array2; + delete[] m_uint_array2; + delete[] m_long_array2; + delete[] m_ulong_array2; + + delete[] m_float_array2; + delete[] m_double_array2; + delete[] m_complex_array2; + delete[] m_ccomplex_array2; + + m_owns_arrays = false; + } +} + +//- getters ----------------------------------------------------------------- +bool CppjitTestData::get_bool() { return m_bool; } +char CppjitTestData::get_char() { return m_char; } +signed char CppjitTestData::get_schar() { return m_schar; } +unsigned char CppjitTestData::get_uchar() { return m_uchar; } +wchar_t CppjitTestData::get_wchar() { return m_wchar; } +char16_t CppjitTestData::get_char16() { return m_char16; } +char32_t CppjitTestData::get_char32() { return m_char32; } +std::byte CppjitTestData::get_byte() { return m_byte; } +int8_t CppjitTestData::get_int8() { return m_int8; } +uint8_t CppjitTestData::get_uint8() { return m_uint8; } +short CppjitTestData::get_short() { return m_short; } +unsigned short CppjitTestData::get_ushort() { return m_ushort; } +int CppjitTestData::get_int() { return m_int; } +unsigned int CppjitTestData::get_uint() { return m_uint; } +long CppjitTestData::get_long() { return m_long; } +unsigned long CppjitTestData::get_ulong() { return m_ulong; } +long long CppjitTestData::get_llong() { return m_llong; } +unsigned long long CppjitTestData::get_ullong() { return m_ullong; } +Long64_t CppjitTestData::get_long64() { return m_long64; } +ULong64_t CppjitTestData::get_ulong64() { return m_ulong64; } +float CppjitTestData::get_float() { return m_float; } +double CppjitTestData::get_double() { return m_double; } +long double CppjitTestData::get_ldouble() { return m_ldouble; } +long double CppjitTestData::get_ldouble_def(long double ld) { return ld; } +complex_t CppjitTestData::get_complex() { return m_complex; } +icomplex_t CppjitTestData::get_icomplex() { return m_icomplex; } +ccomplex_t CppjitTestData::get_ccomplex() { return m_ccomplex; } +CppjitTestData::EWhat CppjitTestData::get_enum() { return m_enum; } +void* CppjitTestData::get_voidp() { return m_voidp; } + +bool* CppjitTestData::get_bool_array() { return m_bool_array; } +bool* CppjitTestData::get_bool_array2() { return m_bool_array2; } +signed char* CppjitTestData::get_schar_array() { return m_schar_array; } +signed char* CppjitTestData::get_schar_array2() { return m_schar_array2; } +unsigned char* CppjitTestData::get_uchar_array() { return m_uchar_array; } +unsigned char* CppjitTestData::get_uchar_array2() { return m_uchar_array2; } +std::byte* CppjitTestData::get_byte_array() { return m_byte_array; } +std::byte* CppjitTestData::get_byte_array2() { return m_byte_array2; } +int8_t* CppjitTestData::get_int8_array() { return m_int8_array; } +int8_t* CppjitTestData::get_int8_array2() { return m_int8_array2; } +uint8_t* CppjitTestData::get_uint8_array() { return m_uint8_array; } +uint8_t* CppjitTestData::get_uint8_array2() { return m_uint8_array2; } +short* CppjitTestData::get_short_array() { return m_short_array; } +short* CppjitTestData::get_short_array2() { return m_short_array2; } +unsigned short* CppjitTestData::get_ushort_array() { return m_ushort_array; } +unsigned short* CppjitTestData::get_ushort_array2() { return m_ushort_array2; } +int* CppjitTestData::get_int_array() { return m_int_array; } +int* CppjitTestData::get_int_array2() { return m_int_array2; } +unsigned int* CppjitTestData::get_uint_array() { return m_uint_array; } +unsigned int* CppjitTestData::get_uint_array2() { return m_uint_array2; } +long* CppjitTestData::get_long_array() { return m_long_array; } +long* CppjitTestData::get_long_array2() { return m_long_array2; } +unsigned long* CppjitTestData::get_ulong_array() { return m_ulong_array; } +unsigned long* CppjitTestData::get_ulong_array2() { return m_ulong_array2; } + +float* CppjitTestData::get_float_array() { return m_float_array; } +float* CppjitTestData::get_float_array2() { return m_float_array2; } +double* CppjitTestData::get_double_array() { return m_double_array; } +double* CppjitTestData::get_double_array2() { return m_double_array2; } +complex_t* CppjitTestData::get_complex_array() { return m_complex_array; } +complex_t* CppjitTestData::get_complex_array2() { return m_complex_array2; } +ccomplex_t* CppjitTestData::get_ccomplex_array() { return m_ccomplex_array; } +ccomplex_t* CppjitTestData::get_ccomplex_array2() { return m_ccomplex_array2; } + +CppjitTestPod CppjitTestData::get_pod_val() { return m_pod; } +CppjitTestPod* CppjitTestData::get_pod_val_ptr() { return &m_pod; } +CppjitTestPod& CppjitTestData::get_pod_val_ref() { return m_pod; } +CppjitTestPod*& CppjitTestData::get_pod_ptrref() { return m_ppod; } + +CppjitTestPod* CppjitTestData::get_pod_ptr() { return m_ppod; } + +//- getters const-ref ------------------------------------------------------- +const bool& CppjitTestData::get_bool_cr() { return m_bool; } +const char& CppjitTestData::get_char_cr() { return m_char; } +const signed char& CppjitTestData::get_schar_cr() { return m_schar; } +const unsigned char& CppjitTestData::get_uchar_cr() { return m_uchar; } +const wchar_t& CppjitTestData::get_wchar_cr() { return m_wchar; } +const char16_t& CppjitTestData::get_char16_cr() { return m_char16; } +const char32_t& CppjitTestData::get_char32_cr() { return m_char32; } +const std::byte& CppjitTestData::get_byte_cr() { return m_byte; } +const int8_t& CppjitTestData::get_int8_cr() { return m_int8; } +const uint8_t& CppjitTestData::get_uint8_cr() { return m_uint8; } +const short& CppjitTestData::get_short_cr() { return m_short; } +const unsigned short& CppjitTestData::get_ushort_cr() { return m_ushort; } +const int& CppjitTestData::get_int_cr() { return m_int; } +const unsigned int& CppjitTestData::get_uint_cr() { return m_uint; } +const long& CppjitTestData::get_long_cr() { return m_long; } +const unsigned long& CppjitTestData::get_ulong_cr() { return m_ulong; } +const long long& CppjitTestData::get_llong_cr() { return m_llong; } +const unsigned long long& CppjitTestData::get_ullong_cr() { return m_ullong; } +const Long64_t& CppjitTestData::get_long64_cr() { return m_long64; } +const ULong64_t& CppjitTestData::get_ulong64_cr() { return m_ulong64; } +const float& CppjitTestData::get_float_cr() { return m_float; } +const double& CppjitTestData::get_double_cr() { return m_double; } +const long double& CppjitTestData::get_ldouble_cr() { return m_ldouble; } +const complex_t& CppjitTestData::get_complex_cr() { return m_complex; } +const icomplex_t& CppjitTestData::get_icomplex_cr() { return m_icomplex; } +const ccomplex_t& CppjitTestData::get_ccomplex_cr() { return m_ccomplex; } +const CppjitTestData::EWhat& CppjitTestData::get_enum_cr() { return m_enum; } + +//- getters ref ------------------------------------------------------------- +bool& CppjitTestData::get_bool_r() { return m_bool; } +char& CppjitTestData::get_char_r() { return m_char; } +signed char& CppjitTestData::get_schar_r() { return m_schar; } +unsigned char& CppjitTestData::get_uchar_r() { return m_uchar; } +wchar_t& CppjitTestData::get_wchar_r() { return m_wchar; } +char16_t& CppjitTestData::get_char16_r() { return m_char16; } +char32_t& CppjitTestData::get_char32_r() { return m_char32; } +std::byte& CppjitTestData::get_byte_r() { return m_byte; } +int8_t& CppjitTestData::get_int8_r() { return m_int8; } +uint8_t& CppjitTestData::get_uint8_r() { return m_uint8; } +short& CppjitTestData::get_short_r() { return m_short; } +unsigned short& CppjitTestData::get_ushort_r() { return m_ushort; } +int& CppjitTestData::get_int_r() { return m_int; } +unsigned int& CppjitTestData::get_uint_r() { return m_uint; } +long& CppjitTestData::get_long_r() { return m_long; } +unsigned long& CppjitTestData::get_ulong_r() { return m_ulong; } +long long& CppjitTestData::get_llong_r() { return m_llong; } +unsigned long long& CppjitTestData::get_ullong_r() { return m_ullong; } +Long64_t& CppjitTestData::get_long64_r() { return m_long64; } +ULong64_t& CppjitTestData::get_ulong64_r() { return m_ulong64; } +float& CppjitTestData::get_float_r() { return m_float; } +double& CppjitTestData::get_double_r() { return m_double; } +long double& CppjitTestData::get_ldouble_r() { return m_ldouble; } +complex_t& CppjitTestData::get_complex_r() { return m_complex; } +icomplex_t& CppjitTestData::get_icomplex_r() { return m_icomplex; } +ccomplex_t& CppjitTestData::get_ccomplex_r() { return m_ccomplex; } +CppjitTestData::EWhat& CppjitTestData::get_enum_r() { return m_enum; } + +//- setters ----------------------------------------------------------------- +void CppjitTestData::set_bool(bool b) { m_bool = b; } +void CppjitTestData::set_char(char c) { m_char = c; } +void CppjitTestData::set_schar(signed char sc) { m_schar = sc; } +void CppjitTestData::set_uchar(unsigned char uc) { m_uchar = uc; } +void CppjitTestData::set_wchar(wchar_t wc) { m_wchar = wc; } +void CppjitTestData::set_char16(char16_t c16) { m_char16 = c16; } +void CppjitTestData::set_char32(char32_t c32) { m_char32 = c32; } +void CppjitTestData::set_byte(std::byte b) { m_byte = b; } +void CppjitTestData::set_int8(int8_t s8) { m_int8 = s8; } +void CppjitTestData::set_uint8(uint8_t u8) { m_uint8 = u8; } +void CppjitTestData::set_short(short s) { m_short = s; } +void CppjitTestData::set_ushort(unsigned short us) { m_ushort = us; } +void CppjitTestData::set_int(int i) { m_int = i; } +void CppjitTestData::set_uint(unsigned int ui) { m_uint = ui; } +void CppjitTestData::set_long(long l) { m_long = l; } +void CppjitTestData::set_ulong(unsigned long ul) { m_ulong = ul; } +void CppjitTestData::set_llong(long long ll) { m_llong = ll; } +void CppjitTestData::set_ullong(unsigned long long ull) { m_ullong = ull; } +void CppjitTestData::set_long64(Long64_t l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64(ULong64_t ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float(float f) { m_float = f; } +void CppjitTestData::set_double(double d) { m_double = d; } +void CppjitTestData::set_ldouble(long double ld) { m_ldouble = ld; } +void CppjitTestData::set_complex(complex_t cd) { m_complex = cd; } +void CppjitTestData::set_icomplex(icomplex_t ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex(ccomplex_t cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum(EWhat w) { m_enum = w; } +void CppjitTestData::set_voidp(void* p) { m_voidp = p; } + +void CppjitTestData::set_pod_val(CppjitTestPod p) { m_pod = p; } +void CppjitTestData::set_pod_ptr_in(CppjitTestPod* pp) { m_pod = *pp; } +void CppjitTestData::set_pod_ptr_out(CppjitTestPod* pp) { *pp = m_pod; } +void CppjitTestData::set_pod_ref(const CppjitTestPod& rp) { m_pod = rp; } +void CppjitTestData::set_pod_ptrptr_in(CppjitTestPod** ppp) { m_pod = **ppp; } +void CppjitTestData::set_pod_void_ptrptr_in(void** pp) { + m_pod = **((CppjitTestPod**)pp); +} +void CppjitTestData::set_pod_ptrptr_out(CppjitTestPod** ppp) { + delete *ppp; + *ppp = new CppjitTestPod(m_pod); +} +void CppjitTestData::set_pod_void_ptrptr_out(void** pp) { + delete *((CppjitTestPod**)pp); + *((CppjitTestPod**)pp) = new CppjitTestPod(m_pod); +} + +void CppjitTestData::set_pod_ptr(CppjitTestPod* pp) { m_ppod = pp; } + +//- setters const-ref ------------------------------------------------------- +void CppjitTestData::set_bool_cr(const bool& b) { m_bool = b; } +void CppjitTestData::set_char_cr(const char& c) { m_char = c; } +void CppjitTestData::set_schar_cr(const signed char& sc) { m_schar = sc; } +void CppjitTestData::set_uchar_cr(const unsigned char& uc) { m_uchar = uc; } +void CppjitTestData::set_wchar_cr(const wchar_t& wc) { m_wchar = wc; } +void CppjitTestData::set_char16_cr(const char16_t& c16) { m_char16 = c16; } +void CppjitTestData::set_char32_cr(const char32_t& c32) { m_char32 = c32; } +void CppjitTestData::set_byte_cr(const std::byte& b) { m_byte = b; } +void CppjitTestData::set_int8_cr(const int8_t& s8) { m_int8 = s8; } +void CppjitTestData::set_uint8_cr(const uint8_t& u8) { m_uint8 = u8; } +void CppjitTestData::set_short_cr(const short& s) { m_short = s; } +void CppjitTestData::set_ushort_cr(const unsigned short& us) { m_ushort = us; } +void CppjitTestData::set_int_cr(const int& i) { m_int = i; } +void CppjitTestData::set_uint_cr(const unsigned int& ui) { m_uint = ui; } +void CppjitTestData::set_long_cr(const long& l) { m_long = l; } +void CppjitTestData::set_ulong_cr(const unsigned long& ul) { m_ulong = ul; } +void CppjitTestData::set_llong_cr(const long long& ll) { m_llong = ll; } +void CppjitTestData::set_ullong_cr(const unsigned long long& ull) { + m_ullong = ull; +} +void CppjitTestData::set_long64_cr(const Long64_t& l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64_cr(const ULong64_t& ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float_cr(const float& f) { m_float = f; } +void CppjitTestData::set_double_cr(const double& d) { m_double = d; } +void CppjitTestData::set_ldouble_cr(const long double& ld) { m_ldouble = ld; } +void CppjitTestData::set_complex_cr(const complex_t& cd) { m_complex = cd; } +void CppjitTestData::set_icomplex_cr(const icomplex_t& ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex_cr(const ccomplex_t& cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum_cr(const EWhat& w) { m_enum = w; } + +//- setters ref ------------------------------------------------------------- +void CppjitTestData::set_bool_r(bool& b) { b = true; } +void CppjitTestData::set_char_r(char& c) { c = 'a'; } +void CppjitTestData::set_wchar_r(wchar_t& wc) { wc = 'b'; } +void CppjitTestData::set_char16_r(char16_t& c16) { c16 = u'\u6c24'; } +void CppjitTestData::set_char32_r(char32_t& c32) { c32 = U'\U0001f34e'; } +void CppjitTestData::set_schar_r(signed char& sc) { sc = 'c'; } +void CppjitTestData::set_uchar_r(unsigned char& uc) { uc = 'd'; } +void CppjitTestData::set_byte_r(std::byte& b) { b = (std::byte)'e'; } +void CppjitTestData::set_short_r(short& s) { s = -1; } +void CppjitTestData::set_ushort_r(unsigned short& us) { us = 2; } +void CppjitTestData::set_int_r(int& i) { i = -3; } +void CppjitTestData::set_uint_r(unsigned int& ui) { ui = 4; } +void CppjitTestData::set_long_r(long& l) { l = -5; } +void CppjitTestData::set_ulong_r(unsigned long& ul) { ul = 6; } +void CppjitTestData::set_llong_r(long long& ll) { ll = -7; } +void CppjitTestData::set_ullong_r(unsigned long long& ull) { ull = 8; } +void CppjitTestData::set_float_r(float& f) { f = 5.f; } +void CppjitTestData::set_double_r(double& d) { d = -5.; } +void CppjitTestData::set_ldouble_r(long double& ld) { ld = 10.l; } + +//- setters ptr ------------------------------------------------------------- +void CppjitTestData::set_bool_p(bool* b) { *b = true; } +void CppjitTestData::set_char_p(char* c) { *c = 'a'; } +void CppjitTestData::set_wchar_p(wchar_t* wc) { *wc = 'b'; } +void CppjitTestData::set_char16_p(char16_t* c16) { *c16 = u'\u6c24'; } +void CppjitTestData::set_char32_p(char32_t* c32) { *c32 = U'\U0001f34e'; } +void CppjitTestData::set_schar_p(signed char* sc) { *sc = 'c'; } +void CppjitTestData::set_uchar_p(unsigned char* uc) { *uc = 'd'; } +void CppjitTestData::set_byte_p(std::byte* b) { *b = (std::byte)'e'; } +void CppjitTestData::set_int8_p(int8_t* i8) { *i8 = -27; } +void CppjitTestData::set_uint8_p(uint8_t* ui8) { *ui8 = 28; } +void CppjitTestData::set_short_p(short* s) { *s = -1; } +void CppjitTestData::set_ushort_p(unsigned short* us) { *us = 2; } +void CppjitTestData::set_int_p(int* i) { *i = -3; } +void CppjitTestData::set_uint_p(unsigned int* ui) { *ui = 4; } +void CppjitTestData::set_long_p(long* l) { *l = -5; } +void CppjitTestData::set_ulong_p(unsigned long* ul) { *ul = 6; } +void CppjitTestData::set_llong_p(long long* ll) { *ll = -7; } +void CppjitTestData::set_ullong_p(unsigned long long* ull) { *ull = 8; } +void CppjitTestData::set_float_p(float* f) { *f = 5.f; } +void CppjitTestData::set_double_p(double* d) { *d = -5.; } +void CppjitTestData::set_ldouble_p(long double* ld) { *ld = 10.l; } + +//- setters ptrptr ---------------------------------------------------------- +void CppjitTestData::set_bool_ppa(bool** b) { + (*b) = new bool[3]; + (*b)[0] = true; + (*b)[1] = false; + (*b)[2] = true; +} +void CppjitTestData::set_char_ppa(char** c) { + (*c) = new char[3]; + (*c)[0] = 'a'; + (*c)[1] = 'b'; + (*c)[2] = 'c'; +} +void CppjitTestData::set_wchar_ppa(wchar_t** wc) { + (*wc) = new wchar_t[3]; + (*wc)[0] = 'd'; + (*wc)[1] = 'e'; + (*wc)[2] = 'f'; +} +void CppjitTestData::set_char16_ppa(char16_t** c16) { + (*c16) = new char16_t[3]; + (*c16)[0] = u'\u6c24'; + (*c16)[1] = u'\u6c25'; + (*c16)[2] = u'\u6c26'; +} +void CppjitTestData::set_char32_ppa(char32_t** c32) { + (*c32) = new char32_t[3]; + (*c32)[0] = U'\U0001f34d'; + (*c32)[1] = U'\U0001f34e'; + (*c32)[2] = U'\U0001f34f'; +} +void CppjitTestData::set_schar_ppa(signed char** sc) { + (*sc) = new signed char[3]; + (*sc)[0] = 'g'; + (*sc)[1] = 'h'; + (*sc)[2] = 'j'; +} +void CppjitTestData::set_uchar_ppa(unsigned char** uc) { + (*uc) = new unsigned char[3]; + (*uc)[0] = 'k'; + (*uc)[1] = 'l'; + (*uc)[2] = 'm'; +} +void CppjitTestData::set_byte_ppa(std::byte** b) { + (*b) = new std::byte[3]; + (*b)[0] = (std::byte)'n'; + (*b)[1] = (std::byte)'o'; + (*b)[2] = (std::byte)'p'; +} +void CppjitTestData::set_int8_ppa(int8_t** i8) { + (*i8) = new int8_t[3]; + (*i8)[0] = -27; + (*i8)[1] = -28; + (*i8)[2] = -29; +} +void CppjitTestData::set_uint8_ppa(uint8_t** ui8) { + (*ui8) = new uint8_t[3]; + (*ui8)[0] = 28; + (*ui8)[1] = 29; + (*ui8)[2] = 30; +} +void CppjitTestData::set_short_ppa(short** s) { + (*s) = new short[3]; + (*s)[0] = -1; + (*s)[1] = -2; + (*s)[2] = -3; +} +void CppjitTestData::set_ushort_ppa(unsigned short** us) { + (*us) = new unsigned short[3]; + (*us)[0] = 4; + (*us)[1] = 5; + (*us)[2] = 6; +} +void CppjitTestData::set_int_ppa(int** i) { + (*i) = new int[3]; + (*i)[0] = -7; + (*i)[1] = -8; + (*i)[2] = -9; +} +void CppjitTestData::set_uint_ppa(unsigned int** ui) { + (*ui) = new unsigned int[3]; + (*ui)[0] = 10; + (*ui)[1] = 11; + (*ui)[2] = 12; +} +void CppjitTestData::set_long_ppa(long** l) { + (*l) = new long[3]; + (*l)[0] = -13; + (*l)[1] = -14; + (*l)[2] = -15; +} +void CppjitTestData::set_ulong_ppa(unsigned long** ul) { + (*ul) = new unsigned long[3]; + (*ul)[0] = 16; + (*ul)[1] = 17; + (*ul)[2] = 18; +} +void CppjitTestData::set_llong_ppa(long long** ll) { + (*ll) = new long long[3]; + (*ll)[0] = -19; + (*ll)[1] = -20; + (*ll)[2] = -21; +} +void CppjitTestData::set_ullong_ppa(unsigned long long** ull) { + (*ull) = new unsigned long long[3]; + (*ull)[0] = 22; + (*ull)[1] = 23; + (*ull)[2] = 24; +} +void CppjitTestData::set_float_ppa(float** f) { + (*f) = new float[3]; + (*f)[0] = 5.f; + (*f)[1] = 10.f; + (*f)[2] = 20.f; +} +void CppjitTestData::set_double_ppa(double** d) { + (*d) = new double[3]; + (*d)[0] = -5; + (*d)[1] = -10.; + (*d)[2] = -20.; +} +void CppjitTestData::set_ldouble_ppa(long double** ld) { + (*ld) = new long double[3]; + (*ld)[0] = 5.l; + (*ld)[1] = 10.f; + (*ld)[2] = 20.l; +} + +intptr_t CppjitTestData::set_char_ppm(char** c) { + *c = (char*)malloc(4 * sizeof(char)); + return (intptr_t)*c; +} +intptr_t CppjitTestData::set_cchar_ppm(const char** cc) { + *cc = (const char*)malloc(4 * sizeof(char)); + return (intptr_t)*cc; +} +intptr_t CppjitTestData::set_wchar_ppm(wchar_t** w) { + *w = (wchar_t*)malloc(4 * sizeof(wchar_t)); + return (intptr_t)*w; +} +intptr_t CppjitTestData::set_char16_ppm(char16_t** c16) { + *c16 = (char16_t*)malloc(4 * sizeof(char16_t)); + return (intptr_t)*c16; +} +intptr_t CppjitTestData::set_char32_ppm(char32_t** c32) { + *c32 = (char32_t*)malloc(4 * sizeof(char32_t)); + return (intptr_t)*c32; +} +intptr_t CppjitTestData::set_cwchar_ppm(const wchar_t** cw) { + *cw = (const wchar_t*)malloc(4 * sizeof(wchar_t)); + return (intptr_t)*cw; +} +intptr_t CppjitTestData::set_cchar16_ppm(const char16_t** c16) { + *c16 = (const char16_t*)malloc(4 * sizeof(char16_t)); + return (intptr_t)*c16; +} +intptr_t CppjitTestData::set_cchar32_ppm(const char32_t** c32) { + *c32 = (const char32_t*)malloc(4 * sizeof(char32_t)); + return (intptr_t)*c32; +} +intptr_t CppjitTestData::set_void_ppm(void** v) { + *v = malloc(4 * sizeof(void*)); + return (intptr_t)*v; +} + +intptr_t CppjitTestData::freeit(void* ptr) { + intptr_t out = (intptr_t)ptr; + free(ptr); + return out; +} + +//- setters r-value --------------------------------------------------------- +void CppjitTestData::set_bool_rv(bool&& b) { m_bool = b; } +void CppjitTestData::set_char_rv(char&& c) { m_char = c; } +void CppjitTestData::set_schar_rv(signed char&& sc) { m_schar = sc; } +void CppjitTestData::set_uchar_rv(unsigned char&& uc) { m_uchar = uc; } +void CppjitTestData::set_wchar_rv(wchar_t&& wc) { m_wchar = wc; } +void CppjitTestData::set_char16_rv(char16_t&& c16) { m_char16 = c16; } +void CppjitTestData::set_char32_rv(char32_t&& c32) { m_char32 = c32; } +void CppjitTestData::set_byte_rv(std::byte&& b) { m_byte = b; } +void CppjitTestData::set_int8_rv(int8_t&& s8) { m_int8 = s8; } +void CppjitTestData::set_uint8_rv(uint8_t&& u8) { m_uint8 = u8; } +void CppjitTestData::set_short_rv(short&& s) { m_short = s; } +void CppjitTestData::set_ushort_rv(unsigned short&& us) { m_ushort = us; } +void CppjitTestData::set_int_rv(int&& i) { m_int = i; } +void CppjitTestData::set_uint_rv(unsigned int&& ui) { m_uint = ui; } +void CppjitTestData::set_long_rv(long&& l) { m_long = l; } +void CppjitTestData::set_ulong_rv(unsigned long&& ul) { m_ulong = ul; } +void CppjitTestData::set_llong_rv(long long&& ll) { m_llong = ll; } +void CppjitTestData::set_ullong_rv(unsigned long long&& ull) { m_ullong = ull; } +void CppjitTestData::set_long64_rv(Long64_t&& l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64_rv(ULong64_t&& ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float_rv(float&& f) { m_float = f; } +void CppjitTestData::set_double_rv(double&& d) { m_double = d; } +void CppjitTestData::set_ldouble_rv(long double&& ld) { m_ldouble = ld; } +void CppjitTestData::set_complex_rv(complex_t&& cd) { m_complex = cd; } +void CppjitTestData::set_icomplex_rv(icomplex_t&& ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex_rv(ccomplex_t&& cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum_rv(EWhat&& w) { m_enum = w; } + +//- passers ----------------------------------------------------------------- +unsigned char* CppjitTestData::pass_array(unsigned char* a) { return a; } +short* CppjitTestData::pass_array(short* a) { return a; } +unsigned short* CppjitTestData::pass_array(unsigned short* a) { return a; } +int* CppjitTestData::pass_array(int* a) { return a; } +unsigned int* CppjitTestData::pass_array(unsigned int* a) { return a; } +long* CppjitTestData::pass_array(long* a) { return a; } +unsigned long* CppjitTestData::pass_array(unsigned long* a) { return a; } +float* CppjitTestData::pass_array(float* a) { return a; } +double* CppjitTestData::pass_array(double* a) { return a; } +complex_t* CppjitTestData::pass_array(complex_t* a) { return a; } +ccomplex_t* CppjitTestData::pass_array(ccomplex_t* a) { return a; } + +//- static data members ----------------------------------------------------- +bool CppjitTestData::s_bool = false; +char CppjitTestData::s_char = 'c'; +signed char CppjitTestData::s_schar = 's'; +unsigned char CppjitTestData::s_uchar = 'u'; +wchar_t CppjitTestData::s_wchar = L'U'; +char16_t CppjitTestData::s_char16 = u'\u6c29'; +char32_t CppjitTestData::s_char32 = U'\U0001f34b'; +std::byte CppjitTestData::s_byte = (std::byte)'b'; +int8_t CppjitTestData::s_int8 = -87; +uint8_t CppjitTestData::s_uint8 = 87; +short CppjitTestData::s_short = -101; +unsigned short CppjitTestData::s_ushort = 255u; +int CppjitTestData::s_int = -202; +unsigned int CppjitTestData::s_uint = 202u; +long CppjitTestData::s_long = -303l; +unsigned long CppjitTestData::s_ulong = 303ul; +long long CppjitTestData::s_llong = -404ll; +unsigned long long CppjitTestData::s_ullong = 404ull; +Long64_t CppjitTestData::s_long64 = -505ll; +ULong64_t CppjitTestData::s_ulong64 = 505ull; +float CppjitTestData::s_float = -606.f; +double CppjitTestData::s_double = -707.; +long double CppjitTestData::s_ldouble = -808.l; +complex_t CppjitTestData::s_complex = {909., -909.}; +icomplex_t CppjitTestData::s_icomplex = {979, -979}; +ccomplex_t CppjitTestData::s_ccomplex = {919., -919.}; +CppjitTestData::EWhat CppjitTestData::s_enum = CppjitTestData::kNothing; +void* CppjitTestData::s_voidp = (void*)0; +std::string CppjitTestData::s_strv = "Hello"; +std::string* CppjitTestData::s_strp = nullptr; + +//- strings ----------------------------------------------------------------- +const char* CppjitTestData::get_valid_string(const char* in) { return in; } +const char* CppjitTestData::get_invalid_string() { return (const char*)0; } +const wchar_t* CppjitTestData::get_valid_wstring(const wchar_t* in) { + return in; +} +const wchar_t* CppjitTestData::get_invalid_wstring() { + return (const wchar_t*)0; +} +const char16_t* CppjitTestData::get_valid_string16(const char16_t* in) { + return in; +} +const char16_t* CppjitTestData::get_invalid_string16() { + return (const char16_t*)0; +} +const char32_t* CppjitTestData::get_valid_string32(const char32_t* in) { + return in; +} +const char32_t* CppjitTestData::get_invalid_string32() { + return (const char32_t*)0; +} + +//= global functions ======================================================== +intptr_t get_pod_address(CppjitTestData& c) { return (intptr_t)&c.m_pod; } + +intptr_t get_int_address(CppjitTestData& c) { return (intptr_t)&c.m_pod.m_int; } + +intptr_t get_double_address(CppjitTestData& c) { + return (intptr_t)&c.m_pod.m_double; +} + +//= global variables/pointers =============================================== +bool g_bool = false; +char g_char = 'w'; +signed char g_schar = 'v'; +unsigned char g_uchar = 'u'; +wchar_t g_wchar = L'U'; +char16_t g_char16 = u'\u6c21'; +char32_t g_char32 = U'\u6c21'; +std::byte g_byte = (std::byte)'x'; +int8_t g_int8 = -66; +uint8_t g_uint8 = 66; +short g_short = -88; +unsigned short g_ushort = 88u; +int g_int = -188; +unsigned int g_uint = 188u; +long g_long = -288; +unsigned long g_ulong = 288ul; +long long g_llong = -388ll; +unsigned long long g_ullong = 388ull; +Long64_t g_long64 = -488ll; +ULong64_t g_ulong64 = 488ull; +float g_float = -588.f; +double g_double = -688.; +long double g_ldouble = -788.l; +complex_t g_complex = {808., -808.}; +icomplex_t g_icomplex = {909, -909}; +ccomplex_t g_ccomplex = {858., -858.}; +EFruit g_enum = kBanana; +void* g_voidp = nullptr; + +//= global accessors ======================================================== +void set_global_int(int i) { g_int = i; } + +int get_global_int() { return g_int; } + +CppjitTestPod* g_pod = (CppjitTestPod*)0; + +bool is_global_pod(CppjitTestPod* t) { return t == g_pod; } + +void set_global_pod(CppjitTestPod* t) { g_pod = t; } + +CppjitTestPod* get_global_pod() { return g_pod; } + +CppjitTestPod* get_null_pod() { return (CppjitTestPod*)0; } + +std::string g_some_global_string = "C++"; +std::string get_some_global_string() { return g_some_global_string; } +std::string g_some_global_string2 = "C++"; +std::string get_some_global_string2() { return g_some_global_string2; } + +const char16_t* g_some_global_string16 = u"z\u00df\u6c34"; +const char32_t* g_some_global_string32 = U"z\u00df\u6c34\U0001f34c"; + +std::string SomeStaticDataNS::s_some_static_string = "C++"; +std::string SomeStaticDataNS::get_some_static_string() { + return s_some_static_string; +} +std::string SomeStaticDataNS::s_some_static_string2 = "C++"; +std::string SomeStaticDataNS::get_some_static_string2() { + return s_some_static_string2; +} + +StorableData gData{5.}; + +//= special case of "byte" arrays =========================================== +int64_t sum_uc_data(unsigned char* data, int size) { + int64_t total = 0; + for (int i = 0; i < size; ++i) + total += int64_t(data[i]); + return total; +} + +int64_t sum_byte_data(std::byte* data, int size) { + return sum_uc_data((unsigned char*)data, size); +} + +//= function pointer passing ================================================ +int sum_of_int1(int i1, int i2) { return i1 + i2; } + +int sum_of_int2(int i1, int i2) { return 2 * i1 + i2; } + +int (*sum_of_int_ptr)(int, int) = sum_of_int1; + +int call_sum_of_int(int i1, int i2) { + if (sum_of_int_ptr) + return (*sum_of_int_ptr)(i1, i2); + return -1; +} + +double sum_of_double(double d1, double d2) { return d1 + d2; } + +double call_double_double(double (*f)(double, double), double d1, double d2) { + if (!f) + return -1.; + return f(d1, d2); +} + +//= callable passing ======================================================== +int call_int_int(int (*f)(int, int), int i1, int i2) { return f(i1, i2); } + +void call_void(void (*f)(int), int i) { f(i); } + +int call_refi(void (*f)(int&)) { + int i = -1; + f(i); + return i; +} + +int call_refl(void (*f)(long&)) { + long l = -1L; + f(l); + return l; +} + +int call_refd(void (*f)(double&)) { + double d = -1.; + f(d); + return d; +} + +StoreCallable::StoreCallable(double (*f)(double, double)) : fF(f) { + /* empty */ +} + +void StoreCallable::set_callable(double (*f)(double, double)) { fF = f; } + +double StoreCallable::operator()(double d1, double d2) { return fF(d1, d2); } + +//= callable through std::function ========================================== +double call_double_double_sf(const std::function& f, + double d1, double d2) { + return f(d1, d2); +} + +int call_int_int_sf(const std::function& f, int i1, int i2) { + return f(i1, i2); +} + +void call_void_sf(const std::function& f, int i) { f(i); } + +int call_refi_sf(const std::function& f) { + int i = -1; + f(i); + return i; +} + +int call_refl_sf(const std::function& f) { + long l = -1L; + f(l); + return l; +} + +int call_refd_sf(const std::function& f) { + double d = -1.; + f(d); + return d; +} + +StoreCallable_sf::StoreCallable_sf( + const std::function& f) + : fF(f) { + /* empty */ +} + +void StoreCallable_sf::set_callable( + const std::function& f) { + fF = f; +} + +double StoreCallable_sf::operator()(double d1, double d2) { return fF(d1, d2); } + +//= array of C strings passing ============================================== +std::vector +ArrayOfCStrings::takes_array_of_cstrings(const char* args[], int len) { + std::vector v; + v.reserve(len); + for (int i = 0; i < len; ++i) + v.emplace_back(args[i]); + + return v; +} + +//= aggregate testing ====================================================== +int AggregateTest::Aggregate1::sInt = 17; +int AggregateTest::Aggregate2::sInt = 27; + +//= multi-dim arrays ======================================================= +namespace MultiDimArrays { + +template static inline T** allocate_2d(size_t N, size_t M) { + T** arr = (T**)malloc(sizeof(void*) * N); + for (size_t i = 0; i < N; ++i) + arr[i] = (T*)malloc(sizeof(T) * M); + return arr; +} + +static inline void free_2d(void** arr, size_t N) { + if (arr) { + for (size_t i = 0; i < N; ++i) + free(arr[i]); + free(arr); + } +} + +template +static inline T*** allocate_3d(size_t N, size_t M, size_t K) { + T*** arr = (T***)malloc(sizeof(void*) * N); + for (size_t i = 0; i < N; ++i) { + arr[i] = (T**)malloc(sizeof(void*) * M); + for (size_t j = 0; j < M; ++j) + arr[i][j] = (T*)malloc(sizeof(T) * K); + } + return arr; +} + +static inline void free_3d(void*** arr, size_t N, size_t M) { + if (arr) { + for (size_t i = 0; i < N; ++i) { + for (size_t j = 0; j < M; ++j) + free(arr[i][j]); + free(arr[i]); + } + free(arr); + } +} + +} // namespace MultiDimArrays + +MultiDimArrays::DataHolder::DataHolder() { + m_short2a = allocate_2d(5, 7); + m_unsigned_short2a = allocate_2d(5, 7); + m_int2a = allocate_2d(5, 7); + m_unsigned_int2a = allocate_2d(5, 7); + m_long2a = allocate_2d(5, 7); + m_unsigned_long2a = allocate_2d(5, 7); + m_long_long2a = allocate_2d(5, 7); + m_unsigned_long_long2a = allocate_2d(5, 7); + m_float2a = allocate_2d(5, 7); + m_double2a = allocate_2d(5, 7); + + for (size_t i = 0; i < 5; ++i) { + for (size_t j = 0; j < 7; ++j) { + size_t val = 5 * i + j; + m_short2a[i][j] = (short)val; + m_unsigned_short2a[i][j] = (unsigned short)val; + m_int2a[i][j] = (int)val; + m_unsigned_int2a[i][j] = (unsigned int)val; + m_long2a[i][j] = (long)val; + m_unsigned_long2a[i][j] = (unsigned long)val; + m_long_long2a[i][j] = (long long)val; + m_unsigned_long_long2a[i][j] = (unsigned long long)val; + m_float2a[i][j] = (float)val; + m_double2a[i][j] = (double)val; + } + } + + m_short2b = nullptr; + m_unsigned_short2b = nullptr; + m_int2b = nullptr; + m_unsigned_int2b = nullptr; + m_long2b = nullptr; + m_unsigned_long2b = nullptr; + m_long_long2b = nullptr; + m_unsigned_long_long2b = nullptr; + m_float2b = nullptr; + m_double2b = nullptr; + + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 5; ++j) { + size_t val = 3 * i + j; + m_short2c[i][j] = (short)val; + m_unsigned_short2c[i][j] = (unsigned short)val; + m_int2c[i][j] = (int)val; + m_unsigned_int2c[i][j] = (unsigned int)val; + m_long2c[i][j] = (long)val; + m_unsigned_long2c[i][j] = (unsigned long)val; + m_long_long2c[i][j] = (long long)val; + m_unsigned_long_long2c[i][j] = (unsigned long long)val; + m_float2c[i][j] = (float)val; + m_double2c[i][j] = (double)val; + + for (size_t k = 0; k < 7; ++k) { + val = 3 * i + 2 * j + k; + m_short3c[i][j][k] = (short)val; + m_unsigned_short3c[i][j][k] = (unsigned short)val; + m_int3c[i][j][k] = (int)val; + m_unsigned_int3c[i][j][k] = (unsigned int)val; + m_long3c[i][j][k] = (long)val; + m_unsigned_long3c[i][j][k] = (unsigned long)val; + m_long_long3c[i][j][k] = (long long)val; + m_unsigned_long_long3c[i][j][k] = (unsigned long long)val; + m_float3c[i][j][k] = (float)val; + m_double3c[i][j][k] = (double)val; + } + } + } + + m_short3a = allocate_3d(5, 7, 11); + m_unsigned_short3a = allocate_3d(5, 7, 11); + m_int3a = allocate_3d(5, 7, 11); + m_unsigned_int3a = allocate_3d(5, 7, 11); + m_long3a = allocate_3d(5, 7, 11); + m_unsigned_long3a = allocate_3d(5, 7, 11); + m_long_long3a = allocate_3d(5, 7, 11); + m_unsigned_long_long3a = allocate_3d(5, 7, 11); + m_float3a = allocate_3d(5, 7, 11); + m_double3a = allocate_3d(5, 7, 11); + + for (size_t i = 0; i < 5; ++i) { + for (size_t j = 0; j < 7; ++j) { + for (size_t k = 0; k < 11; ++k) { + size_t val = 7 * i + 3 * j + k; + m_short3a[i][j][k] = (short)val; + m_unsigned_short3a[i][j][k] = (unsigned short)val; + m_int3a[i][j][k] = (int)val; + m_unsigned_int3a[i][j][k] = (unsigned int)val; + m_long3a[i][j][k] = (long)val; + m_unsigned_long3a[i][j][k] = (unsigned long)val; + m_long_long3a[i][j][k] = (long long)val; + m_unsigned_long_long3a[i][j][k] = (unsigned long long)val; + m_float3a[i][j][k] = (float)val; + m_double3a[i][j][k] = (double)val; + } + } + } +} + +MultiDimArrays::DataHolder::~DataHolder() { + free_2d((void**)m_short2a, 5); + free_2d((void**)m_unsigned_short2a, 5); + free_2d((void**)m_int2a, 5); + free_2d((void**)m_unsigned_int2a, 5); + free_2d((void**)m_long2a, 5); + free_2d((void**)m_unsigned_long2a, 5); + free_2d((void**)m_long_long2a, 5); + free_2d((void**)m_unsigned_long_long2a, 5); + free_2d((void**)m_float2a, 5); + free_2d((void**)m_double2a, 5); + + free_2d((void**)m_short2b, 5); + free_2d((void**)m_unsigned_short2b, 5); + free_2d((void**)m_int2b, 5); + free_2d((void**)m_unsigned_int2b, 5); + free_2d((void**)m_long2b, 5); + free_2d((void**)m_unsigned_long2b, 5); + free_2d((void**)m_long_long2b, 5); + free_2d((void**)m_unsigned_long_long2b, 5); + free_2d((void**)m_float2b, 5); + free_2d((void**)m_double2b, 5); + + free_3d((void***)m_short3a, 5, 7); + free_3d((void***)m_unsigned_short3a, 5, 7); + free_3d((void***)m_int3a, 5, 7); + free_3d((void***)m_unsigned_int3a, 5, 7); + free_3d((void***)m_long3a, 5, 7); + free_3d((void***)m_unsigned_long3a, 5, 7); + free_3d((void***)m_long_long3a, 5, 7); + free_3d((void***)m_unsigned_long_long3a, 5, 7); + free_3d((void***)m_float3a, 5, 7); + free_3d((void***)m_double3a, 5, 7); +} + +#define MULTIDIM_ARRAYS_NEW2D(type, name) \ + type** MultiDimArrays::DataHolder::new_##name##2d(int N, int M) { \ + type** arr = allocate_2d(N, M); \ + for (size_t i = 0; i < N; ++i) { \ + for (size_t j = 0; j < M; ++j) { \ + size_t val = 7 * i + j; \ + arr[i][j] = (type)val; \ + } \ + } \ + return arr; \ + } + +MULTIDIM_ARRAYS_NEW2D(short, short) +MULTIDIM_ARRAYS_NEW2D(unsigned short, ushort) +MULTIDIM_ARRAYS_NEW2D(int, int) +MULTIDIM_ARRAYS_NEW2D(unsigned int, uint) +MULTIDIM_ARRAYS_NEW2D(long, long) +MULTIDIM_ARRAYS_NEW2D(unsigned long, ulong) +MULTIDIM_ARRAYS_NEW2D(long long, llong) +MULTIDIM_ARRAYS_NEW2D(unsigned long long, ullong) +MULTIDIM_ARRAYS_NEW2D(float, float) +MULTIDIM_ARRAYS_NEW2D(double, double) + +//=========================================================================== +namespace Int8_Uint8_Arrays { +int8_t test[6] = {-0x12, -0x34, -0x56, -0x78}; +uint8_t utest[6] = {0x12, 0x34, 0x56, 0x78}; +} // namespace Int8_Uint8_Arrays diff --git a/bindings/pyroot/cppjit/test/cpp/datatypes.h b/bindings/pyroot/cppjit/test/cpp/datatypes.h new file mode 100644 index 0000000000000..2a3a3a5afd512 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/datatypes.h @@ -0,0 +1,835 @@ +#ifndef CPPJIT_TEST_DATATYPES_H +#define CPPJIT_TEST_DATATYPES_H + +#ifdef _WIN32 +typedef __int64 Long64_t; +typedef unsigned __int64 ULong64_t; +#else +typedef long long Long64_t; +typedef unsigned long long ULong64_t; +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +const int N = 5; + +//=========================================================================== +struct CppjitTestPod { + int m_int; + double m_double; +}; + +//=========================================================================== +enum EFruit { kApple = 78, kBanana = 29, kCitrus = 34 }; +extern std::vector vecFruits; + +//=========================================================================== +enum class NamedClassEnum { E1 = 42 }; + +namespace EnumSpace { +enum E { E1 = 1, E2 }; +class EnumClass { +public: + enum { E1 = -1 }; + enum EE { E2 = -1 }; +}; + +typedef enum { AA = 1, BB, CC, DD } letter_code; + +enum class NamedClassEnum { E1 = -42 }; +} // namespace EnumSpace + +//=========================================================================== +class FourVector { +public: + FourVector(double x, double y, double z, double t) + : m_cc_called(false), m_x(x), m_y(y), m_z(z), m_t(t) {} + FourVector(const FourVector& s) + : m_cc_called(true), m_x(s.m_x), m_y(s.m_y), m_z(s.m_z), m_t(s.m_t) {} + + double operator[](int i) { + if (i == 0) + return m_x; + if (i == 1) + return m_y; + if (i == 2) + return m_z; + if (i == 3) + return m_t; + return -1; + } + + bool operator==(const FourVector& o) { + return (m_x == o.m_x && m_y == o.m_y && m_z == o.m_z && m_t == o.m_t); + } + +public: + bool m_cc_called; + +private: + double m_x, m_y, m_z, m_t; +}; + +//=========================================================================== +typedef std::complex complex_t; // maps to Py_complex +typedef std::complex icomplex_t; // no equivalent +#ifndef _WIN32 +typedef _Complex double ccomplex_t; // C-style complex, maps to Py_complex +#else +typedef _C_double_complex ccomplex_t; // id. +#endif + +class CppjitTestData { +public: + CppjitTestData(); + ~CppjitTestData(); + + // special cases + enum EWhat { kNothing = 6, kSomething = 111, kLots = 42 }; + + // helper + void destroy_arrays(); + + // getters + bool get_bool(); + char get_char(); + signed char get_schar(); + unsigned char get_uchar(); + wchar_t get_wchar(); + char16_t get_char16(); + char32_t get_char32(); + std::byte get_byte(); + int8_t get_int8(); + uint8_t get_uint8(); + short get_short(); + unsigned short get_ushort(); + int get_int(); + unsigned int get_uint(); + long get_long(); + unsigned long get_ulong(); + long long get_llong(); + unsigned long long get_ullong(); + Long64_t get_long64(); + ULong64_t get_ulong64(); + float get_float(); + double get_double(); + long double get_ldouble(); + long double get_ldouble_def(long double ld = 1); + complex_t get_complex(); + icomplex_t get_icomplex(); + ccomplex_t get_ccomplex(); + EWhat get_enum(); + void* get_voidp(); + + bool* get_bool_array(); + bool* get_bool_array2(); + signed char* get_schar_array(); + signed char* get_schar_array2(); + unsigned char* get_uchar_array(); + unsigned char* get_uchar_array2(); + std::byte* get_byte_array(); + std::byte* get_byte_array2(); + int8_t* get_int8_array(); + int8_t* get_int8_array2(); + uint8_t* get_uint8_array(); + uint8_t* get_uint8_array2(); + short* get_short_array(); + short* get_short_array2(); + unsigned short* get_ushort_array(); + unsigned short* get_ushort_array2(); + int* get_int_array(); + int* get_int_array2(); + unsigned int* get_uint_array(); + unsigned int* get_uint_array2(); + long* get_long_array(); + long* get_long_array2(); + unsigned long* get_ulong_array(); + unsigned long* get_ulong_array2(); + + float* get_float_array(); + float* get_float_array2(); + double* get_double_array(); + double* get_double_array2(); + complex_t* get_complex_array(); + complex_t* get_complex_array2(); + ccomplex_t* get_ccomplex_array(); + ccomplex_t* get_ccomplex_array2(); + + CppjitTestPod get_pod_val(); // for m_pod + CppjitTestPod* get_pod_val_ptr(); + CppjitTestPod& get_pod_val_ref(); + CppjitTestPod*& get_pod_ptrref(); + + CppjitTestPod* get_pod_ptr(); // for m_ppod + + // getters const-ref + const bool& get_bool_cr(); + const char& get_char_cr(); + const signed char& get_schar_cr(); + const unsigned char& get_uchar_cr(); + const wchar_t& get_wchar_cr(); + const char16_t& get_char16_cr(); + const char32_t& get_char32_cr(); + const std::byte& get_byte_cr(); + const int8_t& get_int8_cr(); + const uint8_t& get_uint8_cr(); + const short& get_short_cr(); + const unsigned short& get_ushort_cr(); + const int& get_int_cr(); + const unsigned int& get_uint_cr(); + const long& get_long_cr(); + const unsigned long& get_ulong_cr(); + const long long& get_llong_cr(); + const unsigned long long& get_ullong_cr(); + const Long64_t& get_long64_cr(); + const ULong64_t& get_ulong64_cr(); + const float& get_float_cr(); + const double& get_double_cr(); + const long double& get_ldouble_cr(); + const complex_t& get_complex_cr(); + const icomplex_t& get_icomplex_cr(); + const ccomplex_t& get_ccomplex_cr(); + const EWhat& get_enum_cr(); + + // getters ref + bool& get_bool_r(); + char& get_char_r(); + signed char& get_schar_r(); + unsigned char& get_uchar_r(); + wchar_t& get_wchar_r(); + char16_t& get_char16_r(); + char32_t& get_char32_r(); + std::byte& get_byte_r(); + int8_t& get_int8_r(); + uint8_t& get_uint8_r(); + short& get_short_r(); + unsigned short& get_ushort_r(); + int& get_int_r(); + unsigned int& get_uint_r(); + long& get_long_r(); + unsigned long& get_ulong_r(); + long long& get_llong_r(); + unsigned long long& get_ullong_r(); + Long64_t& get_long64_r(); + ULong64_t& get_ulong64_r(); + float& get_float_r(); + double& get_double_r(); + long double& get_ldouble_r(); + complex_t& get_complex_r(); + icomplex_t& get_icomplex_r(); + ccomplex_t& get_ccomplex_r(); + EWhat& get_enum_r(); + + // setters + void set_bool(bool); + void set_char(char); + void set_schar(signed char); + void set_uchar(unsigned char); + void set_wchar(wchar_t); + void set_char16(char16_t); + void set_char32(char32_t); + void set_byte(std::byte); + void set_int8(int8_t); + void set_uint8(uint8_t); + void set_short(short); + void set_ushort(unsigned short); + void set_int(int); + void set_uint(unsigned int); + void set_long(long); + void set_ulong(unsigned long); + void set_llong(long long); + void set_ullong(unsigned long long); + void set_long64(Long64_t); + void set_ulong64(ULong64_t); + void set_float(float); + void set_double(double); + void set_ldouble(long double); + void set_complex(complex_t); + void set_icomplex(icomplex_t); + void set_ccomplex(ccomplex_t); + void set_enum(EWhat); + void set_voidp(void*); + + void set_pod_val(CppjitTestPod); // for m_pod + void set_pod_ptr_in(CppjitTestPod*); + void set_pod_ptr_out(CppjitTestPod*); + void set_pod_ref(const CppjitTestPod&); + void set_pod_ptrptr_in(CppjitTestPod**); + void set_pod_void_ptrptr_in(void**); + void set_pod_ptrptr_out(CppjitTestPod**); + void set_pod_void_ptrptr_out(void**); + + void set_pod_ptr(CppjitTestPod*); // for m_ppod + + // setters const-ref + void set_bool_cr(const bool&); + void set_char_cr(const char&); + void set_schar_cr(const signed char&); + void set_uchar_cr(const unsigned char&); + void set_wchar_cr(const wchar_t&); + void set_char16_cr(const char16_t&); + void set_char32_cr(const char32_t&); + void set_byte_cr(const std::byte&); + void set_int8_cr(const int8_t&); + void set_uint8_cr(const uint8_t&); + void set_short_cr(const short&); + void set_ushort_cr(const unsigned short&); + void set_int_cr(const int&); + void set_uint_cr(const unsigned int&); + void set_long_cr(const long&); + void set_ulong_cr(const unsigned long&); + void set_llong_cr(const long long&); + void set_ullong_cr(const unsigned long long&); + void set_long64_cr(const Long64_t&); + void set_ulong64_cr(const ULong64_t&); + void set_float_cr(const float&); + void set_double_cr(const double&); + void set_ldouble_cr(const long double&); + void set_complex_cr(const complex_t&); + void set_icomplex_cr(const icomplex_t&); + void set_ccomplex_cr(const ccomplex_t&); + void set_enum_cr(const EWhat&); + + // setters ref + void set_bool_r(bool&); + void set_char_r(char&); + void set_wchar_r(wchar_t&); + void set_char16_r(char16_t&); + void set_char32_r(char32_t&); + void set_schar_r(signed char&); + void set_uchar_r(unsigned char&); + void set_byte_r(std::byte&); + void set_short_r(short&); + void set_ushort_r(unsigned short&); + void set_int_r(int&); + void set_uint_r(unsigned int&); + void set_long_r(long&); + void set_ulong_r(unsigned long&); + void set_llong_r(long long&); + void set_ullong_r(unsigned long long&); + void set_float_r(float&); + void set_double_r(double&); + void set_ldouble_r(long double&); + + // setters ptr + void set_bool_p(bool*); + void set_char_p(char*); + void set_wchar_p(wchar_t*); + void set_char16_p(char16_t*); + void set_char32_p(char32_t*); + void set_schar_p(signed char*); + void set_uchar_p(unsigned char*); + void set_byte_p(std::byte*); + void set_int8_p(int8_t*); + void set_uint8_p(uint8_t*); + void set_short_p(short*); + void set_ushort_p(unsigned short*); + void set_int_p(int*); + void set_uint_p(unsigned int*); + void set_long_p(long*); + void set_ulong_p(unsigned long*); + void set_llong_p(long long*); + void set_ullong_p(unsigned long long*); + void set_float_p(float*); + void set_double_p(double*); + void set_ldouble_p(long double*); + + // setters ptrptr + void set_bool_ppa(bool**); + void set_char_ppa(char**); + void set_wchar_ppa(wchar_t**); + void set_char16_ppa(char16_t**); + void set_char32_ppa(char32_t**); + void set_schar_ppa(signed char**); + void set_uchar_ppa(unsigned char**); + void set_byte_ppa(std::byte**); + void set_int8_ppa(int8_t**); + void set_uint8_ppa(uint8_t**); + void set_short_ppa(short**); + void set_ushort_ppa(unsigned short**); + void set_int_ppa(int**); + void set_uint_ppa(unsigned int**); + void set_long_ppa(long**); + void set_ulong_ppa(unsigned long**); + void set_llong_ppa(long long**); + void set_ullong_ppa(unsigned long long**); + void set_float_ppa(float**); + void set_double_ppa(double**); + void set_ldouble_ppa(long double**); + + intptr_t set_char_ppm(char**); + intptr_t set_cchar_ppm(const char**); + intptr_t set_wchar_ppm(wchar_t**); + intptr_t set_char16_ppm(char16_t**); + intptr_t set_char32_ppm(char32_t**); + intptr_t set_cwchar_ppm(const wchar_t**); + intptr_t set_cchar16_ppm(const char16_t**); + intptr_t set_cchar32_ppm(const char32_t**); + intptr_t set_void_ppm(void**); + + intptr_t freeit(void*); + + // setters r-value + void set_bool_rv(bool&&); + void set_char_rv(char&&); + void set_schar_rv(signed char&&); + void set_uchar_rv(unsigned char&&); + void set_wchar_rv(wchar_t&&); + void set_char16_rv(char16_t&&); + void set_char32_rv(char32_t&&); + void set_byte_rv(std::byte&&); + void set_int8_rv(int8_t&&); + void set_uint8_rv(uint8_t&&); + void set_short_rv(short&&); + void set_ushort_rv(unsigned short&&); + void set_int_rv(int&&); + void set_uint_rv(unsigned int&&); + void set_long_rv(long&&); + void set_ulong_rv(unsigned long&&); + void set_llong_rv(long long&&); + void set_ullong_rv(unsigned long long&&); + void set_long64_rv(Long64_t&&); + void set_ulong64_rv(ULong64_t&&); + void set_float_rv(float&&); + void set_double_rv(double&&); + void set_ldouble_rv(long double&&); + void set_complex_rv(complex_t&&); + void set_icomplex_rv(icomplex_t&&); + void set_ccomplex_rv(ccomplex_t&&); + void set_enum_rv(EWhat&&); + + // passers + unsigned char* pass_array(unsigned char*); + short* pass_array(short*); + unsigned short* pass_array(unsigned short*); + int* pass_array(int*); + unsigned int* pass_array(unsigned int*); + long* pass_array(long*); + unsigned long* pass_array(unsigned long*); + float* pass_array(float*); + double* pass_array(double*); + complex_t* pass_array(complex_t*); + ccomplex_t* pass_array(ccomplex_t*); + + unsigned char* pass_void_array_B(void* a) { + return pass_array((unsigned char*)a); + } + short* pass_void_array_h(void* a) { return pass_array((short*)a); } + unsigned short* pass_void_array_H(void* a) { + return pass_array((unsigned short*)a); + } + int* pass_void_array_i(void* a) { return pass_array((int*)a); } + unsigned int* pass_void_array_I(void* a) { + return pass_array((unsigned int*)a); + } + long* pass_void_array_l(void* a) { return pass_array((long*)a); } + unsigned long* pass_void_array_L(void* a) { + return pass_array((unsigned long*)a); + } + float* pass_void_array_f(void* a) { return pass_array((float*)a); } + double* pass_void_array_d(void* a) { return pass_array((double*)a); } + complex_t* pass_void_array_Z(void* a) { return pass_array((complex_t*)a); } + ccomplex_t* pass_void_array_cZ(void* a) { return pass_array((ccomplex_t*)a); } + + // strings + const char* get_valid_string(const char* in); + const char* get_invalid_string(); + const wchar_t* get_valid_wstring(const wchar_t* in); + const wchar_t* get_invalid_wstring(); + const char16_t* get_valid_string16(const char16_t* in); + const char16_t* get_invalid_string16(); + const char32_t* get_valid_string32(const char32_t* in); + const char32_t* get_invalid_string32(); + +public: + // basic types + bool m_bool; + char m_char; + signed char m_schar; + unsigned char m_uchar; + wchar_t m_wchar; + char16_t m_char16; + char32_t m_char32; + std::byte m_byte; + int8_t m_int8; + uint8_t m_uint8; + short m_short; + unsigned short m_ushort; + int m_int; + const int m_const_int; // special case: const testing + unsigned int m_uint; + long m_long; + unsigned long m_ulong; + long long m_llong; + unsigned long long m_ullong; + Long64_t m_long64; + ULong64_t m_ulong64; + float m_float; + double m_double; + long double m_ldouble; + complex_t m_complex; + icomplex_t m_icomplex; + ccomplex_t m_ccomplex; + EWhat m_enum; + void* m_voidp; + + // array types + bool m_bool_array[N]; + bool* m_bool_array2; + signed char m_schar_array[N]; + signed char* m_schar_array2; + unsigned char m_uchar_array[N]; + unsigned char* m_uchar_array2; + std::byte m_byte_array[N]; + std::byte* m_byte_array2; + int8_t m_int8_array[N]; + int8_t* m_int8_array2; + uint8_t m_uint8_array[N]; + uint8_t* m_uint8_array2; + short m_short_array[N]; + short* m_short_array2; + unsigned short m_ushort_array[N]; + unsigned short* m_ushort_array2; + int m_int_array[N]; + int* m_int_array2; + unsigned int m_uint_array[N]; + unsigned int* m_uint_array2; + long m_long_array[N]; + long* m_long_array2; + unsigned long m_ulong_array[N]; + unsigned long* m_ulong_array2; + + float m_float_array[N]; + float* m_float_array2; + double m_double_array[N]; + double* m_double_array2; + complex_t m_complex_array[N]; + complex_t* m_complex_array2; + icomplex_t m_icomplex_array[N]; + icomplex_t* m_icomplex_array2; + ccomplex_t m_ccomplex_array[N]; + ccomplex_t* m_ccomplex_array2; + + // object types + CppjitTestPod m_pod; + CppjitTestPod* m_ppod; + +public: + static bool s_bool; + static char s_char; + static signed char s_schar; + static unsigned char s_uchar; + static wchar_t s_wchar; + static char16_t s_char16; + static char32_t s_char32; + static std::byte s_byte; + static int8_t s_int8; + static uint8_t s_uint8; + static short s_short; + static unsigned short s_ushort; + static int s_int; + static unsigned int s_uint; + static long s_long; + static unsigned long s_ulong; + static long long s_llong; + static unsigned long long s_ullong; + static Long64_t s_long64; + static ULong64_t s_ulong64; + static float s_float; + static double s_double; + static long double s_ldouble; + static complex_t s_complex; + static icomplex_t s_icomplex; + static ccomplex_t s_ccomplex; + static EWhat s_enum; + static void* s_voidp; + static std::string s_strv; + static std::string* s_strp; + +private: + bool m_owns_arrays; +}; + +//= global functions ======================================================== +intptr_t get_pod_address(CppjitTestData& c); +intptr_t get_int_address(CppjitTestData& c); +intptr_t get_double_address(CppjitTestData& c); + +//= global variables/pointers =============================================== +extern bool g_bool; +extern char g_char; +extern signed char g_schar; +extern unsigned char g_uchar; +extern wchar_t g_wchar; +extern char16_t g_char16; +extern char32_t g_char32; +extern std::byte g_byte; +extern int8_t g_int8; +extern uint8_t g_uint8; +extern short g_short; +extern unsigned short g_ushort; +extern int g_int; +extern unsigned int g_uint; +extern long g_long; +extern unsigned long g_ulong; +extern long long g_llong; +extern unsigned long long g_ullong; +extern Long64_t g_long64; +extern ULong64_t g_ulong64; +extern float g_float; +extern double g_double; +extern long double g_ldouble; +extern complex_t g_complex; +extern icomplex_t g_icomplex; +extern ccomplex_t g_ccomplex; +extern EFruit g_enum; +extern void* g_voidp; + +static const bool g_c_bool = true; +static const char g_c_char = 'z'; +static const signed char g_c_schar = 'y'; +static const unsigned char g_c_uchar = 'x'; +static const wchar_t g_c_wchar = L'U'; +static const char16_t g_c_char16 = u'\u6c34'; +static const char32_t g_c_char32 = U'\U0001f34c'; +static const std::byte g_c_byte = (std::byte)'u'; +static const int8_t g_c_int8 = -12; +static const uint8_t g_c_uint8 = 12; +static const short g_c_short = -99; +static const unsigned short g_c_ushort = 99u; +static const int g_c_int = -199; +static const unsigned int g_c_uint = 199u; +static const long g_c_long = -299; +static const unsigned long g_c_ulong = 299ul; +static const long long g_c_llong = -399ll; +static const unsigned long long g_c_ullong = 399ull; +static const Long64_t g_c_long64 = -499ll; +static const ULong64_t g_c_ulong64 = 499ull; +static const float g_c_float = -599.f; +static const double g_c_double = -699.; +static const long double g_c_ldouble = -799.l; +static const complex_t g_c_complex = {1., 2.}; +static const icomplex_t g_c_icomplex = {3, 4}; +static const ccomplex_t g_c_ccomplex = {5., 6.}; +static const EFruit g_c_enum = kApple; +static const void* g_c_voidp = nullptr; + +//= global accessors ======================================================== +void set_global_int(int i); +int get_global_int(); + +extern CppjitTestPod* g_pod; +bool is_global_pod(CppjitTestPod* t); +void set_global_pod(CppjitTestPod* t); +CppjitTestPod* get_global_pod(); +CppjitTestPod* get_null_pod(); + +extern std::string g_some_global_string; +std::string get_some_global_string(); +extern std::string g_some_global_string2; +std::string get_some_global_string2(); + +extern const char16_t* g_some_global_string16; +extern const char32_t* g_some_global_string32; + +namespace SomeStaticDataNS { +extern std::string s_some_static_string; +std::string get_some_static_string(); +extern std::string s_some_static_string2; +std::string get_some_static_string2(); +} // namespace SomeStaticDataNS + +struct StorableData { + StorableData(double d) : fData(d) {} + double fData; +}; + +extern StorableData gData; + +//= special case of "byte" arrays =========================================== +int64_t sum_uc_data(unsigned char* data, int size); +int64_t sum_byte_data(std::byte* data, int size); + +//= function pointer passing ================================================ +int sum_of_int1(int i1, int i2); +int sum_of_int2(int i1, int i2); +extern int (*sum_of_int_ptr)(int, int); +int call_sum_of_int(int i1, int i2); + +double sum_of_double(double d1, double d2); +double call_double_double(double (*d)(double, double), double d1, double d2); + +struct sum_of_int_struct { + int (*sum_of_int_ptr)(int, int); +}; + +//= callable passing ======================================================== +int call_int_int(int (*)(int, int), int, int); +void call_void(void (*f)(int), int i); +int call_refi(void (*fcn)(int&)); +int call_refl(void (*fcn)(long&)); +int call_refd(void (*fcn)(double&)); + +class StoreCallable { + double (*fF)(double, double); + +public: + StoreCallable(double (*)(double, double)); + void set_callable(double (*)(double, double)); + double operator()(double, double); +}; + +//= callable through std::function ========================================== +double call_double_double_sf(const std::function&, + double d1, double d2); + +int call_int_int_sf(const std::function&, int, int); +void call_void_sf(const std::function&, int i); +int call_refi_sf(const std::function&); +int call_refl_sf(const std::function&); +int call_refd_sf(const std::function&); + +class StoreCallable_sf { + std::function fF; + +public: + StoreCallable_sf(const std::function&); + void set_callable(const std::function&); + double operator()(double, double); +}; + +//= array of struct variants ================================================ +namespace ArrayOfStruct { + +struct Foo { + int fVal; +}; + +struct Bar1 { + Bar1() : fArr(new Foo[2]) { + fArr[0].fVal = 42; + fArr[1].fVal = 13; + } + Bar1(const Bar1&) = delete; + Bar1& operator=(const Bar1&) = delete; + ~Bar1() { delete[] fArr; } + Foo* fArr; +}; + +struct Bar2 { + Bar2(int num_foo) : fArr(std::unique_ptr{new Foo[num_foo]}) { + for (int i = 0; i < num_foo; ++i) + fArr[i].fVal = 2 * i; + } + std::unique_ptr fArr; +}; + +} // namespace ArrayOfStruct + +//= array of C strings passing ============================================== +namespace ArrayOfCStrings { +std::vector takes_array_of_cstrings(const char* args[], int len); +} + +//= aggregate testing ====================================================== +namespace AggregateTest { + +struct Aggregate1 { + static int sInt; +}; + +struct Aggregate2 { + static int sInt; + int fInt = 42; +}; + +} // namespace AggregateTest + +//= multi-dim arrays ======================================================= +namespace MultiDimArrays { + +struct DataHolder { + DataHolder(); + ~DataHolder(); + + short** m_short2a; + unsigned short** m_unsigned_short2a; + int** m_int2a; + unsigned int** m_unsigned_int2a; + long** m_long2a; + unsigned long** m_unsigned_long2a; + long long** m_long_long2a; + unsigned long long** m_unsigned_long_long2a; + float** m_float2a; + double** m_double2a; + + short** m_short2b; + short** new_short2d(int N, int M); + unsigned short** m_unsigned_short2b; + unsigned short** new_ushort2d(int N, int M); + int** m_int2b; + int** new_int2d(int N, int M); + unsigned int** m_unsigned_int2b; + unsigned int** new_uint2d(int N, int M); + long** m_long2b; + long** new_long2d(int N, int M); + unsigned long** m_unsigned_long2b; + unsigned long** new_ulong2d(int N, int M); + long long** m_long_long2b; + long long** new_llong2d(int N, int M); + unsigned long long** m_unsigned_long_long2b; + unsigned long long** new_ullong2d(int N, int M); + float** m_float2b; + float** new_float2d(int N, int M); + double** m_double2b; + double** new_double2d(int N, int M); + + short m_short2c[3][5]; + unsigned short m_unsigned_short2c[3][5]; + int m_int2c[3][5]; + unsigned int m_unsigned_int2c[3][5]; + long m_long2c[3][5]; + unsigned long m_unsigned_long2c[3][5]; + long long m_long_long2c[3][5]; + unsigned long long m_unsigned_long_long2c[3][5]; + float m_float2c[3][5]; + double m_double2c[3][5]; + + short*** m_short3a; + unsigned short*** m_unsigned_short3a; + int*** m_int3a; + unsigned int*** m_unsigned_int3a; + long*** m_long3a; + unsigned long*** m_unsigned_long3a; + long long*** m_long_long3a; + unsigned long long*** m_unsigned_long_long3a; + float*** m_float3a; + double*** m_double3a; + + short m_short3c[3][5][7]; + unsigned short m_unsigned_short3c[3][5][7]; + int m_int3c[3][5][7]; + unsigned int m_unsigned_int3c[3][5][7]; + long m_long3c[3][5][7]; + unsigned long m_unsigned_long3c[3][5][7]; + long long m_long_long3c[3][5][7]; + unsigned long long m_unsigned_long_long3c[3][5][7]; + float m_float3c[3][5][7]; + double m_double3c[3][5][7]; +}; + +} // namespace MultiDimArrays + +//= int8_t/uint8_t arrays =================================================== +namespace Int8_Uint8_Arrays { +extern int8_t test[6]; +extern uint8_t utest[6]; +} // namespace Int8_Uint8_Arrays + +#endif // !CPPJIT_TEST_DATATYPES_H diff --git a/bindings/pyroot/cppjit/test/cpp/doc_helper.cxx b/bindings/pyroot/cppjit/test/cpp/doc_helper.cxx new file mode 100644 index 0000000000000..783baf95401ba --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/doc_helper.cxx @@ -0,0 +1,7 @@ +#include "doc_helper.h" + +void DocHelper::throw_an_error(int i) { + if (i) + throw SomeError{"this is an error"}; + throw SomeOtherError{"this is another error"}; +} diff --git a/bindings/pyroot/cppjit/test/cpp/doc_helper.h b/bindings/pyroot/cppjit/test/cpp/doc_helper.h new file mode 100644 index 0000000000000..75b62e3e47c01 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/doc_helper.h @@ -0,0 +1,30 @@ +#ifndef CPPJIT_TEST_DOCHELPER_H +#define CPPJIT_TEST_DOCHELPER_H + +#include +#include + +class SomeError : public std::exception { +public: + explicit SomeError(const std::string& msg) : fMsg(msg) {} + SomeError(const SomeError& s) : fMsg(s.fMsg) {} + + const char* what() const throw() override { return fMsg.c_str(); } + +private: + std::string fMsg; +}; + +class SomeOtherError : public SomeError { +public: + explicit SomeOtherError(const std::string& msg) : SomeError(msg) {} + SomeOtherError(const SomeOtherError& s) : SomeError(s) {} +}; + +namespace DocHelper { + +void throw_an_error(int i); + +} // namespace DocHelper + +#endif // !CPPJIT_TEST_DOCHELPER_H diff --git a/bindings/pyroot/cppjit/test/cpp/example01.cxx b/bindings/pyroot/cppjit/test/cpp/example01.cxx new file mode 100644 index 0000000000000..4791f7f73127a --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/example01.cxx @@ -0,0 +1,164 @@ +#include +#include +#include +#include + +#include "example01.h" + +//=========================================================================== +payload::payload(double d) : m_data(d) { count++; } +payload::payload(const payload& p) : m_data(p.m_data) { count++; } +payload& payload::operator=(const payload& p) { + if (this != &p) { + m_data = p.m_data; + } + return *this; +} +payload::~payload() { count--; } + +double payload::getData() { return m_data; } +void payload::setData(double d) { m_data = d; } + +// class-level data +int payload::count = 0; + +//=========================================================================== +example01::example01() : m_somedata(-99) { count++; } +example01::example01(int a) : m_somedata(a) { count++; } +example01::example01(const example01& e) : m_somedata(e.m_somedata) { count++; } +example01& example01::operator=(const example01& e) { + if (this != &e) { + m_somedata = e.m_somedata; + } + return *this; +} +example01::~example01() { count--; } + +// class-level methods +int example01::staticAddOneToInt(int a) { return a + 1; } +int example01::staticAddOneToInt(int a, int b) { return a + b + 1; } +double example01::staticAddToDouble(double a) { return a + 0.01; } +int example01::staticAtoi(const char* str) { return ::atoi(str); } +char* example01::staticStrcpy(const char* strin) { + char* strout = (char*)malloc(::strlen(strin) + 1); + ::strcpy(strout, strin); + return strout; +} +void example01::staticSetPayload(payload* p, double d) { p->setData(d); } + +payload* example01::staticCyclePayload(payload* p, double d) { + staticSetPayload(p, d); + return p; +} + +payload example01::staticCopyCyclePayload(payload* p, double d) { + staticSetPayload(p, d); + return *p; +} + +int example01::getCount() { return count; } + +void example01::setCount(int value) { count = value; } + +// instance methods +int example01::addDataToInt(int a) { return m_somedata + a; } + +int example01::addDataToIntConstRef(const int& a) { return m_somedata + a; } + +int example01::overloadedAddDataToInt(int a, int b) { + return m_somedata + a + b; +} + +int example01::overloadedAddDataToInt(int a) { return m_somedata + a; } + +int example01::overloadedAddDataToInt(int a, int b, int c) { + return m_somedata + a + b + c; +} + +double example01::addDataToDouble(double a) { return m_somedata + a; } + +int example01::addDataToAtoi(const char* str) { + return ::atoi(str) + m_somedata; +} + +char* example01::addToStringValue(const char* str) { + int out = ::atoi(str) + m_somedata; + std::ostringstream ss; + ss << out << std::ends; + std::string result = ss.str(); + char* cresult = (char*)malloc(result.size() + 1); + ::strcpy(cresult, result.c_str()); + return cresult; +} + +void example01::setPayload(payload* p) { p->setData(m_somedata); } + +payload* example01::cyclePayload(payload* p) { + setPayload(p); + return p; +} + +payload example01::copyCyclePayload(payload* p) { + setPayload(p); + return *p; +} + +// class-level data +int example01::count = 0; + +// global +int globalAddOneToInt(int a) { return a + 1; } + +int ns_example01::globalAddOneToInt(int a) { return ::globalAddOneToInt(a); } + +int installableAddOneToInt(example01& e, int a) { + return e.staticAddOneToInt(a); +} + +int ns_example01::gMyGlobalInt = 99; + +// argument passing +#define typeValueImp(itype, tname) \ + itype ArgPasser::tname##Value(itype arg0, int argn, itype arg1, \ + itype arg2) { \ + switch (argn) { \ + case 0: \ + return arg0; \ + case 1: \ + return arg1; \ + case 2: \ + return arg2; \ + default: \ + break; \ + } \ + \ + return (itype) - 1; \ + } + +typeValueImp(short, short) typeValueImp(unsigned short, ushort) + typeValueImp(int, int) typeValueImp(unsigned int, uint) + typeValueImp(long, long) typeValueImp(unsigned long, ulong) + + typeValueImp(float, float) typeValueImp(double, double) + + std::string ArgPasser::stringValue(std::string arg0, int argn, + std::string arg1) { + switch (argn) { + case 0: + return arg0; + case 1: + return arg1; + default: + break; + } + + return "argn invalid"; +} + +std::string ArgPasser::stringRef(const std::string& arg0, int argn, + const std::string& arg1) { + return stringValue(arg0, argn, arg1); +} + +// special case naming +z_& z_::gime_z_(z_& z) { return z; } diff --git a/bindings/pyroot/cppjit/test/cpp/example01.h b/bindings/pyroot/cppjit/test/cpp/example01.h new file mode 100644 index 0000000000000..e0a95375ad814 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/example01.h @@ -0,0 +1,115 @@ +#ifndef EXAMPLE_01_H +#define EXAMPLE_01_H + +#include + +class payload { +public: + payload(double d = 0.); + payload(const payload& p); + payload& operator=(const payload& e); + ~payload(); + + double getData(); + void setData(double d); + +public: // class-level data + static int count; + +private: + double m_data; +}; + +class example01 { +public: + example01(); + example01(int a); + example01(const example01& e); + example01& operator=(const example01& e); + virtual ~example01(); + +public: // class-level methods + static int staticAddOneToInt(int a); + static int staticAddOneToInt(int a, int b); + static double staticAddToDouble(double a); + static int staticAtoi(const char* str); + static char* staticStrcpy(const char* strin); + static void staticSetPayload(payload* p, double d); + static payload* staticCyclePayload(payload* p, double d); + static payload staticCopyCyclePayload(payload* p, double d); + static int getCount(); + static void setCount(int); + +public: // instance methods + int addDataToInt(int a); + int addDataToIntConstRef(const int& a); + int overloadedAddDataToInt(int a, int b); + int overloadedAddDataToInt(int a); + int overloadedAddDataToInt(int a, int b, int c); + double addDataToDouble(double a); + int addDataToAtoi(const char* str); + char* addToStringValue(const char* str); + + void setPayload(payload* p); + payload* cyclePayload(payload* p); + payload copyCyclePayload(payload* p); + +public: // class-level data + static int count; + +public: // instance data + int m_somedata; +}; + +// global functions and data +int globalAddOneToInt(int a); +namespace ns_example01 { +int globalAddOneToInt(int a); +extern int gMyGlobalInt; +} // namespace ns_example01 + +int installableAddOneToInt(example01&, int a); + +#define itypeValue(itype, tname) \ + itype tname##Value(itype arg0, int argn = 0, itype arg1 = 1, itype arg2 = 2) + +#define ftypeValue(ftype) \ + ftype ftype##Value(ftype arg0, int argn = 0, ftype arg1 = 1., ftype arg2 = 2.) + +// argument passing +class ArgPasser { // use a class for now as methptrgetter not +public: // implemented for global functions + itypeValue(short, short); + itypeValue(unsigned short, ushort); + itypeValue(int, int); + itypeValue(unsigned int, uint); + itypeValue(long, long); + itypeValue(unsigned long, ulong); + + ftypeValue(float); + ftypeValue(double); + + std::string stringValue(std::string arg0, int argn = 0, + std::string arg1 = "default"); + + std::string stringRef(const std::string& arg0, int argn = 0, + const std::string& arg1 = "default"); +}; + +// typedefs +typedef example01 example01_t; + +// special case naming +class z_ { +public: + z_& gime_z_(z_& z); + int myint; +}; + +// for pythonization checking +class example01a : public example01 { +public: + example01a(int a) : example01(a) {} +}; + +#endif // EXAMPLE_01_H diff --git a/bindings/pyroot/cppjit/test/cpp/fragile.cxx b/bindings/pyroot/cppjit/test/cpp/fragile.cxx new file mode 100644 index 0000000000000..f87bc1a637457 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/fragile.cxx @@ -0,0 +1,61 @@ +#include "fragile.h" +#include + +fragile::H::HH* fragile::H::HH::copy() { return (HH*)0; } + +fragile::I fragile::gI; + +void fragile::fglobal(int, double, char) { + /* empty; only used for doc-string testing */ +} + +namespace fragile { + +class Kderived : public K { +public: + virtual ~Kderived(); +}; + +} // namespace fragile + +fragile::Kderived::~Kderived() {} + +fragile::K::~K() {} + +fragile::K* fragile::K::GimeK(bool derived) { + if (!derived) + return this; + else { + static Kderived kd; + return &kd; + } +}; + +fragile::K* fragile::K::GimeL() { + static L l; + return &l; +} + +fragile::L::~L() {} + +int fragile::create_handle(OpaqueHandle_t* handle) { + *handle = (OpaqueHandle_t)0x01; + return 0x01; +} + +int fragile::destroy_handle(OpaqueHandle_t handle, intptr_t addr) { + if ((intptr_t)handle == addr) + return 1; + return 0; +} + +// for signal -> exception testing +void fragile::segfault() { + int* i = 0; + *i = 42; +} + +void fragile::sigabort() { assert(0); } + +// for duplicate testing +int fragile::add42(int i) { return i + 42; } diff --git a/bindings/pyroot/cppjit/test/cpp/fragile.h b/bindings/pyroot/cppjit/test/cpp/fragile.h new file mode 100644 index 0000000000000..d244ca00879ad --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/fragile.h @@ -0,0 +1,143 @@ +#ifndef FRAGILE_H +#define FRAGILE_H + +#include + +namespace fragile { + +class no_such_class; + +class A { +public: + virtual int check() { return (int)'A'; } + virtual A* gime_null() { return (A*)0; } +}; + +class B { +public: + virtual int check() { return (int)'B'; } + no_such_class* gime_no_such() { return 0; } +}; + +class C { +public: + virtual int check() { return (int)'C'; } + void use_no_such(no_such_class*) {} +}; + +class D { +public: + virtual int check() { return (int)'D'; } + virtual int check(int, int) { return (int)'D'; } + void overload() {} + void overload(no_such_class*) {} + void overload(char, int i = 0) {} // Reflex requires a named arg + void overload(int, no_such_class* p = 0) {} +}; + +static const uintptr_t dummy_location = 0xdead; + +class E { +public: + E() : m_pp_no_such((no_such_class**)&dummy_location), m_pp_a(0) {} + + virtual int check() { return (int)'E'; } + void overload(no_such_class**) {} + + no_such_class** m_pp_no_such; + A** m_pp_a; +}; + +class F { +public: + F() : m_int(0) {} + virtual int check() { return (int)'F'; } + int m_int; +}; + +class G { +public: + enum { unnamed1 = 24, unnamed2 = 96 }; + + class GG {}; +}; + +class H { +public: + class HH { + public: + HH* copy(); + }; + HH* m_h; +}; + +class I { +public: + operator bool() { return 0; } +}; + +extern I gI; + +class J { +public: + int method1(int, double) { return 0; } +}; + +void fglobal(int, double, char); + +namespace nested1 { +class A {}; +namespace nested2 { +class A {}; +namespace nested3 { +class A {}; +} // namespace nested3 +} // namespace nested2 +} // namespace nested1 + +class K { +public: + virtual ~K(); + K* GimeK(bool derived); + K* GimeL(); +}; + +class L : public K { +public: + virtual ~L(); + no_such_class* m_no_such; +}; + +class M { +public: + virtual ~M() {} + enum E1 { kOnce = 42 }; + enum E2 { kTwice = 12 }; +}; + +class N : public M { +public: + enum E2 { kTwice = 12 }; +}; + +class O { +public: + virtual int abstract() = 0; +}; + +class OpaqueType; +typedef OpaqueType* OpaqueHandle_t; + +int create_handle(OpaqueHandle_t* handle); +int destroy_handle(OpaqueHandle_t handle, intptr_t addr); + +// for signal -> exception testing +void segfault(); +void sigabort(); + +// for duplicate testing +int add42(int i); + +} // namespace fragile + +#endif // FRAGILE_H \ No newline at end of file diff --git a/bindings/pyroot/cppjit/test/cpp/operators.cxx b/bindings/pyroot/cppjit/test/cpp/operators.cxx new file mode 100644 index 0000000000000..96d1e2baac315 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/operators.cxx @@ -0,0 +1,140 @@ +#include "operators.h" + +// for testing the case of virtual operator== +v_opeq_base::v_opeq_base(int val) : m_val(val) {} +v_opeq_base::~v_opeq_base() {} + +bool v_opeq_base::operator==(const v_opeq_base& other) { + return m_val == other.m_val; +} + +v_opeq_derived::v_opeq_derived(int val) : v_opeq_base(val) {} +v_opeq_derived::~v_opeq_derived() {} + +bool v_opeq_derived::operator==(const v_opeq_derived& other) { + return m_val != other.m_val; +} + +// for indexing tests +int& YAMatrix1::operator()(int, int) { return m_val; } + +const int& YAMatrix1::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix2::operator[](int) { return m_val; } + +const int& YAMatrix2::operator[](int) const { return m_val; } + +//- +int& YAMatrix3::operator()(int, int) { return m_val; } + +const int& YAMatrix3::operator()(int, int) const { return m_val; } + +int& YAMatrix3::operator[](int) { return m_val; } + +const int& YAMatrix3::operator[](int) const { return m_val; } + +//- +int& YAMatrix4::operator[](int) { return m_val; } + +const int& YAMatrix4::operator[](int) const { return m_val; } + +int& YAMatrix4::operator()(int, int) { return m_val; } + +const int& YAMatrix4::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix5::operator[](int) { return m_val; } + +int YAMatrix5::operator[](int) const { return m_val; } + +int& YAMatrix5::operator()(int, int) { return m_val; } + +int YAMatrix5::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix6::operator[](int) { return m_val; } + +int YAMatrix6::operator[](int) const { return m_val; } + +int& YAMatrix6::operator()(int, int) { return m_val; } + +//- +int& YAMatrix7::operator[](int) { return m_val; } + +int& YAMatrix7::operator()(int, int) { return m_val; } + +//- for __radd__/__rmul__, non-associative +AssocADD operator+(int n, AssocADD& a) { return AssocADD(n + a.fx); } + +AssocADD operator+(AssocADD& a, int n) { return AssocADD(a.fx + n); } + +NonAssocRADD operator+(int n, NonAssocRADD& a) { + return NonAssocRADD(n + a.fx); +} + +AssocMUL operator*(int n, AssocMUL& m) { return AssocMUL(n * m.fx); } + +AssocMUL operator*(AssocMUL& m, int n) { return AssocMUL(m.fx * n); } + +NonAssocRMUL operator*(int n, NonAssocRMUL& m) { + return NonAssocRMUL(n * m.fx); +} + +//- for multi-lookup +double MultiLookup::operator*(const Vector2& v1, const Vector2& v2) { + return v1.x * v2.x + v1.y * v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator*(const Vector2& v, double a) { + return Vector2{v.x * a, v.y * a}; +} + +double MultiLookup::operator/(const Vector2& v1, const Vector2& v2) { + return v1.x / v2.x + v1.y / v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator/(const Vector2& v, double a) { + return Vector2{v.x / a, v.y / a}; +} + +double MultiLookup::operator+(const Vector2& v1, const Vector2& v2) { + return v1.x + v2.x + v1.y + v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator+(const Vector2& v, double a) { + return Vector2{v.x + a, v.y + a}; +} + +double MultiLookup::operator-(const Vector2& v1, const Vector2& v2) { + return v1.x - v2.x + v1.y - v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator-(const Vector2& v, double a) { + return Vector2{v.x - a, v.y - a}; +} + +//- for unary functions +SomeGlobalNumber operator-(const SomeGlobalNumber& n) { + return SomeGlobalNumber{-n.i}; +} + +SomeGlobalNumber operator+(const SomeGlobalNumber& n) { + return SomeGlobalNumber{+n.i}; +} + +SomeGlobalNumber operator~(const SomeGlobalNumber& n) { + return SomeGlobalNumber{~n.i}; +} + +Unary::SomeNumber Unary::operator-(const SomeNumber& n) { + return SomeNumber{-n.i}; +} + +Unary::SomeNumber Unary::operator+(const SomeNumber& n) { + return SomeNumber{+n.i}; +} + +Unary::SomeNumber Unary::operator~(const SomeNumber& n) { + return SomeNumber{~n.i}; +} diff --git a/bindings/pyroot/cppjit/test/cpp/operators.h b/bindings/pyroot/cppjit/test/cpp/operators.h new file mode 100644 index 0000000000000..e284779d03dec --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/operators.h @@ -0,0 +1,350 @@ +#ifndef CPPJIT_TEST_OPERATORS_H +#define CPPJIT_TEST_OPERATORS_H + +class number { +public: + number() { m_int = 0; } + number(int i) { m_int = i; } + + number operator+(const number& n) const { return number(m_int + n.m_int); } + number operator+(int n) const { return number(m_int + n); } + number operator-(const number& n) const { return number(m_int - n.m_int); } + number operator-(int n) const { return number(m_int - n); } + number operator*(const number& n) const { return number(m_int * n.m_int); } + number operator*(int n) const { return number(m_int * n); } + number operator/(const number& n) const { return number(m_int / n.m_int); } + number operator/(int n) const { return number(m_int / n); } + number operator%(const number& n) const { return number(m_int % n.m_int); } + number operator%(int n) const { return number(m_int % n); } + + number& operator+=(const number& n) { + m_int += n.m_int; + return *this; + } + number& operator-=(const number& n) { + m_int -= n.m_int; + return *this; + } + number& operator*=(const number& n) { + m_int *= n.m_int; + return *this; + } + number& operator/=(const number& n) { + m_int /= n.m_int; + return *this; + } + number& operator%=(const number& n) { + m_int %= n.m_int; + return *this; + } + + number operator-() { return number(-m_int); } + + bool operator<(const number& n) const { return m_int < n.m_int; } + bool operator>(const number& n) const { return m_int > n.m_int; } + bool operator<=(const number& n) const { return m_int <= n.m_int; } + bool operator>=(const number& n) const { return m_int >= n.m_int; } + bool operator!=(const number& n) const { return m_int != n.m_int; } + bool operator==(const number& n) const { return m_int == n.m_int; } + + operator bool() { return m_int != 0; } + + number operator&(const number& n) const { return number(m_int & n.m_int); } + number operator|(const number& n) const { return number(m_int | n.m_int); } + number operator^(const number& n) const { return number(m_int ^ n.m_int); } + + number& operator&=(const number& n) { + m_int &= n.m_int; + return *this; + } + number& operator|=(const number& n) { + m_int |= n.m_int; + return *this; + } + number& operator^=(const number& n) { + m_int ^= n.m_int; + return *this; + } + + number operator<<(int i) const { return number(m_int << i); } + number operator>>(int i) const { return number(m_int >> i); } + +private: + int m_int; +}; + +//---------------------------------------------------------------------------- +struct operator_char_star { // for testing user-defined implicit casts + operator_char_star() : m_str((char*)"operator_char_star") {} + operator char*() { return m_str; } + char* m_str; +}; + +struct operator_const_char_star { + operator_const_char_star() : m_str("operator_const_char_star") {} + operator const char*() { return m_str; } + const char* m_str; +}; + +struct operator_int { + operator int() { return m_int; } + int m_int; +}; + +struct operator_long { + operator long() { return m_long; } + long m_long; +}; + +struct operator_double { + operator double() { return m_double; } + double m_double; +}; + +struct operator_short { + operator short() { return m_short; } + unsigned short m_short; +}; + +struct operator_unsigned_int { + operator unsigned int() { return m_uint; } + unsigned int m_uint; +}; + +struct operator_unsigned_long { + operator unsigned long() { return m_ulong; } + unsigned long m_ulong; +}; + +struct operator_float { + operator float() { return m_float; } + float m_float; +}; + +//---------------------------------------------------------------------------- +class v_opeq_base { +public: + v_opeq_base(int val); + virtual ~v_opeq_base(); + + virtual bool operator==(const v_opeq_base& other); + +protected: + int m_val; +}; + +class v_opeq_derived : public v_opeq_base { +public: + v_opeq_derived(int val); + virtual ~v_opeq_derived(); + + virtual bool operator==(const v_opeq_derived& other); +}; + +//---------------------------------------------------------------------------- +class YAMatrix1 { // YetAnotherMatrix class for indexing tests +public: + YAMatrix1() : m_val(42) {} + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix2 { +public: + YAMatrix2() : m_val(42) {} + + int& operator[](int i); + const int& operator[](int i) const; + + int m_val; +}; + +class YAMatrix3 { +public: + YAMatrix3() : m_val(42) {} + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int& operator[](int i); + const int& operator[](int i) const; + + int m_val; +}; + +class YAMatrix4 { +public: + YAMatrix4() : m_val(42) {} + + // opposite order of method declarations from YAMatrix3 + int& operator[](int i); + const int& operator[](int i) const; + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix5 { +public: + YAMatrix5() : m_val(42) {} + + int& operator[](int i); + int operator[](int i) const; + + int& operator()(int i, int j); + int operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix6 { +public: + YAMatrix6() : m_val(42) {} + + int& operator[](int i); + int operator[](int i) const; + + int& operator()(int i, int j); + + int m_val; +}; + +class YAMatrix7 { +public: + YAMatrix7() : m_val(42) {} + + int& operator[](int i); + int& operator()(int i, int j); + + int m_val; +}; + +//---------------------------------------------------------------------------- +// templated operator +class TOIClass { +public: + template bool operator<(T t) { return true; } +}; + +//---------------------------------------------------------------------------- +// __radd__/__rmul__, (non-)associative +class AssocADD { +public: + AssocADD(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend AssocADD operator+(int n, AssocADD& a); + friend AssocADD operator+(AssocADD& a, int n); + +private: + double fx; +}; + +AssocADD operator+(int n, AssocADD& a); +AssocADD operator+(AssocADD& a, int n); + +class NonAssocRADD { +public: + NonAssocRADD(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend NonAssocRADD operator+(int n, NonAssocRADD& a); + +private: + double fx; +}; + +NonAssocRADD operator+(int n, NonAssocRADD& a); + +class AssocMUL { +public: + AssocMUL(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend AssocMUL operator*(int n, AssocMUL& m); + friend AssocMUL operator*(AssocMUL& m, int n); + +private: + double fx; +}; + +AssocMUL operator*(int n, AssocMUL& m); +AssocMUL operator*(AssocMUL& m, int n); + +class NonAssocRMUL { +public: + NonAssocRMUL(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend NonAssocRMUL operator*(int n, NonAssocRMUL& m); + +private: + double fx; +}; + +NonAssocRMUL operator*(int n, NonAssocRMUL& m); + +//---------------------------------------------------------------------------- +// multi-lookup +namespace MultiLookup { + +struct Vector2 { + Vector2(double d1, double d2) : x(d1), y(d2) {} + double x, y; +}; + +double operator*(const Vector2&, const Vector2&); +Vector2 operator*(const Vector2&, double); + +double operator/(const Vector2&, const Vector2&); +Vector2 operator/(const Vector2&, double); + +double operator+(const Vector2&, const Vector2&); +Vector2 operator+(const Vector2&, double); + +double operator-(const Vector2&, const Vector2&); +Vector2 operator-(const Vector2&, double); + +} // namespace MultiLookup + +//---------------------------------------------------------------------------- +// unary functions +struct SomeGlobalNumber { + SomeGlobalNumber(int n) : i(n) {} + int i; +}; + +SomeGlobalNumber operator-(const SomeGlobalNumber&); +SomeGlobalNumber operator+(const SomeGlobalNumber&); +SomeGlobalNumber operator~(const SomeGlobalNumber&); + +namespace Unary { + +struct SomeNumber { + SomeNumber(int n) : i(n) {} + int i; +}; + +SomeNumber operator-(const SomeNumber&); +SomeNumber operator+(const SomeNumber&); +SomeNumber operator~(const SomeNumber&); + +} // namespace Unary + +//---------------------------------------------------------------------------- +// comma operator (mapped to __comma__) +struct CommaOperator { + CommaOperator(int i) : fInt(i) {} + CommaOperator& operator,(int i) { + fInt += i; + return *this; + } + int fInt; +}; + +#endif // !CPPJIT_TEST_OPERATORS_H diff --git a/bindings/pyroot/cppyy/cppyy/test/overloads.cxx b/bindings/pyroot/cppjit/test/cpp/overloads.cxx similarity index 51% rename from bindings/pyroot/cppyy/cppyy/test/overloads.cxx rename to bindings/pyroot/cppjit/test/cpp/overloads.cxx index eeab7dde49719..dd89b3fe2cb58 100644 --- a/bindings/pyroot/cppyy/cppyy/test/overloads.cxx +++ b/bindings/pyroot/cppjit/test/cpp/overloads.cxx @@ -1,30 +1,41 @@ #include "overloads.h" - -a_overload::a_overload() { i1 = 42; i2 = -1; } - -ns_a_overload::a_overload::a_overload() { i1 = 88; i2 = -34; } +a_overload::a_overload() { + i1 = 42; + i2 = -1; +} + +ns_a_overload::a_overload::a_overload() { + i1 = 88; + i2 = -34; +} int ns_a_overload::b_overload::f(const std::vector* v) { return (*v)[0]; } -ns_b_overload::a_overload::a_overload() { i1 = -33; i2 = 89; } +ns_b_overload::a_overload::a_overload() { + i1 = -33; + i2 = 89; +} -b_overload::b_overload() { i1 = -2; i2 = 13; } +b_overload::b_overload() { + i1 = -2; + i2 = 13; +} c_overload::c_overload() {} -int c_overload::get_int(a_overload* a) { return a->i1; } +int c_overload::get_int(a_overload* a) { return a->i1; } int c_overload::get_int(ns_a_overload::a_overload* a) { return a->i1; } int c_overload::get_int(ns_b_overload::a_overload* a) { return a->i1; } -int c_overload::get_int(short* p) { return *p; } -int c_overload::get_int(b_overload* b) { return b->i2; } -int c_overload::get_int(int* p) { return *p; } +int c_overload::get_int(short* p) { return *p; } +int c_overload::get_int(b_overload* b) { return b->i2; } +int c_overload::get_int(int* p) { return *p; } d_overload::d_overload() {} -int d_overload::get_int(int* p) { return *p; } -int d_overload::get_int(b_overload* b) { return b->i2; } -int d_overload::get_int(short* p) { return *p; } +int d_overload::get_int(int* p) { return *p; } +int d_overload::get_int(b_overload* b) { return b->i2; } +int d_overload::get_int(short* p) { return *p; } int d_overload::get_int(ns_b_overload::a_overload* a) { return a->i1; } int d_overload::get_int(ns_a_overload::a_overload* a) { return a->i1; } -int d_overload::get_int(a_overload* a) { return a->i1; } +int d_overload::get_int(a_overload* a) { return a->i1; } class bb_ol {}; bb_ol* get_bb_ol() { return new bb_ol{}; } @@ -33,18 +44,20 @@ dd_ol* get_dd_ol() { return new dd_ol{}; } more_overloads::more_overloads() {} std::string more_overloads::call(const aa_ol&) { return "aa_ol"; } -std::string more_overloads::call(const bb_ol&, void* n) { n = 0; return "bb_ol"; } +std::string more_overloads::call(const bb_ol&, void* n) { + n = 0; + return "bb_ol"; +} std::string more_overloads::call(const cc_ol&) { return "cc_ol"; } std::string more_overloads::call(const dd_ol&) { return "dd_ol"; } std::string more_overloads::call_unknown(const dd_ol&) { return "dd_ol"; } -std::string more_overloads::call(double) { return "double"; } -std::string more_overloads::call(int) { return "int"; } -std::string more_overloads::call1(int) { return "int"; } +std::string more_overloads::call(double) { return "double"; } +std::string more_overloads::call(int) { return "int"; } +std::string more_overloads::call1(int) { return "int"; } std::string more_overloads::call1(double) { return "double"; } - more_overloads2::more_overloads2() {} std::string more_overloads2::call(const bb_ol&) { return "bb_olref"; } std::string more_overloads2::call(const bb_ol*) { return "bb_olptr"; } @@ -52,16 +65,13 @@ std::string more_overloads2::call(const bb_ol*) { return "bb_olptr"; } std::string more_overloads2::call(const dd_ol*, int) { return "dd_olptr"; } std::string more_overloads2::call(const dd_ol&, int) { return "dd_olref"; } - -double calc_mean(long n, const float* a) { return calc_mean(n, a); } -double calc_mean(long n, const double* a) { return calc_mean(n, a); } -double calc_mean(long n, const int* a) { return calc_mean(n, a); } -double calc_mean(long n, const short* a) { return calc_mean(n, a); } -double calc_mean(long n, const long* a) { return calc_mean(n, a); } - +double calc_mean(long n, const float* a) { return calc_mean(n, a); } +double calc_mean(long n, const double* a) { return calc_mean(n, a); } +double calc_mean(long n, const int* a) { return calc_mean(n, a); } +double calc_mean(long n, const short* a) { return calc_mean(n, a); } +double calc_mean(long n, const long* a) { return calc_mean(n, a); } std::string more_overloads3::slice(size_t) const { return "const"; } std::string more_overloads3::slice(size_t) { return "non-const"; } std::string more_overloads3::slice(size_t, size_t) const { return "const"; } std::string more_overloads3::slice(size_t, size_t) { return "non-const"; } - diff --git a/bindings/pyroot/cppjit/test/cpp/overloads.h b/bindings/pyroot/cppjit/test/cpp/overloads.h new file mode 100644 index 0000000000000..4d330fa9e7db1 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/overloads.h @@ -0,0 +1,116 @@ +#include +#include + +class a_overload { +public: + a_overload(); + int i1, i2; +}; + +namespace ns_a_overload { +class a_overload { +public: + a_overload(); + int i1, i2; +}; + +class b_overload { +public: + int f(const std::vector* v); +}; +} // namespace ns_a_overload + +namespace ns_b_overload { +class a_overload { +public: + a_overload(); + int i1, i2; +}; +} // namespace ns_b_overload + +class b_overload { +public: + b_overload(); + int i1, i2; +}; + +class c_overload { +public: + c_overload(); + int get_int(a_overload* a); + int get_int(ns_a_overload::a_overload* a); + int get_int(ns_b_overload::a_overload* a); + int get_int(short* p); + int get_int(b_overload* b); + int get_int(int* p); +}; + +class d_overload { +public: + d_overload(); + // int get_int(void* p) { return *(int*)p; } + int get_int(int* p); + int get_int(b_overload* b); + int get_int(short* p); + int get_int(ns_b_overload::a_overload* a); + int get_int(ns_a_overload::a_overload* a); + int get_int(a_overload* a); +}; + +class aa_ol {}; +class bb_ol; +bb_ol* get_bb_ol(); +class cc_ol {}; +class dd_ol; +dd_ol* get_dd_ol(); + +class more_overloads { +public: + more_overloads(); + std::string call(const aa_ol&); + std::string call(const bb_ol&, void* n = 0); + std::string call(const cc_ol&); + std::string call(const dd_ol&); + + std::string call_unknown(const dd_ol&); + + std::string call(double); + std::string call(int); + std::string call1(int); + std::string call1(double); +}; + +class more_overloads2 { +public: + more_overloads2(); + std::string call(const bb_ol&); + std::string call(const bb_ol*); + + std::string call(const dd_ol*, int); + std::string call(const dd_ol&, int); +}; + +template double calc_mean(long n, const T* a) { + double sum = 0., sumw = 0.; + const T* end = a + n; + while (a != end) { + sum += *a++; + sumw += 1; + } + + return sum / sumw; +} + +double calc_mean(long n, const float* a); +double calc_mean(long n, const double* a); +double calc_mean(long n, const int* a); +double calc_mean(long n, const short* a); +double calc_mean(long n, const long* a); + +class more_overloads3 { +public: + std::string slice(size_t) const; + std::string slice(size_t); + std::string slice(size_t, size_t); + std::string slice(size_t, size_t) const; +}; diff --git a/bindings/pyroot/cppjit/test/cpp/pythonizables.cxx b/bindings/pyroot/cppjit/test/cpp/pythonizables.cxx new file mode 100644 index 0000000000000..c6c7eade2f476 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/pythonizables.cxx @@ -0,0 +1,112 @@ +#include "pythonizables.h" + +#include "Python.h" + +//=========================================================================== +pyzables::NakedBuffers::NakedBuffers(int size, double valx, double valy) + : m_size(size) { + m_Xbuf = new double[size]; + m_Ybuf = new double[size]; + + for (int i = 0; i < size; ++i) { + m_Xbuf[i] = valx * i; + m_Ybuf[i] = valy * i; + } +} + +pyzables::NakedBuffers::~NakedBuffers() { + delete[] m_Xbuf; + delete[] m_Ybuf; +} + +int pyzables::NakedBuffers::GetN() { return m_size; } + +double* pyzables::NakedBuffers::GetX() { return m_Xbuf; } +double* pyzables::NakedBuffers::GetY() { return m_Ybuf; } + +//=========================================================================== +pyzables::MyBase::~MyBase() {} +pyzables::MyDerived::~MyDerived() {} + +pyzables::MyBase* pyzables::GimeDerived() { return new MyDerived(); } + +//=========================================================================== +int pyzables::Countable::sInstances = 0; +pyzables::SharedCountable_t pyzables::mine = + pyzables::SharedCountable_t(new pyzables::Countable); + +void pyzables::renew_mine() { + mine = std::shared_ptr(new Countable); +} + +pyzables::SharedCountable_t pyzables::gime_mine() { return mine; } +pyzables::SharedCountable_t* pyzables::gime_mine_ptr() { return &mine; } +pyzables::SharedCountable_t& pyzables::gime_mine_ref() { return mine; } + +unsigned int pyzables::pass_mine_sp(std::shared_ptr ptr) { + return ptr->m_check; +} +unsigned int pyzables::pass_mine_sp_ref(std::shared_ptr& ptr) { + return ptr->m_check; +} +unsigned int pyzables::pass_mine_sp_ptr(std::shared_ptr* ptr) { + return (*ptr)->m_check; +} + +unsigned int pyzables::pass_mine_rp(Countable c) { return c.m_check; } +unsigned int pyzables::pass_mine_rp_ref(const Countable& c) { + return c.m_check; +} +unsigned int pyzables::pass_mine_rp_ptr(const Countable* c) { + return c->m_check; +} + +pyzables::Countable* pyzables::gime_naked_countable() { + return new Countable{}; +} + +//=========================================================================== +pyzables::WithCallback1::WithCallback1(int i) : m_int(i) {} + +int pyzables::WithCallback1::get_int() { return m_int; } +void pyzables::WithCallback1::set_int(int i) { m_int = i; } + +static inline void replace_method_name(PyObject* klass, const char* n1, + const char* n2) { + PyObject* meth = PyObject_GetAttrString(klass, n1); + PyObject_SetAttrString(klass, n2, meth); + Py_DECREF(meth); + PyObject_DelAttrString(klass, n1); +} + +void pyzables::WithCallback1::WithCallback1::__cppjit_explicit_pythonize__( + PyObject* klass, const std::string& name) { + // change methods to camel case + replace_method_name(klass, "get_int", "GetInt"); + replace_method_name(klass, "set_int", "SetInt"); + + // store the provided class name + klass_name = name; +} + +std::string pyzables::WithCallback1::klass_name{"not set"}; + +pyzables::WithCallback2::WithCallback2(int i) : m_int(i) {} + +int pyzables::WithCallback2::get_int() { return m_int; } +void pyzables::WithCallback2::set_int(int i) { m_int = i; } + +void pyzables::WithCallback2::WithCallback2::__cppjit_pythonize__( + PyObject* klass, const std::string& name) { + // change methods to camel case + replace_method_name(klass, "get_int", "GetInt"); + replace_method_name(klass, "set_int", "SetInt"); + + // store the provided class name + klass_name = name; +} + +std::string pyzables::WithCallback2::klass_name{"not set"}; + +int pyzables::WithCallback3::get_int() { return 2 * m_int; } +void pyzables::WithCallback3::set_int(int i) { m_int = 2 * i; } diff --git a/bindings/pyroot/cppjit/test/cpp/pythonizables.h b/bindings/pyroot/cppjit/test/cpp/pythonizables.h new file mode 100644 index 0000000000000..3ec1c6f50d800 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/pythonizables.h @@ -0,0 +1,164 @@ +#include +#include +#include + +// Python +struct _object; +typedef _object PyObject; + +namespace pyzables { + +//=========================================================================== +class SomeDummy1 {}; +class SomeDummy2 {}; + +//=========================================================================== +class NakedBuffers { +public: + NakedBuffers(int size, double valx, double valy); + NakedBuffers(const NakedBuffers&) = delete; + NakedBuffers& operator=(const NakedBuffers&) = delete; + ~NakedBuffers(); + +public: + int GetN(); + double* GetX(); + double* GetY(); + +private: + double* m_Xbuf; + double* m_Ybuf; + int m_size; +}; + +template class NakedBuffers2 { +public: + NakedBuffers2(int size, double valx, double valy) + : m_Xbuf(size), m_Ybuf(size) { + for (int i = 0; i < size; ++i) { + m_Xbuf[i] = valx * i; + m_Ybuf[i] = valy * i; + } + } + +public: + int GetN() { return m_Xbuf.size(); } + double* GetX() { return m_Xbuf.data(); } + double* GetY() { return m_Ybuf.data(); } + +private: + C m_Xbuf; + C m_Ybuf; + int m_size; +}; + +class Vector : public std::vector { +public: + Vector(int size) : std::vector(size) {} +}; + +//=========================================================================== +class MyBase { +public: + virtual ~MyBase(); +}; +class MyDerived : public MyBase { +public: + virtual ~MyDerived(); +}; + +MyBase* GimeDerived(); + +//=========================================================================== +class Countable { +public: + Countable() { ++sInstances; } + Countable(const Countable&) { ++sInstances; } + Countable& operator=(const Countable&) { return *this; } + ~Countable() { --sInstances; } + +public: + virtual const char* say_hi() { return "Hi!"; } + +public: + unsigned int m_check = 0xcdcdcdcd; + +public: + static int sInstances; +}; + +typedef std::shared_ptr SharedCountable_t; +extern SharedCountable_t mine; + +void renew_mine(); + +SharedCountable_t gime_mine(); +SharedCountable_t* gime_mine_ptr(); +SharedCountable_t& gime_mine_ref(); + +unsigned int pass_mine_sp(SharedCountable_t p); +unsigned int pass_mine_sp_ref(SharedCountable_t& p); +unsigned int pass_mine_sp_ptr(SharedCountable_t* p); + +unsigned int pass_mine_rp(Countable); +unsigned int pass_mine_rp_ref(const Countable&); +unsigned int pass_mine_rp_ptr(const Countable*); + +Countable* gime_naked_countable(); + +//=========================================================================== +class unknown_iterator; +class IndexableBase { +public: + unknown_iterator* begin() { return nullptr; } + unknown_iterator* end() { return (unknown_iterator*)1; } + int operator[](int) { return 42; } + int size() { return 1; } +}; + +class IndexableDerived : public IndexableBase {}; + +//=========================================================================== +class WithCallback1 { +public: + WithCallback1(int i); + +public: + int get_int(); + void set_int(int i); + +private: + int m_int; + +public: + static void __cppjit_explicit_pythonize__(PyObject* klass, + const std::string&); + static std::string klass_name; +}; + +class WithCallback2 { +public: + WithCallback2(int i); + +public: + int get_int(); + void set_int(int i); + +protected: + int m_int; + +public: + static void __cppjit_pythonize__(PyObject* klass, const std::string&); + static std::string klass_name; +}; + +class WithCallback3 : public WithCallback2 { +public: + using WithCallback2::WithCallback2; + +public: + int get_int(); + void set_int(int i); +}; + +} // namespace pyzables diff --git a/bindings/pyroot/cppjit/test/cpp/std_streams.cxx b/bindings/pyroot/cppjit/test/cpp/std_streams.cxx new file mode 100644 index 0000000000000..dcbb59994649c --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/std_streams.cxx @@ -0,0 +1,5 @@ +#include "std_streams.h" + +#ifndef _WIN32 +template class std::basic_ios>; +#endif diff --git a/bindings/pyroot/cppyy/cppyy/test/std_streams.h b/bindings/pyroot/cppjit/test/cpp/std_streams.h similarity index 63% rename from bindings/pyroot/cppyy/cppyy/test/std_streams.h rename to bindings/pyroot/cppjit/test/cpp/std_streams.h index e67622373c7f6..db5cac8a3d3f7 100644 --- a/bindings/pyroot/cppyy/cppyy/test/std_streams.h +++ b/bindings/pyroot/cppjit/test/cpp/std_streams.h @@ -1,4 +1,4 @@ -#ifndef STD_STREAMS_H +#ifndef STD_STREAMS_H #define STD_STREAMS_H 1 #ifndef __CLING__ @@ -8,7 +8,7 @@ #ifndef __CLING__ #ifndef _WIN32 -extern template class std::basic_ios >; +extern template class std::basic_ios>; #endif #endif diff --git a/bindings/pyroot/cppjit/test/cpp/stltypes.cxx b/bindings/pyroot/cppjit/test/cpp/stltypes.cxx new file mode 100644 index 0000000000000..6dbd7d2c2da90 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/stltypes.cxx @@ -0,0 +1,83 @@ +#include "stltypes.h" + +#include + +//- adverse effect of implicit conversion on vector +int vectest_ol1(const std::vector&) { return 1; } +int vectest_ol1(std::string) { return 2; } +int vectest_ol2(std::string) { return 2; } +int vectest_ol2(const std::vector&) { return 1; } + +//- helpers for testing array +int ArrayTest::get_pp_px(Point** p, int idx) { return p[idx]->px; } + +int ArrayTest::get_pp_py(Point** p, int idx) { return p[idx]->py; } + +int ArrayTest::get_pa_px(Point* p[], int idx) { return p[idx]->px; } + +int ArrayTest::get_pa_py(Point* p[], int idx) { return p[idx]->py; } + +// helpers for string testing +std::string str_array_1[3] = {"a", "b", "c"}; +std::string str_array_2[] = {"d", "e", "f", "g"}; +std::string str_array_3[3][2] = {{"a", "b"}, {"c", "d"}, {"e", "f"}}; +std::string str_array_4[4][2][2] = { + {{"a", "b"}, {"c", "d"}}, + {{"e", "f"}, {"g", "h"}}, + {{"i", "j"}, {"k", "l"}}, + {{"m", "n"}, {"o", "p"}}, +}; + +// helpers for mixing unicode and std::string +size_t UnicodeAndSTL::get_size(std::string s) { return s.size(); } +size_t UnicodeAndSTL::get_size_cr(const std::string& s) { return s.size(); } +size_t UnicodeAndSTL::get_size_cc(const char* s) { return strlen(s); } +size_t UnicodeAndSTL::get_size_w(std::wstring s) { return s.size(); } +size_t UnicodeAndSTL::get_size_wcr(const std::wstring& s) { return s.size(); } +std::string UnicodeAndSTL::get_string(std::string s) { return s; } +std::string UnicodeAndSTL::get_string_cr(const std::string& s) { return s; } +std::string UnicodeAndSTL::get_string_cc(const char* s) { return s; } +std::wstring UnicodeAndSTL::get_string_w(std::wstring s) { return s; } +std::wstring UnicodeAndSTL::get_string_wcr(const std::wstring& s) { return s; } + +// helpers for string_view testing +std::string_view::size_type StringViewTest::count(const std::string_view arg) { + return arg.size(); +} + +std::string_view::size_type +StringViewTest::count_cr(const std::string_view& arg) { + return arg.size(); +} + +// helper for exception base class testing +int MyError::s_count = 0; +int MyError::get_count() { return s_count; } + +MyError::MyError(const std::string& msg) : fMsg(msg) { s_count += 1; } + +MyError::MyError(const MyError& other) : fMsg(other.fMsg) { s_count += 1; } + +MyError::~MyError() { s_count -= 1; } + +const char* MyError::what() const throw() { return fMsg.c_str(); } + +int GetMyErrorCount() { return MyError::s_count; } + +YourError::YourError(const std::string& msg) : MyError(msg) {} +YourError::YourError(const YourError& s) : MyError(s) {} + +ErrorNamespace::MyError::MyError(const std::string& msg) : fMsg(msg) {} +const char* ErrorNamespace::MyError::what() const throw() { + return fMsg.c_str(); +} + +void ErrorNamespace::throw_error(int i) { + if (i == 0) + throw ::MyError("first error"); + else if (i == 1) + throw ::YourError("second error"); + else if (i == 2) + throw MyError("third error"); + throw YourError("fourth error"); +} diff --git a/bindings/pyroot/cppjit/test/cpp/stltypes.h b/bindings/pyroot/cppjit/test/cpp/stltypes.h new file mode 100644 index 0000000000000..c354879c1cd94 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/stltypes.h @@ -0,0 +1,261 @@ +#ifndef CPPJIT_TEST_STLTYPES_H +#define CPPJIT_TEST_STLTYPES_H + +#include "datatypes.h" // for gbl.N +#include +#include +#include +#include +#include +#include +#include +#include + +//- basic example class +class just_a_class { +public: + int m_i; +}; + +// enum for vector of enums setitem tests +enum VecTestEnum { EVal1 = 1, EVal2 = 3 }; + +namespace VecTestEnumNS { +enum VecTestEnum { EVal1 = 5, EVal2 = 42 }; +} + +//- adverse effect of implicit conversion on vector +int vectest_ol1(const std::vector&); +int vectest_ol1(std::string); +int vectest_ol2(std::string); +int vectest_ol2(const std::vector&); + +//- class with lots of std::[w]string handling +template class stringy_class { +public: + stringy_class(const typename S::value_type* s) : m_string(s) {} + + S get_string1() { return m_string; } + void get_string2(S& s) { s = m_string; } + + void set_string1(const S& s) { m_string = s; } + void set_string2(S s) { m_string = s; } + + S m_string; +}; + +typedef stringy_class stringy_class_t; +typedef stringy_class wstringy_class_t; + +//- class that has an STL-like interface +class no_dict_available; + +template class stl_like_class { +public: + no_dict_available* begin() { return 0; } + no_dict_available* end() { return (no_dict_available*)1; } + int size() { return 4; } + int operator[](int i) { return i; } + std::string operator[](double) { return "double"; } + std::string operator[](const std::string&) { return "string"; } +}; + +namespace { +stl_like_class stlc_1; +} + +//- similar, but now the iterators don't work b/c they don't compile +template class stl_like_class2 { +protected: + value_type fData[sz]; + +public: + static const size_t size() { return sz; } + value_type& operator[](ptrdiff_t i) { return fData[i]; } +}; + +template +class stl_like_class3 : public stl_like_class2 { + using stl_like_class2::fData; + +public: + size_t size() { return sz; } + value_type& begin() { return fData; } + value_type& end() { return fData + sz; } +}; + +class stl_like_class4 { +public: + struct iterator { + iterator(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const iterator& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + iterator begin() { return iterator{0}; } + iterator end() { return iterator{10}; } +}; + +class stl_like_class5 { +public: + struct iterator { + iterator(int p) : pos(p) {} + int operator++(int) { + int val = pos; + ++pos; + return val; + } + bool operator!=(const iterator& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + iterator begin() { return iterator{0}; } + iterator end() { return iterator{10}; } +}; + +class stl_like_class6 { +public: + struct some_name { + some_name(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const some_name& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + some_name begin() { return some_name{0}; } + some_name end() { return some_name{10}; } +}; + +struct some_name7 { + some_name7(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const some_name7& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; +}; + +class stl_like_class7 { +public: + some_name7 begin() { return some_name7{0}; } + some_name7 end() { return some_name7{10}; } +}; + +// similar, but now with a base/derives split +class stl_like_class_base { +protected: + std::vector fVec; + +public: + void push_back(double d) { fVec.push_back(d); } + std::vector::size_type size() { return fVec.size(); } +}; + +class stl_like_class8 : public stl_like_class_base { +public: + double& operator[](int idx) { return fVec[idx]; } +}; + +class stl_like_class9 : public stl_like_class_base { +public: + double* begin() { return fVec.data(); } + double* end() { return fVec.data() + fVec.size(); } +}; + +//- helpers for testing array +namespace ArrayTest { + +struct Point { + Point() : px(0), py(0) {} + Point(int x, int y) : px(x), py(y) {} + int px, py; +}; + +int get_pp_px(Point** p, int idx); +int get_pp_py(Point** p, int idx); +int get_pa_px(Point* p[], int idx); +int get_pa_py(Point* p[], int idx); + +} // namespace ArrayTest + +// helpers for string testing +extern std::string str_array_1[3]; +extern std::string str_array_2[]; +extern std::string str_array_3[3][2]; +extern std::string str_array_4[4][2][2]; + +// helpers for mixing unicode and std::string +namespace UnicodeAndSTL { +size_t get_size(std::string s); +size_t get_size_cr(const std::string& s); +size_t get_size_cc(const char* s); +size_t get_size_w(std::wstring s); +size_t get_size_wcr(const std::wstring& s); +std::string get_string(std::string s); +std::string get_string_cr(const std::string& s); +std::string get_string_cc(const char* s); +std::wstring get_string_w(std::wstring s); +std::wstring get_string_wcr(const std::wstring& s); +} // namespace UnicodeAndSTL + +// helpers for string_view testing +namespace StringViewTest { +std::string_view::size_type count(const std::string_view arg); +std::string_view::size_type count_cr(const std::string_view& arg); +} // namespace StringViewTest + +// helper for exception base class testing +class MyError : public std::exception { +public: + static int s_count; + static int get_count(); + +public: + explicit MyError(const std::string& msg); + MyError(const MyError&); + virtual ~MyError(); + MyError& operator=(const MyError&) = default; + const char* what() const throw() override; + +private: + std::string fMsg; +}; + +int GetMyErrorCount(); + +class YourError : public MyError { +public: + explicit YourError(const std::string& msg); + YourError(const YourError& s); +}; + +namespace ErrorNamespace { + +class MyError : public std::exception { +public: + explicit MyError(const std::string& msg); + const char* what() const throw() override; + +private: + std::string fMsg; +}; + +class YourError : public MyError { +public: + using MyError::MyError; +}; + +void throw_error(int i); + +} // namespace ErrorNamespace + +#endif // !CPPJIT_TEST_STLTYPES_H diff --git a/bindings/pyroot/cppyy/cppyy/test/templates.cxx b/bindings/pyroot/cppjit/test/cpp/templates.cxx similarity index 57% rename from bindings/pyroot/cppyy/cppyy/test/templates.cxx rename to bindings/pyroot/cppjit/test/cpp/templates.cxx index 90c3ec48e6da5..b748e09349114 100644 --- a/bindings/pyroot/cppyy/cppyy/test/templates.cxx +++ b/bindings/pyroot/cppjit/test/cpp/templates.cxx @@ -1,16 +1,16 @@ #include "templates.h" - // template methods long MyTemplatedMethodClass::get_size() { return -1; } -long MyTemplatedMethodClass::get_char_size() { return (long)sizeof(char); } -long MyTemplatedMethodClass::get_int_size() { return (long)sizeof(int); } -long MyTemplatedMethodClass::get_long_size() { return (long)42; /* "lying" */ } -long MyTemplatedMethodClass::get_float_size() { return (long)sizeof(float); } +long MyTemplatedMethodClass::get_char_size() { return (long)sizeof(char); } +long MyTemplatedMethodClass::get_int_size() { return (long)sizeof(int); } +long MyTemplatedMethodClass::get_long_size() { return (long)42; /* "lying" */ } +long MyTemplatedMethodClass::get_float_size() { return (long)sizeof(float); } long MyTemplatedMethodClass::get_double_size() { return (long)sizeof(double); } -long MyTemplatedMethodClass::get_self_size() { return (long)sizeof(MyTemplatedMethodClass); } - +long MyTemplatedMethodClass::get_self_size() { + return (long)sizeof(MyTemplatedMethodClass); +} // variadic templates #ifdef WIN32 @@ -18,7 +18,6 @@ __declspec(dllexport) #endif std::string some_variadic::gTypeName = ""; - // template with empty body namespace T_WithEmptyBody { @@ -27,15 +26,11 @@ __declspec(dllexport) #endif std::string side_effect = "not set"; -template -void some_empty() { - side_effect = "side effect"; -} +template void some_empty() { side_effect = "side effect"; } template void some_empty(); -} // namespace T_WithRValue - +} // namespace T_WithEmptyBody // The following is hidden from the Cling interpreter, but available to the // linker; it allows for testing whether a function return is picked up from @@ -43,10 +38,9 @@ template void some_empty(); namespace FailedTypeDeducer { -template -class A { +template class A { public: - T result() { return T{42}; } + T result() { return T{42}; } }; template class A; diff --git a/bindings/pyroot/cppjit/test/cpp/templates.h b/bindings/pyroot/cppjit/test/cpp/templates.h new file mode 100644 index 0000000000000..ddf8568b24254 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/templates.h @@ -0,0 +1,478 @@ +#ifndef CPPJIT_TEST_TEMPLATES_H +#define CPPJIT_TEST_TEMPLATES_H + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +inline std::string demangle_it(const char* name, const char* errmsg) { + int status; + std::string res = abi::__cxa_demangle(name, 0, 0, &status); + if (status != 0) + throw std::runtime_error(errmsg); + return res; +} +#else +inline std::string demangle_it(const char* name, const char*) { + return name; // typeinfo's name() is already demangled +} +#endif + +//=========================================================================== +class MyTemplatedMethodClass { // template methods +public: + template long get_size(A&); + template long get_size(const A&); + + long get_size(); + template long get_size(); + + long get_char_size(); + long get_int_size(); + long get_long_size(); + long get_float_size(); + long get_double_size(); + + long get_self_size(); + +private: + double m_data[3]; +}; + +template long MyTemplatedMethodClass::get_size(A&) { + return sizeof(A); +} + +template long MyTemplatedMethodClass::get_size(const A&) { + return sizeof(A) + 1; +} + +template inline long MyTemplatedMethodClass::get_size() { + return sizeof(B); +} + +// +typedef MyTemplatedMethodClass MyTMCTypedef_t; + +// explicit instantiation +template long MyTemplatedMethodClass::get_size(); +template long MyTemplatedMethodClass::get_size(); + +// "lying" specialization +template <> inline long MyTemplatedMethodClass::get_size() { return 42; } + +//=========================================================================== +// global templated functions +template long global_get_size() { return sizeof(T); } + +template int global_some_foo(T) { return 42; } + +template int global_some_bar() { return T; } + +template int global_some_bar_var(T) { return 13; } + +template struct SomeResult { + F m_retval; +}; + +template +SomeResult global_get_some_result(const I& carrier) { + SomeResult r{}; + r.m_retval = O(carrier[0]); + return r; +} + +//=========================================================================== +// variadic functions +inline bool isSomeInt(int) { return true; } +inline bool isSomeInt(double) { return false; } +template inline bool isSomeInt(Args...) { return false; } + +namespace AttrTesting { + +struct Obj1 { + int var1; +}; +struct Obj2 { + int var2; +}; + +template constexpr auto has_var1(T t) -> decltype(t.var1, true) { + return true; +} + +template constexpr bool has_var1(Args...) { return false; } + +template constexpr bool call_has_var1(T&& t) { + return AttrTesting::has_var1(std::forward(t)); +} + +template struct select_template_arg {}; + +template struct select_template_arg<0, T0, T...> { + typedef T0 type; + typedef type argument; +}; + +template +struct select_template_arg { + typedef typename select_template_arg::type argument; +}; + +} // namespace AttrTesting + +namespace SomeNS { + +template int some_foo(T) { return 42; } + +template int some_bar() { return T; } + +struct SomeStruct { + template static int some_bar() { return T; } +}; + +inline std::string tuplify(std::ostringstream& out) { + out << "NULL)"; + return out.str(); +} + +template +std::string tuplify(std::ostringstream& out, T value, Args... args) { + out << value << ", "; + return tuplify(out, args...); +} + +} // namespace SomeNS + +//=========================================================================== +// using of static data +// TODO: this should live here instead of in test_templates.test08 +/* +template struct BaseClassWithStatic { + static T const ref_value; +}; + +template +T const BaseClassWithStatic::ref_value = 42; + +template +struct DerivedClassUsingStatic : public BaseClassWithStatic { + using BaseClassWithStatic::ref_value; + + explicit DerivedClassUsingStatic(T x) : BaseClassWithStatic() { + m_value = x > ref_value ? ref_value : x; + } + + T m_value; +}; +*/ + +//=========================================================================== +// templated callable +class TemplatedCallable { +public: + template O operator()(const I& in) const { + return O(in); + } +}; + +//=========================================================================== +// templated typedefs +namespace TemplatedTypedefs { + +template +struct BaseWithEnumAndTypedefs { + enum { vsize = _vsize }; + typedef TYPE_IN in_type; + typedef TYPE_OUT out_type; +}; + +template +struct DerivedWithUsing + : public BaseWithEnumAndTypedefs { + typedef BaseWithEnumAndTypedefs base_type; + using base_type::vsize; + using typename base_type::in_type; + typedef typename base_type::in_type in_type_tt; + using typename base_type::out_type; +}; + +struct SomeDummy {}; + +} // namespace TemplatedTypedefs + +//=========================================================================== +// hiding templated methods +namespace TemplateHiding { + +struct Base { + template int callme(T t = T(1)) { return 2 * t; } +}; + +struct Derived : public Base { + int callme(int t = 2) { return t; } +}; + +} // namespace TemplateHiding + +//=========================================================================== +// 'using' of templates +template using DA_vector = std::vector; + +namespace using_problem { + +template struct vector { + vector() : m_val(SZ) {} + T m_val; +}; + +template struct matryoshka { + typedef T type; +}; + +template +struct matryoshka { + typedef vector::type, SZ> type; +}; + +template +using make_vector = typename matryoshka::type; +typedef make_vector iiv_t; +}; // namespace using_problem + +namespace using_problem { + +template class Base { +public: + template R get1(T t) { return t + R{5}; } + T get2() { return T{5}; } + template R get3(T t) { return t + R{5}; } + T get3() { return T{5}; } +}; + +template class Derived : public Base { +public: + typedef Base _Mybase; + using _Mybase::get1; + using _Mybase::get2; + using _Mybase::get3; +}; + +template class Foo { + T x; +}; + +namespace Bar { +using ::using_problem::Foo; +} + +} // namespace using_problem + +//=========================================================================== +// template with r-value +namespace T_WithRValue { + +template bool is_valid(T&& new_value) { return new_value != T{}; } + +} // namespace T_WithRValue + +//=========================================================================== +// variadic templates +namespace some_variadic { + +#ifdef _WIN32 +#ifdef __CLING__ +extern __declspec(dllimport) std::string gTypeName; +#else +extern __declspec(dllexport) std::string gTypeName; +#endif +#else +extern std::string gTypeName; +#endif + +template class A { +public: + A() { gTypeName = demangle_it(typeid(A).name(), "A::A"); } + A(const A&) = default; + A(A&&) = default; + A& operator=(const A&) = default; + A& operator=(A&&) = default; + + template void a(FArgs&&... args) { + gTypeName = demangle_it(typeid(&A::a).name(), "A::a-2"); + } + + template T a_T(FArgs&&... args) { + gTypeName = + demangle_it(typeid(&A::a_T).name(), "A::a_T-2"); + return T{}; + } + + template static void sa(FArgs&&... args) { + gTypeName = demangle_it(typeid(A).name(), "A::sa-1"); + gTypeName += "::"; + gTypeName += + demangle_it(typeid(A::sa).name(), "A::sa-2"); + } + + template static T sa_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(A).name(), "A::sa_T-1"); + gTypeName += "::"; + gTypeName += + demangle_it(typeid(A::sa_T).name(), "A::sa_T-2"); + return T{}; + } +}; + +class B { +public: + B() { gTypeName = demangle_it(typeid(B).name(), "B::B"); } + B(const B&) = default; + B(B&&) = default; + B& operator=(const B&) = default; + B& operator=(B&&) = default; + + template void b(FArgs&&... args) { + gTypeName = demangle_it(typeid(&B::b).name(), "B::b-2"); + } + + template T b_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(&B::b_T).name(), "B::b_T-2"); + return T{}; + } + + template static void sb(FArgs&&... args) { + gTypeName = demangle_it(typeid(B).name(), "B::sb-1"); + gTypeName += "::"; + gTypeName += demangle_it(typeid(B::sb).name(), "B::sb-2"); + } + + template static T sb_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(B).name(), "B::sb_T-1"); + gTypeName += "::"; + gTypeName += demangle_it(typeid(B::sb_T).name(), "B::sb_T-2"); + return T{}; + } +}; + +template void fn(Args&&... args) { + gTypeName = demangle_it(typeid(fn).name(), "fn"); +} + +template T fn_T(Args&&... args) { + gTypeName = demangle_it(typeid(fn).name(), "fn_T"); + return T{}; +} + +} // namespace some_variadic + +//=========================================================================== +// template with empty body +namespace T_WithEmptyBody { + +#ifdef _WIN32 +#ifdef __CLING__ +extern __declspec(dllimport) std::string side_effect; +#else +extern __declspec(dllexport) std::string side_effect; +#endif +#else +extern std::string side_effect; +#endif + +template void some_empty(); + +} // namespace T_WithEmptyBody + +//=========================================================================== +// template with catch-all (void*, void**)overloads +namespace T_WithGreedyOverloads { + +class SomeClass { + double fD; +}; + +class WithGreedy1 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(void*, bool force = false) { return -1; } +}; + +class WithGreedy2 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(void**, bool force = false) { return -1; } +}; + +class DoesNotExist; + +class WithGreedy3 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(DoesNotExist*, bool force = false) { return -1; } +}; + +} // namespace T_WithGreedyOverloads + +//=========================================================================== +// template with overloaded non-templated and templated setitem +namespace TemplateWithSetItem { + +template class MyVec { +private: + std::vector fData; + +public: + using size_type = typename std::vector::size_type; + + MyVec(size_type count) : fData(count) {} + + T& operator[](size_type index) { return fData[index]; } + + // The definition of this templated operator causes the issue + template MyVec operator[](const MyVec& conds) const { + return MyVec(2); + } +}; + +} // namespace TemplateWithSetItem + +//=========================================================================== +// type reduction examples on gmpxx-like template expressions +namespace TypeReduction { + +template struct BinaryExpr; + +template struct Expr { + Expr() {} + Expr(const BinaryExpr&) {} +}; + +template struct BinaryExpr { + BinaryExpr(const Expr&, const Expr&) {} +}; + +template +BinaryExpr operator+(const Expr& e1, const Expr& e2) { + return BinaryExpr(e1, e2); +} + +} // namespace TypeReduction + +//=========================================================================== +// type deduction examples +namespace FailedTypeDeducer { + +template class B { +public: + auto result() { return 5.; } +}; + +extern template class B; + +} // namespace FailedTypeDeducer + +#endif // !CPPJIT_TEST_TEMPLATES_H diff --git a/bindings/pyroot/cppjit/test/support.py b/bindings/pyroot/cppjit/test/support.py new file mode 100644 index 0000000000000..94c47383f4747 --- /dev/null +++ b/bindings/pyroot/cppjit/test/support.py @@ -0,0 +1,166 @@ +from __future__ import print_function + +import os +import shutil +import subprocess +import sys + +import py +import pytest + +try: + import fcntl +except ImportError: # Windows: no concurrent make workflow to serialize + fcntl = None + +currpath = py.path.local(__file__).dirpath() + +_NO_TOOLCHAIN = "no make and C++ compiler to build the test dictionaries" +# a build system that supplies the dictionaries itself sets CPPJIT_TEST_SKIP_MAKE +_PREBUILT = bool(os.getenv("CPPJIT_TEST_SKIP_MAKE", False)) +# otherwise test/Makefile builds them with make and its default $(CXX) +_HAS_TOOLCHAIN = bool( + shutil.which("make") and shutil.which(os.environ.get("CXX") or "g++") +) + +# for a test that loads a dictionary in a module whose other tests need none +needs_dictionary = pytest.mark.skipif( + not (_PREBUILT or _HAS_TOOLCHAIN), reason=_NO_TOOLCHAIN +) + + +def setup_make(targetname, optional=False): + if _PREBUILT: + return + + if not _HAS_TOOLCHAIN: + if optional: + return + pytest.skip(_NO_TOOLCHAIN, allow_module_level=True) + + # several files share a dictionary, so workers race make for it; the lock + # is per target to keep unrelated builds parallel + lockf = open(str(currpath.join("cpp", targetname + "Dict.lock")), "a") + try: + if fcntl is not None: + fcntl.flock(lockf, fcntl.LOCK_EX) + popen = subprocess.Popen( + ["make", targetname + "Dict" + soext], + cwd=str(currpath), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + stdout, _ = popen.communicate() + finally: + lockf.close() + if popen.returncode: + raise OSError("'make' failed:\n%s" % (stdout,)) + + +if sys.hexversion >= 0x3000000: + pylong = int + pyunicode = str + maxvalue = sys.maxsize +else: + pylong = long # noqa: F821 + pyunicode = unicode # noqa: F821 + maxvalue = sys.maxint + +IS_WINDOWS = 0 +if "win32" in sys.platform: + import platform + + if "64" in platform.architecture()[0]: + IS_WINDOWS = 64 + maxvalue = 2**31 - 1 + else: + IS_WINDOWS = 32 + +soext = ".dll" if IS_WINDOWS else ".so" + +IS_MAC_ARM = 0 +IS_MAC_X86 = 0 +if "darwin" in sys.platform: + import platform + + if "arm64" in platform.machine(): + IS_MAC_ARM = 64 + os.environ["CPPJIT_UNCAUGHT_QUIET"] = "1" + else: + IS_MAC_X86 = 1 +IS_MAC = IS_MAC_ARM or IS_MAC_X86 + +IS_LINUX = 0 +IS_LINUX_ARM = 0 +IS_LINUX_X86 = 0 +if "linux" in sys.platform: + IS_LINUX = 1 + import platform + + if "aarch64" in platform.machine(): + IS_LINUX_ARM = 64 + os.environ["CPPJIT_UNCAUGHT_QUIET"] = "1" + else: + IS_LINUX_X86 = 1 + +try: + import __pypy__ # noqa: F401 + + ispypy = True +except ImportError: + ispypy = False + +import cppjit # noqa: E402 + +IS_CLANG_REPL = ( + cppjit.evaluate("""#ifndef __CLING__ + true + #else + false + #endif\n""") + == 1 +) +IS_CLANG_DEBUG = ( + cppjit.evaluate("""#ifdef NDEBUG + false + #else + true + #endif\n""") + == 1 +) +IS_CLING = not IS_CLANG_REPL +IS_CPP23 = ( + cppjit.evaluate("""#if __cplusplus >= 202302L + true + #else + false + #endif\n""") + == 1 +) + + +def _jit_resolves_std_filesystem(): + # The JIT resolves std::filesystem against the loaded libstdc++.so, not + # the compile headers -- a manylinux image (gcc-toolset headers over a + # GCC-8 base runtime) compiles the include yet dies resolving the + # symbols. That failure is a fatal JIT error, so ask in a child process. + probe = ( + "import cppjit\n" + 'cppjit.cppdef("""#include \n' + "unsigned long fs_probe() { return std::filesystem::temp_directory_path().string().size(); }\n" + '""")\n' + "assert cppjit.gbl.fs_probe() > 0\n" + ) + try: + return ( + subprocess.run( + [sys.executable, "-c", probe], capture_output=True, timeout=300 + ).returncode + == 0 + ) + except subprocess.TimeoutExpired: + return False + + +CAN_JIT_STD_FILESYSTEM = _jit_resolves_std_filesystem() +IS_VALGRIND = True if os.getenv("IS_VALGRIND") else False diff --git a/bindings/pyroot/cppjit/test/test_aclassloader.py b/bindings/pyroot/cppjit/test/test_aclassloader.py new file mode 100644 index 0000000000000..a8f4da6fa2054 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_aclassloader.py @@ -0,0 +1,27 @@ +import py +from support import setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = "example01_cxx" + + +def setup_module(mod): + setup_make("example01") + + +class TestACLASSLOADER: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) + + def test01_class_autoloading(self): + """Test whether a class can be found""" + import cppjit + + example01_class = cppjit.gbl.example01 + assert example01_class + cl2 = cppjit.gbl.example01 + assert cl2 + assert example01_class is cl2 diff --git a/bindings/pyroot/cppjit/test/test_advancedcpp.py b/bindings/pyroot/cppjit/test/test_advancedcpp.py new file mode 100644 index 0000000000000..b59ddc59e1af4 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_advancedcpp.py @@ -0,0 +1,1053 @@ +import py +from pytest import mark, raises, skip +from support import ( + IS_MAC, + IS_MAC_ARM, + IS_VALGRIND, + IS_WINDOWS, + ispypy, + pylong, + setup_make, +) + +currpath = py.path.local(__file__).dirpath() +test_dct = "advancedcpp_cxx" + + +def setup_module(mod): + setup_make("advancedcpp") + setup_make("advancedcpp2") + + +class TestADVANCEDCPP: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.advanced = cppjit.load_reflection_info(cls.test_dct) + + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") + def test01_default_arguments(self): + """Test usage of default arguments""" + + import cppjit + + def test_defaulter(n, t): + defaulter = getattr(cppjit.gbl, "%s_defaulter" % n) + + d = defaulter() + assert d.m_a == t(11) + assert d.m_b == t(22) + assert d.m_c == t(33) + d.__destruct__() + + d = defaulter(0) + assert d.m_a == t(0) + assert d.m_b == t(22) + assert d.m_c == t(33) + d.__destruct__() + + d = defaulter(1, 2) + assert d.m_a == t(1) + assert d.m_b == t(2) + assert d.m_c == t(33) + d.__destruct__() + + d = defaulter(3, 4, 5) + assert d.m_a == t(3) + assert d.m_b == t(4) + assert d.m_c == t(5) + d.__destruct__() + + defaulter_func = getattr(cppjit.gbl, "%s_defaulter_func" % n) + answers = [11, 22, 33, 3] + for idx in range(4): + assert defaulter_func(idx) == answers[idx] + + test_defaulter("short", int) + test_defaulter("ushort", int) + test_defaulter("int", int) + test_defaulter("uint", int) + test_defaulter("long", pylong) + test_defaulter("ulong", pylong) + test_defaulter("llong", pylong) + test_defaulter("ullong", pylong) + test_defaulter("float", float) + test_defaulter("double", float) + + assert cppjit.gbl.string_defaulter_func(0) == "aap" + assert cppjit.gbl.string_defaulter_func(0, "zus") == "zus" + assert cppjit.gbl.string_defaulter_func(1) == "noot" + assert cppjit.gbl.string_defaulter_func(1, "zus") == "noot" + assert cppjit.gbl.string_defaulter_func(1, "zus", "jet") == "jet" + assert cppjit.gbl.string_defaulter_func(2) == "mies" + + def test02_simple_inheritance(self): + """Test binding of a basic inheritance structure""" + + import cppjit + + base_class = cppjit.gbl.base_class + derived_class = cppjit.gbl.derived_class + + assert issubclass(derived_class, base_class) + assert not issubclass(base_class, derived_class) + + b = base_class() + assert isinstance(b, base_class) + assert not isinstance(b, derived_class) + + assert b.m_b == 1 + assert b.get_value() == 1 + assert b.m_db == 1.1 + assert b.get_base_value() == 1.1 + + b.m_b, b.m_db = 11, 11.11 + assert b.m_b == 11 + assert b.get_value() == 11 + assert b.m_db == 11.11 + assert b.get_base_value() == 11.11 + + b.__destruct__() + + d = derived_class() + assert isinstance(d, derived_class) + assert isinstance(d, base_class) + + assert d.m_d == 2 + assert d.get_value() == 2 + assert d.m_dd == 2.2 + assert d.get_derived_value() == 2.2 + + assert d.m_b == 1 + assert d.m_db == 1.1 + assert d.get_base_value() == 1.1 + + d.m_b, d.m_db = 11, 11.11 + d.m_d, d.m_dd = 22, 22.22 + + assert d.m_d == 22 + assert d.get_value() == 22 + assert d.m_dd == 22.22 + assert d.get_derived_value() == 22.22 + + assert d.m_b == 11 + assert d.m_db == 11.11 + assert d.get_base_value() == 11.11 + + d.__destruct__() + + def test03_namespaces(self): + """Test access to namespaces and inner classes""" + + import cppjit + + gbl = cppjit.gbl + + assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns.d_ns is gbl.a_ns.d_ns + + assert gbl.a_ns.b_class is gbl.a_ns.b_class + assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class + assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class + assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class + + assert gbl.a_ns.g_a == 11 + assert gbl.a_ns.get_g_a() == 11 + assert gbl.a_ns.b_class.s_b == 22 + assert gbl.a_ns.b_class().m_b == -2 + assert gbl.a_ns.b_class.c_class.s_c == 33 + assert gbl.a_ns.b_class.c_class().m_c == -3 + assert gbl.a_ns.d_ns.g_d == 44 + assert gbl.a_ns.d_ns.get_g_d() == 44 + assert gbl.a_ns.d_ns.e_class.s_e == 55 + assert gbl.a_ns.d_ns.e_class().m_e == -5 + assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 + assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 + + raises(TypeError, gbl.a_ns) + + def test03a_namespace_lookup_on_update(self): + """Test whether namespaces can be shared across dictionaries.""" + + import cppjit + + gbl = cppjit.gbl + + lib2 = cppjit.load_reflection_info("advancedcpp2_cxx") + + assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns.d_ns is gbl.a_ns.d_ns + + assert gbl.a_ns.g_class is gbl.a_ns.g_class + assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class + assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class + assert gbl.a_ns.d_ns.i_class.j_class is gbl.a_ns.d_ns.i_class.j_class + + assert gbl.a_ns.g_g == 77 + assert gbl.a_ns.get_g_g() == 77 + assert gbl.a_ns.g_class.s_g == 88 + assert gbl.a_ns.g_class().m_g == -7 + assert gbl.a_ns.g_class.h_class.s_h == 99 + assert gbl.a_ns.g_class.h_class().m_h == -8 + assert gbl.a_ns.d_ns.g_i == 111 + assert gbl.a_ns.d_ns.get_g_i() == 111 + assert gbl.a_ns.d_ns.i_class.s_i == 222 + assert gbl.a_ns.d_ns.i_class().m_i == -9 + assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 + assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 + + def test04_template_types(self): + """Test bindings of templated types""" + + import cppjit + + gbl = cppjit.gbl + + assert gbl.T1 is gbl.T1 + assert gbl.T2 is gbl.T2 + assert gbl.T3 is gbl.T3 + assert gbl.T1 is not gbl.T2 + assert gbl.T2 is not gbl.T3 + + assert gbl.T1("int") is gbl.T1("int") + assert gbl.T1(int) is gbl.T1("int") + assert gbl.T2("T1") is gbl.T2("T1") + assert gbl.T2(gbl.T1("int")) is gbl.T2("T1") + assert gbl.T2(gbl.T1(int)) is gbl.T2("T1") + assert gbl.T3("int,double") is gbl.T3("int,double") + assert gbl.T3("int", "double") is gbl.T3("int,double") + assert gbl.T3(int, "double") is gbl.T3("int,double") + assert gbl.T3("T1,T2 >") is gbl.T3("T1,T2 >") + assert gbl.T3("T1", gbl.T2(gbl.T1(int))) is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4(int) is gbl.a_ns.T4("int") + assert gbl.a_ns.T4("a_ns::T4 >") is gbl.a_ns.T4( + gbl.a_ns.T4(gbl.T3(int, "double")) + ) + + # ----- mix in some of the alternative syntax + assert gbl.T1["int"] is gbl.T1("int") + assert gbl.T1[int] is gbl.T1("int") + assert gbl.T2["T1"] is gbl.T2("T1") + assert gbl.T2[gbl.T1("int")] is gbl.T2("T1") + assert gbl.T2[gbl.T1(int)] is gbl.T2("T1") + assert gbl.T3["int,double"] is gbl.T3("int,double") + assert gbl.T3["int", "double"] is gbl.T3("int,double") + assert gbl.T3[int, "double"] is gbl.T3("int,double") + assert gbl.T3["T1,T2 >"] is gbl.T3("T1,T2 >") + assert gbl.T3["T1", gbl.T2[gbl.T1[int]]] is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4[int] is gbl.a_ns.T4("int") + assert gbl.a_ns.T4["a_ns::T4 >"] is gbl.a_ns.T4( + gbl.a_ns.T4(gbl.T3(int, "double")) + ) + + # ----- + t1 = gbl.T1(int)() + assert t1.m_t1 == 1 + assert t1.get_value() == 1 + t1.__destruct__() + + # ----- + t1 = gbl.T1(int)(11) + assert t1.m_t1 == 11 + assert t1.get_value() == 11 + t1.m_t1 = 111 + assert t1.get_value() == 111 + assert t1.m_t1 == 111 + t1.__destruct__() + + # ----- + t2 = gbl.T2(gbl.T1(int))(gbl.T1(int)(32)) + t2.m_t2.m_t1 = 32 + assert t2.m_t2.get_value() == 32 + assert t2.m_t2.m_t1 == 32 + t2.__destruct__() + + def test05_abstract_classes(self): + """Test non-instatiatability of abstract classes""" + + import cppjit + + gbl = cppjit.gbl + + raises(TypeError, gbl.a_class) + raises(TypeError, gbl.some_abstract_class) + + assert issubclass(gbl.some_concrete_class, gbl.some_abstract_class) + + c = gbl.some_concrete_class() + assert isinstance(c, gbl.some_concrete_class) + assert isinstance(c, gbl.some_abstract_class) + + def test06_datamembers(self): + """Test data member access when using virtual inheritence""" + + import cppjit + + a_class = cppjit.gbl.a_class + b_class = cppjit.gbl.b_class + c_class_1 = cppjit.gbl.c_class_1 + c_class_2 = cppjit.gbl.c_class_2 + d_class = cppjit.gbl.d_class + + assert issubclass(b_class, a_class) + assert issubclass(c_class_1, a_class) + assert issubclass(c_class_1, b_class) + assert issubclass(c_class_2, a_class) + assert issubclass(c_class_2, b_class) + assert issubclass(d_class, a_class) + assert issubclass(d_class, b_class) + assert issubclass(d_class, c_class_2) + + # ----- + b = b_class() + assert b.m_a == 1 + assert b.m_da == 1.1 + assert b.m_b == 2 + assert b.m_db == 2.2 + + b.m_a = 11 + assert b.m_a == 11 + assert b.m_b == 2 + + b.m_da = 11.11 + assert b.m_da == 11.11 + assert b.m_db == 2.2 + + b.m_b = 22 + assert b.m_a == 11 + assert b.m_da == 11.11 + assert b.m_b == 22 + assert b.get_value() == 22 + + b.m_db = 22.22 + assert b.m_db == 22.22 + + b.__destruct__() + + # ----- + c1 = c_class_1() + assert c1.m_a == 1 + assert c1.m_b == 2 + assert c1.m_c == 3 + + c1.m_a = 11 + assert c1.m_a == 11 + + c1.m_b = 22 + assert c1.m_a == 11 + assert c1.m_b == 22 + + c1.m_c = 33 + assert c1.m_a == 11 + assert c1.m_b == 22 + assert c1.m_c == 33 + assert c1.get_value() == 33 + + c1.__destruct__() + + # ----- + d = d_class() + assert d.m_a == 1 + assert d.m_b == 2 + assert d.m_c == 3 + assert d.m_d == 4 + + d.m_a = 11 + assert d.m_a == 11 + + d.m_b = 22 + assert d.m_a == 11 + assert d.m_b == 22 + + d.m_c = 33 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 + + d.m_d = 44 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 + assert d.m_d == 44 + assert d.get_value() == 44 + + d.__destruct__() + + def test07_pass_by_reference(self): + """Test reference passing when using virtual inheritance""" + + import cppjit + + gbl = cppjit.gbl + b_class = gbl.b_class + c_class = gbl.c_class_2 + d_class = gbl.d_class + + # ----- + b = b_class() + b.m_a, b.m_b = 11, 22 + assert gbl.get_a(b) == 11 + assert gbl.get_b(b) == 22 + b.__destruct__() + + # ----- + c = c_class() + c.m_a, c.m_b, c.m_c = 11, 22, 33 + assert gbl.get_a(c) == 11 + assert gbl.get_b(c) == 22 + assert gbl.get_c(c) == 33 + c.__destruct__() + + # ----- + d = d_class() + d.m_a, d.m_b, d.m_c, d.m_d = 11, 22, 33, 44 + assert gbl.get_a(d) == 11 + assert gbl.get_b(d) == 22 + assert gbl.get_c(d) == 33 + assert gbl.get_d(d) == 44 + d.__destruct__() + + def test08_void_pointer_passing(self): + """Test passing of variants of void pointer arguments""" + + import cppjit + + pointer_pass = cppjit.gbl.pointer_pass + some_concrete_class = cppjit.gbl.some_concrete_class + + pp = pointer_pass() + o = some_concrete_class() + + assert cppjit.addressof(o) == pp.gime_address_ptr(o) + assert cppjit.addressof(o) == pp.gime_address_ptr_ptr(o) + assert cppjit.addressof(o) == pp.gime_address_ptr_ref(o) + + if IS_WINDOWS != 64: + # there is no 8-byte integer type array on Windows 64b + import array + + addressofo = array.array("l", [cppjit.addressof(o)]) + assert addressofo[0] == pp.gime_address_ptr_ptr(addressofo) + + assert 0 == pp.gime_address_ptr(0) + raises(TypeError, pp.gime_address_ptr, None) + + ptr = cppjit.bind_object(0, some_concrete_class) + assert cppjit.addressof(ptr) == 0 + pp.set_address_ptr_ref(ptr) + assert cppjit.addressof(ptr) == 0x1234 + + # alternate path through static method handling, which does NOT + # provide 'pp' as argument (and thus need no removing) + sf = pp.set_address_ptr_ref + sf(ptr) + assert cppjit.addressof(ptr) == 0x1234 + + pp.set_address_ptr_ptr(ptr) + assert cppjit.addressof(ptr) == 0x4321 + + assert cppjit.addressof(cppjit.nullptr) == 0 + raises(TypeError, cppjit.addressof, None) + assert cppjit.addressof(0) == 0 + + def test09_opaque_pointer_passing(self): + """Test passing around of opaque pointers""" + + import cppjit + + some_concrete_class = cppjit.gbl.some_concrete_class + + o = some_concrete_class() + + # TODO: figure out the PyPy equivalent of CObject (may have to do this + # through the C-API from C++) + + # cobj = cppjit.as_cobject(o) + addr = cppjit.addressof(o) + + # assert o == cppjit.bind_object(cobj, some_concrete_class) + # assert o == cppjit.bind_object(cobj, type(o)) + # assert o == cppjit.bind_object(cobj, o.__class__) + # assert o == cppjit.bind_object(cobj, "some_concrete_class") + assert cppjit.addressof(o) == cppjit.addressof( + cppjit.bind_object(addr, some_concrete_class) + ) + assert o is cppjit.bind_object(addr, some_concrete_class) + assert o is cppjit.bind_object(addr, type(o)) + assert o is cppjit.bind_object(addr, o.__class__) + assert o is cppjit.bind_object(addr, "some_concrete_class") + raises(TypeError, cppjit.bind_object, addr, "does_not_exist") + raises(TypeError, cppjit.bind_object, addr, 1) + + def test10_object_identity(self): + """Test object identity""" + + import cppjit + + some_concrete_class = cppjit.gbl.some_concrete_class + some_class_with_data = cppjit.gbl.some_class_with_data + + o = some_concrete_class() + addr = cppjit.addressof(o) + + o2 = cppjit.bind_object(addr, some_concrete_class) + assert o is o2 + + o3 = cppjit.bind_object(addr, some_class_with_data) + assert o is not o3 + + d1 = some_class_with_data() + d2 = d1.gime_copy() + assert d1 is not d2 + + dd1a = d1.gime_data() + dd1b = d1.gime_data() + assert dd1a is dd1b + + dd2 = d2.gime_data() + assert dd1a is not dd2 + assert dd1b is not dd2 + + d2.__destruct__() + d1.__destruct__() + + RTS = cppjit.gbl.refers_to_self + + r1 = RTS() + r2 = RTS() + r1.m_other = r2 + + r3 = r1.m_other + r4 = r1.m_other + assert r3 is r4 + + assert r3 == r2 + assert r3 is r2 + + r3.extra = 42 + assert r2.extra == 42 + assert r4.extra == 42 + + def test11_multi_methods(self): + """Test calling of methods from multiple inheritance""" + + import cppjit + + multi = cppjit.gbl.multi + + assert cppjit.gbl.multi1 is multi.__bases__[0] + assert cppjit.gbl.multi2 is multi.__bases__[1] + + dict_keys = list(multi.__dict__.keys()) + assert dict_keys.count("get_my_own_int") == 1 + assert dict_keys.count("get_multi1_int") == 0 + assert dict_keys.count("get_multi2_int") == 0 + + m = multi(1, 2, 3) + assert m.get_multi1_int() == 1 + assert m.get_multi2_int() == 2 + assert m.get_my_own_int() == 3 + + def test12_actual_type(self): + """Test that a pointer to base return does an auto-downcast""" + + import cppjit + + base_class = cppjit.gbl.base_class + base_class.clone.__creates__ = True + derived_class = cppjit.gbl.derived_class + derived_class.clone.__creates__ = True + + b = base_class() + d = derived_class() + + assert b is b.cycle(b) + assert id(b) == id(b.cycle(b)) + assert b is d.cycle(b) + assert id(b) == id(d.cycle(b)) + assert d is b.cycle(d) + assert id(d) == id(b.cycle(d)) + assert d is d.cycle(d) + assert id(d) == id(d.cycle(d)) + + assert isinstance(b.cycle(b), base_class) + assert isinstance(d.cycle(b), base_class) + assert isinstance(b.cycle(d), derived_class) + assert isinstance(d.cycle(d), derived_class) + + base_class.clone.__creates__ = True + assert isinstance(b.clone(), base_class) + derived_class.clone.__creates__ = True + assert isinstance(d.clone(), derived_class) + + # special case when round-tripping through a void* ptr + voidp = b.mask(d) + assert not isinstance(voidp, base_class) + assert not isinstance(voidp, derived_class) + + d1 = cppjit.bind_object(voidp, base_class, cast=True) + assert isinstance(d1, derived_class) + assert d1 is d + + b1 = cppjit.bind_object(voidp, base_class) + assert isinstance(b1, base_class) + assert cppjit.addressof(b1) == cppjit.addressof(d) + assert b1 is not d + + def test13_actual_type_virtual_multi(self): + """Test auto-downcast in adverse inheritance situation""" + + import cppjit + + cppjit.gbl.create_c1.__creates__ = True + c1 = cppjit.gbl.create_c1() + assert type(c1) == cppjit.gbl.c_class_1 + assert c1.m_c == 3 + c1.__destruct__() + + cppjit.gbl.create_c2.__creates__ = True + c2 = cppjit.gbl.create_c2() + assert type(c2) == cppjit.gbl.c_class_2 + assert c2.m_c == 3 + c2.__destruct__() + + def test14_new_overloader(self): + """Verify that class-level overloaded new/delete are called""" + + import cppjit + + cppjit.include("new") + assert cppjit.gbl.new_overloader.s_instances == 0 + nl = cppjit.gbl.new_overloader() + assert cppjit.gbl.new_overloader.s_instances == 1 + nl.__destruct__() + + import gc + + gc.collect() + assert cppjit.gbl.new_overloader.s_instances == 0 + + def test15_template_instantiation_with_vector_of_float(self): + """Test template instantiation with a std::vector""" + + import cppjit + + # the following will simply fail if there is a naming problem (e.g. + # std::, allocator, etc., etc.); note the parsing required ... + b = cppjit.gbl.my_templated_class(cppjit.gbl.std.vector(float))() + + for i in range(5): + b.m_b.push_back(i) + assert round(b.m_b[i], 5) == float(i) + + def test16_template_global_functions(self): + """Test template global function lookup and calls""" + + import cppjit + + f = cppjit.gbl.my_templated_function + + assert f("c") == "c" + assert type(f("c")) == type("c") + assert f(3.0) == 3.0 + assert type(f(4.0)) == type(4.0) + + def test17_assign_to_return_byref(self): + """Test assignment to an instance returned by reference""" + + if ispypy: + skip("segfaults in pypy") + + from cppjit import gbl + + a = gbl.std.vector(gbl.ref_tester)() + a.push_back(gbl.ref_tester(42)) + + assert len(a) == 1 + assert a[0].m_i == 42 + + a[0] = gbl.ref_tester(33) + assert len(a) == 1 + assert a[0].m_i == 33 + + def test18_math_converters(self): + """Test operator int/long/double incl. typedef""" + + from cppjit import gbl + + a = gbl.some_convertible() + a.m_i = 1234 + a.m_d = 4321.0 + + assert int(a) == 1234 + assert int(a) == a.m_i + assert pylong(a) == a.m_i + + assert float(a) == 4321.0 + assert float(a) == a.m_d + + def test19_comparator(self): + """Check that the global operator!=/== is picked up""" + + from cppjit import gbl + + a, b = gbl.some_comparable(), gbl.some_comparable() + + assert a == b + assert b == a + assert a.__eq__(b) + assert b.__eq__(a) + assert a.__ne__(a) + assert b.__ne__(b) + assert a.__eq__(b) == True + assert b.__eq__(a) == True + assert a.__eq__(a) == False + assert b.__eq__(b) == False + + def test20_overload_order_with_proper_return(self): + """Test return type against proper overload w/ const and covariance""" + + import cppjit + + assert cppjit.gbl.overload_one_way().gime() == 1 + assert cppjit.gbl.overload_the_other_way().gime() == "aap" + + @mark.xfail(condition=IS_VALGRIND, run=False, reason="hangs under valgrind") + def test21_access_to_global_variables(self): + """Access global_variables_and_pointers""" + + import cppjit + + assert cppjit.gbl.my_global_double == 12.0 + assert len(cppjit.gbl.my_global_array) == 500 + assert cppjit.gbl.my_global_string1 == "aap noot mies" + assert cppjit.gbl.my_global_string2 == "zus jet teun" + assert list(cppjit.gbl.my_global_string3) == ["aap", "noot", "mies"] + assert cppjit.gbl.my_global_ptr[0] == 1234.0 + + v = cppjit.gbl.my_global_int_holders + assert len(v) == 5 + expected_vals = [13, 42, 88, -1, 17] + for i in range(len(v)): + assert v[i].m_val == expected_vals[i] + + o = cppjit.gbl.some_concrete_class() + assert type(o) != type(cppjit.gbl.g_abstract_ptr) + cppjit.gbl.g_abstract_ptr = o + assert type(o) != type(cppjit.gbl.g_abstract_ptr) + assert cppjit.gbl.g_abstract_ptr == o + cppjit.gbl.g_abstract_ptr = cppjit.nullptr + + cppjit.cppexec("std::vector* gtestv1 = nullptr;") + pyv = cppjit.gbl.std.vector[int]() + cppjit.gbl.gtestv1 = pyv + cppjit.cppexec("auto* gtestv2 = gtestv1;") + pyv.push_back(42) + assert len(cppjit.gbl.gtestv1) == 1 + assert len(cppjit.gbl.gtestv2) == 1 + + def test21a_autocast_pointer_variables(self): + """Pointer variables read from memory down-cast to the actual class""" + + import cppjit + + cppjit.cppdef("""\ + namespace AutoCastMem { + struct B { virtual ~B() {} int x = 1; }; + struct D : B { int y = 2; }; + struct Other { virtual ~Other() {} int o = 7; }; + struct MI : Other, B { int z = 3; }; + D g_d; + MI g_mi; + B* g_ptr = &g_d; + B* g_mi_ptr = &g_mi; + B* g_null = nullptr; + }""") + + ns = cppjit.gbl.AutoCastMem + + assert type(ns.g_ptr) is ns.D + assert ns.g_ptr.y == 2 + + # a non-primary base has its own address, so the proxy stays at B + mi = ns.g_mi_ptr + assert type(mi) is ns.B + assert mi.x == 1 + + assert not ns.g_null + + @mark.xfail( + condition=IS_MAC_ARM, + run=False, + reason="Crashes with exception not being caught on Apple Silicon", + ) + @mark.xfail( + condition=IS_WINDOWS == 64, + run=False, + reason="Crashes with some versions of Visual Studio", + ) + def test22_exceptions(self): + """Catching of C++ exceptions""" + + import cppjit + + Thrower = cppjit.gbl.Thrower + + Thrower.throw_anything.__useffi__ = False + Thrower.throw_exception.__useffi__ = False + + t = Thrower() + + assert raises(Exception, t.throw_anything) + assert raises(Exception, t.throw_exception) + + caught = False + try: + t.throw_exception() + except Exception as e: + assert "C++ function failed" in str(e) + caught = True + assert caught == True + + def test23_using(self): + """Accessibility of using declarations""" + + import cppjit + + assert cppjit.gbl.UsingBase1().vcheck() == "A" + + D1 = cppjit.gbl.UsingDerived1 + assert "UsingBase1" not in D1.__init__.__doc__ + + d1a = D1() + assert d1a.m_int == 13 + assert d1a.m_int2 == 42 + assert d1a.vcheck() == "B" + + d1b = D1(10) + assert d1b.m_int == 10 + assert d1b.m_int2 == 42 + assert d1b.vcheck() == "B" + + d1c = D1(d1b) + assert d1c.m_int == 10 + assert d1c.m_int2 == 42 + assert d1c.vcheck() == "B" + + D2 = cppjit.gbl.UsingDerived2 + assert "vcheck(int)" in D2.vcheck.__doc__ + assert "vcheck()" in D2.vcheck.__doc__ + + d2 = D2() + assert d2.vcheck() == "A" + assert d2.vcheck(1) == "B" + + def test24_typedef_to_private_class(self): + """Typedefs to private classes should not resolve""" + + import cppjit + + assert cppjit.gbl.TypedefToPrivateClass().f().m_val == 42 + + def test25_ostream_printing(self): + """Mapping of __str__ through operator<<(ostream&)""" + + import cppjit + + ns = cppjit.gbl.Cpp2PyPrinting + + assert str(ns.Printable1()) == "Printable1::operator<<" + for tst in [ + (ns.Printable2, "Cpp2PyPrinting::operator<<"), + (ns.Printable3, "::operator<<(3)"), + (cppjit.gbl.Printable4, "::operator<<(4)"), + (cppjit.gbl.Printable6, "Printable6"), + ]: + assert str(tst[0]()) == tst[1] + if "__lshiftc__" in tst[0].__dict__: + # only cached for global functions and in principle should + # not be needed anymore ... + assert tst[0].__lshiftc__ + del tst[0].__lshiftc__ + assert str(tst[0]()) == tst[1] + assert tst[0].__lshiftc__ + s = cppjit.gbl.std.ostringstream() + tst[0].__lshiftc__(s, tst[0]()) + assert s.str() == tst[1] + + # print through base class (used to fail with compilation error) + assert str(cppjit.gbl.Printable5()) == "Ok." + + # print through friend + cppjit.cppdef("""\ + namespace PrintingNS { + class X { + friend std::ostream& operator<<(std::ostream& os, const X&) { return os << "X"; } + }; + + class Y { + }; + + std::ostream& operator<<(std::ostream& os, const Y&) { return os << "Y"; } + } """) + + x = cppjit.gbl.PrintingNS.X() + assert str(x) == "X" + + y = cppjit.gbl.PrintingNS.Y() + assert str(y) == "Y" + + def test26_using_directive(self): + """Test using directive in namespaces""" + + import cppjit + + assert cppjit.gbl.UserDirs.foo1() == cppjit.gbl.UsedSpace1.foo1() + assert cppjit.gbl.UserDirs.bar() == cppjit.gbl.UsedSpace2.bar() + assert cppjit.gbl.UserDirs.foo2() == cppjit.gbl.UsedSpace1.inner.foo2() + + def test27_shadowed_typedef(self): + """Test that typedefs are not shadowed""" + + import cppjit + + cppjit.cppdef(""" + namespace ShadowedTypedef { + struct A { + typedef std::shared_ptr Ptr; + typedef int Val; + }; + + struct B : public A { + typedef std::shared_ptr Ptr; + typedef double Val; + }; + + struct C : public A { + /* empty */ + }; }""") + + ns = cppjit.gbl.ShadowedTypedef + + ns.A.Ptr # pull A::Ptr first + ns.A.Val # id. A::Val + ns.B.Ptr # used to be A.Ptr through python-side dict lookup + ns.B.Val # id. B::Val + ns.C.Ptr # is A.Ptr + ns.C.Val # is A.Val + + assert ns.A.Ptr == ns.A.Ptr + assert ns.B.Ptr == ns.B.Ptr + assert ns.A.Ptr != ns.B.Ptr + assert ns.A.Ptr == ns.C.Ptr + assert ns.A.Val == ns.C.Val + + # TODO: currently only classes are checked; typedefs of builtin types are + # mapped through the type mapper and as such can be anything + # assert ns.A.Val != ns.B.Val + # assert type(ns.A.Val(1)) == int + # assert type(ns.B.Val(1)) == float + + def test28_extern_C_in_namespace(self): + """Access to extern "C" declared functions in namespaces""" + + import cppjit + + cppjit.cppdef("""\ + namespace extern_c_in_ns { + extern "C" int some_func_xc(void) { return 21; } + int some_func() { return some_func_xc(); } + + namespace deeper { + extern "C" int some_other_func_xc(void) { return 42; } + } }""") + + ns = cppjit.gbl.extern_c_in_ns + + assert ns.some_func() == 21 + assert ns.some_func_xc() == 21 + + assert ns.deeper.some_other_func_xc() == 42 + + def test29_castcpp(self): + """Allow casting a Python class to a C++ one""" + + import math + + import cppjit + + cppjit.cppdef("""\ + namespace castcpp { + struct MyPoint { + double x, y; + }; + + double norm_cr(const MyPoint& p) { + return std::sqrt(p.x*p.x + p.y*p.y); + } + + double norm_r(MyPoint& p) { + return std::sqrt(p.x*p.x + p.y*p.y); + } + + double norm_m(MyPoint&& p) { + return std::sqrt(p.x*p.x + p.y*p.y); + } + + double norm_v(MyPoint p) { + return std::sqrt(p.x*p.x + p.y*p.y); + } + + double norm_p(MyPoint* p) { + return std::sqrt(p->x*p->x + p->y*p->y); + } }""") + + ns = cppjit.gbl.castcpp + + class MyPyPoint1: + def __init__(self, x, y): + self.x = x + self.y = y + + class MyPyPoint2(MyPyPoint1): + def __cast_cpp__(self): + return ns.MyPoint(self.x, self.y) + + class MyPyPoint3(MyPyPoint1): + def __cast_cpp__(self): + return (self.x, self.y) + + p1 = MyPyPoint1(5, 10) + p2 = MyPyPoint2(p1.x, p1.y) + p3 = MyPyPoint3(p1.x, p1.y) + pynorm = math.sqrt(p2.x**2 + p2.y**2) + + for norm in [ns.norm_cr, ns.norm_r, ns.norm_v, ns.norm_p]: + with raises(TypeError): + norm(p1) + + assert round(norm(p2) - pynorm, 8) == 0 + + for norm in [ns.norm_cr, ns.norm_m, ns.norm_v]: + assert round(norm(p3) - pynorm, 8) == 0 + + +class TestQUALIFIEDLOOKUP: + def test01_qualified_name_in_namespace(self): + """A qualified name resolves relative to a namespace""" + + import cppjit + + cppjit.cppdef("""\ + namespace QualLookup { + namespace Inner { struct Thing { int v = 7; }; } + struct Outer { struct Nested { int w = 9; }; }; + }""") + + ns = cppjit.gbl.QualLookup + + assert getattr(ns, "Inner::Thing")().v == 7 + assert getattr(ns, "Outer::Nested")().w == 9 diff --git a/bindings/pyroot/cppjit/test/test_api.py b/bindings/pyroot/cppjit/test/test_api.py new file mode 100644 index 0000000000000..f6f49262ba9a5 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_api.py @@ -0,0 +1,231 @@ +from pytest import mark, skip +from support import IS_LINUX_ARM, IS_MAC, ispypy + + +class TestAPI: + def setup_class(cls): + if ispypy: + skip("C++ API only available on CPython") + + import cppjit + + cppjit.include("cpyrt/API.h") + + def test01_type_checking(self): + """Python class type checks""" + + import cppjit + + cpp = cppjit.gbl + API = cpp.cppjit.cpyrt + + cppjit.cppdef(""" + class APICheck { + public: + void some_method() {} + };""") + + assert API.Scope_Check(cpp.APICheck) + assert not API.Scope_CheckExact(cpp.APICheck) + + a = cpp.APICheck() + assert API.Instance_Check(a) + assert not API.Instance_CheckExact(a) + + m = a.some_method + assert API.Overload_Check(m) + assert API.Overload_CheckExact(m) + + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") + def test02_interpreter_access(self): + """Access to the python interpreter""" + + import cppjit + + API = cppjit.gbl.cppjit.cpyrt + + assert API.Exec("import sys") + + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") + def test03_instance_conversion(self): + """Proxy object conversions""" + + import cppjit + + cpp = cppjit.gbl + API = cpp.cppjit.cpyrt + + cppjit.cppdef(""" + class APICheck2 { + public: + virtual ~APICheck2() {} + };""") + + m = cpp.APICheck2() + + voidp = API.Instance_AsVoidPtr(m) + m2 = API.Instance_FromVoidPtr(voidp, "APICheck2") + assert m is m2 + + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") + def test04_custom_converter(self): + """Custom type converter""" + + import cppjit + + cppjit.cppdef(""" + #include "cpyrt/API.h" + + class APICheck3 { + int fFlags; + public: + APICheck3() : fFlags(0) {} + virtual ~APICheck3() {} + + void setSetArgCalled() { fFlags |= 0x01; } + bool wasSetArgCalled() { return fFlags & 0x01; } + void setFromMemoryCalled() { fFlags |= 0x02; } + bool wasFromMemoryCalled() { return fFlags & 0x02; } + void setToMemoryCalled() { fFlags |= 0x04; } + bool wasToMemoryCalled() { return fFlags & 0x04; } + }; + + class APICheck3Converter : public cppjit::cpyrt::Converter { + public: + virtual bool SetArg(PyObject* pyobject, cppjit::cpyrt::Parameter& para, cppjit::cpyrt::CallContext* = nullptr) { + APICheck3* a3 = (APICheck3*)cppjit::cpyrt::Instance_AsVoidPtr(pyobject); + a3->setSetArgCalled(); + para.fValue.fVoidp = a3; + para.fTypeCode = 'V'; + return true; + } + + virtual PyObject* FromMemory(void* address) { + APICheck3* a3 = (APICheck3*)address; + a3->setFromMemoryCalled(); + return cppjit::cpyrt::Instance_FromVoidPtr(a3, "APICheck3"); + } + + virtual bool ToMemory(PyObject* value, void* address) { + APICheck3* a3 = (APICheck3*)address; + a3->setToMemoryCalled(); + *a3 = *(APICheck3*)cppjit::cpyrt::Instance_AsVoidPtr(value); + return true; + } + }; + + typedef cppjit::cpyrt::ConverterFactory_t cf_t; + void register_a3() { + cppjit::cpyrt::RegisterConverter("APICheck3", (cf_t)+[](cppjit::cpyrt::cdims_t) { static APICheck3Converter c{}; return &c; }); + cppjit::cpyrt::RegisterConverter("APICheck3&", (cf_t)+[](cppjit::cpyrt::cdims_t) { static APICheck3Converter c{}; return &c; }); + } + void unregister_a3() { + cppjit::cpyrt::UnregisterConverter("APICheck3"); + cppjit::cpyrt::UnregisterConverter("APICheck3&"); + } + + APICheck3 gA3a, gA3b; + void CallWithAPICheck3(APICheck3&) {} + """) + + cppjit.gbl.register_a3() + + gA3a = cppjit.gbl.gA3a + assert gA3a + assert type(gA3a) == cppjit.gbl.APICheck3 + assert gA3a.wasFromMemoryCalled() + + assert not gA3a.wasSetArgCalled() + cppjit.gbl.CallWithAPICheck3(gA3a) + assert gA3a.wasSetArgCalled() + + cppjit.gbl.unregister_a3() + + gA3b = cppjit.gbl.gA3b + assert gA3b + assert type(gA3b) == cppjit.gbl.APICheck3 + assert not gA3b.wasFromMemoryCalled() + + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") + def test05_custom_executor(self): + """Custom type executor""" + + import cppjit + + cppjit.cppdef(""" + #include "cpyrt/API.h" + + class APICheck4 { + int fFlags; + public: + APICheck4() : fFlags(0) {} + virtual ~APICheck4() {} + + void setExecutorCalled() { fFlags |= 0x01; } + bool wasExecutorCalled() { return fFlags & 0x01; } + }; + + class APICheck4Executor : public cppjit::cpyrt::Executor { + public: + virtual PyObject* Execute(cppjit::interop::TCppMethod_t meth, cppjit::interop::TCppObject_t obj, cppjit::cpyrt::CallContext* ctxt) { + APICheck4* a4 = (APICheck4*)cppjit::cpyrt::CallVoidP(meth, obj, ctxt); + a4->setExecutorCalled(); + return cppjit::cpyrt::Instance_FromVoidPtr(a4, "APICheck4", true); + } + }; + + typedef cppjit::cpyrt::ExecutorFactory_t ef_t; + void register_a4() { + cppjit::cpyrt::RegisterExecutor("APICheck4*", (ef_t)+[](cppjit::cpyrt::cdims_t) { static APICheck4Executor c{}; return &c; }); + } + void unregister_a4() { + cppjit::cpyrt::UnregisterExecutor("APICheck4*"); + } + + APICheck4* CreateAPICheck4() { return new APICheck4{}; } + APICheck4* CreateAPICheck4b() { return new APICheck4{}; } + """) + + cppjit.gbl.register_a4() + + a4 = cppjit.gbl.CreateAPICheck4() + assert a4 + assert type(a4) == cppjit.gbl.APICheck4 + assert a4.wasExecutorCalled() + del a4 + + cppjit.gbl.unregister_a4() + + a4 = cppjit.gbl.CreateAPICheck4b() + assert a4 + assert type(a4) == cppjit.gbl.APICheck4 + assert not a4.wasExecutorCalled() + + def test06_custom_executor(self): + """Custom type executor""" + + import cppjit + + cppjit.cppdef(""" + #include "cpyrt/API.h" + + namespace ArrayLike { + class MyClass{}; + MyClass* my = nullptr; + MyClass myA[5]; + + class MyArray { + public: + int operator[](int) { return 42; } + }; }""") + + ns = cppjit.gbl.ArrayLike + Sequence_Check = cppjit.gbl.cppjit.cpyrt.Sequence_Check + + assert not Sequence_Check(ns.my) + assert Sequence_Check(ns.myA) + assert not Sequence_Check(ns.MyClass()) + assert Sequence_Check(ns.MyArray()) + assert Sequence_Check(tuple()) + assert Sequence_Check(cppjit.gbl.std.vector[ns.MyClass]()) + assert not Sequence_Check(cppjit.gbl.std.list[ns.MyClass]()) diff --git a/bindings/pyroot/cppjit/test/test_basic_api.py b/bindings/pyroot/cppjit/test/test_basic_api.py new file mode 100644 index 0000000000000..c6f9402679014 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_basic_api.py @@ -0,0 +1,94 @@ +import os +import shutil +import tempfile + +import py +from pytest import mark, raises +from support import needs_dictionary, setup_make, soext + +# reuse the example01 +currpath = py.path.local(__file__).dirpath() +test_dct = "example01_cxx" + + +def setup_module(mod): + # only test03_add_library_path loads the dictionary + setup_make("example01", optional=True) + + +class TestBASICAPI: + def test01_evaluate(self): + import cppjit + + # we use evaluate as a way to set these flags in support, for example + # for IS_CLANG_REPL, etc. let's make sure basic uses work + out = cppjit.evaluate("""#define TEST01_EVALUATE + #ifdef TEST01_EVALUATE + true + #else + false + #endif""") + assert out + + cppjit.evaluate("__cplusplus") >= 201100 + + x = 42 + assert cppjit.evaluate(str(x)) == x + + def test02_cppdef(self): + import cppjit + + assert cppjit.cppdef("namespace test02_NS { int x = 42; }") + assert cppjit.gbl.test02_NS.x == 42 + + @needs_dictionary + def test03_add_library_path(self): + import cppjit + + with raises(OSError, match="No such directory"): + cppjit.add_library_path("not/a/real/path") + + # once loaded, the copied library is mapped into the process; Windows + # then refuses to delete it, which would fail the cleanup + with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tpath: + cppjit.add_library_path(tpath) + + # now we should actually see if load library can follow this... + # first, try to load without moving to directory... + with raises(RuntimeError, match="Unable to load library"): + cppjit.load_library("test" + soext) + + # then copy to our rpath, and make sure it can be loaded now + shutil.copyfile(test_dct + soext, tpath + "/test" + soext) + cppjit.load_library("test" + soext) + + @mark.xfail(reason="ROOT loads through gSystem.Load and its message carries no loader reason") + def test03a_load_library_failure_reason(self): + """load_library reports the loader's failure reason""" + + import cppjit + + with tempfile.TemporaryDirectory() as tpath: + missing = os.path.join(tpath, "libdlerrmissing.so") + with raises(RuntimeError, match="libdlerrmissing.*library not found"): + cppjit.load_library(missing) + + # a truncated ELF header: found on disk, rejected before dlopen + invalid = os.path.join(tpath, "libdlerrinvalid.so") + with open(invalid, "wb") as out: + out.write(b"\x7fELF" + b"\0" * 12) + with raises(RuntimeError, match="libdlerrinvalid"): + cppjit.load_library(invalid) + + def test04_add_include_path(self): + import cppjit + + with tempfile.TemporaryDirectory() as tpath: + cppjit.add_include_path(tpath) + + header = "test_add_include_path.h" + with open(tpath + "/" + header, "w") as out: + print("int test04f() { return 42; }", file=out) + + assert cppjit.include(header) + assert cppjit.gbl.test04f() == 42 diff --git a/bindings/pyroot/cppjit/test/test_boost.py b/bindings/pyroot/cppjit/test/test_boost.py new file mode 100644 index 0000000000000..157c066ffb448 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_boost.py @@ -0,0 +1,213 @@ +import os + +from pytest import mark, raises, skip +from support import IS_MAC_ARM, IS_MAC_X86, IS_VALGRIND + +# /usr/include and /usr/local/include are on the compiler's default search +# path; the Homebrew (arm64) and MacPorts prefixes are not, so a hit there +# is remembered and added explicitly before the first include. +boost_extra_inc = None +noboost = not ( + os.path.exists(os.path.join(os.path.sep, "usr", "include", "boost")) + or os.path.exists(os.path.join(os.path.sep, "usr", "local", "include", "boost")) +) +if noboost: + for p in ( + os.path.join(os.path.sep, "opt", "homebrew", "include"), + os.path.join(os.path.sep, "opt", "local", "include"), + ): + if os.path.exists(os.path.join(p, "boost")): + boost_extra_inc = p + noboost = False + break + + +def add_boost_include_path(): + if boost_extra_inc is not None: + import cppjit + + cppjit.add_include_path(boost_extra_inc) + + +@mark.skipif(noboost == True, reason="boost not found") +class TestBOOSTANY: + def setup_class(cls): + import cppjit + + add_boost_include_path() + try: + cppjit.include("boost/any.hpp") + except ImportError: + skip("boost headers not loadable") + + @mark.skipif((IS_MAC_ARM or IS_MAC_X86), reason="Fails to include boost on OS X") + def test01_any_class(self): + """Availability of boost::any""" + + import cppjit + + assert cppjit.gbl.boost.any + + from cppjit.gbl import std + from cppjit.gbl.boost import any + + assert std.list[any] + + @mark.xfail( + condition=IS_VALGRIND, + run=False, + reason="invalid reads in the JITed boost constructors under valgrind", + ) + def test02_any_usage(self): + """boost::any assignment and casting""" + + import cppjit + + assert cppjit.gbl.boost + + from cppjit.gbl import boost, std + + val = boost.any() + # test both by-ref and by rvalue + v = std.vector[int]() + val.__assign__(v) + val.__assign__(std.move(std.vector[int](range(100)))) + assert val.type() == cppjit.typeid(std.vector[int]) + + extract = boost.any_cast[std.vector[int]](val) + assert type(extract) is std.vector[int] + assert len(extract) == 100 + extract += range(100) + assert len(extract) == 200 + + val.__assign__(std.move(extract)) # move forced + # assert len(extract) == 0 # not guaranteed by the standard + + # TODO: we hit boost::any_cast(boost::any* operand) instead + # of the reference version which raises + boost.any_cast.__useffi__ = False + try: + # raises(Exception, boost.any_cast[int], val) + assert not boost.any_cast[int](val) + except Exception: + # getting here is good, too ... + pass + + extract = boost.any_cast[std.vector[int]](val) + assert len(extract) == 200 + + +@mark.skipif(((noboost == True) or IS_MAC_ARM or IS_MAC_X86), reason="boost not found") +class TestBOOSTOPERATORS: + def setup_class(cls): + import cppjit + + add_boost_include_path() + cppjit.include("boost/operators.hpp") + + def test01_ordered(self): + """ordered_field_operators as base used to crash""" + + import cppjit + + try: + cppjit.include("gmpxx.h") + except ImportError: + skip("gmpxx not installed") + cppjit.cppdef(""" + namespace boost_test { + class Derived : boost::ordered_field_operators, boost::ordered_field_operators {}; + } + """) + + assert cppjit.gbl.boost_test.Derived + + +@mark.skipif(noboost == True, reason="boost not found") +class TestBOOSTVARIANT: + def setup_class(cls): + import cppjit + + add_boost_include_path() + try: + cppjit.include("boost/variant/variant.hpp") + cppjit.include("boost/variant/get.hpp") + except ImportError: + skip("boost headers not loadable") + + @mark.xfail( + condition=IS_VALGRIND, + run=False, + reason="invalid reads in the JITed boost constructors under valgrind", + ) + def test01_variant_usage(self): + """boost::variant usage""" + + # as posted on stackoverflow as example + import cppjit + + cpp = cppjit.gbl + std = cpp.std + boost = cpp.boost + + cppjit.cppdef("""namespace BV { + class A { }; + class B { }; + class C { }; } """) + + VariantType = boost.variant["BV::A, BV::B, BV::C"] + VariantTypeList = std.vector[VariantType] + + v = VariantTypeList() + + v.push_back(VariantType(cpp.BV.A())) + assert v.back().which() == 0 + v.push_back(VariantType(cpp.BV.B())) + assert v.back().which() == 1 + v.push_back(VariantType(cpp.BV.C())) + assert v.back().which() == 2 + + assert type(boost.get["BV::A"](v[0])) == cpp.BV.A + + # Trying to raise this exception seg faults, by trying to execute an unfit instantiation. + # This comes from `Instantiate` obtaining a single handle and providing a result + # The same issue happens with trying `BestOverloadFunctionMatch` first since the candidate set is single + raises(Exception, boost.get["BV::B"], v[0]) + assert type(boost.get["BV::B"](v[1])) == cpp.BV.B + assert type(boost.get["BV::C"](v[2])) == cpp.BV.C + + +@mark.skipif(((noboost == True) or IS_MAC_ARM or IS_MAC_X86), reason="boost not found") +class TestBOOSTERASURE: + def setup_class(cls): + import cppjit + + add_boost_include_path() + cppjit.include("boost/type_erasure/any.hpp") + cppjit.include("boost/type_erasure/member.hpp") + cppjit.include("boost/mpl/vector.hpp") + + def test01_erasure_usage(self): + """boost::type_erasure usage""" + + import cppjit + + cppjit.cppdef(""" + BOOST_TYPE_ERASURE_MEMBER((has_member_f), f, 0) + + using LengthsInterface = boost::mpl::vector< + boost::type_erasure::copy_constructible<>, + has_member_f() const>>; + + using Lengths = boost::type_erasure::any; + + struct Unerased { + std::vector f() const { return std::vector{}; } + }; + + Lengths lengths() { + return Unerased{}; + } + """) + + assert cppjit.gbl.lengths() is not None diff --git a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py b/bindings/pyroot/cppjit/test/test_concurrent.py similarity index 65% rename from bindings/pyroot/cppyy/cppyy/test/test_concurrent.py rename to bindings/pyroot/cppjit/test/test_concurrent.py index 748c8fae4533c..cae84025be112 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py +++ b/bindings/pyroot/cppjit/test/test_concurrent.py @@ -1,39 +1,32 @@ -import pytest -from pytest import raises, skip, mark -from support import IS_MAC_ARM, IS_WINDOWS +from pytest import mark, skip +from support import IS_LINUX_ARM, IS_MAC_ARM, IS_WINDOWS class TestCONCURRENT: - def setup_class(cls): - import cppyy + import cppjit cls.data = [3.1415, 2.7183, 1.4142, 1.3807, -9.2848] - cppyy.cppdef("""\ - // as recommended by: - // https://docs.python.org/3/c-api/intro.html#include-files - #define PY_SSIZE_T_CLEAN - #include "Python.h" - + cppjit.cppdef("""\ namespace Workers { double calc(double d) { return d*42.; } }""") - cppyy.gbl.Workers.calc.__release_gil__ = True + cppjit.gbl.Workers.calc.__release_gil__ = True - @mark.xfail(run=False, reason="Crashes because TClingCallFunc generates wrong code") def test01_simple_threads(self): """Run basic Python threads""" - import cppyy import threading - cppyy.gbl.Workers.calc.__release_gil__ = True + import cppjit + + cppjit.gbl.Workers.calc.__release_gil__ = True threads = [] for d in self.data: - threads.append(threading.Thread(target=cppyy.gbl.Workers.calc, args=(d,))) + threads.append(threading.Thread(target=cppjit.gbl.Workers.calc, args=(d,))) for t in threads: t.start() @@ -41,69 +34,75 @@ def test01_simple_threads(self): for t in threads: t.join() - @mark.xfail(run=False, reason="Crashes because the interpreter emits too many warnings") def test02_futures(self): """Run with Python futures""" - import cppyy + import cppjit try: import concurrent.futures except ImportError: skip("module concurrent is not installed") - total = 0. - with concurrent.futures.ThreadPoolExecutor(max_workers=len(self.data)) as executor: - futures = [executor.submit(cppyy.gbl.Workers.calc, d) for d in self.data] + total = 0.0 + with concurrent.futures.ThreadPoolExecutor( + max_workers=len(self.data) + ) as executor: + futures = [executor.submit(cppjit.gbl.Workers.calc, d) for d in self.data] for f in concurrent.futures.as_completed(futures): total += f.result() - assert round(total+26.4642, 8) == 0.0 + assert round(total + 26.4642, 8) == 0.0 def test03_timeout(self): """Time-out with threads""" return - import cppyy - import threading, time + import threading + import time - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace test12_timeout { bool _islive = false; volatile bool* islive = &_islive; bool _stopit = false; volatile bool* stopit = &_stopit; }""") - cppyy.gbl.gInterpreter.ProcessLine.__release_gil__ = True + cppjit.gbl.cling.runtime.gCling.process.__release_gil__ = True cmd = r"""\ *test12_timeout::islive = true; while (!*test12_timeout::stopit); """ - t = threading.Thread(target=cppyy.gbl.gInterpreter.ProcessLine, args=(cmd,)) + t = threading.Thread( + target=cppjit.gbl.cling.runtime.gCling.process, args=(cmd,) + ) t.start() - # have to give ProcessLine() time to actually start doing work - while not cppyy.gbl.test12_timeout.islive: - time.sleep(0.1) # in seconds + # have to give ProcessLine() time to actually start doing work + while not cppjit.gbl.test12_timeout.islive: + time.sleep(0.1) # in seconds - # join the thread with a timeout after 0.1s - t.join(0.1) # id. + # join the thread with a timeout after 0.1s + t.join(0.1) # id. - if t.is_alive(): # was timed-out - cppyy.gbl.test12_timeout.stopit[0] = True + if t.is_alive(): # was timed-out + cppjit.gbl.test12_timeout.stopit[0] = True - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test04_cpp_threading_with_exceptions(self): """Threads and Python exceptions""" if IS_MAC_ARM: skip("JIT exceptions can not be caught in JITed code on Mac ARM") - import cppyy + import cppjit - cppyy.include("CPyCppyy/PyException.h") + cppjit.include("cpyrt/PyException.h") - cppyy.cppdef("""namespace thread_test { + cppjit.cppdef(""" #include + namespace thread_test { struct consumer { virtual ~consumer() {} @@ -111,7 +110,7 @@ def test04_cpp_threading_with_exceptions(self): }; struct worker { - worker(consumer* c) : cons(c) {} + worker(consumer* c) : cons(c) { } ~worker() { wait(); } void start() { @@ -120,7 +119,7 @@ def test04_cpp_threading_with_exceptions(self): while (counter++ < 10) try { cons->process(counter); - } catch (CPyCppyy::PyException& e) { + } catch (cppjit::cpyrt::PyException& e) { err_msg = e.what(); return; } @@ -137,13 +136,14 @@ def test04_cpp_threading_with_exceptions(self): std::string err_msg; }; }""") - ns = cppyy.gbl.thread_test + ns = cppjit.gbl.thread_test consumer = ns.consumer worker = ns.worker worker.wait.__release_gil__ = True class C(consumer): count = 0 + def process(self, c): self.count += 1 @@ -158,6 +158,7 @@ def process(self, c): class C(consumer): count = 0 + def process(self, c): raise RuntimeError("all wrong") @@ -168,16 +169,17 @@ def process(self, c): w.wait() assert "RuntimeError" in w.err_msg - assert "all wrong" in w.err_msg + assert "all wrong" in w.err_msg + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test05_float2d_callback(self): """Passing of 2-dim float arguments""" - import cppyy + import cppjit - cppyy.cppdef("""\ - namespace FloatDim2 { + cppjit.cppdef("""\ #include + namespace FloatDim2 { struct Buffer { Buffer() = default; @@ -218,10 +220,10 @@ def test05_float2d_callback(self): t.join(); } }""") - cppyy.gbl.FloatDim2.callback.__release_gil__ = True + cppjit.gbl.FloatDim2.callback.__release_gil__ = True - class Processor(cppyy.gbl.FloatDim2.Processor): - buffer = cppyy.gbl.FloatDim2.Buffer() + class Processor(cppjit.gbl.FloatDim2.Processor): + buffer = cppjit.gbl.FloatDim2.Buffer() def process(self, data, channels, samples): self.buffer.setData(data) @@ -229,20 +231,21 @@ def process(self, data, channels, samples): try: for c in range(channels): for s in range(samples): - self.buffer.setSample(c, s, 0.0) # < used to crash here + self.buffer.setSample(c, s, 0.0) # < used to crash here except Exception as e: print(e) p = Processor() - cppyy.gbl.FloatDim2.callback(p) + cppjit.gbl.FloatDim2.callback(p) def test06_overload_reuse_in_threads(self): """Threads reuse overload objects; check for clashes""" - import cppyy import threading - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace CPPOverloadReuse { class Simulation1 { public: @@ -250,8 +253,8 @@ class Simulation1 { }; }""") def test(): - o = {"a": "b"} # causes a temporary to be created - simulation = cppyy.gbl.CPPOverloadReuse.Simulation1() + o = {"a": "b"} # causes a temporary to be created + simulation = cppjit.gbl.CPPOverloadReuse.Simulation1() simulation.set_something(o, ".") threads = [threading.Thread(target=test) for i in range(0, 100)] @@ -262,18 +265,18 @@ def test(): for t in threads: t.join() - @mark.xfail(run=False, reason="segmentation violation") def test07_overload_reuse_in_threads_wo_gil(self): """Threads reuse overload objects; check for clashes if no GIL""" if IS_MAC_ARM: - # the culprit here is occasional std::system_error if a thread can not be joined + # the culprit here is occasional std::system_error if a thread can not be joined skip("JIT exceptions can not be caught in JITed code on Mac ARM") - import cppyy import threading - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace CPPOverloadReuse { class Simulation2 { int fCount; @@ -282,16 +285,16 @@ class Simulation2 { virtual int do_something() { return fCount; } }; }""") - cppyy.gbl.CPPOverloadReuse.Simulation2.do_something.__release_gil__ = True + cppjit.gbl.CPPOverloadReuse.Simulation2.do_something.__release_gil__ = True class State(object): lock = threading.Lock() c1, c2, c3 = 0, 0, 0 def test(i, state=State): - #global c1, c2, c3, lock + # global c1, c2, c3, lock - simulation = cppyy.gbl.CPPOverloadReuse.Simulation2(i) + simulation = cppjit.gbl.CPPOverloadReuse.Simulation2(i) res = simulation.do_something() with state.lock: @@ -310,6 +313,22 @@ def test(i, state=State): assert State.c1 == 1000 assert State.c2 == State.c3 + def test08_python_callabcks_with_release_gil(self): + """Test release GIL, when the C++ func calls back into Python""" + import cppjit + from cppjit import gbl + + cppjit.cppdef(r""" + namespace NS1 { + int callback(int(*fn)(int, int), int x, int y) { + return fn(x, y); + } + }; + """) + + def add(x: int, y: int) -> int: + return x + y -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) + callback = gbl.NS1.callback + callback.__release_gil__ = True + assert callback(add, 1, 2) == 3 diff --git a/bindings/pyroot/cppjit/test/test_conversions.py b/bindings/pyroot/cppjit/test/test_conversions.py new file mode 100644 index 0000000000000..803d48b5cfc0b --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_conversions.py @@ -0,0 +1,205 @@ +import py +from pytest import mark, raises +from support import IS_CLANG_REPL, IS_CLING, IS_MAC, setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = "conversions_cxx" + + +def setup_module(mod): + setup_make("conversions") + + +class TestCONVERSIONS: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.conversion = cppjit.load_reflection_info(cls.test_dct) + + def test01_implicit_vector_conversions(self): + """Test implicit conversions of std::vector""" + + import cppjit + + CNS = cppjit.gbl.CNS + + N = 10 + total = float(sum(range(N))) + + v = cppjit.gbl.std.vector["double"](range(N)) + assert CNS.sumit(v) == total + assert sum(v) == total + assert CNS.sumit(range(N)) == total + + M = 5 + total = float(sum(range(N)) + sum(range(M, N))) + v1 = cppjit.gbl.std.vector["double"](range(N)) + v2 = cppjit.gbl.std.vector["double"](range(M, N)) + assert CNS.sumit(v1, v2) == total + assert sum(v1) + sum(v2) == total + assert CNS.sumit(v1, range(M, N)) == total + assert CNS.sumit(range(N), v2) == total + assert CNS.sumit(range(N), range(M, N)) == total + + def test02_memory_handling_of_temporaries(self): + """Verify that memory of temporaries is properly cleaned up""" + + import gc + + import cppjit + + CNS, CC = cppjit.gbl.CNS, cppjit.gbl.CNS.Counter + + assert CC.s_count == 0 + c = CC() + assert c.__python_owns__ + assert CC.s_count == 1 + del c + gc.collect() + assert CC.s_count == 0 + + assert CNS.myhowmany((CC(), CC(), CC())) == 3 + gc.collect() + assert CC.s_count == 0 + + assert CNS.myhowmany((CC(), CC(), CC()), [CC(), CC()]) == 5 + gc.collect() + assert CC.s_count == 0 + + def test03_error_handling(self): + """Verify error handling""" + + import gc + + import cppjit + + CNS, CC = cppjit.gbl.CNS, cppjit.gbl.CNS.Counter + + N = 13 + total = sum(range(N)) + assert CNS.sumints(range(N)) == total + assert CNS.sumit([float(x) for x in range(N)]) == float(total) + raises(TypeError, CNS.sumints, [float(x) for x in range(N)]) + raises(TypeError, CNS.sumints, list(range(N)) + [0.0]) + + assert CC.s_count == 0 + + raises(TypeError, CNS.sumints, list(range(N)) + [CC()]) + gc.collect() + assert CC.s_count == 0 + + raises(TypeError, CNS.sumints, range(N), [CC()]) + gc.collect() + + assert CC.s_count == 0 + raises(TypeError, CNS.sumints, [CC()], range(N)) + gc.collect() + assert CC.s_count == 0 + + @mark.xfail( + condition=IS_MAC or IS_CLING, run=IS_CLANG_REPL, reason="Crashes on Cling" + ) + def test04_implicit_conversion_from_tuple(self): + """Allow implicit conversions from tuples as arguments {}-like""" + + # Note: fails on windows b/c the assignment operator for strings is + # template, which ("operator=(std::string)") doesn't instantiate + import cppjit + + m = cppjit.gbl.std.map[str, str]() + m.insert(("a", "b")) # implicit conversion to std::pair + + assert m["a"] == "b" + + def test05_bool_conversions(self): + """Test operator bool() and null pointer behavior""" + + import cppjit + + cppjit.cppdef("""\ + namespace BoolConversions { + struct Test1 {}; + struct Test2 { + Test2(bool b) : m_b(b) {} + explicit operator bool() const { return m_b; } + bool m_b; + }; + + Test1* CreateNullTest1() { return nullptr; } + Test2* CreateNullTest2() { return nullptr; } + }""") + + ns = cppjit.gbl.BoolConversions + + for t in [ns.CreateNullTest1(), ns.CreateNullTest2()]: + assert not t + + assert ns.Test1() + assert ns.Test2(True) + assert not ns.Test2(False) + + def test06_pointer_const_reference(self): + """A T* const& argument is accepted; only T*& is banned""" + + import cppjit + + cppjit.cppdef("""\ + namespace PtrConstRef { + struct A { int v = 3; }; + int by_const_ref(A* const& a) { return a->v; } + void by_mutable_ref(A*& a) { a = nullptr; } + }""") + + ns = cppjit.gbl.PtrConstRef + a = ns.A() + + # the callee cannot rebind the pointer, so this is safe to pass + assert ns.by_const_ref(a) == 3 + + # a mutable pointer reference stays unsupported + with raises(TypeError): + ns.by_mutable_ref(a) + + def test07_mutable_voidp_reference(self): + """An object can be passed through a non-const void*& argument""" + + import cppjit + + cppjit.cppdef("""\ + namespace VoidPtrRef { + struct Obj { int v = 5; }; + bool is_same(void*& p, Obj* o) { return p == (void*)o; } + }""") + + ns = cppjit.gbl.VoidPtrRef + o = ns.Obj() + + assert ns.is_same(o, o) + + +class TestSMARTPTRPOLICY: + def test01_implicit_smartptr_conversion_policy(self): + """Wrapping an object into a smart-pointer argument is off by default""" + + import cppjit + from pytest import raises + + cppjit.cppdef("""\ + #include + namespace SPPolicy { + struct Payload { int x = 5; }; + int take(std::shared_ptr p) { return p ? p->x : -1; } + }""") + + obj = cppjit.gbl.SPPolicy.Payload() + with raises(TypeError): + cppjit.gbl.SPPolicy.take(obj) + + # a smart pointer passes regardless of the policy + sp = cppjit.gbl.std.make_shared["SPPolicy::Payload"]() + assert cppjit.gbl.SPPolicy.take(sp) == 5 + + # the toggle is exposed and returns the prior setting + assert cppjit.libcppjit.SetImplicitSmartPointerConversion(True) is False + assert cppjit.libcppjit.SetImplicitSmartPointerConversion(False) is True diff --git a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py b/bindings/pyroot/cppjit/test/test_cpp11features.py similarity index 61% rename from bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py rename to bindings/pyroot/cppjit/test/test_cpp11features.py index f143501bb7f27..0a475572bae85 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py +++ b/bindings/pyroot/cppjit/test/test_cpp11features.py @@ -1,24 +1,43 @@ -import sys, pytest, os +import sys + +import py from pytest import mark, raises -from support import setup_make, ispypy +from support import ( + IS_LINUX, + IS_LINUX_ARM, + IS_MAC, + IS_VALGRIND, + ispypy, + setup_make, +) + +currpath = py.path.local(__file__).dirpath() +test_dct = "cpp11features_cxx" -test_dct = "cpp11features_cxx" +def setup_module(mod): + setup_make("cpp11features") class TestCPP11FEATURES: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.cpp11features = cppyy.load_reflection_info(cls.test_dct) + import cppjit + cls.cpp11features = cppjit.load_reflection_info(cls.test_dct) + + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test01_smart_ptr(self): """Usage and access of std::shared/unique_ptr<>""" - from cppyy.gbl import TestSmartPtr - from cppyy.gbl import create_shared_ptr_instance, create_unique_ptr_instance import gc + from cppjit.gbl import ( + TestSmartPtr, + create_shared_ptr_instance, + create_unique_ptr_instance, + ) + for cf in [create_shared_ptr_instance, create_unique_ptr_instance]: assert TestSmartPtr.s_counter == 0 @@ -40,12 +59,18 @@ def test01_smart_ptr(self): gc.collect() assert TestSmartPtr.s_counter == 0 + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Valgrind issues on ARM", + ) def test02_smart_ptr_construction(self): """Shared/Unique pointer ctor is templated, requiring special care""" - from cppyy.gbl import std, TestSmartPtr import gc + from cppjit.gbl import TestSmartPtr, std + class C(TestSmartPtr): pass @@ -67,12 +92,14 @@ class C(TestSmartPtr): gc.collect() assert TestSmartPtr.s_counter == 0 + @mark.xfail(condition=IS_LINUX and IS_VALGRIND, run=False, reason="Valgrind issue") def test03_smart_ptr_memory_handling(self): """Test shared/unique pointer memory ownership""" - from cppyy.gbl import std, TestSmartPtr import gc + from cppjit.gbl import TestSmartPtr, std + class C(TestSmartPtr): pass @@ -97,13 +124,21 @@ class C(TestSmartPtr): gc.collect() assert TestSmartPtr.s_counter == 0 + @mark.xfail(condition=IS_VALGRIND, run=False, reason="Crashes on Valgrind") def test04_shared_ptr_passing(self): """Ability to pass shared_ptr through shared_ptr""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import pass_shared_ptr, move_shared_ptr, create_TestSmartPtr_by_value import gc + from cppjit.gbl import ( + DerivedTestSmartPtr, + TestSmartPtr, + create_TestSmartPtr_by_value, + move_shared_ptr, + pass_shared_ptr, + std, + ) + for ff, mv in [(pass_shared_ptr, lambda x: x), (move_shared_ptr, std.move)]: assert TestSmartPtr.s_counter == 0 @@ -115,7 +150,7 @@ def test04_shared_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # ability to take over by-value python-owned objects + # ability to take over by-value python-owned objects tsp = create_TestSmartPtr_by_value() assert TestSmartPtr.s_counter == 1 assert tsp.__python_owns__ @@ -128,7 +163,7 @@ def test04_shared_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # alternative make_shared with type taken from pointer + # alternative make_shared with type taken from pointer tsp = create_TestSmartPtr_by_value() shared_stp = std.make_shared(tsp) assert TestSmartPtr.s_counter == 1 @@ -139,14 +174,20 @@ def test04_shared_ptr_passing(self): def test05_unique_ptr_passing(self): """Ability to pass unique_ptr through unique_ptr""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import move_unique_ptr, move_unique_ptr_derived - from cppyy.gbl import create_TestSmartPtr_by_value import gc + from cppjit.gbl import ( + DerivedTestSmartPtr, + TestSmartPtr, + create_TestSmartPtr_by_value, + move_unique_ptr, # noqa: F401 + move_unique_ptr_derived, + std, + ) + assert TestSmartPtr.s_counter == 0 - # move matching unique_ptr + # move matching unique_ptr dd = std.make_unique[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) assert TestSmartPtr.s_counter == 1 assert move_unique_ptr_derived(std.move(dd)) == 100 @@ -156,7 +197,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # move with conversion + # move with conversion dd = std.make_unique[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) assert TestSmartPtr.s_counter == 1 # TODO: why does the following fail, but succeed for shared_ptr?? @@ -167,7 +208,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # ability to take over by-value python-owned objects + # ability to take over by-value python-owned objects tsp = create_TestSmartPtr_by_value() assert TestSmartPtr.s_counter == 1 assert tsp.__python_owns__ @@ -180,7 +221,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # alternative make_unique with type taken from pointer + # alternative make_unique with type taken from pointer tsp = create_TestSmartPtr_by_value() unique_stp = std.make_unique(tsp) assert TestSmartPtr.s_counter == 1 @@ -192,28 +233,32 @@ def test05_unique_ptr_passing(self): def test06_nullptr(self): """Allow the programmer to pass NULL in certain cases""" - import cppyy + import cppjit - # test existence - nullptr = cppyy.nullptr - # assert not hasattr(cppyy.gbl, 'nullptr') + # test existence + nullptr = cppjit.nullptr + # assert not hasattr(cppjit.gbl, 'nullptr') - assert cppyy.bind_object(cppyy.nullptr, 'std::vector') == cppyy.nullptr - assert not cppyy.bind_object(cppyy.nullptr, 'std::vector') != cppyy.nullptr + assert cppjit.bind_object(cppjit.nullptr, "std::vector") == cppjit.nullptr + assert ( + not cppjit.bind_object(cppjit.nullptr, "std::vector") != cppjit.nullptr + ) - # further usage is tested in datatypes.py:test15_nullptr_passing + # further usage is tested in datatypes.py:test15_nullptr_passing def test07_move(self): """Move construction, assignment, and methods""" - import cppyy, gc + import gc + + import cppjit def moveit(T): assert T.s_instance_counter == 0 - from cppyy.gbl import std + from cppjit.gbl import std - # move constructor + # move constructor i1 = T() assert T.s_move_counter == 0 @@ -221,23 +266,23 @@ def moveit(T): assert T.s_move_counter == 0 if ispypy or 0x3000000 <= sys.hexversion: - i3 = T(std.move(T())) # can't check ref-count + i3 = T(std.move(T())) # can't check ref-count else: - i3 = T(T()) # should call move, not memoized cctor + i3 = T(T()) # should call move, not memoized cctor assert T.s_move_counter == 1 - i3 = T(std.move(T())) # both move and ref-count + i3 = T(std.move(T())) # both move and ref-count assert T.s_move_counter == 2 i4 = T(std.move(i1)) assert T.s_move_counter == 3 - # move assignment + # move assignment i4.__assign__(i2) assert T.s_move_counter == 3 if ispypy or 0x3000000 <= sys.hexversion: - i4.__assign__(std.move(T())) # can't check ref-count + i4.__assign__(std.move(T())) # can't check ref-count else: i4.__assign__(T()) assert T.s_move_counter == 4 @@ -249,32 +294,33 @@ def moveit(T): gc.collect() assert T.s_instance_counter == 0 - # order of moving and normal functions are reversed in 1, 2, for - # overload resolution testing - moveit(cppyy.gbl.TestMoving1) - moveit(cppyy.gbl.TestMoving2) + # order of moving and normal functions are reversed in 1, 2, for + # overload resolution testing + moveit(cppjit.gbl.TestMoving1) + moveit(cppjit.gbl.TestMoving2) - # implicit conversion and move - assert cppyy.gbl.TestMoving1.s_instance_counter == 0 - assert cppyy.gbl.TestMoving2.s_instance_counter == 0 - cppyy.gbl.implicit_converion_move(cppyy.gbl.TestMoving1()) - cppyy.gbl.implicit_converion_move(cppyy.gbl.TestMoving2()) + # implicit conversion and move + assert cppjit.gbl.TestMoving1.s_instance_counter == 0 + assert cppjit.gbl.TestMoving2.s_instance_counter == 0 + cppjit.gbl.implicit_converion_move(cppjit.gbl.TestMoving1()) + cppjit.gbl.implicit_converion_move(cppjit.gbl.TestMoving2()) gc.collect() - assert cppyy.gbl.TestMoving1.s_instance_counter == 0 - assert cppyy.gbl.TestMoving2.s_instance_counter == 0 + assert cppjit.gbl.TestMoving1.s_instance_counter == 0 + assert cppjit.gbl.TestMoving2.s_instance_counter == 0 + @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test08_initializer_list(self): """Initializer list construction""" - from cppyy.gbl import std, TestData, TestData2, WithInitList + from cppjit.gbl import TestData, TestData2, WithInitList, std v = std.vector[int]((1, 2, 3, 4)) assert list(v) == [1, 2, 3, 4] - v = std.vector['double']((1, 2, 3, 4)) - assert list(v) == [1., 2., 3., 4.] + v = std.vector["double"]((1, 2, 3, 4)) + assert list(v) == [1.0, 2.0, 3.0, 4.0] - raises(TypeError, std.vector[int], [1., 2., 3., 4.]) + raises(TypeError, std.vector[int], [1.0, 2.0, 3.0, 4.0]) for cls in [std.vector, WithInitList]: for cls_arg in [TestData, TestData2]: @@ -287,82 +333,82 @@ def test08_initializer_list(self): for i in range(len(l)): assert v[i].m_int == l[i].m_int - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace InitializerListTest { std::vector foo(const std::initializer_list& vals) { return std::vector{vals}; } }""") - ns = cppyy.gbl.InitializerListTest + ns = cppjit.gbl.InitializerListTest - for l in (['x'], ['x', 'y', 'z']): - assert ns.foo(l) == std.vector['std::string'](l) + for l in (["x"], ["x", "y", "z"]): + assert ns.foo(l) == std.vector["std::string"](l) - @mark.xfail(strict=True) def test09_lambda_calls(self): """Call (global) lambdas""" - import cppyy + import cppjit - cppyy.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") + cppjit.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") - assert cppyy.gbl.gMyLambda - assert cppyy.gbl.gMyLambda(2) == 42 - assert cppyy.gbl.gMyLambda(40) == 80 + assert cppjit.gbl.gMyLambda + assert cppjit.gbl.gMyLambda(2) == 42 + assert cppjit.gbl.gMyLambda(40) == 80 - cppyy.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") - l1 = cppyy.gbl.gime_a_lambda1() + cppjit.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") + l1 = cppjit.gbl.gime_a_lambda1() assert l1 assert l1() == 42 - cppyy.cppdef("auto gime_a_lambda2() { int a = 4; return [a](int b) { return 42+a+b; }; }") - l2 = cppyy.gbl.gime_a_lambda2() + cppjit.cppdef( + "auto gime_a_lambda2() { int a = 4; return [a](int b) { return 42+a+b; }; }" + ) + l2 = cppjit.gbl.gime_a_lambda2() assert l2 assert l2(2) == 48 - cppyy.cppdef("auto gime_a_lambda3(int a ) { return [a](int b) { return 42+a+b; }; }") - l3 = cppyy.gbl.gime_a_lambda3(4) + cppjit.cppdef( + "auto gime_a_lambda3(int a ) { return [a](int b) { return 42+a+b; }; }" + ) + l3 = cppjit.gbl.gime_a_lambda3(4) assert l3 assert l3(2) == 48 def test10_optional(self): """Use of optional and nullopt""" - import cppyy + import cppjit - assert cppyy.gbl.std.optional - assert cppyy.gbl.std.nullopt + assert cppjit.gbl.std.optional + assert cppjit.gbl.std.nullopt - cppyy.cppdef(""" + cppjit.cppdef(""" enum Enum { A = -1 }; bool callopt(std::optional) { return true; } """) - a = cppyy.gbl.std.optional[cppyy.gbl.Enum]() - assert cppyy.gbl.callopt(a) + a = cppjit.gbl.std.optional[cppjit.gbl.Enum]() + assert cppjit.gbl.callopt(a) - c = cppyy.gbl.std.nullopt - assert cppyy.gbl.callopt(c) + c = cppjit.gbl.std.nullopt + assert cppjit.gbl.callopt(c) - @mark.xfail(run=False, reason = "Crashes") def test11_chrono(self): """Use of chrono and overloaded operator+""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std t = std.chrono.system_clock.now() - std.chrono.seconds(1) # following used to fail with compilation error t = std.chrono.system_clock.now() + std.chrono.seconds(1) - @mark.xfail(strict=True) def test12_stdfunction(self): """Use of std::function with arguments in a namespace""" - import cppyy - from cppyy.gbl import FunctionNS, FNTestStruct, FNCreateTestStructFunc + import cppjit + from cppjit.gbl import FNCreateTestStructFunc, FNTestStruct, FunctionNS t = FNTestStruct(42) f = FNCreateTestStructFunc() @@ -372,13 +418,13 @@ def test12_stdfunction(self): f = FunctionNS.FNCreateTestStructFunc() assert f(t) == 13 - # and for good measure, inline - cppyy.cppdef("""namespace FunctionNS2 { + # and for good measure, inline + cppjit.cppdef("""namespace FunctionNS2 { struct FNTestStruct { FNTestStruct(int i) : t(i) {} int t; }; std::function FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } }""") - from cppyy.gbl import FunctionNS2 + from cppjit.gbl import FunctionNS2 # noqa: F401 t = FunctionNS.FNTestStruct(27) f = FunctionNS.FNCreateTestStructFunc() @@ -387,40 +433,34 @@ def test12_stdfunction(self): def test13_stdhash(self): """Use of std::hash""" - import cppyy - - cppyy.cppdef(""" - struct StructWithHash {}; // for std::hash<> testing - struct StructWithoutHash {}; + from cppjit.gbl import StructWithHash, StructWithoutHash - namespace std { - template<> - struct hash { - size_t operator()(const StructWithHash&) const { return 17; } - }; - } // namespace std - """) - - from cppyy.gbl import StructWithHash, StructWithoutHash - - for i in range(3): # to test effect of caching + for i in range(3): # to test effect of caching swo = StructWithoutHash() assert hash(swo) == object.__hash__(swo) assert hash(swo) == object.__hash__(swo) sw = StructWithHash() - assert hash(sw) == 17 - assert hash(sw) == 17 + assert hash(sw) == 17 + assert hash(sw) == 17 - @mark.xfail(strict=True) + @mark.xfail(reason="plain pointer does not convert to a shared_ptr argument") def test14_shared_ptr_passing(self): """Ability to pass normal pointers through shared_ptr by value""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import pass_shared_ptr import gc - for cls, val in [(lambda: TestSmartPtr(), 17), (lambda: DerivedTestSmartPtr(24), 100)]: + from cppjit.gbl import ( # noqa: F401 + DerivedTestSmartPtr, + TestSmartPtr, + pass_shared_ptr, + std, + ) + + for cls, val in [ + (lambda: TestSmartPtr(), 17), + (lambda: DerivedTestSmartPtr(24), 100), + ]: assert TestSmartPtr.s_counter == 0 obj = cls() @@ -432,36 +472,39 @@ def test14_shared_ptr_passing(self): assert obj.__python_owns__ assert TestSmartPtr.s_counter == 1 - assert not not obj # pass was by shared copy + assert not not obj # pass was by shared copy del obj gc.collect() assert TestSmartPtr.s_counter == 0 + @mark.xfail(condition=IS_MAC, reason="Fails for MacOS 26") def test15_unique_ptr_template_deduction(self): """Argument type deduction with std::unique_ptr""" - import cppyy + import cppjit - cppyy.cppdef("""namespace UniqueTempl { + cppjit.cppdef("""namespace UniqueTempl { template std::unique_ptr returnptr(std::unique_ptr&& a) { return std::move(a); } }""") - uptr_in = cppyy.gbl.std.make_unique[int]() - uptr_out = cppyy.gbl.UniqueTempl.returnptr["int"](cppyy.gbl.std.move(uptr_in)) + uptr_in = cppjit.gbl.std.make_unique[int]() + uptr_out = cppjit.gbl.UniqueTempl.returnptr["int"](cppjit.gbl.std.move(uptr_in)) assert not not uptr_out - uptr_in = cppyy.gbl.std.make_unique['int']() + uptr_in = cppjit.gbl.std.make_unique["int"]() with raises(ValueError): # not an RValue - cppyy.gbl.UniqueTempl.returnptr[int](uptr_in) + cppjit.gbl.UniqueTempl.returnptr[int](uptr_in) + @mark.xfail(condition=IS_MAC, reason="Fails on Mac platforms") def test16_unique_ptr_moves(self): """std::unique_ptr requires moves""" - import cppyy - cppyy.cppdef("""namespace unique_ptr_moves { + import cppjit + + cppjit.cppdef("""namespace unique_ptr_moves { template std::unique_ptr returnptr_value(std::unique_ptr a) { return std::move(a); @@ -471,12 +514,15 @@ def test16_unique_ptr_moves(self): return std::move(a); } }""") - up = cppyy.gbl.std.make_unique[int](42) + up = cppjit.gbl.std.make_unique[int](42) - ns = cppyy.gbl.unique_ptr_moves - up = ns.returnptr_value(up) ; assert up and up.get()[0] == 42 - up = ns.returnptr_value(cppyy.gbl.std.move(up)); assert up and up.get()[0] == 42 - up = ns.returnptr_move(cppyy.gbl.std.move(up)) ; assert up and up.get()[0] == 42 + ns = cppjit.gbl.unique_ptr_moves + up = ns.returnptr_value(up) + assert up and up.get()[0] == 42 + up = ns.returnptr_value(cppjit.gbl.std.move(up)) + assert up and up.get()[0] == 42 + up = ns.returnptr_move(cppjit.gbl.std.move(up)) + assert up and up.get()[0] == 42 with raises(TypeError): ns.returnptr_move(up) @@ -484,9 +530,9 @@ def test16_unique_ptr_moves(self): def test17_unique_ptr_data(self): """std::unique_ptr as data means implicitly no copy ctor""" - import cppyy + import cppjit - cppyy.cppdef("""namespace unique_ptr_data{ + cppjit.cppdef("""namespace unique_ptr_data{ class Example { private: std::unique_ptr x; @@ -496,19 +542,19 @@ class Example { double y = 66.; }; }""") - class Inherit(cppyy.gbl.unique_ptr_data.Example): + class Inherit(cppjit.gbl.unique_ptr_data.Example): pass a = Inherit() - # Test whether this attribute was inherited - assert a.y == 66. + # Test whether this attribute was inherited + assert a.y == 66.0 def test18_unique_ptr_identity(self): """std::unique_ptr identity preservation""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace UniqueIdentity { struct A { A(int _a) : a(_a) {} @@ -530,7 +576,7 @@ def test18_unique_ptr_identity(self): std::unique_ptr fPtr; }; }""") - ns = cppyy.gbl.UniqueIdentity + ns = cppjit.gbl.UniqueIdentity x = ns.create() assert x.a == 37 @@ -543,13 +589,17 @@ def test18_unique_ptr_identity(self): p2 = c.pget() assert p1 is p2 - @mark.xfail(strict=True) + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Valgrind issues on ARM", + ) def test19_smartptr_from_callback(self): """Return a smart pointer from a callback""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace SmartPtrCallback { struct Dummy { virtual ~Dummy() = default; @@ -565,38 +615,20 @@ def test19_smartptr_from_callback(self): return func(); }}""") - std = cppyy.gbl.std - ns = cppyy.gbl.SmartPtrCallback + std = cppjit.gbl.std + ns = cppjit.gbl.SmartPtrCallback def pyfunc() -> std.shared_ptr[ns.Dummy]: - return ns.dummy_create() + return ns.dummy_create() assert ns.call_creator(pyfunc) - def test20_tuple_element(self): - """ - Check that std::tuple_element works. - - See: https://github.com/root-project/root/issues/14232. - """ - - import cppyy - - cppyy.cppdef(""" - #include - #include - using ATuple = std::tuple; - """) - from cppyy.gbl import ATuple - - cppyy.gbl.std.tuple_element[1, ATuple].type - def test21_smart_ptr_downcast(self): """Object returned through a smart pointer is auto-downcast""" - import cppyy + import cppjit - gbl = cppyy.gbl + gbl = cppjit.gbl # unique_ptr holding a Derived comes back as Derived, with the # derived-only method callable, just like a raw pointer return @@ -604,7 +636,7 @@ def test21_smart_ptr_downcast(self): obj = cf() assert type(obj) == gbl.PubDerivedTestSmartPtr assert obj.only_in_derived() == 27 - assert obj.__smartptr__() # smart-pointer semantics preserved + assert obj.__smartptr__() # smart-pointer semantics preserved # an object that really is of the declared type stays that type obj = gbl.create_unique_ptr_instance() @@ -621,8 +653,16 @@ def test21_smart_ptr_downcast(self): # still embeds a smart pointer to the *base* type, which does not convert # to a smart pointer to the derived type (no implicit down-conversion of # smart pointers in C++), so passing it to such a sink must be rejected - raises(TypeError, gbl.pass_unique_ptr_to_derived, gbl.create_unique_ptr_to_derived()) - raises(TypeError, gbl.pass_shared_ptr_to_derived, gbl.create_shared_ptr_to_derived()) + raises( + TypeError, + gbl.pass_unique_ptr_to_derived, + gbl.create_unique_ptr_to_derived(), + ) + raises( + TypeError, + gbl.pass_shared_ptr_to_derived, + gbl.create_shared_ptr_to_derived(), + ) # passing it where the matching base smart pointer is expected still works assert gbl.pass_shared_ptr(gbl.create_shared_ptr_to_derived()) == 17 @@ -631,13 +671,27 @@ def test21_smart_ptr_downcast(self): # derived class should resolve to the downcasted type overload, # no matter if the Python proxy is a regular proxy or wraps a smart pointer # (should hold for pointer, reference, and value types) - assert gbl.pass_ptr_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ptr_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ref_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ref_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" - assert gbl.pass_val_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_val_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) + assert ( + gbl.pass_ptr_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ptr_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ref_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ref_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_val_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_val_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) diff --git a/bindings/pyroot/cppjit/test/test_cpp23features.py b/bindings/pyroot/cppjit/test/test_cpp23features.py new file mode 100644 index 0000000000000..683345ed48909 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_cpp23features.py @@ -0,0 +1,433 @@ +from pytest import mark +from support import IS_CPP23 + +# The interpreter is a process-wide singleton whose C++ standard is pinned at +# the first `import cppjit`, so these tests only run when the suite itself is +# under C++23 (the cxx-standard=23 CI cells). To run them locally (the env var +# reaches CreateInterpreter for both clang-repl and cling): +# +# CPPINTEROP_EXTRA_INTERPRETER_ARGS=-std=c++23 pytest -v test_cpp23features.py + + +@mark.skipif( + not IS_CPP23, + reason="C++23 tests need the interpreter to run under -std=c++23", +) +class TestCPP23FEATURES: + """C++23 features driven via cppjit.cppdef through the JIT (clang-repl/cling).""" + + @classmethod + def setup_class(cls): + import cppjit + + cls.cppjit = cppjit + + def test01_deducing_this_basic(self): + """Explicit object parameter on a member function (P0847R7)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct Widget { + int value = 42; + int get(this Widget& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.Widget() + assert w.value == 42 + assert w.get() == 42 + + def test02_deducing_this_const(self): + """const explicit object parameter (this const Widget& self)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct CWidget { + int value = 7; + int read(this const CWidget& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.CWidget() + assert w.read() == 7 + + def test03_deducing_this_by_value(self): + """by-value explicit object parameter (this Widget self)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct VWidget { + int value = 5; + int snapshot(this VWidget self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.VWidget() + w.value = 11 + assert w.snapshot() == 11 + + def test04_deducing_this_with_extra_args(self): + """explicit object parameter alongside regular arguments""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct AWidget { + int base = 100; + int add(this AWidget& self, int x, int y) { return self.base + x + y; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.AWidget() + assert w.add(20, 3) == 123 + + def test05_deducing_this_rvalue_ref(self): + """rvalue-ref-qualified explicit object parameter (this Widget&&)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct RWidget { + int value = 13; + int consume(this RWidget&& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.RWidget() + assert w.consume() == 13 + + def test05b_traditional_rvalue_ref_qualifier(self): + """traditional rvalue-ref-qualified member (int f() &&), no deducing this""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct QWidget { + int value = 17; + int consume() && { return value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.QWidget() + assert w.consume() == 17 + + def test06_deducing_this_chaining(self): + """builder-style chaining: explicit object parameter returns self""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct BWidget { + int value = 0; + BWidget& set(this BWidget& self, int v) { self.value = v; return self; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.BWidget() + r = w.set(5).set(8) + assert r.value == 8 + assert w.value == 8 # same object returned by reference + + def test07_deducing_this_default_arg(self): + """explicit object parameter with a defaulted regular argument""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct DWidget { + int base = 3; + int scale(this DWidget& self, int factor = 4) { return self.base * factor; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.DWidget() + assert w.scale() == 12 # default factor=4 + assert w.scale(10) == 30 + + def test08_deducing_this_call_operator(self): + """call operator with an explicit object parameter""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct Adder { + int base = 100; + int operator()(this Adder& self, int x) { return self.base + x; } + }; + } + """) + + a = cppjit.gbl.Cpp23DeducingThis.Adder() + assert a(23) == 123 + + def test09_deducing_this_mixed_overload(self): + """overload set mixing an explicit-object and a normal member function""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct MWidget { + int value = 50; + int get(this MWidget& self) { return self.value; } + int get(int extra) { return value + extra; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.MWidget() + assert w.get() == 50 # explicit-object overload + assert w.get(7) == 57 # normal overload + + def test10_deducing_this_templated(self): + """templated explicit object parameter (the CRTP-replacement idiom)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct TWidget { + int value = 9; + template + int via(this Self&& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.TWidget() + assert w.via() == 9 + + def test11_deducing_this_templated_with_args(self): + """templated explicit object parameter alongside a regular argument""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct TAWidget { + int base = 40; + template + int plus(this Self&& self, int x) { return self.base + x; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.TAWidget() + assert w.plus(2) == 42 + + def test12_deducing_this_templated_returns_self_type(self): + """deduced Self drives the return: returns the deduced object's field""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct TRWidget { + int value = 99; + template + auto identity(this Self&& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.TRWidget() + assert w.identity() == 99 + + def test13_deducing_this_abbreviated_auto(self): + """abbreviated `this auto&&` form (the canonical CRTP-replacement idiom)""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct AAWidget { + int value = 23; + int get(this auto&& self) { return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.AAWidget() + assert w.get() == 23 + + def test14_deducing_this_by_value_copy_semantics(self): + """by-value `this W self` operates on an independent copy""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct CopyWidget { + int value = 1; + int bump(this CopyWidget self) { self.value += 100; return self.value; } + }; + } + """) + + w = cppjit.gbl.Cpp23DeducingThis.CopyWidget() + assert w.bump() == 101 # the copy is mutated + assert w.value == 1 # ... the original is untouched + + def test15_deducing_this_inheritance(self): + """base-class explicit object method invoked on a derived object""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct Base { + int value = 8; + int get(this Base& self) { return self.value; } + }; + struct Derived : Base { }; + } + """) + + d = cppjit.gbl.Cpp23DeducingThis.Derived() + assert d.get() == 8 + + # ------------------------------------------------------------------ # + # Use-cases from the Microsoft C++ blog post "C++23's Deducing this": + # https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/ + # ------------------------------------------------------------------ # + + @mark.xfail( + strict=False, + reason="on newer libstdc++ the deduced value() accessor comes back " + "by value instead of as a writable reference view", + ) + def test16_blog_deduplication(self): + """Blog use-case 1: code de-duplication of cv/ref accessors.""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + #include + namespace Cpp23DeducingThis { + struct Optional { + int m_value = 5; + // one accessor, all value categories (returns a reference) + template auto&& value(this Self&& self) { + return std::forward(self).m_value; + } + // by-value flavor, for a clean read from Python + template auto read(this Self&& self) { + return std::forward(self).m_value; + } + }; + } + """) + + o = cppjit.gbl.Cpp23DeducingThis.Optional() + assert o.read() == 5 # forwarding read + o.value()[0] = 17 # write through the forwarded reference + assert o.read() == 17 # ... mutation is visible on the object + + def test17_blog_crtp_postfix_increment(self): + """Blog use-case 2: CRTP postfix increment without templating the base. + + The using-declaration re-exposes the inherited postfix (standard name + hiding); driven from C++ as Python has no postfix-increment syntax. + """ + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct add_postfix_increment { + template + auto operator++(this Self&& self, int) { auto tmp = self; ++self; return tmp; } + }; + struct some_type : add_postfix_increment { + using add_postfix_increment::operator++; // un-hide inherited postfix + int v = 0; + some_type& operator++() { ++v; return *this; } + }; + // old.v should be the pre-increment value, c.v the post. + int drive_postfix() { some_type c; auto old = c++; return old.v * 100 + c.v; } + } + """) + + assert cppjit.gbl.Cpp23DeducingThis.drive_postfix() == 1 # old.v=0, c.v=1 + + def test18_blog_recursive_lambda(self): + """Blog use-case 4: recursive lambda via the explicit object parameter.""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + int fib(int n) { + auto f = [](this auto const& self, int n) -> int { + return n < 2 ? n : self(n - 1) + self(n - 2); + }; + return f(n); + } + } + """) + + assert cppjit.gbl.Cpp23DeducingThis.fib(10) == 55 + + def test19_blog_lambda_forwarding(self): + """Blog use-case 3: closure with an explicit object parameter. + + The blog uses std::forward_like (not in the host libstdc++); this + exercises the explicit-object closure mechanism itself. + """ + cppjit = self.cppjit + + assert cppjit.cppdef(""" + #include + namespace Cpp23DeducingThis { + struct Scheduler { int submitted = 0; int submit(int m) { submitted = m; return m; } }; + int run_callback() { + Scheduler scheduler; + int message = 42; + auto callback = [message, &scheduler](this auto&& self) -> int { + return scheduler.submit(message); + }; + return callback(); + } + } + """) + + assert cppjit.gbl.Cpp23DeducingThis.run_callback() == 42 + + def test20_blog_pass_by_value(self): + """Blog use-case 5: pass the object by value for better codegen on small types.""" + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + struct just_a_little_guy { + int how_smol = 21; + int uwu(this just_a_little_guy self) { return self.how_smol * 2; } + }; + } + """) + + assert cppjit.gbl.Cpp23DeducingThis.just_a_little_guy().uwu() == 42 + + def test21_blog_sfinae_friendly_transform(self): + """Blog use-case 6: SFINAE-friendly optional::transform. + + Driven from C++: cppjit can't resolve a two-template-parameter + explicit-object method against a Python callable. + """ + cppjit = self.cppjit + + assert cppjit.cppdef(""" + namespace Cpp23DeducingThis { + template + struct Optional6 { + T m_value; + template + auto transform(this Self&& self, F&& f) { return f(self.m_value); } + }; + int triple(int x) { return x * 3; } + int drive_transform() { Optional6 o{14}; return o.transform(triple); } + } + """) + + assert cppjit.gbl.Cpp23DeducingThis.drive_transform() == 42 diff --git a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py b/bindings/pyroot/cppjit/test/test_crossinheritance.py similarity index 76% rename from bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py rename to bindings/pyroot/cppjit/test/test_crossinheritance.py index c9f30c0295924..05ffb20a998eb 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py +++ b/bindings/pyroot/cppjit/test/test_crossinheritance.py @@ -1,22 +1,38 @@ -import os, pytest -from pytest import raises, skip, mark -from support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS - +import os + +import py +from pytest import mark, raises, skip +from support import ( + IS_CLING, + IS_LINUX_ARM, + IS_MAC, + IS_MAC_ARM, + IS_VALGRIND, + IS_WINDOWS, + setup_make, +) + +currpath = py.path.local(__file__).dirpath() test_dct = "crossinheritance_cxx" +def setup_module(mod): + setup_make("crossinheritance") + + class TestCROSSINHERITANCE: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) - @mark.xfail(strict=True) def test01_override_function(self): """Test ability to override a simple function""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1().get_value() == 42 @@ -26,17 +42,19 @@ def get_value(self): assert Derived().get_value() == 13 - assert 'Derived' in str(Derived()) - assert 'Derived' in repr(Derived()) + assert "Derived" in str(Derived()) + assert "Derived" in repr(Derived()) - assert Base1.call_get_value(Base1()) == 42 + assert Base1.call_get_value(Base1()) == 42 assert Base1.call_get_value(Derived()) == 13 + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test02_constructor(self): """Test constructor usage for derived classes""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).get_value() == 27 @@ -46,12 +64,12 @@ def __init__(self, pyval): self.m_pyint = pyval def get_value(self): - return self.m_pyint+self.m_int + return self.m_pyint + self.m_int d = Derived1(2) - assert d.m_int == 42 - assert d.m_pyint == 2 - assert d.get_value() == 44 + assert d.m_int == 42 + assert d.m_pyint == 2 + assert d.get_value() == 44 assert Base1.call_get_value(d) == 44 class Derived2(Base1): @@ -60,19 +78,21 @@ def __init__(self, pyval, cppval): self.m_pyint = pyval def get_value(self): - return self.m_pyint+self.m_int + return self.m_pyint + self.m_int d = Derived2(2, 27) - assert d.m_int == 27 - assert d.m_pyint == 2 - assert d.get_value() == 29 + assert d.m_int == 27 + assert d.m_pyint == 2 + assert d.get_value() == 29 assert Base1.call_get_value(d) == 29 + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test03_override_function_abstract_base(self): """Test ability to override a simple function with an abstract base""" - import cppyy - CX = cppyy.gbl.CrossInheritance + import cppjit + + CX = cppjit.gbl.CrossInheritance class C1PyBase2(CX.IBase2): def __init__(self): @@ -100,10 +120,10 @@ def get_value(self): return 13 try: - c2 = C2PyBase2() # direct call to init can not work + c2 = C2PyBase2() # direct call to init can not work assert not "should have raised TypeError" except TypeError as e: - assert "super" in str(e) # clarifying message + assert "super" in str(e) # clarifying message assert "abstract" in str(e) c1, c3, c4 = C1PyBase2(), C3PyBase2(), C4PyBase2() @@ -124,11 +144,17 @@ def get_value(self): assert c4.m_int == 88 assert CX.IBase2.call_get_value(c4) == 77 + @mark.xfail( + condition=IS_MAC_ARM, + run=False, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test04_arguments(self): """Test ability to override functions that take arguments""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 @@ -137,35 +163,41 @@ def sum_value(self, val): return val + 13 d1 = Derived1() - assert d1.m_int == 42 - assert d1.sum_value(-7) == 6 + assert d1.m_int == 42 + assert d1.sum_value(-7) == 6 assert Base1.call_sum_value(d1, -7) == 6 b1 = Base1() - assert Base1.sum_pass_value(b1) == 6+2*b1.m_int + assert Base1.sum_pass_value(b1) == 6 + 2 * b1.m_int class Derived2(Base1): def pass_value1(self, a): - return a*2 + return a * 2 + def pass_value2(self, a): - return a.value*2 + return a.value * 2 + def pass_value3(self, a): - return a.value*2 + return a.value * 2 + def pass_value4(self, b): - return b.m_int*2 + return b.m_int * 2 + def pass_value5(self, b): - return b.m_int*2 + return b.m_int * 2 d2 = Derived2() - assert Base1.sum_pass_value(d2) == 12+4*d2.m_int + assert Base1.sum_pass_value(d2) == 12 + 4 * d2.m_int + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test05_override_overloads(self): """Test ability to override overloaded functions""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 - assert Base1(27).sum_all(-7) == 20 + assert Base1(27).sum_all(-7) == 20 assert Base1(27).sum_all(-3, -4) == 20 class Derived(Base1): @@ -173,17 +205,19 @@ def sum_all(self, *args): return sum(args) + 13 d = Derived() - assert d.m_int == 42 - assert d.sum_all(-7) == 6 + assert d.m_int == 42 + assert d.sum_all(-7) == 6 assert Base1.call_sum_all(d, -7) == 6 - assert d.sum_all(-7, -5) == 1 + assert d.sum_all(-7, -5) == 1 assert Base1.call_sum_all(d, -7, -5) == 1 + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test06_const_methods(self): """Declared const methods should keep that qualifier""" - import cppyy - CX = cppyy.gbl.CrossInheritance + import cppjit + + CX = cppjit.gbl.CrossInheritance class C1PyBase4(CX.IBase4): def __init__(self): @@ -204,9 +238,10 @@ def __init__(self): def test07_templated_base(self): """Derive from a base class that is instantiated from a template""" - import cppyy + import cppjit - from cppyy.gbl.CrossInheritance import TBase1, TDerived1, TBase1_I + CX = cppjit.gbl.CrossInheritance + TBase1, TBase1_I, TDerived1 = CX.TBase1, CX.TBase1_I, CX.TDerived1 class TPyDerived1(TBase1_I): def __init__(self): @@ -225,17 +260,19 @@ def get_value(self): p1 = TPyDerived1() assert p1.get_value() == 13 - @mark.xfail(strict=True, condition=IS_MAC_ARM | IS_WINDOWS, reason = "Crashes on OS X ARM with" \ - "libc++abi: terminating due to uncaught exception") + @mark.xfail(condition=IS_MAC_ARM, run=False, reason="Fails on macOS arm") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test08_error_handling(self): """Python errors should propagate through wrapper""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 errmsg = "I do not like the given value" + class Derived(Base1): def sum_value(self, val): raise ValueError(errmsg) @@ -252,35 +289,42 @@ def sum_value(self, val): except ValueError as e: assert errmsg in str(e) - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossInheritance { std::string call_base1(Base1* b) { try { b->sum_value(-7); - } catch (CPyCppyy::PyException& e) { + } catch (cppjit::cpyrt::PyException& e) { e.clear(); return e.what(); } return ""; } }""") - res = cppyy.gbl.CrossInheritance.call_base1(d) + res = cppjit.gbl.CrossInheritance.call_base1(d) - assert 'ValueError' in res + assert "ValueError" in res assert os.path.basename(__file__) in res + @mark.xfail( + condition=IS_MAC_ARM, + run=False, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test09_interface_checking(self): """Conversion errors should be Python exceptions""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 errmsg = "I do not like the given value" + class Derived(Base1): def get_value(self): - self.m_int*2 # missing return + self.m_int * 2 # missing return d = Derived(4) @@ -289,9 +333,11 @@ def get_value(self): def test10_python_in_templates(self): """Usage of Python derived objects in std::vector""" - import cppyy, gc + import gc - CB = cppyy.gbl.CrossInheritance.CountableBase + import cppjit + + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -302,8 +348,8 @@ def call(self): start_count = CB.s_count - v = cppyy.gbl.std.vector[PyCountable]() - v.emplace_back(PyCountable()) # uses copy ctor + v = cppjit.gbl.std.vector[PyCountable]() + v.emplace_back(PyCountable()) # uses copy ctor assert len(v) == 1 gc.collect() assert CB.s_count == 1 + start_count @@ -314,7 +360,7 @@ def call(self): assert p.call() == 84 v.emplace_back(p) assert len(v) == 2 - assert v[1].call() == 84 # copy ctor copies python state + assert v[1].call() == 84 # copy ctor copies python state p.extra = 13 assert p.call() == 55 assert v[1].call() == 84 @@ -322,7 +368,7 @@ def call(self): gc.collect() assert CB.s_count == 2 + start_count - v.push_back(PyCountable()) # uses copy ctor + v.push_back(PyCountable()) # uses copy ctor assert len(v) == 3 gc.collect() assert CB.s_count == 3 + start_count @@ -331,12 +377,13 @@ def call(self): gc.collect() assert CB.s_count == 0 + start_count + @mark.xfail(condition=IS_CLING, run=False, reason="Crashes on Cling") def test11_python_in_make_shared(self): """Usage of Python derived objects with std::make_shared""" - import cppyy + import cppjit - cppyy.cppdef("""namespace MakeSharedTest { + cppjit.cppdef("""namespace MakeSharedTest { class Abstract { public: virtual ~Abstract() = 0; @@ -349,13 +396,14 @@ class Abstract { return ptr->some_imp(); } }""") - from cppyy.gbl import std, MakeSharedTest - from cppyy.gbl.MakeSharedTest import Abstract, call_shared + from cppjit.gbl import MakeSharedTest, std # noqa: F401 + from cppjit.gbl.MakeSharedTest import Abstract, call_shared class PyDerived(Abstract): def __init__(self, val): super(PyDerived, self).__init__() self.val = val + def some_imp(self): return self.val @@ -372,10 +420,12 @@ def some_imp(self): def test12a_counter_test(self): """Test countable base counting""" - import cppyy, gc + import gc - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase + import cppjit + + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -386,7 +436,7 @@ def call(self): start_count = CB.s_count - # test counter + # test counter pyc = PyCountable() assert CB.s_count == 1 + start_count @@ -394,13 +444,16 @@ def call(self): gc.collect() assert CB.s_count == 0 + start_count + @mark.xfail(condition=IS_VALGRIND, run=False, reason="Valgrind issue") def test12_python_shared_ptr_memory(self): """Usage of Python derived objects with std::shared_ptr""" - import cppyy, gc + import gc + + import cppjit - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -424,9 +477,11 @@ def call(self): def test13_virtual_dtors_and_del(self): """Usage of virtual destructors and Python-side del.""" - import cppyy, warnings + import warnings + + import cppjit - cppyy.cppdef("""namespace VirtualDtor { + cppjit.cppdef("""namespace VirtualDtor { class MyClass1 {}; // no virtual dtor ... class MyClass2 { @@ -442,11 +497,11 @@ class MyClass4 { virtual ~MyClass4() {} }; }""") - VD = cppyy.gbl.VirtualDtor + VD = cppjit.gbl.VirtualDtor - # rethought this: just issue a warning if there is no virtual destructor - # as the C++ side now carries the type of the dispatcher, not the type of - # the direct base class + # rethought this: just issue a warning if there is no virtual destructor + # as the C++ side now carries the type of the dispatcher, not the type of + # the direct base class with warnings.catch_warnings(record=True) as w: # ensure warnings are not turned into errors, even if we run python -W error # The reason why we don't turn warnings into errors instead and just @@ -455,42 +510,42 @@ class MyClass4 { warnings.simplefilter("default") class MyPyDerived1(VD.MyClass1): - pass # TODO: verify warning is given + pass # TODO: verify warning is given + assert len(w) == 1 assert issubclass(w[-1].category, RuntimeWarning) assert "has no virtual destructor" in str(w[-1].message) d = MyPyDerived1() - del d # used to crash + del d # used to crash class MyPyDerived2(VD.MyClass2): pass d = MyPyDerived2() - del d # used to crash + del d # used to crash - # check a few more derivations that should not fail + # check a few more derivations that should not fail class MyPyDerived3(VD.MyClass3): pass class MyPyDerived4(VD.MyClass4[int]): pass - @mark.xfail(strict=True) def test14_protected_access(self): """Derived classes should have access to protected members""" - import cppyy + import cppjit - ns = cppyy.gbl.AccessProtected + ns = cppjit.gbl.AccessProtected - assert not 'my_data' in ns.MyBase.__dict__ - assert not hasattr(ns.MyBase(), 'my_data') + assert "my_data" not in ns.MyBase.__dict__ + assert not hasattr(ns.MyBase(), "my_data") class MyPyDerived(ns.MyBase): pass - assert 'my_data' in MyPyDerived.__dict__ + assert "my_data" in MyPyDerived.__dict__ assert MyPyDerived().my_data == 101 class MyPyDerived(ns.MyBase): @@ -501,18 +556,19 @@ def __init__(self): self.my_data = 42 m = MyPyDerived() - assert m.py_data == 13 - assert m.my_data == 42 - assert m.get_data() == 42 + assert m.py_data == 13 + assert m.my_data == 42 + assert m.get_data() == 42 assert m.get_data_v() == 42 + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test15_object_returns(self): """Return of C++ objects from overridden functions""" - import cppyy + import cppjit - # Part 1: return of a new C++ object - cppyy.cppdef("""namespace object_returns { + # Part 1: return of a new C++ object + cppjit.cppdef("""namespace object_returns { class Base { public: virtual Base* foo() { return new Base(); } @@ -528,7 +584,7 @@ class CppDerived : public Base { Base* call_foo(Base& obj) { return obj.foo(); } }""") - ns = cppyy.gbl.object_returns + ns = cppjit.gbl.object_returns class PyDerived1(ns.Base): def foo(self): @@ -546,7 +602,7 @@ def foo(self): obj = PyDerived2() assert not not ns.call_foo(obj) - # Part 2: return of a new Python derived object + # Part 2: return of a new Python derived object class PyDerived3(ns.Base): def foo(self): return PyDerived3() @@ -576,9 +632,9 @@ def whoami(self): def test16_cctor_access_controlled(self): """Python derived class of C++ class with access controlled cctor""" - import cppyy + import cppjit - cppyy.cppdef("""namespace cctor_access_controlled { + cppjit.cppdef("""namespace cctor_access_controlled { class CommonBase { public: virtual ~CommonBase() {} @@ -604,9 +660,10 @@ class Base2 : public CommonBase { std::string callit(CommonBase& obj) { return obj.whoami(); } }""") - ns = cppyy.gbl.cctor_access_controlled + ns = cppjit.gbl.cctor_access_controlled for base in (ns.Base1, ns.Base2): + class PyDerived(base): def whoami(self): return "PyDerived" @@ -617,9 +674,9 @@ def whoami(self): def test17_deep_hierarchy(self): """Test a deep Python hierarchy with pure virtual functions""" - import cppyy + import cppjit - cppyy.cppdef("""namespace deep_hierarchy { + cppjit.cppdef("""namespace deep_hierarchy { class Base { public: virtual ~Base() {} @@ -628,7 +685,7 @@ class Base { std::string callit(Base& obj) { return obj.whoami(); } }""") - ns = cppyy.gbl.deep_hierarchy + ns = cppjit.gbl.deep_hierarchy class PyDerived1(ns.Base): def whoami(self): @@ -641,7 +698,7 @@ class PyDerived2(PyDerived1): pass obj = PyDerived2() - assert obj.whoami() == "PyDerived1" + assert obj.whoami() == "PyDerived1" assert ns.callit(obj) == "PyDerived1" class PyDerived3(PyDerived1): @@ -649,7 +706,7 @@ def whoami(self): return "PyDerived3" obj = PyDerived3() - assert obj.whoami() == "PyDerived3" + assert obj.whoami() == "PyDerived3" assert ns.callit(obj) == "PyDerived3" class PyDerived4(PyDerived2): @@ -657,16 +714,15 @@ def whoami(self): return "PyDerived4" obj = PyDerived4() - assert obj.whoami() == "PyDerived4" + assert obj.whoami() == "PyDerived4" assert ns.callit(obj) == "PyDerived4" def test18_abstract_hierarchy(self): """Hierarchy with abstract classes""" + import cppjit - import cppyy - - cppyy.cppdef("""namespace abstract_classes { + cppjit.cppdef("""namespace abstract_classes { class Base { public: virtual ~Base() {} @@ -677,7 +733,7 @@ class Base { std::string whois(Base& obj) { return obj.whoami(); } std::string saywot(Base& obj) { return obj.message(); } }""") - ns = cppyy.gbl.abstract_classes + ns = cppjit.gbl.abstract_classes class PyDerived1(ns.Base): def __init__(self): @@ -696,18 +752,18 @@ def message(self): return self._message obj = PyDerived2() - assert obj.whoami() == "PyDerived1" + assert obj.whoami() == "PyDerived1" assert ns.whois(obj) == "PyDerived1" - assert obj.message() == "Hello, World!" + assert obj.message() == "Hello, World!" assert ns.saywot(obj) == "Hello, World!" def test19_cpp_side_multiple_inheritance(self): """Hierarchy with multiple inheritance on the C++ side""" - import cppyy + import cppjit - cppyy.cppdef(""" namespace cpp_side_multiple_inheritance { + cppjit.cppdef(""" namespace cpp_side_multiple_inheritance { struct Result { Result() : result(1337) {} Result(int r) : result(r) {} @@ -728,21 +784,22 @@ class Base2 { class Base : public Base1, public Base2 { public: - Result abstract2() override { return Result(999); } + Result abstract2() override { return Result(999); } }; } """) - ns = cppyy.gbl.cpp_side_multiple_inheritance + ns = cppjit.gbl.cpp_side_multiple_inheritance class Derived(ns.Base): def abstract1(self): return ns.Result(1) + @mark.xfail(condition=IS_LINUX_ARM, run=False, reason="Crashes pytest on Linux ARM") def test20_basic_multiple_inheritance(self): """Basic multiple inheritance""" - import cppyy + import cppjit - cppyy.cppdef("""namespace basic_multiple_inheritance { + cppjit.cppdef("""namespace basic_multiple_inheritance { class MyClass1 { public: MyClass1() : m_1(13) {} @@ -776,9 +833,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.basic_multiple_inheritance + ns = cppjit.gbl.basic_multiple_inheritance - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): def x(self): return 16 @@ -794,7 +851,7 @@ def y(self): assert a.m_1 == 13 assert a.m_2 == 42 - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def x(self): return 16 @@ -815,12 +872,17 @@ def z(self): assert a.m_2 == 42 assert a.m_3 == 67 + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Crashes with Valgrind on Linux ARM", + ) def test21_multiple_inheritance_with_constructors(self): """Multiple inheritance with constructors""" - import cppyy + import cppjit - cppyy.cppdef("""namespace multiple_inheritance_with_constructors { + cppjit.cppdef("""namespace multiple_inheritance_with_constructors { class MyClass1 { public: MyClass1() : m_1(13) {} @@ -857,9 +919,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.multiple_inheritance_with_constructors + ns = cppjit.gbl.multiple_inheritance_with_constructors - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): def __init__(self, val1, val2): super(MyPyDerived, self).__init__((val1,), (val2,)) @@ -878,7 +940,7 @@ def y(self): assert a.m_1 == 27 assert a.m_2 == 88 - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def __init__(self, val1, val2, val3): super(MyPyDerived2, self).__init__((val1,), (val2,), (val3,)) @@ -898,16 +960,21 @@ def z(self): assert a.y() == ns.cally(a) assert a.z() == ns.callz(a) - assert a.m_1 == 27 - assert a.m_2 == 88 + assert a.m_1 == 27 + assert a.m_2 == 88 assert a.m_3 == -11 + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Crashes with Valgrind on Linux ARM", + ) def test22_multiple_inheritance_with_defaults(self): """Multiple inheritance with defaults""" - import cppyy + import cppjit - cppyy.cppdef("""namespace multiple_inheritance_with_defaults { + cppjit.cppdef("""namespace multiple_inheritance_with_defaults { class MyClass1 { public: MyClass1(int i=13) : m_1(i) {} @@ -941,9 +1008,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.multiple_inheritance_with_defaults + ns = cppjit.gbl.multiple_inheritance_with_defaults - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def __init__(self, val1=None, val2=None, val3=None, nArgs=3): a1 = val1 is not None and (val1,) or () a2 = val2 is not None and (val2,) or () @@ -992,12 +1059,13 @@ def verify(a, n1, n2, n3): a = MyPyDerived(27, 55, nArgs=2) verify(a, 27, 55, 67) + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test23_const_byvalue_return(self): """Const by-value return in overridden method""" - import cppyy + import cppjit - cppyy.cppdef("""namespace const_byvalue_return { + cppjit.cppdef("""namespace const_byvalue_return { struct Const { Const() = default; explicit Const(const std::string& s) { m_value = s; } @@ -1011,23 +1079,22 @@ def test23_const_byvalue_return(self): const Const callit(Abstract* a) { return a->return_const(); } }""") - ns = cppyy.gbl.const_byvalue_return + ns = cppjit.gbl.const_byvalue_return class ReturnConstByValue(ns.Abstract): - def return_const(self): - return ns.Const("abcdef") + def return_const(self): + return ns.Const("abcdef") a = ReturnConstByValue() assert a.return_const().m_value == "abcdef" - assert ns.callit(a).m_value == "abcdef" + assert ns.callit(a).m_value == "abcdef" - @mark.xfail(strict=True) def test24_non_copyable(self): """Inheriting from a non-copyable base class""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace non_copyable { struct Copyable { Copyable() = default; @@ -1071,30 +1138,30 @@ class NoCopyNoMove { NoCopyNoMove* fActual; }; }""") - ns = cppyy.gbl.non_copyable + ns = cppjit.gbl.non_copyable Copyable = ns.Copyable - Movable = ns.Movable + Movable = ns.Movable NoCopyNoMove = ns.NoCopyNoMove class DerivedCopyable(Copyable): pass - # used to fail with compilation error + # used to fail with compilation error class DerivedMovable(Movable): pass - # used to fail with compilation error - class DerivedMulti(cppyy.multi(Movable, Copyable)): + # used to fail with compilation error + class DerivedMulti(cppjit.multi(Movable, Copyable)): pass - # used to fail with compilation error + # used to fail with compilation error class DerivedNoCopyNoMove(NoCopyNoMove): def __init__(self): super(DerivedNoCopyNoMove, self).__init__(self) - # TODO: chicken-and-egg situation here, 'this' from 'self' is - # nullptr until the constructor has been called, so it can't - # be passed as an argument to the same constructor + # TODO: chicken-and-egg situation here, 'this' from 'self' is + # nullptr until the constructor has been called, so it can't + # be passed as an argument to the same constructor self.fActual = self def callme_imp(self): @@ -1105,9 +1172,9 @@ def callme_imp(self): def test25_default_ctor_and_multiple_inheritance(self): """Regression test: default ctor did not get added""" - import cppyy + import cppjit - cppyy.cppdef("""namespace default_ctor_and_multiple { + cppjit.cppdef("""namespace default_ctor_and_multiple { struct Copyable { Copyable() = default; virtual ~Copyable() {} @@ -1130,12 +1197,12 @@ def test25_default_ctor_and_multiple_inheritance(self): virtual ~SomeClass() {} }; }""") - ns = cppyy.gbl.default_ctor_and_multiple - Copyable = ns.Copyable - Movable = ns.Movable + ns = cppjit.gbl.default_ctor_and_multiple + Copyable = ns.Copyable + Movable = ns.Movable SomeClass = ns.SomeClass - class DerivedMulti(cppyy.multi(Movable, Copyable, SomeClass)): + class DerivedMulti(cppjit.multi(Movable, Copyable, SomeClass)): def __init__(self): super(DerivedMulti, self).__init__() @@ -1145,9 +1212,9 @@ def __init__(self): def test26_no_default_ctor(self): """Make sure no default ctor is created if not viable""" - import cppyy + import cppjit - cppyy.cppdef("""namespace no_default_ctor { + cppjit.cppdef("""namespace no_default_ctor { struct NoDefCtor1 { NoDefCtor1(int) {} virtual ~NoDefCtor1() {} @@ -1170,9 +1237,10 @@ class Simple { }; }""") - ns = cppyy.gbl.no_default_ctor + ns = cppjit.gbl.no_default_ctor for kls in (ns.NoDefCtor1, ns.NoDefCtor2, ns.NoDefCtor3): + class PyDerived(kls): def __init__(self): super(PyDerived, self).__init__() @@ -1180,14 +1248,14 @@ def __init__(self): with raises(TypeError): PyDerived() - class PyDerived(cppyy.multi(kls, ns.Simple)): + class PyDerived(cppjit.multi(kls, ns.Simple)): def __init__(self): super(PyDerived, self).__init__() with raises(TypeError): PyDerived() - class PyDerived(cppyy.multi(ns.Simple, kls)): + class PyDerived(cppjit.multi(ns.Simple, kls)): def __init__(self): super(PyDerived, self).__init__() @@ -1197,9 +1265,9 @@ def __init__(self): def test27_interfaces(self): """Inherit from base with non-standard offset""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace NonStandardOffset { struct Calc1 { virtual int calc1() = 0; @@ -1224,25 +1292,26 @@ def test27_interfaces(self): int callback2(Calc2* c2) { return c2->calc2(); } }""") - ns = cppyy.gbl.NonStandardOffset + ns = cppjit.gbl.NonStandardOffset class MyPyDerived(ns.Derived): pass obj = MyPyDerived() - assert obj.calc1() == 1 + assert obj.calc1() == 1 assert ns.callback1(obj) == 1 - assert obj.calc2() == 2 + assert obj.calc2() == 2 assert ns.callback2(obj) == 2 + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test28_cross_deep(self): """Deep inheritance hierarchy""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossDeep { class A { @@ -1253,14 +1322,14 @@ class A { virtual int fun2() const { return fun1(); } }; }""") - A = cppyy.gbl.CrossDeep.A + A = cppjit.gbl.CrossDeep.A class B(A): - def __init__ (self, name = 'b'): + def __init__(self, name="b"): super(B, self).__init__(name) def fun1(self): - return 1 + return 1 class C(B): def fun1(self): @@ -1269,16 +1338,21 @@ def fun1(self): class D(B): pass - for inst, val1 in [(A('a'), 0), (B('b'), 1), (C('c'), -1), (D('d'), 1)]: + for inst, val1 in [(A("a"), 0), (B("b"), 1), (C("c"), -1), (D("d"), 1)]: assert inst.fun1() == val1 assert inst.fun2() == inst.fun1() + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Crashes with Valgrind on Linux ARM", + ) def test29_cross_deep_multi(self): """Deep multi-inheritance hierarchy""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossMultiDeep { class A { @@ -1297,9 +1371,9 @@ class B { int calc_b(B* b) { return b->calc_b(); } }""") - ns = cppyy.gbl.CrossMultiDeep + ns = cppjit.gbl.CrossMultiDeep - class C(cppyy.multi(ns.A, ns.B)): + class C(cppjit.multi(ns.A, ns.B)): def calc_a(self): return 18 @@ -1317,7 +1391,7 @@ def calc_b(self): d = D() assert ns.calc_b(d) == 44 - class E(cppyy.multi(ns.A, D)): + class E(cppjit.multi(ns.A, D)): def calc_a(self): return 19 @@ -1332,7 +1406,7 @@ def calc_a(self): f = F() assert ns.calc_a(f) == 20 - class G(cppyy.multi(F, ns.B)): + class G(cppjit.multi(F, ns.B)): def calc_b(self): return 45 @@ -1344,27 +1418,26 @@ class H(object): def calc_a(self): return 66 - class I(cppyy.multi(ns.A, H)): + class I(cppjit.multi(ns.A, H)): def calc_a(self): return 77 i = I() assert ns.calc_a(i) == 77 - class J(cppyy.multi(H, ns.A)): + class J(cppjit.multi(H, ns.A)): def calc_a(self): return 88 j = J() assert ns.calc_a(j) == 88 - def test30_access_and_overload(self): """Inheritance with access and overload complications""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AccessAndOverload { class Base { public: @@ -1383,19 +1456,20 @@ class Base { int call3(int i, int j) { return i+j; } }; }""") - ns = cppyy.gbl.AccessAndOverload + ns = cppjit.gbl.AccessAndOverload - # used to produce uncompilable code + # used to produce uncompilable code class PyDerived(ns.Base): pass - @mark.xfail(strict=True) def test31_object_rebind(self): """Usage of bind_object to cast with Python derived objects""" - import cppyy, gc + import gc + + import cppjit - ns = cppyy.gbl.CrossInheritance + ns = cppjit.gbl.CrossInheritance ns.build_component.__creates__ = True assert ns.Component.get_count() == 0 @@ -1408,9 +1482,9 @@ def test31_object_rebind(self): assert ns.Component.get_count() == 1 - # introduce the actual component type; would have been a header, - # but this simply has to match what is in crossinheritance.cxx - cppyy.cppdef("""namespace CrossInheritance { + # introduce the actual component type; would have been a header, + # but this simply has to match what is in crossinheritance.cxx + cppjit.cppdef("""namespace CrossInheritance { class ComponentWithValue : public Component { public: ComponentWithValue(int value) : m_value(value) {} @@ -1420,9 +1494,9 @@ class ComponentWithValue : public Component { int m_value; }; }""") - # rebind cmp1 to its actual C++ class - act_cmp1 = cppyy.bind_object(cmp1, ns.ComponentWithValue) - assert not cmp1.__python_owns__ # b/c transferred + # rebind cmp1 to its actual C++ class + act_cmp1 = cppjit.bind_object(cmp1, ns.ComponentWithValue) + assert not cmp1.__python_owns__ # b/c transferred assert act_cmp1.__python_owns__ assert act_cmp1.getValue() == 42 @@ -1430,13 +1504,14 @@ class ComponentWithValue : public Component { gc.collect() assert ns.Component.get_count() == 0 - # introduce a Python derived class + # introduce a Python derived class ns.ComponentWithValue.__init__.__creates__ = False + class PyComponentWithValue(ns.ComponentWithValue): def getValue(self): return self.m_value + 12 - # wipe the python-side connection + # wipe the python-side connection pycmp2a = PyComponentWithValue(27) assert not pycmp2a.__python_owns__ pycmp2a.__python_owns__ = True @@ -1450,24 +1525,27 @@ def getValue(self): gc.collect() assert ns.Component.get_count() == 0 - cmp2 = cppyy.bind_object(cppyy.addressof(PyComponentWithValue(13)), ns.Component) + cmp2 = cppjit.bind_object( + cppjit.addressof(PyComponentWithValue(13)), ns.Component + ) assert ns.Component.get_count() == 1 - cmp2 = ns.cycle_component(cmp2) # causes auto down-cast + cmp2 = ns.cycle_component(cmp2) # causes auto down-cast assert ns.Component.get_count() == 1 - #assert type(cmp2) != PyComponentWithValue + # assert type(cmp2) != PyComponentWithValue - # rebind cmp2 to the python type - act_cmp2 = cppyy.bind_object(cmp2, PyComponentWithValue) + # rebind cmp2 to the python type + act_cmp2 = cppjit.bind_object(cmp2, PyComponentWithValue) act_cmp2.__python_owns__ = True - assert act_cmp2.getValue() == 13+12 + assert act_cmp2.getValue() == 13 + 12 del cmp2, act_cmp2 gc.collect() assert ns.Component.get_count() == 0 - # introduce a Python derived class with initialization + # introduce a Python derived class with initialization ns.ComponentWithValue.__init__.__creates__ = True + class PyComponentWithInit(ns.ComponentWithValue): def __init__(self, cppvalue): super(PyComponentWithInit, self).__init__(cppvalue) @@ -1476,51 +1554,58 @@ def __init__(self, cppvalue): def getValue(self): return self.m_value + self.m_pyvalue - cmp3 = cppyy.bind_object(PyComponentWithInit(77), PyComponentWithInit) + cmp3 = cppjit.bind_object(PyComponentWithInit(77), PyComponentWithInit) assert type(cmp3) == PyComponentWithInit assert ns.Component.get_count() == 1 - assert cmp3.getValue() == 77+11 + assert cmp3.getValue() == 77 + 11 del cmp3 gc.collect() assert ns.Component.get_count() == 0 pyc = PyComponentWithInit(88) - cmp4 = cppyy.bind_object(cppyy.addressof(pyc), ns.Component) + cmp4 = cppjit.bind_object(cppjit.addressof(pyc), ns.Component) assert type(cmp4) == ns.Component assert ns.Component.get_count() == 1 - # rebind cmp4 to the python type - act_cmp4 = cppyy.bind_object(cmp4, PyComponentWithInit) - assert act_cmp4.getValue() == 88+11 + # rebind cmp4 to the python type + act_cmp4 = cppjit.bind_object(cmp4, PyComponentWithInit) + assert act_cmp4.getValue() == 88 + 11 del cmp4, act_cmp4, pyc gc.collect() assert ns.Component.get_count() == 0 ns.ComponentWithValue.__init__.__creates__ = False - cmp5 = cppyy.bind_object(cppyy.addressof(PyComponentWithInit(22)), ns.Component) + cmp5 = cppjit.bind_object( + cppjit.addressof(PyComponentWithInit(22)), ns.Component + ) cmp5.__python_owns__ = True assert type(cmp5) == ns.Component assert ns.Component.get_count() == 1 - # rebind cmp5 to the python type - act_cmp5 = cppyy.bind_object(cmp5, PyComponentWithInit) + # rebind cmp5 to the python type + act_cmp5 = cppjit.bind_object(cmp5, PyComponentWithInit) assert not cmp5.__python_owns__ assert act_cmp5.__python_owns__ - assert act_cmp5.getValue() == 22+11 + assert act_cmp5.getValue() == 22 + 11 del cmp5, act_cmp5 gc.collect() assert ns.Component.get_count() == 0 + @mark.xfail( + condition=IS_LINUX_ARM and IS_VALGRIND, + run=False, + reason="Crashes with Valgrind on Linux ARM", + ) def test32_by_value_arguments(self): """Override base function taking by-value arguments""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossCallWithValue { struct Data { int value; @@ -1536,7 +1621,7 @@ def test32_by_value_arguments(self): virtual int extra_func(Data d) = 0; }; }""") - ns = cppyy.gbl.CrossCallWithValue + ns = cppjit.gbl.CrossCallWithValue class PyDerived(ns.CppBase): def extra_func(self, d): @@ -1551,9 +1636,9 @@ def extra_func(self, d): def test33_direct_base_methods(self): """Call base class methods directly""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DirectCalls { struct A { virtual ~A() {} @@ -1568,14 +1653,14 @@ def test33_direct_base_methods(self): } }; }""") - ns = cppyy.gbl.DirectCalls + ns = cppjit.gbl.DirectCalls a = ns.A() - assert a.func() == 1 + assert a.func() == 1 assert ns.A.func(a) == 1 b = ns.B() - assert b.func() == 2 + assert b.func() == 2 assert ns.B.func(b) == 2 assert ns.A.func(b) == 1 @@ -1593,9 +1678,9 @@ def func(self): def test34_no_ctors_in_base(self): """Base classes with no constructors""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace BlockedCtors { class Z { public: @@ -1613,7 +1698,7 @@ class Y: Z { Y(const Y&&) = delete; }; }""") - ns = cppyy.gbl.BlockedCtors + ns = cppjit.gbl.BlockedCtors with raises(TypeError): ns.X() @@ -1636,9 +1721,9 @@ def __init__(self): def test35_deletion(self): """C++ side deletion should propagate to the Python side""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeletionTest1 { class Base { public: @@ -1649,10 +1734,11 @@ class Base { void delete_it(Base *p) { delete p; } }""") - ns = cppyy.gbl.DeletionTest1 + ns = cppjit.gbl.DeletionTest1 class Derived(ns.Base): was_deleted = False + def __del__(self): Derived.was_deleted = True @@ -1670,9 +1756,9 @@ def __del__(self): def test36_custom_destruct(self): """C++ side deletion calls __destruct__""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeletionTest2 { class Base { public: @@ -1683,17 +1769,17 @@ class Base { void delete_it(Base *p) { delete p; } }""") - ns = cppyy.gbl.DeletionTest2 + ns = cppjit.gbl.DeletionTest2 class Derived(ns.Base): was_cpp_deleted = False - was_py_deleted = False + was_py_deleted = False def __destruct__(self): Derived.was_cpp_deleted = True def __del__(self): - Derived.was_py_deleted = True + Derived.was_py_deleted = True o1 = Derived() o1.do_work() @@ -1703,16 +1789,16 @@ def __del__(self): o1.do_work() assert Derived.was_cpp_deleted == True - assert Derived.was_py_deleted == False + assert Derived.was_py_deleted == False del o1 - assert Derived.was_py_deleted == True + assert Derived.was_py_deleted == True def test37_deep_tree(self): """Find overridable methods deep in the tree""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeepTree { class Base { @@ -1755,7 +1841,7 @@ class CppSub: public Sub { return res; } }""") - ns = cppyy.gbl.DeepTree + ns = cppjit.gbl.DeepTree cppsub = ns.CppSub() assert cppsub.f1() == "C++: CppSub::f1()" @@ -1779,13 +1865,12 @@ def f3(self): assert pysub.f3() == "Python: PySub::f3()" assert ns.call_fs(pysub) == pysub.f1() + pysub.f2() + pysub.f3() - @mark.xfail(strict=True) def test38_protected_data(self): """Multiple cross inheritance with protected data""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace multiple_inheritance_with_protected_data { class MyBaseClass { public: @@ -1804,7 +1889,7 @@ class MyBaseClass { int get_z() { return z; } }; }""") - ns = cppyy.gbl.multiple_inheritance_with_protected_data + ns = cppjit.gbl.multiple_inheritance_with_protected_data class MyDerivedClass(ns.MyBaseClass): def __init__(self, x, y, z): @@ -1820,9 +1905,9 @@ def __init__(self, x, y, z): def test39_returning_multi_keyword_types(self): """Supporting dispatcher for functions that return multi-keyword types like `unsigned int`""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class MyUIntBaseClass { public: virtual ~MyUIntBaseClass() = default; @@ -1833,9 +1918,6 @@ class MyUIntBaseClass { # Compiling the dispatcher for the overridden member function used to # fail because of the `unsigned int` type, whose name has two keywords. - class MyUIntDerivedClass( cppyy.gbl.MyUIntBaseClass ): - def give_unsigned_int( self ): + class MyUIntDerivedClass(cppjit.gbl.MyUIntBaseClass): + def give_unsigned_int(self): return 1 - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppjit/test/test_datatypes.py b/bindings/pyroot/cppjit/test/test_datatypes.py new file mode 100644 index 0000000000000..5e508d32dcee6 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_datatypes.py @@ -0,0 +1,2796 @@ +import sys + +import py +from pytest import mark, raises, skip +from support import IS_CLANG_REPL, IS_MAC, IS_WINDOWS, pylong, pyunicode, setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = "datatypes_cxx" + + +def setup_module(mod): + setup_make("datatypes") + + +class TestDATATYPES: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.datatypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = 5 # cppjit.gbl.N + + def test01_instance_data_read_access(self): + """Read access to instance public data and verify values""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # reading boolean type + assert c.m_bool == False + assert not c.get_bool() + assert not c.get_bool_cr() + assert not c.get_bool_r() + + # reading char types + assert c.m_char == "a" + assert c.m_schar == "b" + assert c.m_uchar == "c" + assert type(c.m_wchar) == pyunicode + assert c.m_wchar == "D" + assert type(c.m_char16) == pyunicode + assert c.m_char16 == "\u00df" + assert type(c.m_char32) == pyunicode + assert c.m_char32 == "\u00df" + + # reading integer types + assert c.m_int8 == -9 + assert c.get_int8_cr() == -9 + assert c.get_int8_r() == -9 + assert c.m_uint8 == 9 + assert c.get_uint8_cr() == 9 + assert c.get_uint8_r() == 9 + assert c.m_byte == ord("d") + assert c.get_byte_cr() == ord("d") + assert c.get_byte_r() == ord("d") + assert c.m_short == -11 + assert c.get_short_cr() == -11 + assert c.get_short_r() == -11 + assert c.m_ushort == 11 + assert c.get_ushort_cr() == 11 + assert c.get_ushort_r() == 11 + assert c.m_int == -22 + assert c.get_int_cr() == -22 + assert c.get_int_r() == -22 + assert c.m_uint == 22 + assert c.get_uint_cr() == 22 + assert c.get_uint_r() == 22 + assert c.m_long == -33 + assert c.get_long_cr() == -33 + assert c.get_long_r() == -33 + assert c.m_ulong == 33 + assert c.get_ulong_cr() == 33 + assert c.get_ulong_r() == 33 + assert c.m_llong == -44 + assert c.get_llong_cr() == -44 + assert c.get_llong_r() == -44 + assert c.m_ullong == 44 + assert c.get_ullong_cr() == 44 + assert c.get_ullong_r() == 44 + assert c.m_long64 == -55 + assert c.get_long64_cr() == -55 + assert c.get_long64_r() == -55 + assert c.m_ulong64 == 55 + assert c.get_ulong64_cr() == 55 + assert c.get_ulong64_r() == 55 + + # reading floating point types + assert round(c.m_float + 66.0, 5) == 0 + assert round(c.get_float_cr() + 66.0, 5) == 0 + assert round(c.get_float_r() + 66.0, 5) == 0 + assert round(c.m_double + 77.0, 11) == 0 + assert round(c.get_double_cr() + 77.0, 11) == 0 + assert round(c.get_double_r() + 77.0, 11) == 0 + assert round(c.m_ldouble + 88.0, 24) == 0 + assert round(c.get_ldouble_cr() + 88.0, 24) == 0 + assert round(c.get_ldouble_r() + 88.0, 24) == 0 + assert round(c.get_ldouble_def() - 1.0, 24) == 0 + assert round(c.get_ldouble_def(2) - 2.0, 24) == 0 + + # complex type + assert type(c.get_complex()) == complex + assert round(c.get_complex().real - 99.0, 11) == 0 + assert round(c.get_complex().imag - 101.0, 11) == 0 + assert repr(c.get_complex()) == "(99+101j)" + assert round(c.get_complex_cr().real - 99.0, 11) == 0 + assert round(c.get_complex_cr().imag - 101.0, 11) == 0 + assert round(c.get_complex_r().real - 99.0, 11) == 0 + assert round(c.get_complex_r().imag - 101.0, 11) == 0 + assert complex(cppjit.gbl.std.complex["double"](1, 2)) == complex(1, 2) + assert repr(cppjit.gbl.std.complex["double"](1, 2)) == "(1+2j)" + + # complex retains C++ type in all cases (but includes pythonization to + # resemble Python's complex more closely + assert type(c.get_icomplex()) == cppjit.gbl.std.complex[int] + assert round(c.get_icomplex().real - 121.0, 11) == 0 + assert round(c.get_icomplex().imag - 141.0, 11) == 0 + assert repr(c.get_icomplex()) == "(121+141j)" + assert round(c.get_icomplex_cr().real - 121.0, 11) == 0 + assert round(c.get_icomplex_cr().imag - 141.0, 11) == 0 + assert type(c.get_icomplex_r()) == cppjit.gbl.std.complex[int] + assert round(c.get_icomplex_r().real - 121.0, 11) == 0 + assert round(c.get_icomplex_r().imag - 141.0, 11) == 0 + assert complex(cppjit.gbl.std.complex["int"](1, 2)) == complex(1, 2) + + # _Complex double type + assert type(c.get_ccomplex()) == complex + assert round(c.get_ccomplex().real - 151.0, 11) == 0 + assert round(c.get_ccomplex().imag - 161.0, 11) == 0 + assert repr(c.get_ccomplex()) == "(151+161j)" + assert round(c.get_ccomplex_cr().real - 151.0, 11) == 0 + assert round(c.get_ccomplex_cr().imag - 161.0, 11) == 0 + assert round(c.get_ccomplex_r().real - 151.0, 11) == 0 + assert round(c.get_ccomplex_r().imag - 161.0, 11) == 0 + + # complex overloads + cppjit.cppdef(""" + namespace ComplexOverload { + template + struct CO { + CO(std::size_t sz) : m_size(sz), m_cplx(std::complex(7,42)) {} + CO(std::complex cplx) : m_size(42), m_cplx(cplx) {} + + std::size_t m_size; + std::complex m_cplx; + }; + }""") + + COd = cppjit.gbl.ComplexOverload.CO["double"] + COf = cppjit.gbl.ComplexOverload.CO["float"] + scf = cppjit.gbl.std.complex["float"] + + assert COd(2).m_size == 2 + assert COd(2).m_cplx == 7.0 + 42j + assert COd(3.14).m_size == 42 + assert COd(3.14).m_cplx == 3.14 + 0j + assert COd(9.0 + 7j).m_size == 42 + assert COd(9.0 + 7j).m_cplx == 9.0 + 7j + + assert COf(2).m_size == 2 + assert COf(2).m_cplx == scf(7, 42) + assert COf(3.14).m_size == 42 + assert COf(3.14).m_cplx == scf(3.14, 0) + assert COf(9.0 + 7j).m_size == 42 + assert COf(9.0 + 7j).m_cplx == scf(9.0, 7.0) + + # reading of enum types + assert c.m_enum == CppjitTestData.kNothing + assert c.m_enum == c.kNothing + + # reading of boolean array + for i in range(self.N): + assert c.m_bool_array[i] == bool(i % 2) + assert c.get_bool_array()[i] == bool(i % 2) + assert c.m_bool_array2[i] == bool((i + 1) % 2) + assert c.get_bool_array2()[i] == bool((i + 1) % 2) + + # reading of integer array types + names = [ + "schar", + "uchar", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "byte", + ] + alpha = [ + (1, 2), + (1, 2), + (-5, -6), + (7, 8), + (-9, -10), + (11, 23), + (-13, -14), + (15, 18), + (3, 4), + ] + + for type_idx, name in enumerate(names): + for j in range(self.N): + assert getattr(c, f"m_{name}_array")[j] == alpha[type_idx][0] * j + assert getattr(c, f"get_{name}_array")()[j] == alpha[type_idx][0] * j + # the signed char* array m_schar_array2 is implicitly converted to basic_string + # hence we only compare the value returned using the getter get_schar_array2 + if name != "schar": + assert getattr(c, f"m_{name}_array2")[j] == alpha[type_idx][1] * j + assert getattr(c, f"get_{name}_array2")()[j] == alpha[type_idx][1] * j + + # reading of floating point array types + for k in range(self.N): + assert round(c.m_float_array[k] + 13.0 * k, 5) == 0 + assert round(c.m_float_array2[k] + 14.0 * k, 5) == 0 + assert round(c.m_double_array[k] + 15.0 * k, 8) == 0 + assert round(c.m_double_array2[k] + 16.0 * k, 8) == 0 + + # out-of-bounds checks + raises(IndexError, c.m_schar_array.__getitem__, self.N) + raises(IndexError, c.m_uchar_array.__getitem__, self.N) + raises(IndexError, c.m_byte_array.__getitem__, self.N) + raises(IndexError, c.m_short_array.__getitem__, self.N) + raises(IndexError, c.m_ushort_array.__getitem__, self.N) + raises(IndexError, c.m_int_array.__getitem__, self.N) + raises(IndexError, c.m_uint_array.__getitem__, self.N) + raises(IndexError, c.m_long_array.__getitem__, self.N) + raises(IndexError, c.m_ulong_array.__getitem__, self.N) + raises(IndexError, c.m_float_array.__getitem__, self.N) + raises(IndexError, c.m_double_array.__getitem__, self.N) + + # can not access an instance member on the class + raises(AttributeError, getattr, CppjitTestData, "m_bool") + raises(AttributeError, getattr, CppjitTestData, "m_int") + + assert not hasattr(CppjitTestData, "m_bool") + assert not hasattr(CppjitTestData, "m_int") + + c.__destruct__() + + def test02_instance_data_write_access(self): + """Test write access to instance public data and verify values""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # boolean types through functions + c.set_bool(True) + assert c.get_bool() == True + c.set_bool(0) + assert c.get_bool() == False + + # boolean types through data members + c.m_bool = True + assert c.get_bool() == True + c.set_bool(True) + assert c.m_bool == True + c.m_bool = 0 + assert c.get_bool() == False + c.set_bool(0) + assert c.m_bool == False + raises(ValueError, c.set_bool, 10) + + # char types through functions + c.set_char("c") + assert c.get_char() == "c" + c.set_uchar("e") + assert c.get_uchar() == "e" + c.set_wchar("F") + assert c.get_wchar() == "F" + assert type(c.get_wchar()) == pyunicode + c.set_char16("\u00f2") + assert c.get_char16() == "\u00f2" + c.set_char32("\U0001f31c") + assert c.get_char32() == "\U0001f31c" + + # char types through data members + c.m_char = "b" + assert c.get_char() == "b" + c.m_char = 40 + assert c.get_char() == chr(40) + c.set_char("c") + assert c.m_char == "c" + c.set_char(41) + assert c.m_char == chr(41) + c.m_uchar = "d" + assert c.get_uchar() == "d" + c.m_uchar = 42 + assert c.get_uchar() == chr(42) + c.set_uchar("e") + assert c.m_uchar == "e" + c.set_uchar(43) + assert c.m_uchar == chr(43) + c.m_wchar = "G" + assert c.get_wchar() == "G" + c.set_wchar("H") + assert c.m_wchar == "H" + c.m_char16 = "\u00f3" + assert c.get_char16() == "\u00f3" + c.set_char16("\u00f4") + assert c.m_char16 == "\u00f4" + c.m_char32 = "\U0001f31d" + assert c.get_char32() == "\U0001f31d" + c.set_char32("\U0001f31e") + assert c.m_char32 == "\U0001f31e" + + raises(ValueError, c.set_char, "string") + raises(ValueError, c.set_char, 500) + raises(ValueError, c.set_uchar, "string") + raises(ValueError, c.set_uchar, -1) + raises(ValueError, c.set_wchar, "string") + raises(ValueError, c.set_char16, "string") + raises(ValueError, c.set_char32, "string") + + # integer types + names = [ + "int8", + "uint8", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "llong", + "ullong", + "byte", + ] + + for i in range(len(names)): + setattr(c, "m_" + names[i], i) + assert eval("c.get_%s()" % names[i]) == i + + for i in range(len(names)): + getattr(c, "set_" + names[i])(2 * i) + assert eval("c.m_%s" % names[i]) == 2 * i + + for i in range(len(names)): + getattr(c, "set_" + names[i] + "_cr")(3 * i) + assert eval("c.m_%s" % names[i]) == 3 * i + + for i in range(len(names)): + getattr(c, "set_" + names[i] + "_rv")(4 * i) + assert eval("c.m_%s" % names[i]) == 4 * i + + for i in range(len(names)): + setattr(c, "m_" + names[i], cppjit.default) + assert eval("c.get_%s()" % names[i]) == 0 + + for i in range(len(names)): + getattr(c, "set_" + names[i])(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0 + + for i in range(len(names)): + getattr(c, "set_" + names[i] + "_cr")(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0 + + # float types through functions + c.set_float(0.123) + assert round(c.get_float() - 0.123, 5) == 0 + c.set_double(0.456) + assert round(c.get_double() - 0.456, 8) == 0 + c.set_ldouble(0.789) + assert round(c.get_ldouble() - 0.789, 8) == 0 + + # float types through data members + c.m_float = 0.123 + assert round(c.get_float() - 0.123, 5) == 0 + c.set_float(0.234) + assert round(c.m_float - 0.234, 5) == 0 + c.set_float_cr(0.456) + assert round(c.m_float - 0.456, 5) == 0 + c.m_double = 0.678 + assert round(c.get_double() - 0.678, 8) == 0 + c.set_double(0.890) + assert round(c.m_double - 0.890, 8) == 0 + c.set_double_cr(0.012) + assert round(c.m_double - 0.012, 8) == 0 + c.m_ldouble = 0.876 + assert round(c.get_ldouble() - 0.876, 8) == 0 + c.set_ldouble(0.098) + assert round(c.m_ldouble - 0.098, 8) == 0 + c.set_ldouble_cr(0.210) + assert round(c.m_ldouble - 0.210, 8) == 0 + + names = ["float", "double", "ldouble"] + for i in range(len(names)): + setattr(c, "m_" + names[i], cppjit.default) + assert eval("c.get_%s()" % names[i]) == 0.0 + + for i in range(len(names)): + getattr(c, "set_" + names[i])(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0.0 + + for i in range(len(names)): + getattr(c, "set_" + names[i] + "_cr")(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0.0 + + # (non-)writing of enum types + raises(TypeError, setattr, CppjitTestData, "kNothing", 42) + + # arrays; there will be pointer copies, so destroy the current ones + c.destroy_arrays() + + # integer arrays + names = ["uchar", "short", "ushort", "int", "uint", "long", "ulong", "byte"] + + import array + + a = range(self.N) + atypes = ["B", "h", "H", "i", "I", "l", "L", "B"] + for j in range(len(names)): + b = array.array(atypes[j], a) + setattr(c, "m_" + names[j] + "_array", b) # buffer copies + for i in range(self.N): + assert eval("c.m_%s_array[i]" % names[j]) == b[i] + + setattr(c, "m_" + names[j] + "_array2", b) # pointer copies + assert 3 < self.N + b[3] = 28 + for i in range(self.N): + assert eval("c.m_%s_array2[i]" % names[j]) == b[i] + + # can not write to constant data + assert c.m_const_int == 17 + raises(TypeError, setattr, c, "m_const_int", 71) + + c.__destruct__() + + def test03_array_passing(self): + """Test passing of array arguments""" + + import array + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + a = range(self.N) + # test arrays in mixed order, to give overload resolution a workout + for t in ["d", "i", "f", "H", "I", "h", "L", "l"]: + b = array.array(t, a) + + # typed passing + ca = c.pass_array(b) + if t != "l": + assert type(ca[0]) == type(b[0]) + else: + assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 + assert len(b) == self.N + for i in range(self.N): + assert ca[i] == b[i] + + # void* passing + ca = eval("c.pass_void_array_%s(b)" % t) + if t != "l": + assert type(ca[0]) == type(b[0]) + else: + assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 + assert len(b) == self.N + for i in range(self.N): + assert ca[i] == b[i] + + # NULL/nullptr passing (will use short*) + assert not c.pass_array(0) + raises(Exception, c.pass_array(0).__getitem__, 0) # raises SegfaultException + assert raises(TypeError, c.pass_array, None) + assert not c.pass_array(cppjit.nullptr) + raises(Exception, c.pass_array(cppjit.nullptr).__getitem__, 0) # id. id. + + c.__destruct__() + + def test04_class_read_access(self): + """Test read access to class public data and verify values""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # char types + assert CppjitTestData.s_char == "c" + assert c.s_char == "c" + assert CppjitTestData.s_uchar == "u" + assert c.s_uchar == "u" + assert CppjitTestData.s_wchar == "U" + assert c.s_wchar == "U" + assert CppjitTestData.s_char16 == "\u6c29" + assert c.s_char16 == "\u6c29" + assert CppjitTestData.s_char32 == "\U0001f34b" + assert c.s_char32 == "\U0001f34b" + + assert type(c.s_wchar) == pyunicode + assert type(CppjitTestData.s_wchar) == pyunicode + assert type(c.s_char16) == pyunicode + assert type(CppjitTestData.s_char16) == pyunicode + assert type(c.s_char32) == pyunicode + assert type(CppjitTestData.s_char32) == pyunicode + + # integer types + assert CppjitTestData.s_byte == ord("b") + assert c.s_byte == ord("b") + assert CppjitTestData.s_int8 == -87 + assert c.s_int8 == -87 + assert CppjitTestData.s_uint8 == 87 + assert c.s_uint8 == 87 + assert CppjitTestData.s_short == -101 + assert c.s_short == -101 + assert c.s_ushort == 255 + assert CppjitTestData.s_ushort == 255 + assert CppjitTestData.s_int == -202 + assert c.s_int == -202 + assert c.s_uint == 202 + assert CppjitTestData.s_uint == 202 + assert CppjitTestData.s_long == -pylong(303) + assert c.s_long == -pylong(303) + assert c.s_ulong == pylong(303) + assert CppjitTestData.s_ulong == pylong(303) + assert CppjitTestData.s_llong == -pylong(404) + assert c.s_llong == -pylong(404) + assert c.s_ullong == pylong(404) + assert CppjitTestData.s_ullong == pylong(404) + + # floating point types + assert round(CppjitTestData.s_float + 606.0, 5) == 0 + assert round(c.s_float + 606.0, 5) == 0 + assert round(CppjitTestData.s_double + 707.0, 8) == 0 + assert round(c.s_double + 707.0, 8) == 0 + assert round(CppjitTestData.s_ldouble + 808.0, 8) == 0 + assert round(c.s_ldouble + 808.0, 8) == 0 + + c.__destruct__() + + def test05_class_data_write_access(self): + """Test write access to class public data and verify values""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # char types + CppjitTestData.s_char = "a" + assert c.s_char == "a" + c.s_char = "b" + assert CppjitTestData.s_char == "b" + CppjitTestData.s_uchar = "c" + assert c.s_uchar == "c" + c.s_uchar = "d" + assert CppjitTestData.s_uchar == "d" + raises(ValueError, setattr, CppjitTestData, "s_uchar", -1) + raises(ValueError, setattr, c, "s_uchar", -1) + CppjitTestData.s_wchar = "K" + assert c.s_wchar == "K" + c.s_wchar = "L" + assert CppjitTestData.s_wchar == "L" + CppjitTestData.s_char16 = "\u00df" + assert c.s_char16 == "\u00df" + c.s_char16 = "\u00ef" + assert CppjitTestData.s_char16 == "\u00ef" + CppjitTestData.s_char32 = "\u00df" + assert c.s_char32 == "\u00df" + c.s_char32 = "\u00ef" + assert CppjitTestData.s_char32 == "\u00ef" + + # integer types + c.s_byte = 66 + assert CppjitTestData.s_byte == 66 + CppjitTestData.s_byte = 66 + assert c.s_byte == 66 + c.s_short = -102 + assert CppjitTestData.s_short == -102 + CppjitTestData.s_short = -203 + assert c.s_short == -203 + c.s_ushort = 127 + assert CppjitTestData.s_ushort == 127 + CppjitTestData.s_ushort = 227 + assert c.s_ushort == 227 + CppjitTestData.s_int = -234 + assert c.s_int == -234 + c.s_int = -321 + assert CppjitTestData.s_int == -321 + CppjitTestData.s_uint = 1234 + assert c.s_uint == 1234 + c.s_uint = 4321 + assert CppjitTestData.s_uint == 4321 + raises(ValueError, setattr, c, "s_uint", -1) + raises(ValueError, setattr, CppjitTestData, "s_uint", -1) + CppjitTestData.s_long = -pylong(87) + assert c.s_long == -pylong(87) + c.s_long = pylong(876) + assert CppjitTestData.s_long == pylong(876) + CppjitTestData.s_ulong = pylong(876) + assert c.s_ulong == pylong(876) + c.s_ulong = pylong(678) + assert CppjitTestData.s_ulong == pylong(678) + raises(ValueError, setattr, CppjitTestData, "s_ulong", -1) + raises(ValueError, setattr, c, "s_ulong", -1) + + # floating point types + CppjitTestData.s_float = -3.1415 + assert round(c.s_float, 5) == -3.1415 + c.s_float = 3.1415 + assert round(CppjitTestData.s_float, 5) == 3.1415 + import math + + c.s_double = -math.pi + assert CppjitTestData.s_double == -math.pi + CppjitTestData.s_double = math.pi + assert c.s_double == math.pi + c.s_ldouble = -math.pi + assert CppjitTestData.s_ldouble == -math.pi + CppjitTestData.s_ldouble = math.pi + assert c.s_ldouble == math.pi + + c.__destruct__() + + def test06_range_access(self): + """Test the ranges of integer types""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # TODO: should these be TypeErrors, or should char/bool raise + # ValueErrors? In any case, consistency is needed ... + raises(ValueError, setattr, c, "m_uint", -1) + raises(ValueError, setattr, c, "m_ulong", -1) + + c.__destruct__() + + def test07_type_conversions(self): + """Test conversions between builtin types""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + c.m_double = -1 + assert round(c.m_double + 1.0, 8) == 0 + + raises(TypeError, setattr, c.m_double, "c") + raises(TypeError, setattr, c.m_int, -1.0) + raises(TypeError, setattr, c.m_int, 1.0) + raises(TypeError, setattr, c.m_long, 3.14) + raises(TypeError, setattr, c.m_ulong, 3.14) + raises(TypeError, setattr, c.m_llong, 3.14) + raises(TypeError, setattr, c.m_ullong, 3.14) + + raises(TypeError, c.set_int, 3.14) + raises(TypeError, c.set_long, 3.14) + raises(TypeError, c.set_ulong, 3.14) + raises(TypeError, c.set_llong, 3.14) + raises(TypeError, c.set_ullong, 3.14) + + raises(TypeError, c.set_int_cr, 3.14) + raises(TypeError, c.set_long_cr, 3.14) + raises(TypeError, c.set_ulong_cr, 3.14) + raises(TypeError, c.set_llong_cr, 3.14) + raises(TypeError, c.set_ullong_cr, 3.14) + + c.__destruct__() + + def test08_global_builtin_type(self): + """Test access to a global builtin type""" + + import cppjit + + gbl = cppjit.gbl + + assert gbl.g_int == gbl.get_global_int() + + gbl.set_global_int(32) + assert gbl.get_global_int() == 32 + assert gbl.g_int == 32 + + gbl.g_int = 22 + assert gbl.get_global_int() == 22 + assert gbl.g_int == 22 + + # if setting before checking, the change must be reflected on the C++ side + gbl.g_some_global_string = "Python" + assert gbl.get_some_global_string() == "Python" + assert gbl.g_some_global_string2 == "C++" + assert gbl.get_some_global_string2() == "C++" + gbl.g_some_global_string2 = "Python" + assert gbl.get_some_global_string2() == "Python" + + assert gbl.g_some_global_string16 == "z\u00df\u6c34" + assert gbl.g_some_global_string32 == "z\u00df\u6c34\U0001f34c" + + NS = gbl.SomeStaticDataNS + NS.s_some_static_string = "Python" + assert NS.get_some_static_string() == "Python" + assert NS.s_some_static_string2 == "C++" + assert NS.get_some_static_string2() == "C++" + NS.s_some_static_string2 = "Python" + assert NS.get_some_static_string2() == "Python" + + # verify that non-C++ data can still be set + gbl.g_python_only = "Python" + assert gbl.g_python_only == "Python" + + NS.s_python_only = "Python" + assert NS.s_python_only == "Python" + + def test08a_global_object(self): + """Test access to global objects by value""" + + import cppjit + + gbl = cppjit.gbl + + assert gbl.gData.fData == 5.0 + + def test09_global_ptr(self): + """Test access of global objects through a pointer""" + + import cppjit + + gbl = cppjit.gbl + + with raises(ReferenceError): + gbl.g_pod.m_int + + c = gbl.CppjitTestPod() + c.m_int = 42 + c.m_double = 3.14 + + gbl.set_global_pod(c) + assert gbl.is_global_pod(c) + assert gbl.g_pod.m_int == 42 + assert gbl.g_pod.m_double == 3.14 + + d = gbl.get_global_pod() + assert gbl.is_global_pod(d) + assert c is d + assert id(c) == id(d) + + e = gbl.CppjitTestPod() + e.m_int = 43 + e.m_double = 2.14 + + gbl.g_pod = e + assert gbl.is_global_pod(e) + assert gbl.g_pod.m_int == 43 + assert gbl.g_pod.m_double == 2.14 + + def test10_enum(self): + """Test access to enums""" + + import cppjit + + gbl = cppjit.gbl + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + # test that the enum is accessible as a type + assert CppjitTestData.EWhat + + assert CppjitTestData.kNothing == 6 + assert CppjitTestData.kSomething == 111 + assert CppjitTestData.kLots == 42 + + assert CppjitTestData.EWhat(CppjitTestData.kNothing) == CppjitTestData.kNothing + assert CppjitTestData.EWhat(6) == CppjitTestData.kNothing + # TODO: only allow instantiations with correct values (C++11) + + assert c.get_enum() == CppjitTestData.kNothing + assert c.m_enum == CppjitTestData.kNothing + + c.m_enum = CppjitTestData.kSomething + assert c.get_enum() == CppjitTestData.kSomething + assert c.m_enum == CppjitTestData.kSomething + + c.set_enum(CppjitTestData.kLots) + assert c.get_enum() == CppjitTestData.kLots + assert c.m_enum == CppjitTestData.kLots + + assert c.s_enum == CppjitTestData.s_enum + assert c.s_enum == CppjitTestData.kNothing + assert CppjitTestData.s_enum == CppjitTestData.kNothing + + c.s_enum = CppjitTestData.kSomething + assert c.s_enum == CppjitTestData.s_enum + assert c.s_enum == CppjitTestData.kSomething + assert CppjitTestData.s_enum == CppjitTestData.kSomething + + # global enums + assert gbl.EFruit # test type accessible + assert gbl.kApple == 78 + assert gbl.kBanana == 29 + assert gbl.kCitrus == 34 + assert gbl.EFruit.__name__ == "EFruit" + assert gbl.EFruit.__cpp_name__ == "EFruit" + + assert gbl.EFruit.kApple == 78 + assert gbl.EFruit.kBanana == 29 + assert gbl.EFruit.kCitrus == 34 + + assert gbl.NamedClassEnum.E1 == 42 + assert gbl.NamedClassEnum.__name__ == "NamedClassEnum" + assert gbl.NamedClassEnum.__cpp_name__ == "NamedClassEnum" + + assert gbl.EnumSpace.E + assert gbl.EnumSpace.EnumClass.E1 == -1 # anonymous + assert gbl.EnumSpace.EnumClass.E2 == -1 # named type + + assert gbl.EnumSpace.NamedClassEnum.E1 == -42 + assert gbl.EnumSpace.NamedClassEnum.__name__ == "NamedClassEnum" + assert gbl.EnumSpace.NamedClassEnum.__cpp_name__ == "EnumSpace::NamedClassEnum" + + raises(TypeError, setattr, gbl.EFruit, "kBanana", 42) + + assert gbl.g_enum == gbl.EFruit.kBanana + gbl.g_enum = gbl.EFruit.kCitrus + assert gbl.g_enum == gbl.EFruit.kCitrus + + # typedef enum + assert gbl.EnumSpace.letter_code + assert gbl.EnumSpace.AA == 1 + assert gbl.EnumSpace.BB == 2 + + def test11_typed_enums(self): + """Determine correct types of enums""" + + import cppjit + + cppjit.cppdef("""\ + namespace TrueEnumTypes { + class Test { + enum nums { ZERO = 0, ONE = 1 }; + enum dir : char { left = 'l', right = 'r' }; + enum rank : long { FIRST = 1, SECOND }; + enum vraioufaux : bool { faux = false, vrai = true }; + }; }""") + + sc = cppjit.gbl.TrueEnumTypes.Test + + assert sc.nums.ZERO == 0 + assert sc.nums.ONE == 1 + assert type(sc.nums.ZERO) == sc.nums + assert isinstance(sc.nums.ZERO, int) + assert "int" in repr(sc.nums.ZERO) + assert str(sc.nums.ZERO) == "0" + + assert sc.dir.left == "l" + assert sc.dir.right == "r" + assert type(sc.dir.left) == sc.dir + assert isinstance(sc.dir.left, str) + assert "char" in repr(sc.dir.left) + assert str(sc.dir.left) == "'l'" + + assert sc.rank.FIRST == 1 + assert sc.rank.SECOND == 2 + assert type(sc.rank.FIRST) == sc.rank + assert isinstance(sc.rank.FIRST, pylong) + assert "long" in repr(sc.rank.FIRST) + assert str(sc.rank.FIRST) == "1" or str(sc.rank.FIRST) == "1L" + + assert sc.vraioufaux.faux == False + assert sc.vraioufaux.vrai == True + assert type(sc.vraioufaux.faux) == sc.vraioufaux + assert isinstance(sc.vraioufaux.faux, int) # no bool as base class + assert "bool" in repr(sc.vraioufaux.faux) + + def test12_enum_scopes(self): + """Enum accessibility and scopes""" + + import cppjit + + cppjit.cppdef("""\ + enum { g_one = 1, g_two = 2 }; + enum GEnum1 { g_three = 3, g_four = 4 }; + enum class GEnum2 { g_five = 5, g_six = 6 }; + + namespace EnumScopes { + enum { n_one = 1, n_two = 2 }; + enum NEnum1 { n_three = 3, n_four = 4 }; + enum class NEnum2 { n_five = 5, n_six = 6 }; + } + + class EnumClass { + public: + enum { c_one = 1, c_two = 2 }; + enum CEnum1 { c_three = 3, c_four = 4 }; + enum class CEnum2 { c_five = 5, c_six = 6 }; + }; """) + + gn = cppjit.gbl + assert not hasattr(gn, "n_one") + assert not hasattr(gn, "c_one") + assert gn.g_two == 2 + assert gn.g_four == 4 + assert gn.GEnum1.g_three == 3 + assert gn.GEnum1.g_three == gn.g_three + assert type(gn.GEnum1.g_three) == type(gn.g_three) + assert not hasattr(gn, "g_five") + assert gn.GEnum2.g_six == 6 + + ns = cppjit.gbl.EnumScopes + assert not hasattr(ns, "g_one") + assert not hasattr(ns, "c_one") + assert ns.n_two == 2 + assert ns.n_four == 4 + assert ns.NEnum1.n_three == 3 + assert ns.NEnum1.n_three == ns.n_three + assert type(ns.NEnum1.n_three) == type(ns.n_three) + assert not hasattr(ns, "n_five") + assert ns.NEnum2.n_six == 6 + + cl = cppjit.gbl.EnumClass + assert not hasattr(cl, "g_one") + assert not hasattr(cl, "n_one") + assert cl.c_two == 2 + assert cl.c_four == 4 + assert cl.CEnum1.c_three == 3 + assert cl.CEnum1.c_three == cl.c_three + assert type(cl.CEnum1.c_three) == type(cl.c_three) + assert not hasattr(cl, "c_five") + assert cl.CEnum2.c_six == 6 + + def test13_string_passing(self): + """Test passing/returning of a const char*""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert c.get_valid_string("aap") == "aap" + assert c.get_invalid_string() == "" + + assert c.get_valid_wstring("aap") == "aap" + assert c.get_invalid_wstring() == "" + + assert c.get_valid_string16("z\u00df\u6c34") == "z\u00df\u6c34" + assert c.get_invalid_string16() == "" + + assert ( + c.get_valid_string32("z\u00df\u6c34\U0001f34c") == "z\u00df\u6c34\U0001f34c" + ) + assert c.get_invalid_string32() == "" + + def test14_copy_constructor(self): + """Test copy constructor""" + + import cppjit + + FourVector = cppjit.gbl.FourVector + + t1 = FourVector(1.0, 2.0, 3.0, -4.0) + t2 = FourVector(0.0, 0.0, 0.0, 0.0) + t3 = FourVector(t1) + + assert t1 == t3 + assert t1 != t2 + + for i in range(4): + assert t1[i] == t3[i] + + def test15_object_returns(self): + """Test access to and return of PODs""" + + import cppjit + + c = cppjit.gbl.CppjitTestData() + + assert c.m_pod.m_int == 888 + assert c.m_pod.m_double == 3.14 + + pod = c.get_pod_val() + assert pod.m_int == 888 + assert pod.m_double == 3.14 + + assert c.get_pod_val_ptr().m_int == 888 + assert c.get_pod_val_ptr().m_double == 3.14 + c.get_pod_val_ptr().m_int = 777 + assert c.get_pod_val_ptr().m_int == 777 + + assert c.get_pod_val_ref().m_int == 777 + assert c.get_pod_val_ref().m_double == 3.14 + c.get_pod_val_ref().m_int = 666 + assert c.get_pod_val_ref().m_int == 666 + + assert c.get_pod_ptrref().m_int == 666 + assert c.get_pod_ptrref().m_double == 3.14 + + def test16_object_arguments(self): + """Test setting and returning of a POD through arguments""" + + import cppjit + + c = cppjit.gbl.CppjitTestData() + assert c.m_pod.m_int == 888 + assert c.m_pod.m_double == 3.14 + + p = cppjit.gbl.CppjitTestPod() + p.m_int = 123 + assert p.m_int == 123 + p.m_double = 321.0 + assert p.m_double == 321.0 + + c.set_pod_val(p) + assert c.m_pod.m_int == 123 + assert c.m_pod.m_double == 321.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_ptr_in(p) + assert c.m_pod.m_int == 123 + assert c.m_pod.m_double == 321.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_ptr_out(p) + assert p.m_int == 888 + assert p.m_double == 3.14 + + p.m_int = 555 + p.m_double = 666.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_ref(p) + assert c.m_pod.m_int == 555 + assert c.m_pod.m_double == 666.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_ptrptr_in(p) + assert c.m_pod.m_int == 555 + assert c.m_pod.m_double == 666.0 + assert p.m_int == 555 + assert p.m_double == 666.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_void_ptrptr_in(p) + assert c.m_pod.m_int == 555 + assert c.m_pod.m_double == 666.0 + assert p.m_int == 555 + assert p.m_double == 666.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_ptrptr_out(p) + assert c.m_pod.m_int == 888 + assert c.m_pod.m_double == 3.14 + assert p.m_int == 888 + assert p.m_double == 3.14 + + p.m_int = 777 + p.m_double = 888.0 + + c = cppjit.gbl.CppjitTestData() + c.set_pod_void_ptrptr_out(p) + assert c.m_pod.m_int == 888 + assert c.m_pod.m_double == 3.14 + assert p.m_int == 888 + assert p.m_double == 3.14 + + def test17_nullptr_passing(self): + """Integer 0 ('NULL') and nullptr allowed to pass through instance*""" + + import cppjit + + for o in (0, cppjit.nullptr): + c = cppjit.gbl.CppjitTestData() + assert c.m_pod.m_int == 888 + assert c.m_pod.m_double == 3.14 + assert not not c.m_ppod + + c.set_pod_ptr(o) + assert not c.m_ppod + assert not c.get_pod_ptr() + + def test18_respect_privacy(self): + """Test that privacy settings are respected""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) + + raises(AttributeError, getattr, c, "m_owns_arrays") + + c.__destruct__() + + def test19_object_and_pointer_comparisons(self): + """Verify object and pointer comparisons""" + + import cppjit + + gbl = cppjit.gbl + + c1 = cppjit.bind_object(0, gbl.CppjitTestData) + assert not c1 + + c2 = cppjit.bind_object(0, gbl.CppjitTestData) + assert c1 == c2 + assert c2 == c1 + + # FourVector overrides operator== + l1 = cppjit.bind_object(0, gbl.FourVector) + assert not l1 + + assert c1 != l1 + assert l1 != c1 + + l2 = cppjit.bind_object(0, gbl.FourVector) + assert l1 == l2 + assert l2 == l1 + + l3 = gbl.FourVector(1, 2, 3, 4) + l4 = gbl.FourVector(1, 2, 3, 4) + l5 = gbl.FourVector(4, 3, 2, 1) + assert l3 == l4 + assert l4 == l3 + + assert l3 + assert l3 != l5 # like this to ensure __ne__ is called + assert l5 != l3 # id. + + def test20_object_comparisons_with_cpp__eq__(self): + """Comparisons with C++ providing __eq__/__ne__""" + + import cppjit + + cppjit.cppdef(""" + namespace MoreComparisons { + struct Comparable1 { + Comparable1(int i) : fInt(i) {} + int fInt; + static bool __eq__(const Comparable1& self, const Comparable1& other){ + return self.fInt == other.fInt; + } + static bool __ne__(const Comparable1& self, const Comparable1& other){ + return self.fInt != other.fInt; + } + }; + + struct Comparable2 { + Comparable2(int i) : fInt(i) {} + int fInt; + bool __eq__(const Comparable2& other){ + return fInt == other.fInt; + } + bool __ne__(const Comparable2& other){ + return fInt != other.fInt; + } + }; }""") + + ns = cppjit.gbl.MoreComparisons + + c1_1 = ns.Comparable1(42) + c1_2 = ns.Comparable1(42) + c1_3 = ns.Comparable1(43) + + assert ns.Comparable1.__dict__["__eq__"](c1_1, c1_2) + assert not ns.Comparable1.__dict__["__eq__"](c1_1, c1_3) + assert not ns.Comparable1.__dict__["__ne__"](c1_1, c1_2) + assert ns.Comparable1.__dict__["__ne__"](c1_1, c1_3) + + # the following works as a side-effect of a workaround for vector calls and + # it is probably preferable to have it working, so leave the discrepancy for + # now: python's aggressive end-of-life schedule will catch up soon enough + if 0x3080000 <= sys.hexversion: + assert c1_1 == c1_2 + assert not c1_1 != c1_2 + else: + with raises(TypeError): + c1_1 == c1_2 + with raises(TypeError): + c1_1 != c1_2 + + c2_1 = ns.Comparable2(27) + c2_2 = ns.Comparable2(27) + c2_3 = ns.Comparable2(28) + + assert c2_1 == c2_1 + assert c2_1 == c2_2 + assert not c2_1 == c2_3 + assert not c2_1 != c2_1 + assert not c2_1 != c2_2 + assert c2_1 != c2_3 + + def test21_object_validity(self): + """Test object validity checking""" + + from cppjit import gbl + + d = gbl.CppjitTestPod() + + assert d + assert not not d + + d2 = gbl.get_null_pod() + + assert not d2 + + def test22_buffer_shapes(self): + """Correctness of declared buffer shapes""" + + import cppjit + + cppjit.cppdef("""\ + namespace ShapeTester { + enum Enum{One, Two, Three}; + + template + struct Foo { + T a[5]; + T aa[5][4]; + T aaa[5][4][3]; + }; }""") + + ns = cppjit.gbl.ShapeTester + + for dtype in [ + "int", + "double", + "long", + "bool", + "char", + "void*", + "ShapeTester::Enum", + ]: + foo = ns.Foo[dtype]() + if dtype != "char": + assert foo.a.shape == (5,) + else: + # TODO: verify the following is for historic reasons and should be modified + # once bug #344 (bitbucket) is fixed + assert len(foo.a) == 5 + assert foo.aa.shape == (5, 4) + assert foo.aaa.shape == (5, 4, 3) + + def test23_buffer_reshaping(self): + """Test usage of buffer sizing""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + for func in [ + "get_bool_array", + "get_bool_array2", + "get_uchar_array", + "get_uchar_array2", + "get_ushort_array", + "get_ushort_array2", + "get_int_array", + "get_int_array2", + "get_uint_array", + "get_uint_array2", + "get_long_array", + "get_long_array2", + "get_ulong_array", + "get_ulong_array2", + "get_byte_array", + "get_byte_array2", + ]: + arr = getattr(c, func)() + arr.reshape((self.N,)) + assert len(arr) == self.N + + raises(ValueError, arr.reshape, (1, 2)) + assert len(arr) == self.N + + raises(TypeError, arr.reshape, 2 * self.N) + assert len(arr) == self.N + + l = list(arr) + for i in range(self.N): + assert arr[i] == l[i] + + # a fixed-size array accepts only its own shape, and stays intact + arr = c.m_int_array + assert arr.shape == (self.N,) + arr.reshape((self.N,)) + arr.shape = (self.N,) + assert arr.shape == (self.N,) + assert list(arr) == list(c.m_int_array) + + # (2, 3) was accepted when sizes were compared as sums (2 + 3 == N) + raises(ValueError, arr.reshape, (2, 3)) + raises(ValueError, arr.reshape, (self.N + 1,)) + raises(ValueError, arr.reshape, (-1,)) + assert arr.shape == (self.N,) + assert list(arr) == list(c.m_int_array) + + # shapes that do not describe an array (-1 means "unknown" and is fine) + arr = c.get_int_array() + raises(ValueError, arr.reshape, ()) + raises(ValueError, arr.reshape, (-2,)) + raises(ValueError, arr.reshape, (sys.maxsize,)) + raises(TypeError, arr.reshape, (1.5, 2)) + + # sizing to (0,) does not fix the size of an unknown view, others do + arr = c.get_int_array() + arr.reshape((-1,)) + assert arr.shape[0] > 0 + assert len(arr) == arr.shape[0] + arr.reshape((0,)) + assert len(arr) == 0 + assert list(arr) == [] + arr.shape = (self.N,) + assert arr.shape == (self.N,) + assert len(list(arr)) == self.N + with raises(ValueError): + arr.shape = (2 * self.N,) + with raises(TypeError): + del arr.shape + assert arr.shape == (self.N,) + + # a flexible array member is of unknown size whatever its type; sizing + # it counts bytes in element strides, not the (overridden) itemsize + cppjit.cppdef("""\ + namespace ReshapeFlex { + struct Names { int n; const char* names[]; }; + }""") + + names = cppjit.gbl.ReshapeFlex.Names() + arr = names.names + arr.reshape((3,)) + assert arr.shape == (3,) + assert memoryview(arr).nbytes == 3 * memoryview(arr).strides[0] + + def test24_voidp(self): + """Test usage of void* data""" + + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + + assert not cppjit.nullptr + + assert c.s_voidp is cppjit.nullptr + assert CppjitTestData.s_voidp is cppjit.nullptr + + assert c.m_voidp is cppjit.nullptr + assert c.get_voidp() is cppjit.nullptr + + c2 = CppjitTestData() + assert c2.m_voidp is cppjit.nullptr + c.set_voidp(c2.m_voidp) + assert c.m_voidp is cppjit.nullptr + c.set_voidp(c2.get_voidp()) + assert c.m_voidp is cppjit.nullptr + c.set_voidp(cppjit.nullptr) + assert c.m_voidp is cppjit.nullptr + + c.set_voidp(c2) + + def address_equality_test(a, b): + assert cppjit.addressof(a) == cppjit.addressof(b) + b2 = cppjit.bind_object(a, CppjitTestData) + assert b is b2 # memory regulator recycles + b3 = cppjit.bind_object(cppjit.addressof(a), CppjitTestData) + assert b is b3 # likewise + + address_equality_test(c.m_voidp, c2) + address_equality_test(c.get_voidp(), c2) + + def null_test(null): + c.m_voidp = null + assert c.m_voidp is cppjit.nullptr + + map(null_test, [0, cppjit.nullptr]) + + c.m_voidp = c2 + address_equality_test(c.m_voidp, c2) + address_equality_test(c.get_voidp(), c2) + + c.s_voidp = c2 + address_equality_test(c.s_voidp, c2) + + cppjit.cppdef("""\ + namespace VoidP { + void* vvv[3][5][7]; + struct Init { + Init() { + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 5; ++j) { + for (size_t k = 0; k < 7; ++k) + vvv[i][j][k] = (void*)(i+j+k); + } + } + } + } _init; }""") + + ns = cppjit.gbl.VoidP + for i in range(3): + for j in range(5): + for k in range(7): + assert int(ns.vvv[i, j, k]) == i + j + k + + def test25_byte_arrays(self): + """Usage of unsigned char* as byte array and std::byte*""" + + import array + import ctypes + + import cppjit + + buf = b"123456789" + total = 0 + for c in buf: + try: + total += ord(c) # p2 + except TypeError: + total += c # p3 + + def run(self, f, buf, total): + + # The following create a unique type for fixed-size C arrays: ctypes.c_char_Array_9 + # and neither inherits from a non-sized type nor implements the buffer interface. + # As such, it can't be handled. TODO? + # pbuf = ctypes.create_string_buffer(len(buf), buf) + # assert f(pbuf, len(buf)) == total + + pbuf = array.array("B", buf) + assert f(pbuf, len(buf)) == total + + pbuf = (ctypes.c_ubyte * len(buf)).from_buffer_copy(buf) + assert f(pbuf, len(buf)) == total + + pbuf = ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte * len(buf)))[0] + assert f(pbuf, len(buf)) == total + + run(self, cppjit.gbl.sum_uc_data, buf, total) + run(self, cppjit.gbl.sum_byte_data, buf, total) + + @mark.xfail(condition=IS_MAC, run=False, reason="Crashes on OSX") + def test26_function_pointers(self): + """Function pointer passing""" + + import cppjit + + fi1 = cppjit.gbl.sum_of_int1 + fi2 = cppjit.gbl.sum_of_int2 + fd = cppjit.gbl.sum_of_double + fdd = cppjit.gbl.call_double_double + + assert 5 == fi1(2, 3) + assert 5.0 == fd(5.0, 0.0) + + raises(TypeError, fdd, fi1, 2, 3) + + assert 5.0 == fdd(fd, 5.0, 0.0) + assert -1.0 == fdd(cppjit.nullptr, 5.0, 0.0) + + fip = cppjit.gbl.sum_of_int_ptr + assert 5 == fip(2, 3) + + cppjit.gbl.sum_of_int_ptr = cppjit.gbl.sum_of_int2 + assert 7 == cppjit.gbl.sum_of_int_ptr(2, 3) + + cppjit.gbl.sum_of_int_ptr = cppjit.nullptr + assert not cppjit.gbl.sum_of_int_ptr + with raises(cppjit.gbl.std.bad_function_call): + cppjit.gbl.sum_of_int_ptr(2, 3) + with raises(AttributeError): + cppjit.gbl.sim_of_int_ptr # incorrect spelling + + cppjit.gbl.sum_of_int_ptr = cppjit.gbl.sum_of_int1 + assert fip is cppjit.gbl.sum_of_int_ptr # b/c cached + + o = cppjit.gbl.sum_of_int_struct() + o.sum_of_int_ptr = cppjit.gbl.sum_of_int1 + assert 5 == o.sum_of_int_ptr(2, 3) + + o.sum_of_int_ptr = cppjit.gbl.sum_of_int2 + assert 7 == o.sum_of_int_ptr(2, 3) + + def sum_in_python(i1, i2): + return i1 - i2 + + cppjit.gbl.sum_of_int_ptr = sum_in_python + assert 1 == cppjit.gbl.call_sum_of_int(3, 2) + + def sum_in_python(i1, i2, i3): + return i1 + i2 + i3 + + cppjit.gbl.sum_of_int_ptr = sum_in_python + with raises(TypeError): + cppjit.gbl.call_sum_of_int(3, 2) + + cppjit.cppdef(r"""\ + namespace FuncPtrReturn { + typedef std::string (*func_t)(void); + std::string hello() { return "Hello, World!"; } + func_t foo() { return hello; } + }""") + + ns = cppjit.gbl.FuncPtrReturn + assert ns.foo()() == "Hello, World!" + + @mark.xfail(condition=IS_MAC, run=False, reason="Crashes") + def test27_callable_passing(self): + """Passing callables through function pointers""" + + import gc + + import cppjit + + fdd = cppjit.gbl.call_double_double + fii = cppjit.gbl.call_int_int + fv = cppjit.gbl.call_void + fri = cppjit.gbl.call_refi + frl = cppjit.gbl.call_refl + frd = cppjit.gbl.call_refd + + assert "call_double_double" in str(fdd) + assert "call_refd" in str(frd) + + def pyf(arg0, arg1): + return arg0 + arg1 + + assert type(fdd(pyf, 2, 3)) == float + assert fdd(pyf, 2, 3) == 5.0 + + assert type(fii(pyf, 2, 3)) == int + assert fii(pyf, 2, 3) == 5 + + def pyf(arg0, arg1): + return arg0 * arg1 + + assert fdd(pyf, 2, 3) == 6.0 + assert fii(pyf, 2, 3) == 6 + + # call of void function + global retval + retval = None + + def voidf(i): + global retval + retval = i + + assert retval is None + assert fv(voidf, 5) == None # noqa: E711 + assert retval == 5 + + # call of function with reference argument + def reff(ref): + ref.value = 5 + + assert fri(reff) == 5 + assert frl(reff) == pylong(5) + assert frd(reff) == 5.0 + + # callable that does not accept weak-ref + import math + + assert fdd(math.atan2, 0, 3.0) == 0.0 + + # error testing + raises(TypeError, fii, None, 2, 3) + + def pyf(arg0, arg1): + return arg0 / arg1 + + raises(ZeroDivisionError, fii, pyf, 1, 0) + + def pyd(arg0, arg1): + return arg0 * arg1 + + c = cppjit.gbl.StoreCallable(pyd) + assert c(3, 3) == 9.0 + + c.set_callable(lambda x, y: x * y) + assert c(3, 3) == 9.0 # life line protected + + c.__dict__.clear() # destroys life lines + gc.collect() + raises(TypeError, c, 3, 3) # lambda gone out of scope + + @mark.xfail(condition=IS_MAC, run=False, reason="Crashes on MacOS") + def test28_callable_through_function_passing(self): + """Passing callables through std::function""" + + import gc + + import cppjit + + fdd = cppjit.gbl.call_double_double_sf + fii = cppjit.gbl.call_int_int_sf + fv = cppjit.gbl.call_void_sf + fri = cppjit.gbl.call_refi_sf + frl = cppjit.gbl.call_refl_sf + frd = cppjit.gbl.call_refd_sf + + assert "call_double_double_sf" in str(fdd) + assert "call_refd_sf" in str(frd) + + def pyf(arg0, arg1): + return arg0 + arg1 + + assert type(fdd(pyf, 2, 3)) == float + assert fdd(pyf, 2, 3) == 5.0 + + assert type(fii(pyf, 2, 3)) == int + assert fii(pyf, 2, 3) == 5 + + def pyf(arg0, arg1): + return arg0 * arg1 + + assert fdd(pyf, 2, 3) == 6.0 + assert fii(pyf, 2, 3) == 6 + + # call of void function + global retval + retval = None + + def voidf(i): + global retval + retval = i + + assert retval is None + assert fv(voidf, 5) == None # noqa: E711 + assert retval == 5 + + # call of function with reference argument + def reff(ref): + ref.value = 5 + + assert fri(reff) == 5 + assert frl(reff) == pylong(5) + assert frd(reff) == 5.0 + + # callable that does not accept weak-ref + import math + + assert fdd(math.atan2, 0, 3.0) == 0.0 + + # error testing + raises(TypeError, fii, None, 2, 3) + + def pyf(arg0, arg1): + return arg0 / arg1 + + raises(ZeroDivisionError, fii, pyf, 1, 0) + + def pyd(arg0, arg1): + return arg0 * arg1 + + c = cppjit.gbl.StoreCallable(pyd) + assert c(3, 3) == 9.0 + + c.set_callable(lambda x, y: x * y) + assert c(3, 3) == 9.0 # life line protected + + c.__dict__.clear() # destroys life lines + gc.collect() + raises(TypeError, c, 3, 3) # lambda gone out of scope + + def test29_std_function_life_lines(self): + """Life lines to std::function data members""" + + import gc + + import cppjit + + cppjit.cppdef("""\ + namespace BoundMethod2StdFunction { + class Base { + public: + virtual ~Base() {} + + std::function execute; + std::string do_execute() { + return execute(); + } + }; } """) + + ns = cppjit.gbl.BoundMethod2StdFunction + + class Derived(ns.Base): + def __init__(self): + super(Derived, self).__init__() + self.execute = self.xyz + + def xyz(self): + return "xyz" + + d = Derived() + assert d.do_execute() == "xyz" + assert d.do_execute() == "xyz" + + gc.collect() + assert d.do_execute() == "xyz" + + d.execute = d.xyz + assert d.do_execute() == "xyz" + + def test30_multi_dim_arrays_of_builtins(test): + """Multi-dim arrays of builtins""" + + import ctypes + + import cppjit + + cppjit.cppdef(""" + template + struct MultiDimTest { + T* layers[nlayers]; + + MultiDimTest(int width, int height) { + for (int i=0; i= cppjit.sizeof(AoS.Foo) + arr = bar.fArr + arr.size = 1 + for f in arr: + assert type(f) == AoS.Foo + assert type(bar.fArr[0]) == AoS.Foo + + @mark.xfail(condition=IS_MAC, reason="Fails in OSX") + def test34_object_pointers(self): + """Read/write access to objects through pointers""" + + import cppjit + + c = cppjit.gbl.CppjitTestData() + + assert cppjit.gbl.CppjitTestData.s_strv == "Hello" + assert c.s_strv == "Hello" + assert not cppjit.gbl.CppjitTestData.s_strp + assert not c.s_strp + + c.s_strv = "World" + assert cppjit.gbl.CppjitTestData.s_strv == "World" + + # assign on nullptr is a pointer copy + sn = cppjit.gbl.std.string("aap") + cppjit.gbl.CppjitTestData.s_strp = sn + assert c.s_strp == "aap" + + # assign onto the existing object + cppjit.gbl.CppjitTestData.s_strp.__assign__(cppjit.gbl.std.string("noot")) + assert c.s_strp == "noot" + assert sn == "noot" # set through pointer + + def test35_restrict(self): + """Strip __restrict keyword from use""" + + import cppjit + + cppjit.cppdef( + "std::string restrict_call(const char*__restrict s) { return s; }" + ) + + assert cppjit.gbl.restrict_call("aap") == "aap" + + def test36_legacy_matrix(self): + """Handling of legacy matrix""" + + import cppjit + + cppjit.cppdef("""\ + namespace Pointer2D { + int** g_matrix; + + int** create_matrix(int n, int m) { + int** mat = (int**)malloc(n*sizeof(int*)); + int* arr = (int*)malloc(n*m*sizeof(int)); + for (int i = 0; i < n; ++i) { + mat[i] = arr + i*m; + for(int j = 0; j < m; ++j) { + mat[i][j] = 13+i*n+j; + } + } + g_matrix = mat; + return mat; + } + + bool destroy_matrix(int** mat, int n, int m) { + for (int i = 0; i < n; ++i) { + for (int j = 0; j < m; ++j) { + if (mat[i][j] != 13+i*n+j) + return false; + } + } + g_matrix = nullptr; + free(mat[0]); + free(mat); + return true; + } }""") + + ns = cppjit.gbl.Pointer2D + N, M = 2, 3 + m = ns.create_matrix(N, M) + g = ns.g_matrix + for i in range(N): + for j in range(M): + assert m[i][j] == 13 + i * N + j + assert g[i][j] == 13 + i * N + j + + assert ns.destroy_matrix(m, N, M) + + m = ns.create_matrix(N, M) + assert ns.destroy_matrix(ns.g_matrix, N, M) + + def test37_legacy_matrix_of_structs(self): + """Handling of legacy matrix of structs""" + + import cppjit + + cppjit.cppdef("""\ + namespace StructPointer2D { + typedef struct { + int x,y,z; // 'z' so that sizeof(xy) != sizeof(void*) + } xy; + + xy** g_matrix; + + xy** create_matrix(int n, int m) { + xy** mat = (xy**)malloc(n*sizeof(xy*)); + xy* arr = (xy*)malloc(n*m*sizeof(xy)); + for(int i=0; i fArr; }; + struct SomePOD_E { int* fPtrInt; }; + struct SomePOD_F { std::array* fPtrArr; }; + + namespace LotsOfPODS { + struct SomePOD_A { }; + struct SomePOD_B { int fInt; }; + struct SomePOD_C { int fInt; double fDouble; }; + struct SomePOD_D { std::array fArr; }; + struct SomePOD_E { int* fPtrInt; }; + struct SomePOD_F { std::array* fPtrArr; }; + }""") + + for ns in [cppjit.gbl, cppjit.gbl.LotsOfPODS]: + # no data member POD + assert ns.SomePOD_A() + + # single data member POD + b0 = ns.SomePOD_B() + assert b0.__python_owns__ + assert b0.fInt == 0 + b1 = ns.SomePOD_B(42) + assert b1.__python_owns__ + assert b1.fInt == 42 + b2 = ns.SomePOD_B(fInt=17) + assert b2.__python_owns__ + assert b2.fInt == 17 + + # dual data member POD + c0 = ns.SomePOD_C() + assert c0.__python_owns__ + assert c0.fInt == 0 + assert c0.fDouble == 0.0 + c1a = ns.SomePOD_C(42) + assert c1a.__python_owns__ + assert c1a.fInt == 42 + assert c1a.fDouble == 0.0 + c1b = ns.SomePOD_C(fInt=17) + assert c1b.__python_owns__ + assert c1b.fInt == 17 + assert c1b.fDouble == 0.0 + c1c = ns.SomePOD_C(fDouble=5.0) + assert c1c.__python_owns__ + assert c1c.fInt == 0 + assert c1c.fDouble == 5.0 + c2a = ns.SomePOD_C(88, 10.0) + assert c2a.__python_owns__ + assert c2a.fInt == 88 + assert c2a.fDouble == 10.0 + c2b = ns.SomePOD_C(fDouble=5.0, fInt=77) + assert c2b.__python_owns__ + assert c2b.fInt == 77 + assert c2b.fDouble == 5.0 + + # object type data member POD + d0 = ns.SomePOD_D() + assert d0.__python_owns__ + assert len(d0.fArr) == 3 + assert d0.fArr[0] == 0.0 + d1 = ns.SomePOD_D((1.0, 2.0, 3.0)) + assert d1.__python_owns__ + assert len(d1.fArr) == 3 + assert list(d1.fArr) == [1.0, 2.0, 3] + + # ptr type data member POD + e0 = ns.SomePOD_E() + assert e0.__python_owns__ + + # ptr to object type data member pOD + f0 = ns.SomePOD_F() + assert f0.__python_owns__ + arr = cppjit.gbl.std.array["double", 3]((1.0, 2.0, 3.0)) + f1 = ns.SomePOD_F(arr) + assert f1.__python_owns__ + assert len(f1.fPtrArr) == 3 + assert list(f1.fPtrArr) == [1.0, 2.0, 3] + + def test39_aggregates(self): + """Initializer construction of aggregates""" + + import cppjit + + cppjit.cppdef("""\ + namespace AggregateTest { + class Atom { + public: + using size_type = std::size_t; + + struct AtomicNumber { + size_type a_n = 0; + }; + + std::array coords; + }; }""") + + ns = cppjit.gbl.AggregateTest + + Z = ns.Atom.AtomicNumber(5) + assert Z.a_n == 5 + + a = ns.Aggregate1() + assert a.sInt == 17 + + a = ns.Aggregate2() + assert a.sInt == 27 + assert a.fInt == 42 + + a = ns.Aggregate2(fInt=13) + assert a.fInt == 13 + + cppjit.cppdef("""\ + namespace AggregateTest { + + typedef enum _TYPE { DATA=0, SHAPE } TYPE; + + typedef struct _Buf { + int val; + const char *name; + TYPE buf_type; + } Buf; }""") + + ns = cppjit.gbl.AggregateTest + + b = ns.Buf(val=10, name="aap", buf_type=ns.SHAPE) + + assert b.val == 10 + assert b.name == "aap" + assert b.buf_type == ns.SHAPE + + def test40_more_aggregates(self): + """More aggregate testings (used to fail/report errors)""" + + import cppjit + + cppjit.cppdef("""\ + namespace AggregateTest { + enum E { A=1 }; + + struct R1 { E e; }; + R1 make_R1() { return {A}; } + + struct S { + S(int x): x(x) {} + S(const S&) = delete; + int x; + }; }""") + + ns = cppjit.gbl.AggregateTest + + r1 = ns.make_R1() + assert r1.e == ns.E.A + + cppjit.cppdef("""\ + namespace AggregateTest { + struct R2 { + std::optional s = {}; + }; + + R2 make_R2() { + return {1}; + } }""") + + r2 = ns.make_R2() + assert r2.s.x == 1 + + def test41_complex_numpy_arrays(self): + """Usage of complex numpy arrays""" + + import cppjit + + try: + import numpy as np + except ImportError: + skip("numpy is not installed") + + cppjit.cppdef("""\ + namespace ComplexArrays { + typedef std::complex fcomp; + fcomp fcompdot(const fcomp* arr1, const fcomp* arr2, const int N) { + fcomp res{0.f, 0.f}; + for (int i=0; i dcomp; + dcomp dcompdot(const dcomp* arr1, const dcomp* arr2, const int N) { + dcomp res{0., 0.}; + for (int i=0; i
- return PyLong_FromUnsignedLong(*((unsigned long*)address)); -} - -bool CPyCppyy::ULongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned long, write it at
- unsigned long u = PyLongOrInt_AsULong(value); - if (u == (unsigned long)-1 && PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - u = (unsigned long)0; - } else - return false; - } - *((unsigned long*)address) = u; - return true; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UIntConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned int read at
- return PyLong_FromUnsignedLong(*((unsigned int*)address)); -} - -bool CPyCppyy::UIntConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned int, write it at
- unsigned long u = PyLongOrInt_AsULong(value); - if (u == (unsigned long)-1 && PyErr_Occurred()) - return false; - - if (u > (unsigned long)UINT_MAX) { - PyErr_SetString(PyExc_OverflowError, "value too large for unsigned int"); - return false; - } - - *((unsigned int*)address) = (unsigned int)u; - return true; -} - -//- floating point converters ------------------------------------------------ -CPPYY_IMPL_BASIC_CONVERTER_NB( - Float, float, double, c_float, PyFloat_FromDouble, PyFloat_AsDouble, 'f') -CPPYY_IMPL_BASIC_CONVERTER_NB( - Double, double, double, c_double, PyFloat_FromDouble, PyFloat_AsDouble, 'd') - -CPPYY_IMPL_BASIC_CONVERTER_NB( - LDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, c_longdouble, PyFloat_FromDouble, PyFloat_AsDouble, 'g') - -CPyCppyy::ComplexDConverter::ComplexDConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::complex"), keepControl) {} - -// special case for std::complex, maps it to/from Python's complex -bool CPyCppyy::ComplexDConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - const Py_complex& pc = PyComplex_AsCComplex(pyobject); - if (pc.real != -1.0 || !PyErr_Occurred()) { - fBuffer.real(pc.real); - fBuffer.imag(pc.imag); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - return this->InstanceConverter::SetArg(pyobject, para, ctxt); -} - -PyObject* CPyCppyy::ComplexDConverter::FromMemory(void* address) -{ - std::complex* dc = (std::complex*)address; - return PyComplex_FromDoubles(dc->real(), dc->imag()); -} - -bool CPyCppyy::ComplexDConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ - const Py_complex& pc = PyComplex_AsCComplex(value); - if (pc.real != -1.0 || !PyErr_Occurred()) { - std::complex* dc = (std::complex*)address; - dc->real(pc.real); - dc->imag(pc.imag); - return true; - } - return this->InstanceConverter::ToMemory(value, address, ctxt); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::DoubleRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ double&, set arg for call -#if PY_VERSION_HEX < 0x03000000 - if (RefFloat_CheckExact(pyobject)) { - para.fValue.fVoidp = (void*)&((PyFloatObject*)pyobject)->ob_fval; - para.fTypeCode = 'V'; - return true; - } -#endif - -#if PY_VERSION_HEX >= 0x02050000 - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_double)) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } -#endif - -// alternate, pass pointer from buffer - Py_ssize_t buflen = Utility::GetBuffer(pyobject, 'd', sizeof(double), para.fValue.fVoidp); - if (para.fValue.fVoidp && buflen) { - para.fTypeCode = 'V'; - return true; - } - -#if PY_VERSION_HEX < 0x02050000 - PyErr_SetString(PyExc_TypeError, "use cppyy.Double for pass-by-ref of doubles"); -#else - PyErr_SetString(PyExc_TypeError, "use ctypes.c_double for pass-by-ref of doubles"); -#endif - return false; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Float, float, c_float, PyFloat_AsDouble) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Double, double, c_double, PyFloat_AsDouble) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(LDouble, PY_LONG_DOUBLE, c_longdouble, PyFloat_AsDouble) - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidConverter::SetArg(PyObject*, Parameter&, CallContext*) -{ -// can't happen (unless a type is mapped wrongly), but implemented for completeness - PyErr_SetString(PyExc_SystemError, "void/unknown arguments can\'t be set"); - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::LLongConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ long long, set arg for call - if (!ImplicitBool(pyobject, ctxt)) - return false; - - para.fValue.fLLong = CPyCppyy_PyLong_AsStrictLongLong(pyobject); - if (PyErr_Occurred()) - return false; - para.fTypeCode = 'q'; - return true; -} - -PyObject* CPyCppyy::LLongConverter::FromMemory(void* address) -{ -// construct python object from C++ long long read at
- return PyLong_FromLongLong(*(PY_LONG_LONG*)address); -} - -bool CPyCppyy::LLongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ long long, write it at
- PY_LONG_LONG ll = PyLong_AsLongLong(value); - if (ll == -1 && PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - ll = (PY_LONG_LONG)0; - } else - return false; - } - *((PY_LONG_LONG*)address) = ll; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::ULLongConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ unsigned long long, set arg for call - if (!ImplicitBool(pyobject, ctxt)) - return false; - - para.fValue.fULLong = PyLongOrInt_AsULong64(pyobject); - if (PyErr_Occurred()) - return false; - para.fTypeCode = 'Q'; - return true; -} - -PyObject* CPyCppyy::ULLongConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned long long read at
- return PyLong_FromUnsignedLongLong(*(PY_ULONG_LONG*)address); -} - -bool CPyCppyy::ULLongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned long long, write it at
- PY_ULONG_LONG ull = PyLongOrInt_AsULong64(value); - if (PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - ull = (PY_ULONG_LONG)0; - } else - return false; - } - *((PY_ULONG_LONG*)address) = ull; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// construct a new string and copy it in new memory - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); - if (!cstr) { - // special case: allow ctypes c_char_p - auto error = CPyCppyy::Utility::FetchPyError(); - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - CPyCppyy::Utility::RestorePyError(error); - return false; - } - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; - - if (!ctxt->fPyContext) { - // use internal buffer as workaround - fBuffer = std::string(cstr, len); - if (fMaxSize != std::string::npos) - fBuffer.resize(fMaxSize, '\0'); // pad remainder of buffer as needed - cstr = fBuffer.c_str(); - } else - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - -// set the value and declare success - para.fValue.fVoidp = (void*)cstr; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::CStringConverter::FromMemory(void* address) -{ -// construct python object from C++ const char* read at
- if (address && *(void**)address) { - if (fMaxSize != std::string::npos) // need to prevent reading beyond boundary - return CPyCppyy_PyText_FromStringAndSize(*(char**)address, (Py_ssize_t)fMaxSize); - - if (*(void**)address == (void*)fBuffer.data()) // if we're buffering, we know the size - return CPyCppyy_PyText_FromStringAndSize((char*)fBuffer.data(), fBuffer.size()); - - // no idea about lentgth: cut on \0 - return CPyCppyy_PyText_FromString(*(char**)address); - } - -// empty string in case there's no address - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; -} - -bool CPyCppyy::CStringConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// convert to C++ const char*, write it at
- Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len); - if (!cstr) return false; - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; - -// if address is available, and it wasn't set by this converter, assume a byte-wise copy; -// otherwise assume a pointer copy (this relies on the converter to be used for properties, -// or for argument passing, but not both at the same time; this is currently the case) - void* ptrval = *(void**)address; - if (ptrval == (void*)fBuffer.data()) { - fBuffer = std::string(cstr, len); - *(void**)address = (void*)fBuffer.data(); - return true; - } else if (ptrval && HasLifeLine(ctxt, (intptr_t)ptrval)) { - ptrval = nullptr; - // fall through; ptrval is nullptr means we're managing it - } - -// the string is (going to be) managed by us: assume pointer copy - if (!ptrval) { - SetLifeLine(ctxt, value, (intptr_t)address); - *(void**)address = (void*)cstr; - return true; - } - -// the pointer value is non-zero and not ours: assume byte copy - if (fMaxSize != std::string::npos) - strncpy(*(char**)address, cstr, fMaxSize); // pads remainder - else - // coverity[secure_coding] - can't help it, it's intentional. - strcpy(*(char**)address, cstr); - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::WCStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// construct a new string and copy it in new memory - Py_ssize_t len = PyUnicode_GetSize(pyobject); - if (len == (Py_ssize_t)-1 && PyErr_Occurred()) - return false; - - fBuffer = (wchar_t*)realloc(fBuffer, sizeof(wchar_t)*(len+1)); - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(pyobject, fBuffer, len); - if (res == -1) - return false; // could free the buffer here - -// set the value and declare success - fBuffer[len] = L'\0'; - para.fValue.fVoidp = (void*)fBuffer; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::WCStringConverter::FromMemory(void* address) -{ -// construct python object from C++ wchar_t* read at
- if (address && *(wchar_t**)address) { - if (fMaxSize != std::wstring::npos) // need to prevent reading beyond boundary - return PyUnicode_FromWideChar(*(wchar_t**)address, (Py_ssize_t)fMaxSize); - // with unknown size - return PyUnicode_FromWideChar(*(wchar_t**)address, wcslen(*(wchar_t**)address)); - } - -// empty string in case there's no valid address - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); -} - -bool CPyCppyy::WCStringConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ wchar_t*, write it at
- Py_ssize_t len = PyUnicode_GetSize(value); - if (len == (Py_ssize_t)-1 && PyErr_Occurred()) - return false; - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)", 1) < 0) - return false; - - Py_ssize_t res = -1; - if (fMaxSize != std::wstring::npos) - res = CPyCppyy_PyUnicode_AsWideChar(value, *(wchar_t**)address, (Py_ssize_t)fMaxSize); - else - // coverity[secure_coding] - can't help it, it's intentional. - res = CPyCppyy_PyUnicode_AsWideChar(value, *(wchar_t**)address, len); - - if (res == -1) return false; - return true; -} - -//---------------------------------------------------------------------------- -#define CPYCPPYY_WIDESTRING_CONVERTER(name, type, encode, decode, snull) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ -/* change string encoding and copy into local buffer */ \ - PyObject* bstr = encode(pyobject); \ - if (!bstr) return false; \ - \ - Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ - fBuffer = (type*)realloc(fBuffer, len + sizeof(type)); \ - memcpy(fBuffer, PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len); \ - Py_DECREF(bstr); \ - \ - fBuffer[len/sizeof(type)] = snull; \ - para.fValue.fVoidp = (void*)fBuffer; \ - para.fTypeCode = 'p'; \ - return true; \ -} \ - \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ -/* construct python object from C++ * read at
*/ \ - if (address && *(type**)address) { \ - if (fMaxSize != std::wstring::npos) \ - return decode(*(const char**)address, (Py_ssize_t)fMaxSize*sizeof(type), nullptr, nullptr);\ - return decode(*(const char**)address, \ - std::char_traits::length(*(type**)address)*sizeof(type), nullptr, nullptr);\ - } \ - \ -/* empty string in case there's no valid address */ \ - type w = snull; \ - return decode((const char*)&w, 0, nullptr, nullptr); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */)\ -{ \ -/* convert to C++ *, write it at
*/ \ - PyObject* bstr = encode(value); \ - if (!bstr) return false; \ - \ - Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ - Py_ssize_t maxbytes = (Py_ssize_t)fMaxSize*sizeof(type); \ - \ -/* verify (too long string will cause truncation, no crash) */ \ - if (fMaxSize != std::wstring::npos && maxbytes < len) { \ - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)", 1) < 0) { \ - Py_DECREF(bstr); \ - return false; \ - } \ - len = maxbytes; \ - } \ - \ - memcpy(*((void**)address), PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len);\ - Py_DECREF(bstr); \ -/* debatable, but probably more convenient in most cases to null-terminate if enough space */\ - if (len/sizeof(type) < fMaxSize) (*(type**)address)[len/sizeof(type)] = snull;\ - return true; \ -} - -CPYCPPYY_WIDESTRING_CONVERTER(CString16, char16_t, PyUnicode_AsUTF16String, PyUnicode_DecodeUTF16, u'\0') -CPYCPPYY_WIDESTRING_CONVERTER(CString32, char32_t, PyUnicode_AsUTF32String, PyUnicode_DecodeUTF32, U'\0') - -//---------------------------------------------------------------------------- -bool CPyCppyy::NonConstCStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// attempt base class first (i.e. passing a string), but if that fails, try a buffer - if (this->CStringConverter::SetArg(pyobject, para, ctxt)) - return true; - -// apparently failed, try char buffer - PyErr_Clear(); - return CArraySetArg(pyobject, para, 'c', sizeof(char)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::NonConstCStringConverter::FromMemory(void* address) -{ -// assume this is a buffer access if the size is known; otherwise assume string - if (fMaxSize != std::string::npos) - return CPyCppyy_PyText_FromStringAndSize(*(char**)address, (Py_ssize_t)fMaxSize); - return this->CStringConverter::FromMemory(address); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, void*& address) -{ -// (1): C++11 style "null pointer" - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - address = nullptr; - return true; - } - -// (2): allow integer zero to act as a null pointer (C NULL), no deriveds - if (PyInt_CheckExact(pyobject) || PyLong_CheckExact(pyobject)) { - intptr_t val = (intptr_t)PyLong_AsLongLong(pyobject); - if (val == 0l) { - address = (void*)val; - return true; - } - - return false; - } - -// (3): opaque PyCapsule (CObject in older pythons) from somewhere - if (CPyCppyy_PyCapsule_CheckExact(pyobject)) { - address = (void*)CPyCppyy_PyCapsule_GetPointer(pyobject, nullptr); - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) -{ -// just convert pointer if it is a C++ object - CPPInstance* pyobj = GetCppInstance(pyobject); - if (pyobj) { - // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership()) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - para.fValue.fVoidp = pyobj->GetObject(); - para.fTypeCode = 'p'; - return true; - } - -// handle special cases - if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = 'p'; - return true; - } - -// allow ctypes voidp (which if got as a buffer will return void**, not void*); use -// isintance instead of an exact check, b/c c_void_p is the type mapper for typedefs -// of void* (typically opaque handles) - if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_void_p))) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - -// allow any other ctypes pointer type - if (IsCTypesArrayOrPointer(pyobject)) { - void** payload = (void**)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - if (payload) { - para.fValue.fVoidp = *payload; - para.fTypeCode = 'p'; - return true; - } - } - -// final try: attempt to get buffer - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - para.fTypeCode = 'p'; - return true; - } - -// give up - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidArrayConverter::FromMemory(void* address) -{ -// nothing sensible can be done, just return
as pylong - if (!address || *(uintptr_t*)address == 0) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - return CreatePointerView(*(uintptr_t**)address); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// just convert pointer if it is a C++ object - CPPInstance* pyobj = GetCppInstance(value); - if (pyobj) { - // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership()) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - *(void**)address = pyobj->GetObject(); - return true; - } - -// handle special cases - void* ptr = nullptr; - if (GetAddressSpecialCase(value, ptr)) { - *(void**)address = ptr; - return true; - } - -// final try: attempt to get buffer - void* buf = nullptr; - Py_ssize_t buflen = Utility::GetBuffer(value, '*', 1, buf, false); - if (!buf || buflen == 0) - return false; - - *(void**)address = buf; - return true; -} - -#if __cplusplus >= 202002L - -namespace CPyCppyy { - -class StdSpanConverter : public InstanceConverter { -public: - StdSpanConverter(std::string const &typeName, Cppyy::TCppType_t klass, bool keepControl = false) - : InstanceConverter{klass, keepControl}, fTypeName{typeName} - { - } - - ~StdSpanConverter() - { - if (fHasBuffer) { - PyBuffer_Release(&fBufinfo); - } - } - - bool SetArg(PyObject *, Parameter &, CallContext * = nullptr) override; - bool HasState() override { return true; } - -private: - std::string fTypeName; - std::span fBuffer; - bool fHasBuffer = false; - Py_buffer fBufinfo; -}; - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, CallContext *ctxt) -{ - auto typecodeFound = Utility::TypecodeMap().find(fTypeName); - -// attempt to get buffer if the C++ type maps to a buffer type - if (typecodeFound == Utility::TypecodeMap().end() || !PyObject_CheckBuffer(pyobject)) { - // Fall back to regular InstanceConverter - return this->InstanceConverter::SetArg(pyobject, para, ctxt); - } - - Py_ssize_t buflen = 0; - char typecode = typecodeFound->second; - memset(&fBufinfo, 0, sizeof(Py_buffer)); - - if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { - if (!strchr(fBufinfo.format, typecode)) { - PyErr_Format(PyExc_TypeError, - "buffer has incompatible type: expected '%c' for C++ type '%s', but got format '%s'", typecode, - fTypeName.c_str(), fBufinfo.format ? fBufinfo.format : ""); - PyBuffer_Release(&fBufinfo); - return false; - } - buflen = Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); - } - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - // We assume the layout for any std::span is the same, and just use - // std::span as a placeholder. Not elegant, but works. - fBuffer = std::span{(std::size_t *)para.fValue.fVoidp, static_cast(buflen)}; - fHasBuffer = true; - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -#endif // __cplusplus >= 202002L - -namespace { - -// Copy a buffer to memory address with an array converter. -template -bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, - const void * buf, Py_ssize_t buflen, - CPyCppyy::dims_t& shape, bool isFixed) -{ - if (buflen == 0) - return false; - - Py_ssize_t oldsz = 1; - for (Py_ssize_t idim = 0; idim < shape.ndim(); ++idim) { - if (shape[idim] == CPyCppyy::UNKNOWN_SIZE) { - oldsz = -1; - break; - } - oldsz *= shape[idim]; - } - if (shape.ndim() != CPyCppyy::UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { - PyErr_SetString(PyExc_ValueError, "buffer too large for value"); - return false; - } - - if (isFixed) - memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1)*sizeof(type)); - else { - *(type**)address = (type*)buf; - shape.ndim(1); - shape[0] = buflen; - SetLifeLine(ctxt, owner, (intptr_t)address); - } - return true; -} - -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ -CPyCppyy::name##ArrayConverter::name##ArrayConverter(cdims_t dims) : \ - fShape(dims) { \ - fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ -} \ - \ -bool CPyCppyy::name##ArrayConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - /* filter ctypes first b/c their buffer conversion will be wrong */ \ - bool convOk = false; \ - \ - /* 2-dim case: ptr-ptr types */ \ - if (fShape.ndim() == 2) { \ - if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ - /* special case: pass address of c_void_p buffer to return the address */\ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (LowLevelView_Check(pyobject) && \ - ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ - strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ - para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - \ - /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ - if (!convOk) { \ - PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ - if (Py_TYPE(pyobject) == ctypes_type) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'V'; \ - convOk = true; \ - } else if (IsPyCArgObject(pyobject)) { \ - CPyCppyy_tagPyCArgObject* carg = (CPyCppyy_tagPyCArgObject*)pyobject;\ - if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)carg->obj)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - } \ - \ - /* cast pointer type */ \ - if (!convOk) { \ - bool ismulti = fShape.ndim() > 1; \ - convOk = CArraySetArg(pyobject, para, code, ismulti ? sizeof(void*) : sizeof(type), true);\ - } \ - \ - /* memory management and offsetting */ \ - if (convOk) SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ - \ - return convOk; \ -} \ - \ -PyObject* CPyCppyy::name##ArrayConverter::FromMemory(void* address) \ -{ \ - if (!fIsFixed) \ - return CreateLowLevelView##suffix((type**)address, fShape); \ - return CreateLowLevelView##suffix(*(type**)address, fShape); \ -} \ - \ -bool CPyCppyy::name##ArrayConverter::ToMemory( \ - PyObject* value, void* address, PyObject* ctxt) \ -{ \ - if (fShape.ndim() <= 1 || fIsFixed) { \ - void* buf = nullptr; \ - Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf);\ - return ToArrayFromBuffer(value, address, ctxt, buf, buflen, fShape, fIsFixed);\ - } else { /* multi-dim, non-flat array; assume structure matches */ \ - void* buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ - Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(void*), buf);\ - if (buflen == 0) return false; \ - *(type**)address = (type*)buf; \ - SetLifeLine(ctxt, value, (intptr_t)address); \ - } \ - return true; \ -} - - -//---------------------------------------------------------------------------- -CPPYY_IMPL_ARRAY_CONVERTER(Bool, c_bool, bool, '?', ) -CPPYY_IMPL_ARRAY_CONVERTER(SChar, c_char, signed char, 'b', ) -CPPYY_IMPL_ARRAY_CONVERTER(UChar, c_ubyte, unsigned char, 'B', ) -CPPYY_IMPL_ARRAY_CONVERTER(Byte, c_ubyte, std::byte, 'B', ) -CPPYY_IMPL_ARRAY_CONVERTER(Int8, c_byte, int8_t, 'b', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(Int16, c_int16, int16_t, 'h', _i16) -CPPYY_IMPL_ARRAY_CONVERTER(Int32, c_int32, int32_t, 'i', _i32) -CPPYY_IMPL_ARRAY_CONVERTER(UInt8, c_ubyte, uint8_t, 'B', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(UInt16, c_uint16, uint16_t, 'H', _i16) -CPPYY_IMPL_ARRAY_CONVERTER(UInt32, c_uint32, uint32_t, 'I', _i32) -CPPYY_IMPL_ARRAY_CONVERTER(Short, c_short, short, 'h', ) -CPPYY_IMPL_ARRAY_CONVERTER(UShort, c_ushort, unsigned short, 'H', ) -CPPYY_IMPL_ARRAY_CONVERTER(Int, c_int, int, 'i', ) -CPPYY_IMPL_ARRAY_CONVERTER(UInt, c_uint, unsigned int, 'I', ) -CPPYY_IMPL_ARRAY_CONVERTER(Long, c_long, long, 'l', ) -CPPYY_IMPL_ARRAY_CONVERTER(ULong, c_ulong, unsigned long, 'L', ) -CPPYY_IMPL_ARRAY_CONVERTER(LLong, c_longlong, long long, 'q', ) -CPPYY_IMPL_ARRAY_CONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q', ) -CPPYY_IMPL_ARRAY_CONVERTER(Float, c_float, float, 'f', ) -CPPYY_IMPL_ARRAY_CONVERTER(Double, c_double, double, 'd', ) -CPPYY_IMPL_ARRAY_CONVERTER(LDouble, c_longdouble, long double, 'g', ) -CPPYY_IMPL_ARRAY_CONVERTER(ComplexF, c_fcomplex, std::complex, 'z', ) -CPPYY_IMPL_ARRAY_CONVERTER(ComplexD, c_complex, std::complex, 'Z', ) - - -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_c_char_p) || \ - (1 < fShape.ndim() && PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_pointer)))) { - // 2nd predicate is ebatable: it's a catch-all for ctypes-styled multi-dimensional objects, - // which at this point does not check further dimensionality - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - - } else if (PySequence_Check(pyobject) && !CPyCppyy_PyText_Check(pyobject) -#if PY_VERSION_HEX >= 0x03000000 - && !PyBytes_Check(pyobject) -#endif - ) { - //for (auto& p : fBuffer) free(p); - fBuffer.clear(); - - size_t len = (size_t)PySequence_Size(pyobject); - if (len == (size_t)-1) { - PyErr_SetString(PyExc_ValueError, "can not convert sequence object of unknown length"); - return false; - } - - fBuffer.reserve(len); - for (size_t i = 0; i < len; ++i) { - PyObject* item = PySequence_GetItem(pyobject, i); - if (item) { - Py_ssize_t sz; - const char* p = CPyCppyy_PyText_AsStringAndSize(item, &sz); - Py_DECREF(item); - - if (p) fBuffer.push_back(p); - else { - PyErr_Format(PyExc_TypeError, "could not convert item %d to string", (int)i); - return false; - } - - } else - return false; - } - - para.fValue.fVoidp = (void*)fBuffer.data(); - para.fTypeCode = 'p'; - return true; - } - - return SCharArrayConverter::SetArg(pyobject, para, ctxt); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringArrayConverter::FromMemory(void* address) -{ - if (fIsFixed) - return CreateLowLevelView(*(char**)address, fShape); - else if (fShape[0] == UNKNOWN_SIZE) - return CreateLowLevelViewString((const char**)address, fShape); - return CreateLowLevelViewString(*(const char***)address, fShape); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringArrayConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// As a special array converter, the CStringArrayConverter one can also copy strings in the array, -// and not only buffers. - Py_ssize_t len; - if (const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len)) { - return ToArrayFromBuffer(value, address, ctxt, cstr, len, fShape, fIsFixed); - } - return SCharArrayConverter::ToMemory(value, address, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::NonConstCStringArrayConverter::FromMemory(void* address) -{ - if (fIsFixed) - return CreateLowLevelView(*(char**)address, fShape); - else if (fShape[0] == UNKNOWN_SIZE) - return CreateLowLevelViewString((char**)address, fShape); - return CreateLowLevelViewString(*(char***)address, fShape); -} - -//- converters for special cases --------------------------------------------- -bool CPyCppyy::NullptrConverter::SetArg(PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// Only allow C++11 style nullptr to pass - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -template -static inline bool CPyCppyy_PyUnicodeAsBytes2Buffer(PyObject* pyobject, T& buffer) { - PyObject* pybytes = nullptr; - if (PyBytes_Check(pyobject)) { - Py_INCREF(pyobject); - pybytes = pyobject; - } else if (PyUnicode_Check(pyobject)) { -#if PY_VERSION_HEX < 0x03030000 - pybytes = PyUnicode_EncodeUTF8( - PyUnicode_AS_UNICODE(pyobject), CPyCppyy_PyUnicode_GET_SIZE(pyobject), nullptr); -#else - pybytes = PyUnicode_AsUTF8String(pyobject); -#endif - } - - if (pybytes) { - Py_ssize_t len; - const char* cstr = nullptr; - PyBytes_AsStringAndSize(pybytes, (char**)&cstr, &len); - if (cstr) buffer = T{cstr, (typename T::size_type)len}; - Py_DECREF(pybytes); - return (bool)cstr; - } - - return false; -} - -#define CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(name, type, F1, F2) \ -CPyCppyy::name##Converter::name##Converter(bool keepControl) : \ - InstanceConverter(Cppyy::GetScope(#type), keepControl) {} \ - \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (CPyCppyy_PyUnicodeAsBytes2Buffer(pyobject, fBuffer)) { \ - para.fValue.fVoidp = &fBuffer; \ - para.fTypeCode = 'V'; \ - return true; \ - } \ - \ - PyErr_Clear(); \ - if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { \ - bool result = InstanceConverter::SetArg(pyobject, para, ctxt); \ - para.fTypeCode = 'V'; \ - return result; \ - } \ - \ - return false; \ -} \ - \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - if (address) \ - return InstanceConverter::FromMemory(address); \ - auto* empty = new type(); \ - return BindCppObjectNoCast(empty, fClass, CPPInstance::kIsOwner); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory( \ - PyObject* value, void* address, PyObject* ctxt) \ -{ \ - if (CPyCppyy_PyUnicodeAsBytes2Buffer(value, *((type*)address))) \ - return true; \ - return InstanceConverter::ToMemory(value, address, ctxt); \ -} - -CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(STLString, std::string, c_str, size) - - -CPyCppyy::STLWStringConverter::STLWStringConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::wstring"), keepControl) {} - -bool CPyCppyy::STLWStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - if (PyUnicode_Check(pyobject)) { - Py_ssize_t len = CPyCppyy_PyUnicode_GET_SIZE(pyobject); - fBuffer.resize(len); - CPyCppyy_PyUnicode_AsWideChar(pyobject, &fBuffer[0], len); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } -#if PY_VERSION_HEX < 0x03000000 - else if (PyString_Check(pyobject)) { -#ifdef HAS_CODECVT - std::wstring_convert> cnv; - fBuffer = cnv.from_bytes(PyString_AS_STRING(pyobject)); -#else - PyObject* pyu = PyUnicode_FromString(PyString_AS_STRING(pyobject)); - if (!pyu) return false; - Py_ssize_t len = CPyCppyy_PyUnicode_GET_SIZE(pyu); - fBuffer.resize(len); - CPyCppyy_PyUnicode_AsWideChar(pyu, &fBuffer[0], len); -#endif - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } -#endif - - if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { - bool result = InstancePtrConverter::SetArg(pyobject, para, ctxt); - para.fTypeCode = 'V'; - return result; - } - - return false; -} - -PyObject* CPyCppyy::STLWStringConverter::FromMemory(void* address) -{ - if (address) - return PyUnicode_FromWideChar(((std::wstring*)address)->c_str(), ((std::wstring*)address)->size()); - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); -} - -bool CPyCppyy::STLWStringConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ - if (PyUnicode_Check(value)) { - Py_ssize_t len = CPyCppyy_PyUnicode_GET_SIZE(value); - wchar_t* buf = new wchar_t[len+1]; - CPyCppyy_PyUnicode_AsWideChar(value, buf, len); - *((std::wstring*)address) = std::wstring(buf, len); - delete[] buf; - return true; - } - return InstanceConverter::ToMemory(value, address, ctxt); -} - - -CPyCppyy::STLStringViewConverter::STLStringViewConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::string_view"), keepControl) {} - -bool CPyCppyy::STLStringViewConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// normal instance convertion (eg. string_view object passed) - if (!PyInt_Check(pyobject) && !PyLong_Check(pyobject)) { - CallContextRAII noimp(ctxt); - if (InstanceConverter::SetArg(pyobject, para, ctxt)) { - para.fTypeCode = 'V'; - return true; - } else - PyErr_Clear(); - } - -// passing of a Python string; buffering done Python-side b/c str is immutable - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); - if (cstr) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - fBuffer = std::string_view(cstr, (std::string_view::size_type)len); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - if (!CPPInstance_Check(pyobject)) - return false; - -// special case of a C++ std::string object; life-time management is left to -// the caller to ensure any external changes propagate correctly - if (CPPInstance_Check(pyobject)) { - static Cppyy::TCppScope_t sStringID = Cppyy::GetScope("std::string"); - CPPInstance* pyobj = (CPPInstance*)pyobject; - if (pyobj->ObjectIsA() == sStringID) { - void* ptr = pyobj->GetObject(); - if (!ptr) - return false; // leaves prior conversion error for report - - PyErr_Clear(); - - fBuffer = *((std::string*)ptr); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - } - - return false; -} - -PyObject* CPyCppyy::STLStringViewConverter::FromMemory(void* address) -{ - if (address) - return InstanceConverter::FromMemory(address); - auto* empty = new std::string_view(); - return BindCppObjectNoCast(empty, fClass, CPPInstance::kIsOwner); -} - -bool CPyCppyy::STLStringViewConverter::ToMemory( - PyObject* value, void* address, PyObject* ctxt) -{ -// common case of simple object assignment - if (InstanceConverter::ToMemory(value, address, ctxt)) - return true; - -// assignment of a Python string; buffering done Python-side b/c str is immutable - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len); - if (cstr) { - SetLifeLine(ctxt, value, (intptr_t)this); - *reinterpret_cast(address) = \ - std::string_view(cstr, (std::string_view::size_type)len); - return true; - } - - return false; -} - - -bool CPyCppyy::STLStringMoveConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ std::string&&, set arg for call - int moveit_reason = 3; // move on temporary fBuffer - if (CPPInstance_Check(pyobject)) { - CPPInstance* pyobj = (CPPInstance*)pyobject; - if (pyobj->fFlags & CPPInstance::kIsRValue) { - pyobj->fFlags &= ~CPPInstance::kIsRValue; - moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { - moveit_reason = 1; - } else - moveit_reason = 0; - } - - if (moveit_reason) { - bool result = this->STLStringConverter::SetArg(pyobject, para, ctxt); - if (!result && moveit_reason == 2) // restore the movability flag? - ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; - return result; - } - - PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); - return false; // not a temporary or movable object -} - - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance*, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, ISCONST ? fClass : (Cppyy::TCppType_t)0); - if (!pyobj) { - if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = 'p'; // allow special cases such as nullptr - return true; - } - - // not a cppyy object (TODO: handle SWIG etc.) - return false; - } - - // smart pointers should only extract the pointer if this is NOT an implicit - // conversion to another smart pointer - if (pyobj->IsSmart() && IsConstructor(ctxt->fFlags) && Cppyy::IsSmartPtr(ctxt->fCurScope)) - return false; - - Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - if (oisa && (oisa == fClass || Cppyy::IsSubtype(oisa, fClass))) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) - pyobj->CppOwns(); - - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (oisa != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - oisa, fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -template -PyObject* CPyCppyy::InstancePtrConverter::FromMemory(void* address) -{ -// construct python object from C++ instance read at
- if (ISCONST) - return BindCppObject(*(void**)address, fClass); // by pointer value - return BindCppObject(address, fClass, CPPInstance::kIsReference); // modifiable -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ instance, write it at
- CPPInstance* pyobj = GetCppInstance(value, ISCONST ? fClass : (Cppyy::TCppType_t)0); - if (!pyobj) { - void* ptr = nullptr; - if (GetAddressSpecialCase(value, ptr)) { - *(void**)address = ptr; // allow special cases such as nullptr - return true; - } - - // not a cppyy object (TODO: handle SWIG etc.) - return false; - } - - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) - ((CPPInstance*)value)->CppOwns(); - - *(void**)address = pyobj->GetObject(); - return true; - } - - return false; -} - -// TODO: CONSOLIDATE Instance, InstanceRef, InstancePtr ... - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fClass); - if (pyobj) { - auto oisa = pyobj->ObjectIsA(); - if (oisa && (oisa == fClass || Cppyy::IsSubtype(oisa, fClass))) { - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (!para.fValue.fVoidp) - return false; - - if (oisa != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - pyobj->ObjectIsA(), fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - para.fTypeCode = 'V'; - return true; - } - } - - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceConverter::FromMemory(void* address) -{ -// This should not need a cast (ie. BindCppObjectNoCast), but performing the cast -// here means callbacks receive down-casted object when passed by-ptr, which is -// needed for object identity. The latter case is assumed to be more common than -// conversion of (global) objects. - return BindCppObject((Cppyy::TCppObject_t)address, fClass); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// assign value to C++ instance living at
through assignment operator - PyObject* pyobj = BindCppObjectNoCast(address, fClass); -#if PY_VERSION_HEX >= 0x03080000 - PyObject* result = PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); -#else - PyObject* result = PyObject_CallMethod(pyobj, (char*)"__assign__", (char*)"O", value); -#endif - Py_DECREF(pyobj); - - if (result) { - Py_DECREF(result); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fIsConst ? fClass : (Cppyy::TCppType_t)0); - if (pyobj) { - - // reject moves - if (pyobj->fFlags & CPPInstance::kIsRValue) - return false; - - // smart pointers can end up here in case of a move, so preferentially match - // the smart type directly - bool argset = false; - Cppyy::TCppType_t cls = 0; - if (pyobj->IsSmart()) { - cls = pyobj->ObjectIsA(false); - if (cls && Cppyy::IsSubtype(cls, fClass)) { - para.fValue.fVoidp = pyobj->GetObjectRaw(); - argset = true; - } - } - - if (!argset) { - cls = pyobj->ObjectIsA(); - if (cls && Cppyy::IsSubtype(cls, fClass)) { - para.fValue.fVoidp = pyobj->GetObject(); - argset = true; - } - } - - if (argset) { - // do not allow null pointers through references - if (!para.fValue.fVoidp) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return false; - } - - // calculate offset between formal and actual arguments - if (cls != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - cls, fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - para.fTypeCode = 'V'; - return true; - } - } - - if (!fIsConst) // no implicit conversion possible - return false; - - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceRefConverter::FromMemory(void* address) -{ - return BindCppObjectNoCast((Cppyy::TCppObject_t)address, fClass, CPPInstance::kIsReference); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceMoveConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance&&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fClass, true /* accept_rvalue */); - if (!pyobj || (pyobj->fFlags & CPPInstance::kIsLValue)) { - // implicit conversion is fine as the temporary by definition is moveable - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); - } - -// moving is same as by-ref, but have to check that move is allowed - int moveit_reason = 0; - if (pyobj->fFlags & CPPInstance::kIsRValue) { - pyobj->fFlags &= ~CPPInstance::kIsRValue; - moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { - moveit_reason = 1; - } - - if (moveit_reason) { - bool result = this->InstanceRefConverter::SetArg(pyobject, para, ctxt); - if (!result && moveit_reason == 2) // restore the movability flag? - ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; - return result; - } - - PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); - return false; // not a temporary or movable object -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) -{ -// convert to C++ instance**, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (!pyobj) { - if (!ISREFERENCE && (pyobject == gNullPtrObject || pyobject == gDefaultObject)) { - // allow nullptr as a special case - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - return false; // not a cppyy object (TODO: handle SWIG etc.) - } - - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - if (pyobj->fFlags & CPPInstance::kIsReference) // already a ptr to object? - para.fValue.fVoidp = pyobj->GetObjectRaw(); - else - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = ISREFERENCE ? 'V' : 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -template -PyObject* CPyCppyy::InstancePtrPtrConverter::FromMemory(void* address) -{ -// construct python object from C++ instance* read at
- return BindCppObject(*(void**)address, fClass, CPPInstance::kIsReference | CPPInstance::kIsPtrPtr); -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrPtrConverter::ToMemory( - PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ instance*, write it at
- CPPInstance* pyobj = GetCppInstance(value); - if (!pyobj) { - if (value == gNullPtrObject || value == gDefaultObject) { - // allow nullptr as a special case - *(void**)address = nullptr; - return true; - } - return false; // not a cppyy object (TODO: handle SWIG etc.) - } - - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) - pyobj->CppOwns(); - - // register the value for potential recycling - MemoryRegulator::RegisterPyObject(pyobj, pyobj->GetObject()); - - // set pointer (may be null) and declare success - *(void**)address = pyobj->GetObject(); - return true; - } - - return false; -} - - -namespace CPyCppyy { -// Instantiate the templates - template class CPyCppyy::InstancePtrConverter; - template class CPyCppyy::InstancePtrConverter; - template class CPyCppyy::InstancePtrPtrConverter; - template class CPyCppyy::InstancePtrPtrConverter; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* txt */) -{ -// convert to C++ instance**, set arg for call - if (!TupleOfInstances_CheckExact(pyobject)) - return false; // no guarantee that the tuple is okay - -// treat the first instance of the tuple as the start of the array, and pass it -// by pointer (TODO: store and check sizes) - if (PyTuple_Size(pyobject) < 1) - return false; - - PyObject* first = PyTuple_GetItem(pyobject, 0); - if (!CPPInstance_Check(first)) - return false; // should not happen - - if (Cppyy::IsSubtype(((CPPInstance*)first)->ObjectIsA(), fClass)) { - // no memory policies supported; set pointer (may be null) and declare success - para.fValue.fVoidp = ((CPPInstance*)first)->GetObject(); - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceArrayConverter::FromMemory(void* address) -{ -// construct python tuple of instances from C++ array read at
- return BindCppObjectArray(*(char**)address, fClass, fShape); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceArrayConverter::ToMemory( - PyObject* /* value */, void* /* address */, PyObject* /* ctxt */) -{ -// convert to C++ array of instances, write it at
- -// TODO: need to have size both for the array and from the input - PyErr_SetString(PyExc_NotImplementedError, - "access to C-arrays of objects not yet implemented!"); - return false; -} - -//___________________________________________________________________________ -// Cling WORKAROUND -- classes for STL iterators are completely undefined in that -// they come in a bazillion different guises, so just do whatever -bool CPyCppyy::STLIteratorConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ - if (!CPPInstance_Check(pyobject)) - return false; - -// just set the pointer value, no check - CPPInstance* pyobj = (CPPInstance*)pyobject; - para.fValue.fVoidp = pyobj->GetObject(); - para.fTypeCode = 'V'; - return true; -} -// -- END Cling WORKAROUND - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidPtrRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ void*&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (pyobj) { - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -CPyCppyy::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims) : - fShape(dims) { - fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidPtrPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ void**, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (pyobj) { - // this is a C++ object, take and set its address - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = 'p'; - return true; - } else if (IsPyCArgObject(pyobject)) { - CPyCppyy_tagPyCArgObject* carg = (CPyCppyy_tagPyCArgObject*)pyobject; - if (carg->obj) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)carg->obj)->b_ptr; - para.fTypeCode = 'p'; - return true; - } - } - -// buffer objects are allowed under "user knows best" (this includes the buffer -// interface to ctypes.c_void_p, which results in a void**) - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidPtrPtrConverter::FromMemory(void* address) -{ -// read a void** from address; since this is unknown, uintptr_t is used (user can cast) - if (!address || *(ptrdiff_t*)address == 0) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - if (!fIsFixed) - return CreatePointerView((uintptr_t**)address, fShape); - return CreatePointerView(*(uintptr_t**)address, fShape); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::PyObjectConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// by definition: set and declare success - para.fValue.fVoidp = pyobject; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::PyObjectConverter::FromMemory(void* address) -{ -// construct python object from C++ PyObject* read at
- PyObject* pyobject = *((PyObject**)address); - - if (!pyobject) { - Py_RETURN_NONE; - } - - Py_INCREF(pyobject); - return pyobject; -} - -bool CPyCppyy::PyObjectConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// no conversion needed, write at
- Py_INCREF(value); - Py_XDECREF(*((PyObject**)address)); - *((PyObject**)address) = value; - return true; -} - - -//- function pointer converter ----------------------------------------------- -static unsigned int sWrapperCounter = 0; -// cache mapping signature/return type to python callable and corresponding wrapper -typedef std::string RetSigKey_t; -static std::map> sWrapperFree; -static std::map> sWrapperLookup; -static std::map> sWrapperWeakRefs; -static std::map sWrapperReference; - -static PyObject* WrapperCacheEraser(PyObject*, PyObject* pyref) -{ - auto ipos = sWrapperWeakRefs.find(pyref); - if (ipos != sWrapperWeakRefs.end()) { - auto key = ipos->second.second; - - // disable this callback and store on free list for possible re-use - void* wpraddress = ipos->second.first; - PyObject** oldref = sWrapperReference[wpraddress]; - const auto& lookup = sWrapperLookup.find(key); - if (lookup != sWrapperLookup.end()) lookup->second.erase(*oldref); - *oldref = nullptr; // to detect deletions - sWrapperFree[ipos->second.second].push_back(wpraddress); - - // clean up and remove weak reference from admin - Py_DECREF(ipos->first); - sWrapperWeakRefs.erase(ipos); - } - - Py_RETURN_NONE; -} -static PyMethodDef gWrapperCacheEraserMethodDef = { - const_cast("internal_WrapperCacheEraser"), - (PyCFunction)WrapperCacheEraser, - METH_O, nullptr -}; - -static void* PyFunction_AsCPointer(PyObject* pyobject, - const std::string& rettype, const std::string& signature, bool allowCppInstance) -{ -// Convert a bound C++ function pointer or callable python object to a C-style -// function pointer. The former is direct, the latter involves a JIT-ed wrapper. - static PyObject* sWrapperCacheEraser = PyCFunction_New(&gWrapperCacheEraserMethodDef, nullptr); - - using namespace CPyCppyy; - - if (CPPOverload_Check(pyobject)) { - CPPOverload* ol = (CPPOverload*)pyobject; - if (!ol->fMethodInfo || ol->fMethodInfo->fMethods.empty()) - return nullptr; - - // find the overload with matching signature - for (auto& m : ol->fMethodInfo->fMethods) { - PyObject* sig = m->GetSignature(false); - bool found = signature == CPyCppyy_PyText_AsString(sig); - Py_DECREF(sig); - if (found) { - void* fptr = (void*)m->GetFunctionAddress(); - if (fptr) return fptr; - break; // fall-through, with calling through Python - } - } - } - - if (TemplateProxy_Check(pyobject)) { - // get the actual underlying template matching the signature - TemplateProxy* pytmpl = (TemplateProxy*)pyobject; - std::string fullname = pytmpl->fTI->fCppName; - if (pytmpl->fTemplateArgs) - fullname += CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); - Cppyy::TCppScope_t scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fullname, signature); - if (cppmeth) { - void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); - if (fptr) return fptr; - } - // fall-through, with calling through Python - } - - if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { - // ctypes function pointer - void* fptr = *(void**)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - return fptr; - } - - - if (PyCallable_Check(pyobject) && (allowCppInstance || !CPPInstance_Check(pyobject))) { - // generic python callable: create a C++ wrapper function - // Sometimes we don't want to take this branch if the object is a C++ - // instance, because C++ doesn't allow converting functor objects to - // function pointers, but only to std::function. - void* wpraddress = nullptr; - - // re-use existing wrapper if possible - auto key = rettype+signature; - const auto& lookup = sWrapperLookup.find(key); - if (lookup != sWrapperLookup.end()) { - const auto& existing = lookup->second.find(pyobject); - if (existing != lookup->second.end() && *sWrapperReference[existing->second] == pyobject) - wpraddress = existing->second; - } - - // check for a pre-existing, unused, wrapper if not found - if (!wpraddress) { - const auto& freewrap = sWrapperFree.find(key); - if (freewrap != sWrapperFree.end() && !freewrap->second.empty()) { - wpraddress = freewrap->second.back(); - freewrap->second.pop_back(); - *(sWrapperReference[wpraddress]) = pyobject; - sWrapperLookup[key][pyobject] = wpraddress; - PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); - if (wref) sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); - else PyErr_Clear(); // happens for builtins which don't need this - } - } - - // create wrapper if no re-use possible - if (!wpraddress) { - if (!Utility::IncludePython()) - return nullptr; - - // extract argument types - const std::vector& argtypes = TypeManip::extract_arg_types(signature); - int nArgs = (int)argtypes.size(); - - // wrapper name - std::ostringstream wname; - wname << "fptr_wrap" << ++sWrapperCounter; - - // build wrapper function code - std::ostringstream code; - code << "namespace __cppyy_internal {\n " - << rettype << " " << wname.str() << "("; - for (int i = 0; i < nArgs; ++i) { - code << argtypes[i] << " arg" << i; - if (i != nArgs-1) code << ", "; - } - code << ") {\n"; - - // start function body - Utility::ConstructCallbackPreamble(rettype, argtypes, code); - - // create a referenceable pointer - PyObject** ref = new PyObject*{pyobject}; - - // function call itself and cleanup - code << " PyObject** ref = (PyObject**)" << (intptr_t)ref << ";\n" - " PyObject* pyresult = nullptr;\n" - " if (*ref) pyresult = PyObject_CallFunctionObjArgs(*ref"; - for (int i = 0; i < nArgs; ++i) - code << ", pyargs[" << i << "]"; - code << ", NULL);\n" - " else PyErr_SetString(PyExc_TypeError, \"callable was deleted\");\n"; - - // close - Utility::ConstructCallbackReturn(rettype, nArgs, code); - - // end of namespace - code << "}"; - - // finally, compile the code - if (!Cppyy::Compile(code.str())) - return nullptr; - - // TODO: is there no easier way? - static Cppyy::TCppScope_t scope = Cppyy::GetScope("__cppyy_internal"); - const auto& idx = Cppyy::GetMethodIndicesFromName(scope, wname.str()); - wpraddress = Cppyy::GetFunctionAddress(Cppyy::GetMethod(scope, idx[0]), false); - sWrapperReference[wpraddress] = ref; - - // cache the new wrapper - sWrapperLookup[key][pyobject] = wpraddress; - PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); - if (wref) sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); - else PyErr_Clear(); // happens for builtins which don't need this - } - - // now pass the pointer to the wrapper function (may be null) - return wpraddress; - } - - return nullptr; -} - -bool CPyCppyy::FunctionPointerConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// special case: allow nullptr singleton: - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - -// normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); - if (fptr) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - para.fValue.fVoidp = fptr; - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -PyObject* CPyCppyy::FunctionPointerConverter::FromMemory(void* address) -{ -// A function pointer in clang is represented by a Type, not a FunctionDecl and it's -// not possible to get the latter from the former: the backend will need to support -// both. Since that is far in the future, we'll use a std::function instead. - if (address) - return Utility::FuncPtr2StdFunction(fRetType, fSignature, *(void**)address); - PyErr_SetString(PyExc_TypeError, "can not convert null function pointer"); - return nullptr; -} - -bool CPyCppyy::FunctionPointerConverter::ToMemory( - PyObject* pyobject, void* address, PyObject* ctxt) -{ -// special case: allow nullptr singleton: - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - *((void**)address) = nullptr; - return true; - } - -// normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); - if (fptr) { - SetLifeLine(ctxt, pyobject, (intptr_t)address); - *((void**)address) = fptr; - return true; - } - - return false; -} - - -//- std::function converter -------------------------------------------------- -bool CPyCppyy::StdFunctionConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// prefer normal "object" conversion - CallContextRAII noimp(ctxt); - if (fConverter->SetArg(pyobject, para, ctxt)) - return true; - - PyErr_Clear(); - -// else create a wrapper function - if (this->FunctionPointerConverter::SetArg(pyobject, para, ctxt)) { - // retrieve the wrapper pointer and capture it in a temporary std::function, - // then try normal conversion a second time - PyObject* func = this->FunctionPointerConverter::FromMemory(¶.fValue.fVoidp); - if (func) { - SetLifeLine(ctxt->fPyContext, func, (intptr_t)this); - bool result = fConverter->SetArg(func, para, ctxt); - if (result) ctxt->AddTemporary(func); - else Py_DECREF(func); - return result; - } - } - - return false; -} - -PyObject* CPyCppyy::StdFunctionConverter::FromMemory(void* address) -{ - return fConverter->FromMemory(address); -} - -bool CPyCppyy::StdFunctionConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// if the value is not an std::function<> but a generic Python callable, the -// conversion is done through the assignment, which may involve a temporary - if (address) SetLifeLine(ctxt, value, (intptr_t)address); - return fConverter->ToMemory(value, address, ctxt); -} - - -//- smart pointer converters ------------------------------------------------- -bool CPyCppyy::SmartPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - char typeCode = fIsRef ? 'p' : 'V'; - - if (!CPPInstance_Check(pyobject)) { - // TODO: not sure how this is correct for pass-by-ref nor does it help with - // implicit conversions for pass-by-value - if (fIsRef && GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = typeCode; // allow special cases such as nullptr - return true; - } - - return false; - } - - CPPInstance* pyobj = (CPPInstance*)pyobject; - Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - -// for the case where we have a 'hidden' smart pointer: - if (Cppyy::TCppType_t tsmart = pyobj->GetSmartIsA()) { - if (Cppyy::IsSubtype(tsmart, fSmartPtrType)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!fKeepControl && !UseStrictOwnership()) - ((CPPInstance*)pyobject)->CppOwns(); - - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetSmartObject(); - if (tsmart != fSmartPtrType) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - tsmart, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = typeCode; - return true; - } - } - -// for the case where we have an 'exposed' smart pointer: - if (!pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fSmartPtrType)) { - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (oisa != fSmartPtrType) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - oisa, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = typeCode; - return true; - } - -// for the case where we have an ordinary object to convert - if ((ctxt->fFlags & CallContext::kImplicitSmartPtrConversion) && !pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) { - // create the relevant smart pointer and make the pyobject "smart" - CPPInstance* pysmart = (CPPInstance*)ConvertImplicit(fSmartPtrType, pyobject, para, ctxt, false); - if (!CPPInstance_Check(pysmart)) { - Py_XDECREF(pysmart); - return false; - } - - // copy internals from the fresh smart object to the original, making it smart - pyobj->GetObjectRaw() = pysmart->GetSmartObject(); - pyobj->SetSmart(CreateScopeProxy(fSmartPtrType)); //(PyObject*)Py_TYPE(pysmart)); - pyobj->PythonOwns(); - pysmart->CppOwns(); - Py_DECREF(pysmart); - - return true; - } - -// final option, try mapping pointer types held (TODO: do not allow for non-const ref) -// Note: this must be decided on the smart pointer's *declared* underlying type, not -// on the (possibly auto-down-cast) type of the dereferenced object. A -// std::unique_ptr holding a Derived must not be accepted where a -// std::unique_ptr is expected: the held smart pointer is still a -// unique_ptr and does not convert to unique_ptr. - if (pyobj->IsSmart() && Cppyy::IsSubtype(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { - para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -PyObject* CPyCppyy::SmartPtrConverter::FromMemory(void* address) -{ - if (!address || !fSmartPtrType) - return nullptr; - - return BindCppObjectNoCast(address, fSmartPtrType); -} - -bool CPyCppyy::SmartPtrConverter::ToMemory(PyObject* value, void* address, PyObject*) -{ -// assign value to C++ instance living at
through assignment operator (this -// is similar to InstanceConverter::ToMemory, but prevents wrapping the smart ptr) - PyObject* pyobj = BindCppObjectNoCast(address, fSmartPtrType, CPPInstance::kNoWrapConv); -#if PY_VERSION_HEX >= 0x03080000 - PyObject* result = PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); -#else - PyObject* result = PyObject_CallMethod(pyobj, (char*)"__assign__", (char*)"O", value); -#endif - Py_DECREF(pyobj); - - if (result) { - Py_DECREF(result); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -namespace { - -// clang libcpp and gcc use the same structure (ptr, size) -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) -struct faux_initlist -{ - typedef size_t size_type; - typedef void* iterator; - iterator _M_array; - size_type _M_len; -}; -#elif defined (_MSC_VER) -struct faux_initlist -{ - typedef char* iterator; - iterator _M_array; // ie. _First; - iterator _Last; -}; -#else -#define NO_KNOWN_INITIALIZER_LIST 1 -#endif - -} // unnamed namespace - -CPyCppyy::InitializerListConverter::InitializerListConverter(Cppyy::TCppType_t klass, std::string const &value_type) - - : InstanceConverter{klass}, - fValueTypeName{value_type}, - fValueType{Cppyy::GetScope(value_type)}, - fValueSize{Cppyy::SizeOf(value_type)} -{ -} - -CPyCppyy::InitializerListConverter::~InitializerListConverter() -{ - for (Converter *converter : fConverters) { - if (converter && converter->HasState()) delete converter; - } - if (fBuffer) Clear(); -} - -void CPyCppyy::InitializerListConverter::Clear() { - if (fValueType) { - faux_initlist* fake = (faux_initlist*)fBuffer; -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { -#elif defined (_MSC_VER) - for (size_t i = 0; (fake->_M_array+i*fValueSize) != fake->_Last; ++i) { -#endif - void* memloc = (char*)fake->_M_array + i*fValueSize; - Cppyy::CallDestructor(fValueType, (Cppyy::TCppObject_t)memloc); - } - } - - free(fBuffer); - fBuffer = nullptr; -} - -bool CPyCppyy::InitializerListConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -#ifdef NO_KNOWN_INITIALIZER_LIST - return false; -#else - if (fBuffer) Clear(); - -// convert the given argument to an initializer list temporary; this is purely meant -// to be a syntactic thing, so only _python_ sequences are allowed; bound C++ proxies -// (likely explicitly created std::initializer_list, go through an instance converter - if (!PySequence_Check(pyobject) || CPyCppyy_PyText_Check(pyobject) -#if PY_VERSION_HEX >= 0x03000000 - || PyBytes_Check(pyobject) -#else - || PyUnicode_Check(pyobject) -#endif - ) - return false; - - if (CPPInstance_Check(pyobject)) - return this->InstanceConverter::SetArg(pyobject, para, ctxt); - - void* buf = nullptr; - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', (int)fValueSize, buf, true); - faux_initlist* fake = nullptr; - size_t entries = 0; - if (buf && buflen) { - // dealing with an array here, pass on whole-sale - fake = (faux_initlist*)malloc(sizeof(faux_initlist)); - fBuffer = (void*)fake; - fake->_M_array = (faux_initlist::iterator)buf; -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)buflen; -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+buflen*fValueSize; -#endif - } else if (fValueSize) { - // Remove any errors set by GetBuffer(); note that if the argument was an array - // that failed to extract because of a type mismatch, the following will perform - // a (rather inefficient) copy. No warning is issued b/c e.g. numpy doesn't do - // so either. - PyErr_Clear(); - - // Can only construct empty lists, so use a fake initializer list. For that we - // need to construct default objects. Fail early if that cannot work. - if (fValueType && !Cppyy::IsDefaultConstructable(fValueType)) { - PyErr_SetString(PyExc_TypeError, "default constructor needed for initializer list of objects"); - return false; - } - - size_t len = (size_t)PySequence_Size(pyobject); - fake = (faux_initlist*)malloc(sizeof(faux_initlist)+fValueSize*len); - fBuffer = (void*)fake; - fake->_M_array = (faux_initlist::iterator)((char*)fake+sizeof(faux_initlist)); -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)len; - for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+len*fValueSize; - for (size_t i = 0; (fake->_M_array+i*fValueSize) != fake->_Last; ++i) { -#endif - PyObject* item = PySequence_GetItem(pyobject, i); - bool convert_ok = false; - if (item) { - if (fConverters.empty()) - fConverters.emplace_back(CreateConverter(fValueTypeName)); - if (!fConverters.back()) { - if (CPPInstance_Check(item)) { - // by convention, use byte copy - memcpy((char*)fake->_M_array + i*fValueSize, - ((CPPInstance*)item)->GetObject(), fValueSize); - convert_ok = true; - } - } else { - void* memloc = (char*)fake->_M_array + i*fValueSize; - if (fValueType) { - // we need to construct a default object for the constructor to assign into; this is - // clunky, but the use of a copy constructor isn't much better as the Python object - // need not be a C++ object - memloc = (void*)Cppyy::Construct(fValueType, memloc); - // We checked above that we are able to construct default objects of fValueType. - assert(memloc); - entries += 1; - } - if (memloc) { - if (i >= fConverters.size()) { - fConverters.emplace_back(CreateConverter(fValueTypeName)); - } - convert_ok = fConverters[i]->ToMemory(item, memloc); - } - } - - - Py_DECREF(item); - } else - PyErr_Format(PyExc_TypeError, "failed to get item %d from sequence", (int)i); - - if (!convert_ok) { -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)entries; -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+entries*fValueSize; -#endif - Clear(); - return false; - } - } - } - - if (!fake) // no buffer and value size indeterminate - return false; - - para.fValue.fVoidp = (void*)fake; - para.fTypeCode = 'V'; // means ptr that backend has to free after call - return true; -#endif -} - -namespace CPyCppyy { - -// raising converter to take out overloads -class NotImplementedConverter : public Converter { -public: - NotImplementedConverter(PyObject *errorType, std::string const &message) : fErrorType{errorType}, fMessage{message} {} - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -private: - PyObject *fErrorType; - std::string fMessage; -}; - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -bool CPyCppyy::NotImplementedConverter::SetArg(PyObject*, Parameter&, CallContext*) -{ -// raise a NotImplemented exception to take a method out of overload resolution - PyErr_SetString(fErrorType, fMessage.c_str()); - return false; -} - - -//- helper to refactor some code from CreateConverter ------------------------ -static inline CPyCppyy::Converter* selectInstanceCnv(Cppyy::TCppScope_t klass, - const std::string& cpd, CPyCppyy::cdims_t dims, bool isConst, bool control) -{ - using namespace CPyCppyy; - Converter* result = nullptr; - - if (cpd == "**" || cpd == "*[]" || cpd == "&*") - result = new InstancePtrPtrConverter(klass, control); - else if (cpd == "*&") - result = new InstancePtrPtrConverter(klass, control); - else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { - if (isConst) result = new InstancePtrConverter(klass, control); - else result = new InstancePtrConverter(klass, control); - } - else if (cpd == "&") - result = new InstanceRefConverter(klass, isConst); - else if (cpd == "&&") - result = new InstanceMoveConverter(klass); - else if (cpd == "[]" || dims) - result = new InstanceArrayConverter(klass, dims, false); - else if (cpd == "") // by value - result = new InstanceConverter(klass, true); - - return result; -} - -//- factories ---------------------------------------------------------------- -CPYCPPYY_EXPORT -CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdims_t dims) -{ -// The matching of the fulltype to a converter factory goes through up to five levels: -// 1) full, exact match -// 2) match of decorated, unqualified type -// 3) accept const ref as by value -// 4) accept ref as pointer -// 5) generalized cases (covers basically all C++ classes) -// -// If all fails, void is used, which will generate a run-time warning when used. - -// an exactly matching converter is best - ConvFactories_t::iterator h = gConvFactories.find(fullType); - if (h != gConvFactories.end()) { - return (h->second)(dims); - } - -// resolve typedefs etc. - const std::string& resolvedType = Cppyy::ResolveName(fullType); - -// a full, qualified matching converter is preferred - if (resolvedType != fullType) { - h = gConvFactories.find(resolvedType); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedType); - std::string realType = TypeManip::clean_type(resolvedType, false, true); - -// mutable pointer references (T*&) are incompatible with Python's object model - if (cpd == "*&") { - return new NotImplementedConverter{PyExc_TypeError, - "argument type '" + resolvedType + "' is not supported: non-const references to pointers (T*&) allow a" - " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" - " C++ API to return the new pointer or use a wrapper"}; - } - -// accept unqualified type (as python does not know about qualifiers) - h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); - if (h != gConvFactories.end()) - return (h->second)(dims); - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - h = gConvFactories.find(realType + cpd); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd.compare(0, 3, "*[]") == 0) { - // special case, array of pointers - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) { - // upstream treats the pointer type as the array element type, but that pointer is - // treated as a low-level view as well, unless it's a void*/char* so adjust the dims - if (realType != "void" && realType != "char") { - dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim()+1; - dims_t newdims = dims_t(newdim); - // TODO: sometimes the array size is known and can thus be verified; however, - // currently the meta layer does not provide this information - newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array - newdims[1] = UNKNOWN_SIZE; // the pointer - if (2 < newdim) { - for (int i = 2; i < (newdim-1); ++i) - newdims[i] = dims[i-1]; - } - - return (h->second)(newdims); - } - return (h->second)(dims); - } - - } else if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - // simple array; set or resize as necessary - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) - return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); - - } else if (2 <= cpd.size() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == cpd.size() / 2) { - // fixed array, dims will have size if available - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- special case: initializer list - if (realType.compare(0, 16, "initializer_list") == 0) { - // get the type of the list and create a converter (TODO: get hold of value_type?) - auto pos = realType.find('<'); - std::string value_type = realType.substr(pos+1, realType.size()-pos-2); - return new InitializerListConverter(Cppyy::GetScope(realType), value_type); - } - -//-- still nothing? use a generalized converter - bool control = cpd == "&" || isConst; - -//-- special case: std::function - auto pos = resolvedType.find("function<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { - - // get actual converter for normal passing - Converter* cnv = selectInstanceCnv( - Cppyy::GetScope(realType), cpd, dims, isConst, control); - - if (cnv) { - // get the type of the underlying (TODO: use target_type?) - auto pos1 = resolvedType.find("(", pos+9); - auto pos2 = resolvedType.rfind(")"); - if (pos1 != std::string::npos && pos2 != std::string::npos) { - auto sz1 = pos1-pos-9; - if (resolvedType[pos+9+sz1-1] == ' ') sz1 -= 1; - - return new StdFunctionConverter(cnv, - resolvedType.substr(pos+9, sz1), resolvedType.substr(pos1, pos2-pos1+1)); - } else if (cnv->HasState()) - delete cnv; - } - } - -#if __cplusplus >= 202002L -//-- special case: std::span - pos = resolvedType.find("span<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { - - auto pos1 = realType.find('<'); - auto pos21 = realType.find(','); // for the case there are more template args - auto pos22 = realType.find('>'); - auto len = std::min(pos21 - pos1, pos22 - pos1) - 1; - std::string value_type = realType.substr(pos1+1, len); - - // strip leading "const " - const std::string cprefix = "const "; - if (value_type.compare(0, cprefix.size(), cprefix) == 0) { - value_type = value_type.substr(cprefix.size()); - } - - std::string span_type = "std::span<" + value_type + ">"; - - return new StdSpanConverter{value_type, Cppyy::GetScope(span_type)}; - } -#endif - -// converters for known C++ classes and default (void*) - Converter* result = nullptr; - if (Cppyy::TCppScope_t klass = Cppyy::GetScope(realType)) { - Cppyy::TCppType_t raw{0}; - if (Cppyy::GetSmartPtrInfo(realType, &raw, nullptr)) { - if (cpd == "") { - result = new SmartPtrConverter(klass, raw, control); - } else if (cpd == "&") { - result = new SmartPtrConverter(klass, raw); - } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { - result = new SmartPtrConverter(klass, raw, control, true); - } - } - - if (!result) { - // Cling WORKAROUND -- special case for STL iterators - if (Utility::IsSTLIterator(realType)) { - static STLIteratorConverter c; - result = &c; - } else - // -- Cling WORKAROUND - result = selectInstanceCnv(klass, cpd, dims, isConst, control); - } - } else { - std::smatch sm; - if (std::regex_search(resolvedType, sm, s_fnptr)) { - // this is a function pointer - auto pos1 = sm.position(0); - auto pos2 = resolvedType.rfind(')'); - result = new FunctionPointerConverter( - resolvedType.substr(0, pos1), resolvedType.substr(pos1+sm.length(), pos2-1)); - } - } - - if (!result && cpd == "&&") { - // for builtin, can use const-ref for r-ref - h = gConvFactories.find("const " + realType + "&"); - if (h != gConvFactories.end()) - return (h->second)(dims); - // else, unhandled moves - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - - if (!result && h != gConvFactories.end()) - // converter factory available, use it to create converter - result = (h->second)(dims); - else if (!result) { - // default to something reasonable, assuming "user knows best" - if (cpd.size() == 2 && cpd != "&&") // "**", "*[]", "*&" - result = new VoidPtrPtrConverter(dims.ndim()); - else if (!cpd.empty()) - result = new VoidArrayConverter(); // "user knows best" - else - // fails on use - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - - return result; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void CPyCppyy::DestroyConverter(Converter* p) -{ - if (p && p->HasState()) - delete p; // state-less converters are always shared -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterConverter(const std::string& name, cf_t fac) -{ -// register a custom converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) - return false; - - gConvFactories[name] = fac; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterConverterAlias(const std::string& name, const std::string& target) -{ -// register a custom converter that is a reference to an existing converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) - return false; - - auto t = gConvFactories.find(target); - if (t == gConvFactories.end()) - return false; - - gConvFactories[name] = t->second; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::UnregisterConverter(const std::string& name) -{ -// remove a custom converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) { - gConvFactories.erase(f); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -namespace { - -using namespace CPyCppyy; - -inline static -std::string::size_type dims2stringsz(cdims_t d) { - return (d && d.ndim() != UNKNOWN_SIZE) ? d[0] : std::string::npos; -} - -#define STRINGVIEW "basic_string_view >" -#define WSTRING1 "std::basic_string" -#define WSTRING2 "std::basic_string,std::allocator>" - -//-- aliasing special case: C complex (is binary compatible with C++ std::complex) -#ifndef _WIN32 -#define CCOMPLEX_D "_Complex double" -#define CCOMPLEX_F "_Complex float" -#else -#define CCOMPLEX_D "_C_double_complex" -#define CCOMPLEX_F "_C_float_complex" -#endif - -static struct InitConvFactories_t { -public: - InitConvFactories_t() { - // load all converter factories in the global map 'gConvFactories' - CPyCppyy::ConvFactories_t& gf = gConvFactories; - - // factories for built-ins - gf["bool"] = (cf_t)+[](cdims_t) { static BoolConverter c{}; return &c; }; - gf["const bool&"] = (cf_t)+[](cdims_t) { static ConstBoolRefConverter c{}; return &c; }; - gf["bool&"] = (cf_t)+[](cdims_t) { static BoolRefConverter c{}; return &c; }; - gf["char"] = (cf_t)+[](cdims_t) { static CharConverter c{}; return &c; }; - gf["const char&"] = (cf_t)+[](cdims_t) { static ConstCharRefConverter c{}; return &c; }; - gf["char&"] = (cf_t)+[](cdims_t) { static CharRefConverter c{}; return &c; }; - gf["signed char&"] = (cf_t)+[](cdims_t) { static SCharRefConverter c{}; return &c; }; - gf["unsigned char"] = (cf_t)+[](cdims_t) { static UCharConverter c{}; return &c; }; - gf["const unsigned char&"] = (cf_t)+[](cdims_t) { static ConstUCharRefConverter c{}; return &c; }; - gf["unsigned char&"] = (cf_t)+[](cdims_t) { static UCharRefConverter c{}; return &c; }; - gf["SCharAsInt"] = (cf_t)+[](cdims_t) { static SCharAsIntConverter c{}; return &c; }; - gf["UCharAsInt"] = (cf_t)+[](cdims_t) { static UCharAsIntConverter c{}; return &c; }; - gf["wchar_t"] = (cf_t)+[](cdims_t) { static WCharConverter c{}; return &c; }; - gf["char16_t"] = (cf_t)+[](cdims_t) { static Char16Converter c{}; return &c; }; - gf["char32_t"] = (cf_t)+[](cdims_t) { static Char32Converter c{}; return &c; }; - gf["wchar_t&"] = (cf_t)+[](cdims_t) { static WCharRefConverter c{}; return &c; }; - gf["char16_t&"] = (cf_t)+[](cdims_t) { static Char16RefConverter c{}; return &c; }; - gf["char32_t&"] = (cf_t)+[](cdims_t) { static Char32RefConverter c{}; return &c; }; - gf["int8_t"] = (cf_t)+[](cdims_t) { static Int8Converter c{}; return &c; }; - gf["const int8_t&"] = (cf_t)+[](cdims_t) { static ConstInt8RefConverter c{}; return &c; }; - gf["int8_t&"] = (cf_t)+[](cdims_t) { static Int8RefConverter c{}; return &c; }; - gf["int16_t"] = (cf_t)+[](cdims_t) { static Int16Converter c{}; return &c; }; - gf["const int16_t&"] = (cf_t)+[](cdims_t) { static ConstInt16RefConverter c{}; return &c; }; - gf["int16_t&"] = (cf_t)+[](cdims_t) { static Int16RefConverter c{}; return &c; }; - gf["int32_t"] = (cf_t)+[](cdims_t) { static Int32Converter c{}; return &c; }; - gf["const int32_t&"] = (cf_t)+[](cdims_t) { static ConstInt32RefConverter c{}; return &c; }; - gf["int32_t&"] = (cf_t)+[](cdims_t) { static Int32RefConverter c{}; return &c; }; - gf["uint8_t"] = (cf_t)+[](cdims_t) { static UInt8Converter c{}; return &c; }; - gf["const uint8_t&"] = (cf_t)+[](cdims_t) { static ConstUInt8RefConverter c{}; return &c; }; - gf["uint8_t&"] = (cf_t)+[](cdims_t) { static UInt8RefConverter c{}; return &c; }; - gf["uint16_t"] = (cf_t)+[](cdims_t) { static UInt16Converter c{}; return &c; }; - gf["const uint16_t&"] = (cf_t)+[](cdims_t) { static ConstUInt16RefConverter c{}; return &c; }; - gf["uint16_t&"] = (cf_t)+[](cdims_t) { static UInt16RefConverter c{}; return &c; }; - gf["uint32_t"] = (cf_t)+[](cdims_t) { static UInt32Converter c{}; return &c; }; - gf["const uint32_t&"] = (cf_t)+[](cdims_t) { static ConstUInt32RefConverter c{}; return &c; }; - gf["uint32_t&"] = (cf_t)+[](cdims_t) { static UInt32RefConverter c{}; return &c; }; - gf["short"] = (cf_t)+[](cdims_t) { static ShortConverter c{}; return &c; }; - gf["const short&"] = (cf_t)+[](cdims_t) { static ConstShortRefConverter c{}; return &c; }; - gf["short&"] = (cf_t)+[](cdims_t) { static ShortRefConverter c{}; return &c; }; - gf["unsigned short"] = (cf_t)+[](cdims_t) { static UShortConverter c{}; return &c; }; - gf["const unsigned short&"] = (cf_t)+[](cdims_t) { static ConstUShortRefConverter c{}; return &c; }; - gf["unsigned short&"] = (cf_t)+[](cdims_t) { static UShortRefConverter c{}; return &c; }; - gf["int"] = (cf_t)+[](cdims_t) { static IntConverter c{}; return &c; }; - gf["int&"] = (cf_t)+[](cdims_t) { static IntRefConverter c{}; return &c; }; - gf["const int&"] = (cf_t)+[](cdims_t) { static ConstIntRefConverter c{}; return &c; }; - gf["unsigned int"] = (cf_t)+[](cdims_t) { static UIntConverter c{}; return &c; }; - gf["const unsigned int&"] = (cf_t)+[](cdims_t) { static ConstUIntRefConverter c{}; return &c; }; - gf["unsigned int&"] = (cf_t)+[](cdims_t) { static UIntRefConverter c{}; return &c; }; - gf["long"] = (cf_t)+[](cdims_t) { static LongConverter c{}; return &c; }; - gf["long&"] = (cf_t)+[](cdims_t) { static LongRefConverter c{}; return &c; }; - gf["const long&"] = (cf_t)+[](cdims_t) { static ConstLongRefConverter c{}; return &c; }; - gf["unsigned long"] = (cf_t)+[](cdims_t) { static ULongConverter c{}; return &c; }; - gf["const unsigned long&"] = (cf_t)+[](cdims_t) { static ConstULongRefConverter c{}; return &c; }; - gf["unsigned long&"] = (cf_t)+[](cdims_t) { static ULongRefConverter c{}; return &c; }; - gf["long long"] = (cf_t)+[](cdims_t) { static LLongConverter c{}; return &c; }; - gf["const long long&"] = (cf_t)+[](cdims_t) { static ConstLLongRefConverter c{}; return &c; }; - gf["long long&"] = (cf_t)+[](cdims_t) { static LLongRefConverter c{}; return &c; }; - gf["unsigned long long"] = (cf_t)+[](cdims_t) { static ULLongConverter c{}; return &c; }; - gf["const unsigned long long&"] = (cf_t)+[](cdims_t) { static ConstULLongRefConverter c{}; return &c; }; - gf["unsigned long long&"] = (cf_t)+[](cdims_t) { static ULLongRefConverter c{}; return &c; }; - - gf["float"] = (cf_t)+[](cdims_t) { static FloatConverter c{}; return &c; }; - gf["const float&"] = (cf_t)+[](cdims_t) { static ConstFloatRefConverter c{}; return &c; }; - gf["float&"] = (cf_t)+[](cdims_t) { static FloatRefConverter c{}; return &c; }; - gf["double"] = (cf_t)+[](cdims_t) { static DoubleConverter c{}; return &c; }; - gf["double&"] = (cf_t)+[](cdims_t) { static DoubleRefConverter c{}; return &c; }; - gf["const double&"] = (cf_t)+[](cdims_t) { static ConstDoubleRefConverter c{}; return &c; }; - gf["long double"] = (cf_t)+[](cdims_t) { static LDoubleConverter c{}; return &c; }; - gf["const long double&"] = (cf_t)+[](cdims_t) { static ConstLDoubleRefConverter c{}; return &c; }; - gf["long double&"] = (cf_t)+[](cdims_t) { static LDoubleRefConverter c{}; return &c; }; - gf["std::complex"] = (cf_t)+[](cdims_t) { return new ComplexDConverter{}; }; - gf["const std::complex&"] = (cf_t)+[](cdims_t) { return new ComplexDConverter{}; }; - gf["void"] = (cf_t)+[](cdims_t) { static VoidConverter c{}; return &c; }; - - // pointer/array factories - gf["bool ptr"] = (cf_t)+[](cdims_t d) { return new BoolArrayConverter{d}; }; - gf["signed char ptr"] = (cf_t)+[](cdims_t d) { return new SCharArrayConverter{d}; }; - gf["signed char**"] = (cf_t)+[](cdims_t) { return new SCharArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}}; }; - gf["const unsigned char*"] = (cf_t)+[](cdims_t d) { return new UCharArrayConverter{d}; }; - gf["unsigned char ptr"] = (cf_t)+[](cdims_t d) { return new UCharArrayConverter{d}; }; - gf["SCharAsInt*"] = gf["signed char ptr"]; - gf["SCharAsInt[]"] = gf["signed char ptr"]; - gf["UCharAsInt*"] = gf["unsigned char ptr"]; - gf["UCharAsInt[]"] = gf["unsigned char ptr"]; - gf["std::byte ptr"] = (cf_t)+[](cdims_t d) { return new ByteArrayConverter{d}; }; - gf["int8_t ptr"] = (cf_t)+[](cdims_t d) { return new Int8ArrayConverter{d}; }; - gf["int16_t ptr"] = (cf_t)+[](cdims_t d) { return new Int16ArrayConverter{d}; }; - gf["int32_t ptr"] = (cf_t)+[](cdims_t d) { return new Int32ArrayConverter{d}; }; - gf["uint8_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt8ArrayConverter{d}; }; - gf["uint16_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt16ArrayConverter{d}; }; - gf["uint32_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt32ArrayConverter{d}; }; - gf["short ptr"] = (cf_t)+[](cdims_t d) { return new ShortArrayConverter{d}; }; - gf["unsigned short ptr"] = (cf_t)+[](cdims_t d) { return new UShortArrayConverter{d}; }; - gf["int ptr"] = (cf_t)+[](cdims_t d) { return new IntArrayConverter{d}; }; - gf["unsigned int ptr"] = (cf_t)+[](cdims_t d) { return new UIntArrayConverter{d}; }; - gf["long ptr"] = (cf_t)+[](cdims_t d) { return new LongArrayConverter{d}; }; - gf["unsigned long ptr"] = (cf_t)+[](cdims_t d) { return new ULongArrayConverter{d}; }; - gf["long long ptr"] = (cf_t)+[](cdims_t d) { return new LLongArrayConverter{d}; }; - gf["unsigned long long ptr"] = (cf_t)+[](cdims_t d) { return new ULLongArrayConverter{d}; }; - gf["float ptr"] = (cf_t)+[](cdims_t d) { return new FloatArrayConverter{d}; }; - gf["double ptr"] = (cf_t)+[](cdims_t d) { return new DoubleArrayConverter{d}; }; - gf["long double ptr"] = (cf_t)+[](cdims_t d) { return new LDoubleArrayConverter{d}; }; - gf["std::complex ptr"] = (cf_t)+[](cdims_t d) { return new ComplexFArrayConverter{d}; }; - gf["std::complex ptr"] = (cf_t)+[](cdims_t d) { return new ComplexDArrayConverter{d}; }; - gf["void*"] = (cf_t)+[](cdims_t d) { return new VoidArrayConverter{(bool)d}; }; - - // aliases - gf["signed char"] = gf["char"]; - gf["const signed char&"] = gf["const char&"]; - gf["std::byte"] = gf["uint8_t"]; - gf["byte"] = gf["uint8_t"]; - gf["const std::byte&"] = gf["const uint8_t&"]; - gf["const byte&"] = gf["const uint8_t&"]; - gf["std::byte&"] = gf["uint8_t&"]; - gf["byte&"] = gf["uint8_t&"]; - gf["std::int8_t"] = gf["int8_t"]; - gf["const std::int8_t&"] = gf["const int8_t&"]; - gf["std::int8_t&"] = gf["int8_t&"]; - gf["std::uint8_t"] = gf["uint8_t"]; - gf["const std::uint8_t&"] = gf["const uint8_t&"]; - gf["std::uint8_t&"] = gf["uint8_t&"]; - gf["internal_enum_type_t"] = gf["int"]; - gf["internal_enum_type_t&"] = gf["int&"]; - gf["const internal_enum_type_t&"] = gf["const int&"]; - gf["internal_enum_type_t ptr"] = gf["int ptr"]; -#ifdef _WIN32 - gf["__int64"] = gf["long long"]; - gf["const __int64&"] = gf["const long long&"]; - gf["__int64&"] = gf["long long&"]; - gf["__int64 ptr"] = gf["long long ptr"]; - gf["unsigned __int64"] = gf["unsigned long long"]; - gf["const unsigned __int64&"] = gf["const unsigned long long&"]; - gf["unsigned __int64&"] = gf["unsigned long long&"]; - gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; -#endif - gf[CCOMPLEX_D] = gf["std::complex"]; - gf["const " CCOMPLEX_D "&"] = gf["const std::complex&"]; - gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; - gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; - - // We always need these converters when cppyy is based on an unpatched - // ROOT, because the "long long" types are always converted to Long64_t - // and ULong64_t already at the ROOT Meta level. - // See https://github.com/root-project/root/issues/15872#issuecomment-2174092763 - gf["Long64_t"] = gf["long long"]; - gf["Long64_t ptr"] = gf["long long ptr"]; - gf["Long64_t&"] = gf["long long&"]; - gf["const Long64_t&"] = gf["const long long&"]; - gf["ULong64_t"] = gf["unsigned long long"]; - gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; - gf["ULong64_t&"] = gf["unsigned long long&"]; - gf["const ULong64_t&"] = gf["const unsigned long long&"]; - - // factories for special cases - gf["nullptr_t"] = (cf_t)+[](cdims_t) { static NullptrConverter c{}; return &c;}; - gf["const char*"] = (cf_t)+[](cdims_t) { return new CStringConverter{}; }; - gf["const signed char*"] = gf["const char*"]; - gf["const char*&&"] = gf["const char*"]; - gf["const char[]"] = (cf_t)+[](cdims_t) { return new CStringConverter{}; }; - gf["char*"] = (cf_t)+[](cdims_t d) { return new NonConstCStringConverter{dims2stringsz(d)}; }; - gf["char[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, true}; }; - gf["signed char*"] = gf["char*"]; - gf["wchar_t*"] = (cf_t)+[](cdims_t) { return new WCStringConverter{}; }; - // TODO: The libc++ library that is used most prominently by Apple for - // macOS is using the _Nonnull Clang attribute for some of its string - // constructors (_LIBCPP_DIAGNOSE_NULLPTR is a preprocessor macro that - // resolves to _Nullable). The cppyy backend doesn't strip away such Clang - // attributes, so the wchar_t* converter fails to match. To make the - // construction of std::wstring work on macOS, we are explicitly creating - // a converter for the wchar_t*_Nonnull type here, but we should try to - // find a more general solution for dealing with Clang attributes. - // [1] https://clang.llvm.org/docs/AttributeReference.html#id656 - // [2] https://github.com/llvm/llvm-project/blob/2078da43e25a4623cab2d0d60decddf709aaea28/libcxx/include/string#L1061 - gf["wchar_t*_Nonnull"] = (cf_t)+[](cdims_t) { return new WCStringConverter{}; }; - gf["char16_t*"] = (cf_t)+[](cdims_t) { return new CString16Converter{}; }; - gf["char16_t[]"] = (cf_t)+[](cdims_t d) { return new CString16Converter{dims2stringsz(d)}; }; - gf["char32_t*"] = (cf_t)+[](cdims_t) { return new CString32Converter{}; }; - gf["char32_t[]"] = (cf_t)+[](cdims_t d) { return new CString32Converter{dims2stringsz(d)}; }; - // TODO: the following are handled incorrectly upstream (char16_t** where char16_t* intended)?! - gf["char16_t**"] = gf["char16_t*"]; - gf["char32_t**"] = gf["char32_t*"]; - gf["const char**"] = (cf_t)+[](cdims_t) { return new CStringArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}, false}; }; - gf["char**"] = gf["const char**"]; - gf["const char*[]"] = (cf_t)+[](cdims_t d) { return new CStringArrayConverter{d, false}; }; - gf["char*[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, false}; }; - gf["char ptr"] = gf["char*[]"]; - gf["std::string"] = (cf_t)+[](cdims_t) { return new STLStringConverter{}; }; - gf["const std::string&"] = gf["std::string"]; - gf["string"] = gf["std::string"]; - gf["const string&"] = gf["std::string"]; - gf["std::string&&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; - gf["string&&"] = gf["std::string&&"]; - gf["std::string_view"] = (cf_t)+[](cdims_t) { return new STLStringViewConverter{}; }; - gf[STRINGVIEW] = gf["std::string_view"]; - gf["std::string_view&"] = gf["std::string_view"]; - gf["const std::string_view&"] = gf["std::string_view"]; - gf["const " STRINGVIEW "&"] = gf["std::string_view"]; - gf["std::wstring"] = (cf_t)+[](cdims_t) { return new STLWStringConverter{}; }; - gf[WSTRING1] = gf["std::wstring"]; - gf[WSTRING2] = gf["std::wstring"]; - gf["const std::wstring&"] = gf["std::wstring"]; - gf["const " WSTRING1 "&"] = gf["std::wstring"]; - gf["const " WSTRING2 "&"] = gf["std::wstring"]; - gf["void*&"] = (cf_t)+[](cdims_t) { static VoidPtrRefConverter c{}; return &c; }; - gf["void**"] = (cf_t)+[](cdims_t d) { return new VoidPtrPtrConverter{d}; }; - gf["void ptr"] = gf["void**"]; - gf["PyObject*"] = (cf_t)+[](cdims_t) { static PyObjectConverter c{}; return &c; }; - gf["_object*"] = gf["PyObject*"]; - gf["FILE*"] = (cf_t)+[](cdims_t) { return new VoidArrayConverter{}; }; - } -} initConvFactories_; - -} // unnamed namespace diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h deleted file mode 100644 index 052e1f2ae1a4d..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef CPYCPPYY_CONVERTERS_H -#define CPYCPPYY_CONVERTERS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -struct Parameter; -struct CallContext; - -class CPYCPPYY_CLASS_EXPORT Converter { -public: - virtual ~Converter(); - - Converter() = default; - - Converter(Converter const& other) = delete; - Converter(Converter && other) = delete; - Converter& operator=(Converter const& other) = delete; - Converter& operator=(Converter && other) = delete; - -public: - virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; - virtual PyObject* FromMemory(void* address); - virtual bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr); - virtual bool HasState() { return false; } -}; - -// create/destroy converter from fully qualified type (public API) -CPYCPPYY_EXPORT Converter* CreateConverter(const std::string& fullType, cdims_t dims = 0); -CPYCPPYY_EXPORT void DestroyConverter(Converter* p); -typedef Converter* (*cf_t)(cdims_t d); -CPYCPPYY_EXPORT bool RegisterConverter(const std::string& name, cf_t fac); -CPYCPPYY_EXPORT bool RegisterConverterAlias(const std::string& name, const std::string& target); -CPYCPPYY_EXPORT bool UnregisterConverter(const std::string& name); - - -// converters for special cases (only here b/c of external use of StrictInstancePtrConverter) -class VoidArrayConverter : public Converter { -public: - VoidArrayConverter(bool keepControl = true) { fKeepControl = keepControl; } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr) override; - bool HasState() override { return true; } - -protected: - virtual bool GetAddressSpecialCase(PyObject* pyobject, void*& address); - bool KeepControl() { return fKeepControl; } - -private: - bool fKeepControl; -}; - -template -class InstancePtrConverter : public VoidArrayConverter { -public: - InstancePtrConverter(Cppyy::TCppType_t klass, bool keepControl = false) : - VoidArrayConverter(keepControl), fClass(klass) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr) override; - -protected: - Cppyy::TCppType_t fClass; -}; - -class StrictInstancePtrConverter : public InstancePtrConverter { -public: - using InstancePtrConverter::InstancePtrConverter; - -protected: - bool GetAddressSpecialCase(PyObject*, void*&) override { return false; } -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CONVERTERS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h deleted file mode 100644 index 436d47fafe43f..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ /dev/null @@ -1,293 +0,0 @@ -#ifndef CPYCPPYY_CPPYY_H -#define CPYCPPYY_CPPYY_H - -// Standard -#include -#include -#include -#include -#include - -// import/export (after precommondefs.h from PyPy) -#define CPPYY_IMPORT extern - -// some more types; assumes Cppyy.h follows Python.h -#ifndef PY_LONG_LONG -#ifdef _WIN32 -typedef __int64 PY_LONG_LONG; -#else -typedef long long PY_LONG_LONG; -#endif -#endif - -#ifndef PY_ULONG_LONG -#ifdef _WIN32 -typedef unsigned __int64 PY_ULONG_LONG; -#else -typedef unsigned long long PY_ULONG_LONG; -#endif -#endif - -#ifndef PY_LONG_DOUBLE -typedef long double PY_LONG_DOUBLE; -#endif - - -namespace Cppyy { - - typedef size_t TCppScope_t; - typedef TCppScope_t TCppType_t; - typedef void* TCppEnum_t; - typedef void* TCppObject_t; - typedef intptr_t TCppMethod_t; - - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; - -// direct interpreter access ------------------------------------------------- - CPPYY_IMPORT - bool Compile(const std::string& code, bool silent = false); - CPPYY_IMPORT - std::string ToString(TCppType_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- - CPPYY_IMPORT - std::string ResolveName(const std::string& cppitem_name); - CPPYY_IMPORT - std::string ResolveEnum(const std::string& enum_type); - CPPYY_IMPORT - TCppScope_t GetScope(const std::string& scope_name); - CPPYY_IMPORT - TCppType_t GetActualClass(TCppType_t klass, TCppObject_t obj); - CPPYY_IMPORT - size_t SizeOf(TCppType_t klass); - CPPYY_IMPORT - size_t SizeOf(const std::string& type_name); - - CPPYY_IMPORT - bool IsBuiltin(const std::string& type_name); - CPPYY_IMPORT - bool IsComplete(const std::string& type_name); - - CPPYY_IMPORT - TCppScope_t gGlobalScope; // for fast access - -// memory management --------------------------------------------------------- - CPPYY_IMPORT - TCppObject_t Allocate(TCppType_t type); - CPPYY_IMPORT - void Deallocate(TCppType_t type, TCppObject_t instance); - CPPYY_IMPORT - TCppObject_t Construct(TCppType_t type, void* arena = nullptr); - CPPYY_IMPORT - void Destruct(TCppType_t type, TCppObject_t instance); - -// method/function dispatching ----------------------------------------------- - CPPYY_IMPORT - void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); - CPPYY_IMPORT - TCppObject_t CallConstructor(TCppMethod_t method, TCppType_t type, size_t nargs, void* args); - CPPYY_IMPORT - void CallDestructor(TCppType_t type, TCppObject_t self); - CPPYY_IMPORT - TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type); - - CPPYY_IMPORT - TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true); - -// handling of function argument buffer -------------------------------------- - CPPYY_IMPORT - void* AllocateFunctionArgs(size_t nargs); - CPPYY_IMPORT - void DeallocateFunctionArgs(void* args); - CPPYY_IMPORT - size_t GetFunctionArgSizeof(); - CPPYY_IMPORT - size_t GetFunctionArgTypeoffset(); - -// scope reflection information ---------------------------------------------- - CPPYY_IMPORT - bool IsNamespace(TCppScope_t scope); - CPPYY_IMPORT - bool IsTemplate(const std::string& template_name); - CPPYY_IMPORT - bool IsAbstract(TCppType_t type); - CPPYY_IMPORT - bool IsEnum(const std::string& type_name); - CPPYY_IMPORT - bool IsAggregate(TCppType_t type); - CPPYY_IMPORT - bool IsIntegerType(const std::string &type_name); - CPPYY_IMPORT - bool IsDefaultConstructable(TCppType_t type); - - CPPYY_IMPORT - void GetAllCppNames(TCppScope_t scope, std::set& cppnames); - -// namespace reflection information ------------------------------------------ - CPPYY_IMPORT - std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- - CPPYY_IMPORT - std::string GetFinalName(TCppType_t type); - CPPYY_IMPORT - std::string GetScopedFinalName(TCppType_t type); - CPPYY_IMPORT - bool HasVirtualDestructor(TCppType_t type); - CPPYY_IMPORT - bool HasComplexHierarchy(TCppType_t type); - CPPYY_IMPORT - TCppIndex_t GetNumBases(TCppType_t type); - CPPYY_IMPORT - TCppIndex_t GetNumBasesLongestBranch(TCppType_t type); - CPPYY_IMPORT - std::string GetBaseName(TCppType_t type, TCppIndex_t ibase); - CPPYY_IMPORT - bool IsSubtype(TCppType_t derived, TCppType_t base); - CPPYY_IMPORT - bool IsSmartPtr(TCppType_t type); - CPPYY_IMPORT - bool GetSmartPtrInfo(const std::string&, TCppType_t* raw, TCppMethod_t* deref); - CPPYY_IMPORT - void AddSmartPtrType(const std::string&); - - CPPYY_IMPORT - void AddTypeReducer(const std::string& reducable, const std::string& reduced); - -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - CPPYY_IMPORT - ptrdiff_t GetBaseOffset( - TCppType_t derived, TCppType_t base, TCppObject_t address, int direction, bool rerror = false); - -// method/function reflection information ------------------------------------ - CPPYY_IMPORT - TCppIndex_t GetNumMethods(TCppScope_t scope, bool accept_namespace = false); - CPPYY_IMPORT - std::vector GetMethodIndicesFromName(TCppScope_t scope, const std::string& name); - - CPPYY_IMPORT - TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth); - - CPPYY_IMPORT - std::string GetMethodName(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodFullName(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodMangledName(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodResultType(TCppMethod_t); - CPPYY_IMPORT - TCppIndex_t GetMethodNumArgs(TCppMethod_t); - CPPYY_IMPORT - TCppIndex_t GetMethodReqArgs(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); - CPPYY_IMPORT - std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - std::string GetMethodSignature(TCppMethod_t, bool show_formalargs, TCppIndex_t maxargs = (TCppIndex_t)-1); - CPPYY_IMPORT - std::string GetMethodPrototype(TCppScope_t scope, TCppMethod_t, bool show_formalargs); - CPPYY_IMPORT - bool IsConstMethod(TCppMethod_t); - - CPPYY_IMPORT - TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); - CPPYY_IMPORT - std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); - CPPYY_IMPORT - bool IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth); - CPPYY_IMPORT - bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); - CPPYY_IMPORT - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); - CPPYY_IMPORT - bool IsMethodTemplate(TCppScope_t scope, TCppIndex_t imeth); - CPPYY_IMPORT - TCppMethod_t GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto); - - CPPYY_IMPORT - TCppIndex_t GetGlobalOperator( - TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& op); - -// method properties --------------------------------------------------------- - CPPYY_IMPORT - bool IsPublicMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsProtectedMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsConstructor(TCppMethod_t method); - CPPYY_IMPORT - bool IsDestructor(TCppMethod_t method); - CPPYY_IMPORT - bool IsStaticMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsExplicit(TCppMethod_t method); - -// data member reflection information ---------------------------------------- - CPPYY_IMPORT - TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false); - CPPYY_IMPORT - std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); - -// data member properties ---------------------------------------------------- - CPPYY_IMPORT - bool IsPublicData(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - bool IsStaticData(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - bool IsConstData(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); - CPPYY_IMPORT - int GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension); - -// enum properties ----------------------------------------------------------- - CPPYY_IMPORT - TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name); - CPPYY_IMPORT - TCppIndex_t GetNumEnumData(TCppEnum_t); - CPPYY_IMPORT - std::string GetEnumDataName(TCppEnum_t, TCppIndex_t idata); - CPPYY_IMPORT - long long GetEnumDataValue(TCppEnum_t, TCppIndex_t idata); - -} // namespace Cppyy - -#endif // !CPYCPPYY_CPPYY_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.cxx deleted file mode 100644 index 6f66012cbb2b5..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.cxx +++ /dev/null @@ -1,463 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CustomPyTypes.h" -#include "CPPInstance.h" -#include "Converters.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// As of Python 3.12, we can't use the PyMethod_GET_FUNCTION and -// PyMethod_GET_SELF macros anymore, as the contain asserts that check if the -// Python type is actually PyMethod_Type. If the Python type is -// CustomInstanceMethod_Type, we need our own macros. Technically they do they -// same, because the actual C++ type of the PyObject is PyMethodObject anyway. -#define CustomInstanceMethod_GET_SELF(meth) reinterpret_cast(meth)->im_self -#define CustomInstanceMethod_GET_FUNCTION(meth) reinterpret_cast(meth)->im_func -#if PY_VERSION_HEX >= 0x03000000 -// TODO: this will break functionality -#define CustomInstanceMethod_GET_CLASS(meth) Py_None -#else -#define CustomInstanceMethod_GET_CLASS(meth) PyMethod_GET_CLASS(meth) -#endif - -namespace CPyCppyy { - -#if PY_VERSION_HEX < 0x03000000 -//= float type allowed for reference passing ================================= -PyTypeObject RefFloat_Type = { // python float is a C/C++ double - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.Double", // tp_name - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"CPyCppyy float object for pass by reference", // tp_doc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - &PyFloat_Type, // tp_base - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -}; - -//= long type allowed for reference passing ================================== -PyTypeObject RefInt_Type = { // python int is a C/C++ long - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.Long", // tp_name - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE -#if PY_VERSION_HEX >= 0x03040000 - | Py_TPFLAGS_LONG_SUBCLASS -#endif - , // tp_flags - (char*)"CPyCppyy long object for pass by reference", // tp_doc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - &PyInt_Type, // tp_base - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -}; -#endif - -//- custom type representing typedef to pointer of class --------------------- -static PyObject* tptc_call(typedefpointertoclassobject* self, PyObject* args, PyObject* /* kwds */) -{ - long long addr = 0; - if (!PyArg_ParseTuple(args, const_cast("|L"), &addr)) - return nullptr; - return BindCppObjectNoCast((Cppyy::TCppObject_t)(intptr_t)addr, self->fCppType); -} - -//----------------------------------------------------------------------------- -static PyObject* tptc_getcppname(typedefpointertoclassobject* self, void*) -{ - return CPyCppyy_PyText_FromString( - (Cppyy::GetScopedFinalName(self->fCppType)+"*").c_str()); -} - -//----------------------------------------------------------------------------- -static PyObject* tptc_name(typedefpointertoclassobject* self, void*) -{ - PyObject* pyclass = CPyCppyy::GetScopeProxy(self->fCppType); - if (pyclass) { - PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gName); - Py_DECREF(pyclass); - return pyname; - } - - return CPyCppyy_PyText_FromString("*"); -} - -//----------------------------------------------------------------------------- -static PyGetSetDef tptc_getset[] = { - {(char*)"__name__", (getter)tptc_name, nullptr, nullptr, nullptr}, - {(char*)"__cpp_name__", (getter)tptc_getcppname, nullptr, nullptr, nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -PyTypeObject TypedefPointerToClass_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.TypedefPointerToClass",// tp_name - sizeof(typedefpointertoclassobject), // tp_basicsize - 0, // tp_itemsize - 0, // tp_dealloc - 0, // tp_vectorcall_offset - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async - 0, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - 0, // tp_as_mapping - 0, // tp_hash - (ternaryfunc)tptc_call, // tp_call - 0, // tp_str - PyObject_GenericGetAttr, // tp_getattro - PyObject_GenericSetAttr, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT, // tp_flags - 0, // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - 0, // tp_methods - 0, // tp_members - tptc_getset, // tp_getset - 0, // tp_base - 0, // tp_dict - 0, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - 0, // tp_new - 0, // tp_free - 0, // tp_is_gc - 0, // tp_bases - 0, // tp_mro - 0, // tp_cache - 0, // tp_subclasses - 0 // tp_weaklist -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - -//= instancemethod object with a more efficient call function ================ -static PyMethodObject* free_list; -static int numfree = 0; -#ifndef PyMethod_MAXFREELIST -#define PyMethod_MAXFREELIST 256 -#endif - -//----------------------------------------------------------------------------- -PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject* -#if PY_VERSION_HEX < 0x03000000 - pyclass -#endif - ) -{ -// from instancemethod, but with custom type (at issue is that instancemethod is not -// meant to be derived from) - PyMethodObject* im; - if (!PyCallable_Check(func)) { - PyErr_Format(PyExc_SystemError, - "%s:%d: bad argument to internal function", __FILE__, __LINE__); - return nullptr; - } - - im = free_list; - if (im != nullptr) { - free_list = (PyMethodObject*)(im->im_self); - (void)PyObject_INIT(im, &CustomInstanceMethod_Type); - } - else { - im = PyObject_GC_New(PyMethodObject, &CustomInstanceMethod_Type); - if (im == nullptr) - return nullptr; - } - - im->im_weakreflist = nullptr; - Py_INCREF(func); - im->im_func = func; - Py_XINCREF(self); - im->im_self = self; -#if PY_VERSION_HEX < 0x03000000 - Py_XINCREF(pyclass); - im->im_class = pyclass; -#endif - PyObject_GC_Track(im); - return (PyObject*)im; -} - -//----------------------------------------------------------------------------- -static void im_dealloc(PyMethodObject* im) -{ -// from instancemethod, but with custom type (at issue is that instancemethod is not -// meant to be derived from) - PyObject_GC_UnTrack(im); - - if (im->im_weakreflist != nullptr) - PyObject_ClearWeakRefs((PyObject*)im); - - Py_DECREF(im->im_func); - Py_XDECREF(im->im_self); -#if PY_VERSION_HEX < 0x03000000 - Py_XDECREF(im->im_class); -#endif - - if (numfree < PyMethod_MAXFREELIST) { - im->im_self = (PyObject*)free_list; - free_list = im; - numfree++; - } else { - PyObject_GC_Del(im); - } -} - -//----------------------------------------------------------------------------- -static PyObject* im_call(PyObject* meth, PyObject* args, PyObject* kw) -{ -// The mapping from a method to a function involves reshuffling of self back -// into the list of arguments. However, the pythonized methods will then have -// to undo that shuffling, which is inefficient. This method is the same as -// the one for the instancemethod object, except for the shuffling. - PyObject* self = CustomInstanceMethod_GET_SELF(meth); - - if (!self) { - // unbound methods must be called with an instance of the class (or a - // derived class) as first argument - Py_ssize_t argc = PyTuple_GET_SIZE(args); - PyObject* pyclass = CustomInstanceMethod_GET_CLASS(meth); - if (1 <= argc && PyObject_IsInstance(PyTuple_GET_ITEM(args, 0), pyclass) == 1) { - self = PyTuple_GET_ITEM(args, 0); - - PyObject* newArgs = PyTuple_New(argc-1); - for (int i = 1; i < argc; ++i) { - PyObject* v = PyTuple_GET_ITEM(args, i); - Py_INCREF(v); - PyTuple_SET_ITEM(newArgs, i-1, v); - } - - args = newArgs; - - } else - return PyMethod_Type.tp_call(meth, args, kw); // will set proper error msg - - } else - Py_INCREF(args); - - PyCFunctionObject* func = (PyCFunctionObject*)CustomInstanceMethod_GET_FUNCTION(meth); - -// the function is globally shared, so set and reset its "self" (ok, b/c of GIL) - Py_INCREF(self); - func->m_self = self; - PyObject* result = CPyCppyy_PyCFunction_Call((PyObject*)func, args, kw); - func->m_self = nullptr; - Py_DECREF(self); - Py_DECREF(args); - return result; -} - -//----------------------------------------------------------------------------- -static PyObject* im_descr_get(PyObject* meth, PyObject* obj, PyObject* pyclass) -{ -// from instancemethod: don't rebind an already bound method, or an unbound method -// of a class that's not a base class of pyclass - if (CustomInstanceMethod_GET_SELF(meth) -#if PY_VERSION_HEX < 0x03000000 - || (CustomInstanceMethod_GET_CLASS(meth) && - !PyObject_IsSubclass(pyclass, CustomInstanceMethod_GET_CLASS(meth))) -#endif - ) { - Py_INCREF(meth); - return meth; - } - - if (obj == Py_None) - obj = nullptr; - - return CustomInstanceMethod_New(CustomInstanceMethod_GET_FUNCTION(meth), obj, pyclass); -} - -//= CPyCppyy custom instance method type ===================================== -PyTypeObject CustomInstanceMethod_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.InstanceMethod", // tp_name - 0, 0, - (destructor)im_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - im_call, // tp_call - 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"CPyCppyy custom instance method (internal)", // tp_doc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - &PyMethod_Type, // tp_base - 0, - im_descr_get, // tp_descr_get - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - - -//= CPyCppyy custom iterator for performance ================================= -static void indexiter_dealloc(indexiterobject* ii) { - PyObject_GC_UnTrack(ii); - Py_XDECREF(ii->ii_container); - PyObject_GC_Del(ii); -} - -static int indexiter_traverse(indexiterobject* ii, visitproc visit, void* arg) { - Py_VISIT(ii->ii_container); - return 0; -} - -static PyObject* indexiter_iternext(indexiterobject* ii) { - if (ii->ii_pos >= ii->ii_len) - return nullptr; - - PyObject* pyindex = PyLong_FromSsize_t(ii->ii_pos); - PyObject* result = PyObject_CallMethodOneArg((PyObject*)ii->ii_container, PyStrings::gGetItem, pyindex); - Py_DECREF(pyindex); - - ii->ii_pos += 1; - return result; -} - -PyTypeObject IndexIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.indexiter", // tp_name - sizeof(indexiterobject), // tp_basicsize - 0, - (destructor)indexiter_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)indexiter_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)indexiter_iternext, // tp_iternext - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - - -static void vectoriter_dealloc(vectoriterobject* vi) { - if (vi->vi_converter && vi->vi_converter->HasState()) delete vi->vi_converter; - indexiter_dealloc(vi); -} - -static PyObject* vectoriter_iternext(vectoriterobject* vi) { - if (vi->ii_pos >= vi->ii_len) - return nullptr; - - PyObject* result = nullptr; - - if (vi->vi_data && vi->vi_converter) { - void* location = (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos); - result = vi->vi_converter->FromMemory(location); - } else if (vi->vi_data && vi->vi_klass) { - // The CPPInstance::kNoMemReg by-passes the memory regulator; the assumption here is - // that objects in vectors are simple and thus do not need to maintain object identity - // (or at least not during the loop anyway). This gains 2x in performance. - Cppyy::TCppObject_t cppobj = (Cppyy::TCppObject_t)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos); - if (vi->vi_flags & vectoriterobject::kIsPolymorphic) - result = CPyCppyy::BindCppObject(*(void**)cppobj, vi->vi_klass, CPyCppyy::CPPInstance::kNoMemReg); - else - result = CPyCppyy::BindCppObjectNoCast(cppobj, vi->vi_klass, CPyCppyy::CPPInstance::kNoMemReg); - if ((vi->vi_flags & vectoriterobject::kNeedLifeLine) && result) - PyObject_SetAttr(result, PyStrings::gLifeLine, vi->ii_container); - } else { - PyObject* pyindex = PyLong_FromSsize_t(vi->ii_pos); - result = PyObject_CallMethodOneArg((PyObject*)vi->ii_container, PyStrings::gGetNoCheck, pyindex); - Py_DECREF(pyindex); - } - - vi->ii_pos += 1; - return result; -} - -PyTypeObject VectorIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.vectoriter", // tp_name - sizeof(vectoriterobject), // tp_basicsize - 0, - (destructor)vectoriter_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)indexiter_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)vectoriter_iternext, // tp_iternext - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.h b/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.h deleted file mode 100644 index b9fbf073dca0e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef CPYCPPYY_CUSTOMPYTYPES_H -#define CPYCPPYY_CUSTOMPYTYPES_H - -namespace CPyCppyy { - -/** Custom "builtins," detectable by type, for pass by ref and improved - performance. - */ - -#if PY_VERSION_HEX < 0x03000000 -//- reference float object type and type verification ------------------------ -extern PyTypeObject RefFloat_Type; - -template -inline bool RefFloat_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &RefFloat_Type); -} - -template -inline bool RefFloat_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &RefFloat_Type; -} - -//- reference long object type and type verification ------------------------- -extern PyTypeObject RefInt_Type; - -template -inline bool RefInt_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &RefInt_Type); -} - -template -inline bool RefInt_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &RefInt_Type; -} -#endif - -//- custom type representing typedef to pointer of class --------------------- -struct typedefpointertoclassobject { - PyObject_HEAD - Cppyy::TCppType_t fCppType; -}; - -extern PyTypeObject TypedefPointerToClass_Type; - -template -inline bool TypedefPointerToClass_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TypedefPointerToClass_Type); -} - -template -inline bool TypedefPointerToClass_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TypedefPointerToClass_Type; -} - -//- custom instance method object type and type verification ----------------- -extern PyTypeObject CustomInstanceMethod_Type; - -template -inline bool CustomInstanceMethod_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &CustomInstanceMethod_Type); -} - -template -inline bool CustomInstanceMethod_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CustomInstanceMethod_Type; -} - -PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject* pyclass); - -//- custom iterator for high performance std::vector iteration --------------- -struct indexiterobject { - PyObject_HEAD - PyObject* ii_container; - Py_ssize_t ii_pos; - Py_ssize_t ii_len; -}; - -extern PyTypeObject IndexIter_Type; - -class Converter; -struct vectoriterobject : public indexiterobject { - void* vi_data; - Py_ssize_t vi_stride; - CPyCppyy::Converter* vi_converter; - Cppyy::TCppType_t vi_klass; - int vi_flags; - - enum EFlags { - kDefault = 0x0000, - kNeedLifeLine = 0x0001, - kIsPolymorphic = 0x0002, - }; -}; - -extern PyTypeObject VectorIter_Type; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CUSTOMPYTYPES_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h deleted file mode 100644 index 3f8ed377d3bd5..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ /dev/null @@ -1,488 +0,0 @@ -#ifndef CPYCPPYY_DECLARECONVERTERS_H -#define CPYCPPYY_DECLARECONVERTERS_H - -// Bindings -#include "Converters.h" -#include "Dimensions.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -namespace { - -#define CPPYY_DECLARE_BASIC_CONVERTER(name) \ -class name##Converter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ -}; \ - \ -class Const##name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ -} - - -#define CPPYY_DECLARE_BASIC_CONVERTER2(name, base) \ -class name##Converter : public base##Converter { \ -public: \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ -}; \ - \ -class Const##name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ -} - -#define CPPYY_DECLARE_REFCONVERTER(name) \ -class name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ -}; - -#define CPPYY_DECLARE_ARRAY_CONVERTER(name) \ -class name##ArrayConverter : public Converter { \ -public: \ - name##ArrayConverter(cdims_t dims); \ - name##ArrayConverter(const name##ArrayConverter&) = delete; \ - name##ArrayConverter& operator=(const name##ArrayConverter&) = delete; \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ -protected: \ - dims_t fShape; \ - bool fIsFixed; \ -}; - - -// converters for built-ins -CPPYY_DECLARE_BASIC_CONVERTER(Long); -CPPYY_DECLARE_BASIC_CONVERTER(Bool); -CPPYY_DECLARE_BASIC_CONVERTER(Char); -class SCharAsIntConverter : public CharConverter { -public: - using CharConverter::CharConverter; - PyObject* FromMemory(void*) override; -}; -CPPYY_DECLARE_BASIC_CONVERTER(UChar); -class UCharAsIntConverter : public UCharConverter { -public: - using UCharConverter::UCharConverter; - PyObject* FromMemory(void*) override; -}; -CPPYY_DECLARE_BASIC_CONVERTER(WChar); -CPPYY_DECLARE_BASIC_CONVERTER(Char16); -CPPYY_DECLARE_BASIC_CONVERTER(Char32); -CPPYY_DECLARE_BASIC_CONVERTER(Int8); -CPPYY_DECLARE_BASIC_CONVERTER(Int16); -CPPYY_DECLARE_BASIC_CONVERTER(Int32); -CPPYY_DECLARE_BASIC_CONVERTER(UInt8); -CPPYY_DECLARE_BASIC_CONVERTER(UInt16); -CPPYY_DECLARE_BASIC_CONVERTER(UInt32); -CPPYY_DECLARE_BASIC_CONVERTER(Short); -CPPYY_DECLARE_BASIC_CONVERTER(UShort); -CPPYY_DECLARE_BASIC_CONVERTER(Int); -CPPYY_DECLARE_BASIC_CONVERTER(ULong); -CPPYY_DECLARE_BASIC_CONVERTER2(UInt, ULong); -CPPYY_DECLARE_BASIC_CONVERTER(LLong); -CPPYY_DECLARE_BASIC_CONVERTER(ULLong); -CPPYY_DECLARE_BASIC_CONVERTER(Double); -CPPYY_DECLARE_BASIC_CONVERTER(Float); -CPPYY_DECLARE_BASIC_CONVERTER(LDouble); - -CPPYY_DECLARE_REFCONVERTER(Bool); -CPPYY_DECLARE_REFCONVERTER(Char); -CPPYY_DECLARE_REFCONVERTER(WChar); -CPPYY_DECLARE_REFCONVERTER(Char16); -CPPYY_DECLARE_REFCONVERTER(Char32); -CPPYY_DECLARE_REFCONVERTER(SChar); -CPPYY_DECLARE_REFCONVERTER(UChar); -CPPYY_DECLARE_REFCONVERTER(Int8); -CPPYY_DECLARE_REFCONVERTER(Int16); -CPPYY_DECLARE_REFCONVERTER(Int32); -CPPYY_DECLARE_REFCONVERTER(UInt8); -CPPYY_DECLARE_REFCONVERTER(UInt16); -CPPYY_DECLARE_REFCONVERTER(UInt32); -CPPYY_DECLARE_REFCONVERTER(Short); -CPPYY_DECLARE_REFCONVERTER(UShort); -CPPYY_DECLARE_REFCONVERTER(UInt); -CPPYY_DECLARE_REFCONVERTER(Int); -CPPYY_DECLARE_REFCONVERTER(Long); -CPPYY_DECLARE_REFCONVERTER(ULong); -CPPYY_DECLARE_REFCONVERTER(LLong); -CPPYY_DECLARE_REFCONVERTER(ULLong); -CPPYY_DECLARE_REFCONVERTER(Float); -CPPYY_DECLARE_REFCONVERTER(Double); -CPPYY_DECLARE_REFCONVERTER(LDouble); - -class VoidConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -class CStringConverter : public Converter { -public: - CStringConverter(std::string::size_type maxSize = std::string::npos) : fMaxSize(maxSize) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - std::string fBuffer; - std::string::size_type fMaxSize; -}; - -class NonConstCStringConverter : public CStringConverter { -public: - using CStringConverter::CStringConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; -}; - -class WCStringConverter : public Converter { -public: - WCStringConverter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - WCStringConverter(const WCStringConverter&) = delete; - WCStringConverter& operator=(const WCStringConverter&) = delete; - virtual ~WCStringConverter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - wchar_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -class CString16Converter : public Converter { -public: - CString16Converter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - CString16Converter(const CString16Converter&) = delete; - CString16Converter& operator=(const CString16Converter&) = delete; - virtual ~CString16Converter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - char16_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -class CString32Converter : public Converter { -public: - CString32Converter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - CString32Converter(const CString32Converter&) = delete; - CString32Converter& operator=(const CString32Converter&) = delete; - virtual ~CString32Converter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - char32_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -// pointer/array conversions -CPPYY_DECLARE_ARRAY_CONVERTER(Bool); -CPPYY_DECLARE_ARRAY_CONVERTER(SChar); -CPPYY_DECLARE_ARRAY_CONVERTER(UChar); -CPPYY_DECLARE_ARRAY_CONVERTER(Byte); -CPPYY_DECLARE_ARRAY_CONVERTER(Int8); -CPPYY_DECLARE_ARRAY_CONVERTER(Int16); -CPPYY_DECLARE_ARRAY_CONVERTER(Int32); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt8); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt16); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt32); -CPPYY_DECLARE_ARRAY_CONVERTER(Short); -CPPYY_DECLARE_ARRAY_CONVERTER(UShort); -CPPYY_DECLARE_ARRAY_CONVERTER(Int); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt); -CPPYY_DECLARE_ARRAY_CONVERTER(Long); -CPPYY_DECLARE_ARRAY_CONVERTER(ULong); -CPPYY_DECLARE_ARRAY_CONVERTER(LLong); -CPPYY_DECLARE_ARRAY_CONVERTER(ULLong); -CPPYY_DECLARE_ARRAY_CONVERTER(Float); -CPPYY_DECLARE_ARRAY_CONVERTER(Double); -CPPYY_DECLARE_ARRAY_CONVERTER(LDouble); -CPPYY_DECLARE_ARRAY_CONVERTER(ComplexF); -CPPYY_DECLARE_ARRAY_CONVERTER(ComplexD); - -class CStringArrayConverter : public SCharArrayConverter { -public: - CStringArrayConverter(cdims_t dims, bool fixed) : SCharArrayConverter(dims) { - fIsFixed = fixed; // overrides SCharArrayConverter decision - } - using SCharArrayConverter::SCharArrayConverter; - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - -private: - std::vector fBuffer; -}; - -class NonConstCStringArrayConverter : public CStringArrayConverter { -public: - using CStringArrayConverter::CStringArrayConverter; - PyObject* FromMemory(void* address) override; -}; - -// converters for special cases -class NullptrConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -class InstanceConverter : public StrictInstancePtrConverter { -public: - using StrictInstancePtrConverter::StrictInstancePtrConverter; - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void*) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; -}; - -class InstanceRefConverter : public Converter { -public: - InstanceRefConverter(Cppyy::TCppType_t klass, bool isConst) : - fClass(klass), fIsConst(isConst) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool HasState() override { return true; } - -protected: - Cppyy::TCppType_t fClass; - bool fIsConst; -}; - -class InstanceMoveConverter : public InstanceRefConverter { -public: - InstanceMoveConverter(Cppyy::TCppType_t klass) : InstanceRefConverter(klass, true) {} - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -template -class InstancePtrPtrConverter : public InstancePtrConverter { -public: - using InstancePtrConverter::InstancePtrConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; -}; - -class InstanceArrayConverter : public InstancePtrConverter { -public: - InstanceArrayConverter(Cppyy::TCppType_t klass, cdims_t dims, bool keepControl = false) : - InstancePtrConverter(klass, keepControl), fShape(dims) { } - InstanceArrayConverter(const InstanceArrayConverter&) = delete; - InstanceArrayConverter& operator=(const InstanceArrayConverter&) = delete; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - -protected: - dims_t fShape; -}; - - -class ComplexDConverter: public InstanceConverter { -public: - ComplexDConverter(bool keepControl = false); - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - -private: - std::complex fBuffer; -}; - - -// Cling WORKAROUND -- classes for STL iterators are completely undefined in that -// they come in a bazillion different guises, so just do whatever -class STLIteratorConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; -// -- END Cling WORKAROUND - - -class VoidPtrRefConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -class VoidPtrPtrConverter : public Converter { -public: - VoidPtrPtrConverter(cdims_t dims); - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool HasState() override { return true; } - -protected: - dims_t fShape; - bool fIsFixed; -}; - -CPPYY_DECLARE_BASIC_CONVERTER(PyObject); - - -#define CPPYY_DECLARE_STRING_CONVERTER(name, strtype) \ -class name##Converter : public InstanceConverter { \ -public: \ - name##Converter(bool keepControl = true); \ - \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void* address) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ - \ -protected: \ - strtype fBuffer; \ -} - -CPPYY_DECLARE_STRING_CONVERTER(STLString, std::string); -CPPYY_DECLARE_STRING_CONVERTER(STLWString, std::wstring); -CPPYY_DECLARE_STRING_CONVERTER(STLStringView, std::string_view); - -class STLStringMoveConverter : public STLStringConverter { -public: - using STLStringConverter::STLStringConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - - -// function pointers -class FunctionPointerConverter : public Converter { -public: - FunctionPointerConverter(const std::string& ret, const std::string& sig) : - fRetType(ret), fSignature(sig) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - std::string fRetType; - std::string fSignature; - bool fAllowCppInstance = false; -}; - -// std::function -class StdFunctionConverter : public FunctionPointerConverter { -public: - StdFunctionConverter(Converter* cnv, const std::string& ret, const std::string& sig) : - FunctionPointerConverter(ret, sig), fConverter(cnv) { - fAllowCppInstance = true; - } - StdFunctionConverter(const StdFunctionConverter&) = delete; - StdFunctionConverter& operator=(const StdFunctionConverter&) = delete; - virtual ~StdFunctionConverter() { delete fConverter; } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - -protected: - Converter* fConverter; -}; - - -// smart pointer converter -class SmartPtrConverter : public Converter { -public: - SmartPtrConverter(Cppyy::TCppType_t smart, - Cppyy::TCppType_t underlying, - bool keepControl = false, - bool isRef = false) - : fSmartPtrType(smart), fUnderlyingType(underlying), - fKeepControl(keepControl), fIsRef(isRef) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - bool HasState() override { return true; } - -protected: - virtual bool GetAddressSpecialCase(PyObject*, void*&) { return false; } - - Cppyy::TCppType_t fSmartPtrType; - Cppyy::TCppType_t fUnderlyingType; - bool fKeepControl; - bool fIsRef; -}; - - -// initializer lists -class InitializerListConverter : public InstanceConverter { -public: - InitializerListConverter(Cppyy::TCppType_t klass, std::string const& value_type); - InitializerListConverter(const InitializerListConverter&) = delete; - InitializerListConverter& operator=(const InitializerListConverter&) = delete; - virtual ~InitializerListConverter(); - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - bool HasState() override { return true; } - -protected: - void Clear(); - -protected: - void* fBuffer = nullptr; - std::vector fConverters; - std::string fValueTypeName; - Cppyy::TCppType_t fValueType; - size_t fValueSize; -}; - -} // unnamed namespace - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DECLARECONVERTERS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h deleted file mode 100644 index ebb62c8f73d22..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h +++ /dev/null @@ -1,199 +0,0 @@ -#ifndef CPYCPPYY_DECLAREEXECUTORS_H -#define CPYCPPYY_DECLAREEXECUTORS_H - -// Bindings -#include "Executors.h" -#include "CallContext.h" -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -namespace { - -#define CPPYY_DECL_EXEC(name) \ -class name##Executor : public Executor { \ -public: \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ -} - -// executors for built-ins -CPPYY_DECL_EXEC(Bool); -CPPYY_DECL_EXEC(BoolConstRef); -CPPYY_DECL_EXEC(Char); -CPPYY_DECL_EXEC(CharConstRef); -CPPYY_DECL_EXEC(UChar); -CPPYY_DECL_EXEC(UCharConstRef); -CPPYY_DECL_EXEC(WChar); -CPPYY_DECL_EXEC(Char16); -CPPYY_DECL_EXEC(Char32); -CPPYY_DECL_EXEC(Int8); -CPPYY_DECL_EXEC(UInt8); -CPPYY_DECL_EXEC(Short); -CPPYY_DECL_EXEC(Int); -CPPYY_DECL_EXEC(Long); -CPPYY_DECL_EXEC(ULong); -CPPYY_DECL_EXEC(LongLong); -CPPYY_DECL_EXEC(ULongLong); -CPPYY_DECL_EXEC(Float); -CPPYY_DECL_EXEC(Double); -CPPYY_DECL_EXEC(LongDouble); -CPPYY_DECL_EXEC(Void); -CPPYY_DECL_EXEC(CString); -CPPYY_DECL_EXEC(CStringRef); -CPPYY_DECL_EXEC(WCString); -CPPYY_DECL_EXEC(CString16); -CPPYY_DECL_EXEC(CString32); - -// pointer/array executors -#define CPPYY_ARRAY_DECL_EXEC(name) \ -class name##ArrayExecutor : public Executor { \ - dims_t fShape; \ -public: \ - name##ArrayExecutor(dims_t dims) : fShape(dims) {} \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ - bool HasState() override { return true; } \ -} -CPPYY_ARRAY_DECL_EXEC(Void); -CPPYY_ARRAY_DECL_EXEC(Bool); -CPPYY_ARRAY_DECL_EXEC(SChar); -CPPYY_ARRAY_DECL_EXEC(UChar); -CPPYY_ARRAY_DECL_EXEC(Byte); -CPPYY_ARRAY_DECL_EXEC(Int8); -CPPYY_ARRAY_DECL_EXEC(UInt8); -CPPYY_ARRAY_DECL_EXEC(Short); -CPPYY_ARRAY_DECL_EXEC(UShort); -CPPYY_ARRAY_DECL_EXEC(Int); -CPPYY_ARRAY_DECL_EXEC(UInt); -CPPYY_ARRAY_DECL_EXEC(Long); -CPPYY_ARRAY_DECL_EXEC(ULong); -CPPYY_ARRAY_DECL_EXEC(LLong); -CPPYY_ARRAY_DECL_EXEC(ULLong); -CPPYY_ARRAY_DECL_EXEC(Float); -CPPYY_ARRAY_DECL_EXEC(Double); -CPPYY_ARRAY_DECL_EXEC(LDouble); -CPPYY_ARRAY_DECL_EXEC(ComplexF); -CPPYY_ARRAY_DECL_EXEC(ComplexD); -CPPYY_ARRAY_DECL_EXEC(ComplexI); -CPPYY_ARRAY_DECL_EXEC(ComplexL); - -// special cases -CPPYY_DECL_EXEC(ComplexD); -CPPYY_DECL_EXEC(STLString); -CPPYY_DECL_EXEC(STLWString); - -class InstancePtrExecutor : public Executor { -public: - InstancePtrExecutor(Cppyy::TCppType_t klass) : fClass(klass) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - bool HasState() override { return true; } - -protected: - Cppyy::TCppType_t fClass; -}; - -class InstanceExecutor : public Executor { -public: - InstanceExecutor(Cppyy::TCppType_t klass); - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - bool HasState() override { return true; } - -protected: - Cppyy::TCppType_t fClass; - uint32_t fFlags; -}; - -class IteratorExecutor : public InstanceExecutor { -public: - IteratorExecutor(Cppyy::TCppType_t klass); -}; - -CPPYY_DECL_EXEC(Constructor); -CPPYY_DECL_EXEC(PyObject); - -#define CPPYY_DECL_REFEXEC(name) \ -class name##RefExecutor : public RefExecutor { \ -public: \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ -} - -CPPYY_DECL_REFEXEC(Bool); -CPPYY_DECL_REFEXEC(Char); -CPPYY_DECL_REFEXEC(UChar); -CPPYY_DECL_REFEXEC(Int8); -CPPYY_DECL_REFEXEC(UInt8); -CPPYY_DECL_REFEXEC(Short); -CPPYY_DECL_REFEXEC(UShort); -CPPYY_DECL_REFEXEC(Int); -CPPYY_DECL_REFEXEC(UInt); -CPPYY_DECL_REFEXEC(Long); -CPPYY_DECL_REFEXEC(ULong); -CPPYY_DECL_REFEXEC(LongLong); -CPPYY_DECL_REFEXEC(ULongLong); -CPPYY_DECL_REFEXEC(Float); -CPPYY_DECL_REFEXEC(Double); -CPPYY_DECL_REFEXEC(LongDouble); -CPPYY_DECL_REFEXEC(ComplexD); -CPPYY_DECL_REFEXEC(STLString); - -// special cases -class InstanceRefExecutor : public RefExecutor { -public: - InstanceRefExecutor(Cppyy::TCppType_t klass) : fClass(klass) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - Cppyy::TCppType_t fClass; -}; - -class InstancePtrPtrExecutor : public InstanceRefExecutor { -public: - using InstanceRefExecutor::InstanceRefExecutor; - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; -}; - -class InstancePtrRefExecutor : public InstanceRefExecutor { -public: - using InstanceRefExecutor::InstanceRefExecutor; - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; -}; - -class InstanceArrayExecutor : public InstancePtrExecutor { -public: - InstanceArrayExecutor(Cppyy::TCppType_t klass, dim_t array_size) - : InstancePtrExecutor(klass), fSize(array_size) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - dim_t fSize; -}; - -class FunctionPointerExecutor : public Executor { -public: - FunctionPointerExecutor(const std::string& ret, const std::string& sig) : - fRetType(ret), fSignature(sig) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - std::string fRetType; - std::string fSignature; -}; - -} // unnamed namespace - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DECLAREEXECUTORS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dimensions.h b/bindings/pyroot/cppyy/CPyCppyy/src/Dimensions.h deleted file mode 100644 index c1bdc600eb3c2..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dimensions.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef CPYCPPYY_DIMENSIONS_H -#define CPYCPPYY_DIMENSIONS_H - -// Bindings -#include "CPyCppyy/CommonDefs.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -static const dim_t UNKNOWN_SIZE = (dim_t)-1; - -class CPYCPPYY_CLASS_EXPORT Dimensions { - dim_t* fDims; - -public: - Dimensions(dim_t ndim = 0, dim_t* dims = nullptr) : fDims(nullptr) { - if (ndim && ndim != UNKNOWN_SIZE) { - fDims = new dim_t[ndim+1]; - fDims[0] = ndim; - if (dims) std::copy(dims, dims+ndim, fDims+1); - else std::fill_n(fDims+1, ndim, UNKNOWN_SIZE); - } - } - Dimensions(std::initializer_list l) { - fDims = new dim_t[l.size()+1]; - fDims[0] = l.size(); - std::copy(l.begin(), l.end(), fDims+1); - } - Dimensions(const Dimensions& d) : fDims(nullptr) { - if (d.fDims) { - fDims = new dim_t[d.fDims[0]+1]; - std::copy(d.fDims, d.fDims+d.fDims[0]+1, fDims); - } - } - Dimensions(Dimensions&& d) : fDims(d.fDims) { - d.fDims = nullptr; - } - Dimensions& operator=(const Dimensions& d) { - if (this != &d) { - if (!d.fDims) { - delete [] fDims; - fDims = nullptr; - } else { - if (!fDims || (fDims && fDims[0] != d.fDims[0])) { - delete [] fDims; - fDims = new dim_t[d.fDims[0]+1]; - } - std::copy(d.fDims, d.fDims+d.fDims[0]+1, fDims); - } - } - return *this; - } - ~Dimensions() { - delete [] fDims; - } - -public: - operator bool() const { return (bool)fDims; } - - dim_t ndim() const { return fDims ? fDims[0] : UNKNOWN_SIZE; } - void ndim(dim_t d) { - if (fDims) { - if (fDims[0] == d) return; - delete [] fDims; - } - - fDims = new dim_t[d+1]; - fDims[0] = d; - std::fill_n(fDims+1, d, UNKNOWN_SIZE); - } - - dim_t operator[](dim_t i) const { return fDims[i+1]; } - dim_t& operator[](dim_t i) { return fDims[i+1]; } - - Dimensions sub() const { return fDims ? Dimensions(fDims[0]-1, fDims+2) : Dimensions(); } -}; - -typedef Dimensions dims_t; -typedef const dims_t& cdims_t; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DIMENSIONS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx deleted file mode 100644 index cf766225a08fa..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx +++ /dev/null @@ -1,111 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/DispatchPtr.h" -#undef CPYCPPYY_INTERNAL -#include "CPPInstance.h" -#include "CPPScope.h" - - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const -{ - PyGILState_STATE state = PyGILState_Ensure(); - PyObject* result = nullptr; - if (fPyHardRef) { - if (!borrowed) Py_INCREF(fPyHardRef); - result = fPyHardRef; - } else if (fPyWeakRef) { - result = CPyCppyy_GetWeakRef(fPyWeakRef); - if (result) { // dispatcher object disappeared? - if (borrowed) Py_DECREF(result); - } - } - PyGILState_Release(state); - return result; -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nullptr) -{ - PyGILState_STATE state = PyGILState_Ensure(); - if (strong) { - Py_INCREF(pyobj); - fPyHardRef = pyobj; - fPyWeakRef = nullptr; - } else { - fPyHardRef = nullptr; - fPyWeakRef = PyWeakref_NewRef(pyobj, nullptr); - } - ((CPPInstance*)pyobj)->SetDispatchPtr(this); - PyGILState_Release(state); -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) : fPyWeakRef(nullptr) -{ - PyGILState_STATE state = PyGILState_Ensure(); - PyObject* pyobj = other.Get(false /* not borrowed */); - fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; - if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); - Py_XDECREF(pyobj); - PyGILState_Release(state); -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::~DispatchPtr() { -// if we're holding a hard reference, or holding weak reference while being part -// of a dispatcher intermediate, then this delete is from the C++ side, and Python -// is "notified" by nulling out the reference and an exception will be raised on -// continued access - PyGILState_STATE state = PyGILState_Ensure(); - if (fPyWeakRef) { - PyObject* pyobj = CPyCppyy_GetWeakRef(fPyWeakRef); - if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) - ((CPPInstance*)pyobj)->GetObjectRaw() = nullptr; - Py_XDECREF(pyobj); - Py_DECREF(fPyWeakRef); - } else if (fPyHardRef) { - ((CPPInstance*)fPyHardRef)->GetObjectRaw() = nullptr; - Py_DECREF(fPyHardRef); - } - PyGILState_Release(state); -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) -{ - PyGILState_STATE state = PyGILState_Ensure(); - if (this != &other) { - Py_XDECREF(fPyWeakRef); fPyWeakRef = nullptr; - Py_XDECREF(fPyHardRef); - PyObject* pyobj = other.Get(false /* not borrowed */); - fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; - if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); - Py_XDECREF(pyobj); - } - PyGILState_Release(state); - return *this; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::DispatchPtr::PythonOwns() -{ -// Python maintains the hardref, so only allowed a weakref here - if (fPyHardRef) { - fPyWeakRef = PyWeakref_NewRef(fPyHardRef, nullptr); - Py_DECREF(fPyHardRef); fPyHardRef = nullptr; - } -} - -//----------------------------------------------------------------------------- -void CPyCppyy::DispatchPtr::CppOwns() -{ -// C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref - PyGILState_STATE state = PyGILState_Ensure(); - if (fPyWeakRef) { - fPyHardRef = CPyCppyy_GetWeakRef(fPyWeakRef); - Py_DECREF(fPyWeakRef); fPyWeakRef = nullptr; - } - PyGILState_Release(state); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx deleted file mode 100644 index 63bd3b9e7dc0e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx +++ /dev/null @@ -1,512 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Dispatcher.h" -#include "CPPScope.h" -#include "PyStrings.h" -#include "ProxyWrappers.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include - - -//---------------------------------------------------------------------------- -static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& mtCppName, std::ostringstream& code) -{ -// inject implementation for an overridden method - using namespace CPyCppyy; - -// method declaration - std::string retType = Cppyy::GetMethodResultType(method); - code << " " << retType << " " << mtCppName << "("; - -// build out the signature with predictable formal names - Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); - std::vector argtypes; argtypes.reserve(nArgs); - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - argtypes.push_back(Cppyy::GetMethodArgType(method, i)); - if (i != 0) code << ", "; - code << argtypes.back() << " arg" << i; - } - code << ") "; - if (Cppyy::IsConstMethod(method)) code << "const "; - code << "{\n"; - -// on destruction, the Python object may go first, in which case provide a diagnostic -// warning (raising a PyException may not be possible as this could happen during -// program shutdown); note that this means that the actual result will be the default -// and the caller may need to act on that, but that's still an improvement over a -// possible crash - code << " PyObject* iself = (PyObject*)_internal_self;\n" - " if (!iself || iself == Py_None) {\n" - " PyGILState_STATE state = PyGILState_Ensure();\n" - " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n" - " PyGILState_Release(state);\n" - " return"; - if (retType != "void") { - if (retType.back() != '*') - code << " (" << CPyCppyy::TypeManip::remove_const(retType) << "){}"; - else - code << " nullptr"; - } - code << ";\n" - " }\n"; - -// start actual function body - Utility::ConstructCallbackPreamble(retType, argtypes, code); - - code << " Py_INCREF(iself);\n"; - -// perform actual method call -#if PY_VERSION_HEX < 0x03000000 - code << " PyObject* mtPyName = PyString_FromString(\"" << mtCppName << "\");\n" // TODO: intern? -#else - code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName << "\");\n" -#endif - " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName"; - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) - code << ", pyargs[" << i << "]"; - code << ", NULL);\n Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; - -// close - Utility::ConstructCallbackReturn(retType, (int)nArgs, code); -} - -//---------------------------------------------------------------------------- -namespace { - struct BaseInfo { - BaseInfo(Cppyy::TCppType_t t, std::string&& bn, std::string&& bns) : - btype(t), bname(bn), bname_scoped(bns) {} - Cppyy::TCppType_t btype; - std::string bname; - std::string bname_scoped; - }; - - typedef std::vector BaseInfos_t; - typedef std::vector Ctors_t; - typedef std::vector AllCtors_t; - typedef std::vector> CtorInfos_t; -} // unnamed namespace - -static void build_constructors( - const std::string& derivedName, const BaseInfos_t& base_infos, const AllCtors_t& ctors, - std::ostringstream& code, const CtorInfos_t& methods = CtorInfos_t{}, size_t idx = 0) -{ - if (idx < ctors.size()) { - for (const auto& method : ctors[idx]) { - size_t argsmin = (size_t)Cppyy::GetMethodReqArgs(method); - size_t argsmax = (size_t)Cppyy::GetMethodNumArgs(method); - for (size_t i = argsmin; i <= argsmax; ++i) { - CtorInfos_t methods1{methods}; - methods1.emplace_back(method, i); - build_constructors(derivedName, base_infos, ctors, code, methods1, idx+1); - } - } - } else { - // this is as deep as we go; start writing - code << " " << derivedName << "("; - - // declare arguments - std::vector arg_tots; arg_tots.reserve(methods.size()); - for (Ctors_t::size_type i = 0; i < methods.size(); ++i) { - const auto& cinfo = methods[i]; - if (i != 0 && (arg_tots.back() || 1 < arg_tots.size())) code << ", "; - size_t nArgs = cinfo.second; - arg_tots.push_back(i == 0 ? nArgs : nArgs+arg_tots.back()); - - if (i != 0) code << "__cppyy_internal::Sep*"; - size_t offset = (i != 0 ? arg_tots[i-1] : 0); - for (size_t j = 0; j < nArgs; ++j) { - if (i != 0 || j != 0) code << ", "; - code << Cppyy::GetMethodArgType(cinfo.first, j) << " a" << (j+offset); - } - } - code << ") : "; - - // pass arguments to base constructors - for (BaseInfos_t::size_type i = 0; i < base_infos.size(); ++i) { - if (i != 0) code << ", "; - code << base_infos[i].bname << "("; - size_t first = (i != 0 ? arg_tots[i-1] : 0); - for (size_t j = first; j < arg_tots[i]; ++j) { - if (j != first) code << ", "; - bool isRValue = CPyCppyy::TypeManip::compound(\ - Cppyy::GetMethodArgType(methods[i].first, j-first)) == "&&"; - if (isRValue) code << "std::move("; - code << "a" << j; - if (isRValue) code << ")"; - } - code << ")"; - } - code << " {}\n"; - } -} - -namespace { - -using namespace Cppyy; - -static inline -std::vector FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) -{ -// Recursively walk the inheritance tree to find the overloads of the named method - std::vector result; - result = GetMethodIndicesFromName(tbase, mtCppName); - if (result.empty()) { - for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) { - TCppScope_t b = GetScope(GetBaseName(tbase, ibase)); - result = FindBaseMethod(b, mtCppName); - if (!result.empty()) - break; - } - } - return result; -} - -} // unnamed namespace - -bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, std::ostringstream& err) -{ -// Scan all methods in dct and where it overloads base methods in klass, create -// dispatchers on the C++ side. Then interject the dispatcher class. - - if (!PyTuple_Check(bases) || !PyTuple_GET_SIZE(bases) || !dct || !PyDict_Check(dct)) { - err << "internal error: expected tuple of bases and proper dictionary"; - return false; - } - - if (!Utility::IncludePython()) { - err << "failed to include Python.h"; - return false; - } - -// collect all bases, error checking the hierarchy along the way - const Py_ssize_t nBases = PyTuple_GET_SIZE(bases); - BaseInfos_t base_infos; base_infos.reserve(nBases); - for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) { - if (!CPPScope_Check(PyTuple_GET_ITEM(bases, ibase))) - continue; - - Cppyy::TCppType_t basetype = ((CPPScope*)PyTuple_GET_ITEM(bases, ibase))->fCppType; - - if (!basetype) { - err << "base class is incomplete"; - break; - } - - if (Cppyy::IsNamespace(basetype)) { - err << Cppyy::GetScopedFinalName(basetype) << " is a namespace"; - break; - } - - if (!Cppyy::HasVirtualDestructor(basetype)) { - const std::string& bname = Cppyy::GetScopedFinalName(basetype); - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)("class \""+bname+"\" has no virtual destructor").c_str(), 1) < 0) - return false; - } - - base_infos.emplace_back( - basetype, TypeManip::template_base(Cppyy::GetFinalName(basetype)), Cppyy::GetScopedFinalName(basetype)); - } - -// TODO: check deep hierarchy for multiple inheritance - bool isDeepHierarchy = klass->fCppType && base_infos.front().btype != klass->fCppType; - -// once classes can be extended, should consider re-use; for now, since derived -// python classes can differ in what they override, simply use different shims - static int counter = 0; - std::ostringstream osname; - osname << "Dispatcher" << ++counter; - const std::string& derivedName = osname.str(); - -// generate proxy class with the relevant method dispatchers - std::ostringstream code; - -// start class declaration - code << "namespace __cppyy_internal {\n" - << "class " << derivedName << " : "; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - if (ibase != 0) code << ", "; - code << "public ::" << base_infos[ibase].bname_scoped; - } - code << " {\n"; - if (!isDeepHierarchy) - code << "protected:\n CPyCppyy::DispatchPtr _internal_self;\n"; - code << "public:\n"; - -// add a virtual destructor for good measure, which is allowed to be "overridden" by -// the conventional __destruct__ method (note that __del__ is always called, too, if -// provided, but only when the Python object goes away; furthermore, if the Python -// object goes before the C++ one, only __del__ is called) - if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { - code << " virtual ~" << derivedName << "() {\n" - "PyGILState_STATE state = PyGILState_Ensure();\n" - " PyObject* iself = (PyObject*)_internal_self;\n" - " if (!iself || iself == Py_None)\n" - " return;\n" // safe, as destructor always returns void - " Py_INCREF(iself);\n" - " PyObject* mtPyName = PyUnicode_FromString(\"__destruct__\");\n" - " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName, NULL);\n" - " Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; - - // this being a destructor, print on exception rather than propagate using the - // magic C++ exception ... - code << " if (!pyresult) PyErr_Print();\n" - " else { Py_DECREF(pyresult); }\n" - " PyGILState_Release(state);\n" - " }\n"; - } else - code << " virtual ~" << derivedName << "() {}\n"; - -// methods: first collect all callables, then get overrides from base classes, for -// those that are still missing, search the hierarchy - PyObject* clbs = PyDict_New(); - PyObject* items = PyDict_Items(dct); - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) { - PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1); - if (PyCallable_Check(value)) - PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), value); - } - Py_DECREF(items); - if (PyDict_DelItem(clbs, PyStrings::gInit) != 0) - PyErr_Clear(); - -// protected methods and data need their access changed in the C++ trampoline and then -// exposed on the Python side; so, collect their names as we go along - std::set protected_names; - -// simple case: methods from current class (collect constructors along the way) - int has_default = 0, has_cctor = 0, has_ctors = 0, has_tmpl_ctors = 0; - AllCtors_t ctors{base_infos.size()}; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - const auto& binfo = base_infos[ibase]; - - const Cppyy::TCppIndex_t nMethods = Cppyy::GetNumMethods(binfo.btype); - bool cctor_found = false, default_found = false, any_ctor_found = false; - for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) { - Cppyy::TCppMethod_t method = Cppyy::GetMethod(binfo.btype, imeth); - - if (Cppyy::IsConstructor(method)) { - any_ctor_found = true; - if (Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method)) { - Cppyy::TCppIndex_t nreq = Cppyy::GetMethodReqArgs(method); - if (nreq == 0) default_found = true; - else if (!cctor_found && nreq == 1) { - const std::string& argtype = Cppyy::GetMethodArgType(method, 0); - if (TypeManip::compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == binfo.bname_scoped) - cctor_found = true; - } - ctors[ibase].push_back(method); - } - continue; - } - - std::string mtCppName = Cppyy::GetMethodName(method); - PyObject* key = CPyCppyy_PyText_FromString(mtCppName.c_str()); - int contains = PyDict_Contains(dct, key); - if (contains == -1) PyErr_Clear(); - if (contains != 1) { - Py_DECREF(key); - - // if the method is protected, we expose it through re-declaration and forwarding (using - // does not work here b/c there may be private overloads) - if (Cppyy::IsProtectedMethod(method)) { - protected_names.insert(mtCppName); - - code << " " << Cppyy::GetMethodResultType(method) << " " << mtCppName << "("; - Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - if (i != 0) code << ", "; - code << Cppyy::GetMethodArgType(method, i) << " arg" << i; - } - code << ") "; - if (Cppyy::IsConstMethod(method)) code << "const "; - code << "{\n return " << binfo.bname << "::" << mtCppName << "("; - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - if (i != 0) code << ", "; - code << "arg" << i; - } - code << ");\n }\n"; - } - - continue; - } - - InjectMethod(method, mtCppName, code); - - if (PyDict_DelItem(clbs, key) != 0) - PyErr_Clear(); // happens for overloads - Py_DECREF(key); - } - - // support for templated ctors in single inheritance (TODO: also multi possible?) - if (base_infos.size() == 1) { - const Cppyy::TCppIndex_t nTemplMethods = Cppyy::GetNumTemplatedMethods(binfo.btype); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nTemplMethods; ++imeth) { - if (Cppyy::IsTemplatedConstructor(binfo.btype, imeth)) { - any_ctor_found = true; - has_tmpl_ctors += 1; - break; // one suffices to map as argument packs are used - } - } - } - - // count the cctors and default ctors to determine whether each base has one - if (cctor_found || (!cctor_found && !any_ctor_found)) has_cctor += 1; - if (default_found || (!default_found && !any_ctor_found)) has_default += 1; - if (any_ctor_found && !has_tmpl_ctors) has_ctors += 1; - } - -// try to locate left-overs in base classes - for (const auto& binfo : base_infos) { - if (PyDict_Size(clbs)) { - size_t nbases = Cppyy::GetNumBases(binfo.btype); - for (size_t ibase = 0; ibase < nbases; ++ibase) { - Cppyy::TCppScope_t tbase = (Cppyy::TCppScope_t)Cppyy::GetScope( \ - Cppyy::GetBaseName(binfo.btype, ibase)); - - PyObject* keys = PyDict_Keys(clbs); - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) { - // TODO: should probably invert this looping; but that makes handling overloads clunky - PyObject* key = PyList_GET_ITEM(keys, i); - std::string mtCppName = CPyCppyy_PyText_AsString(key); - const auto& v = FindBaseMethod(tbase, mtCppName); - for (auto idx : v) - InjectMethod(Cppyy::GetMethod(tbase, idx), mtCppName, code); - if (!v.empty()) { - if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear(); - } - } - Py_DECREF(keys); - } - } - } - Py_DECREF(clbs); - -// constructors: build up from the argument types of the base class, for use by the Python -// derived class (inheriting with/ "using" does not work b/c base class constructors may -// have been deleted), - build_constructors(derivedName, base_infos, ctors, code); - -// for working with C++ templates, additional constructors are needed to make -// sure the python object is properly carried, but they can only be generated -// if the base class supports them - if (1 < nBases && (!has_ctors || has_default == nBases)) - code << " " << derivedName << "() {}\n"; - if (has_cctor == nBases) { - code << " " << derivedName << "(const " << derivedName << "& other) : "; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - if (ibase != 0) code << ", "; - code << base_infos[ibase].bname << "(other)"; - } - if (!isDeepHierarchy) - code << ", _internal_self(other._internal_self, this)"; - code << " {}\n"; - } - if (has_tmpl_ctors && base_infos.size() == 1) { - // support for templated ctors in single inheritance (TODO: also multi possible?) - code << " template\n " << derivedName << "(Args... args) : " - << base_infos[0].bname << "(args...) {}\n"; - } - -// destructor: default is fine - -// pull in data members that are protected - bool setPublic = false; - for (const auto& binfo : base_infos) { - Cppyy::TCppIndex_t nData = Cppyy::GetNumDatamembers(binfo.btype); - for (Cppyy::TCppIndex_t idata = 0; idata < nData; ++idata) { - if (Cppyy::IsProtectedData(binfo.btype, idata)) { - const std::string dm_name = Cppyy::GetDatamemberName(binfo.btype, idata); - if (dm_name != "_internal_self") { - const std::string& dname = Cppyy::GetDatamemberName(binfo.btype, idata); - protected_names.insert(dname); - if (!setPublic) { - code << "public:\n"; - setPublic = true; - } - code << " using " << binfo.bname << "::" << dname << ";\n"; - } - } - } - } - -// initialize the dispatch pointer for all direct bases that have one - BaseInfos_t::size_type disp_inited = 0; - code << "public:\n static void _init_dispatchptr(" << derivedName << "* inst, PyObject* self) {\n"; - if (1 < base_infos.size()) { - for (const auto& binfo : base_infos) { - if (Cppyy::GetDatamemberIndex(binfo.btype, "_internal_self") != (Cppyy::TCppIndex_t)-1) { - code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n"; - disp_inited += 1; - } - } - } -// The dispatch initializer is only used in constructors, and C++ object start out -// as owned by C++, with Python ownership explicitly set only later. To match, the -// dispatch pointer needs to start out with a hard reference, i.e. C++ ownership of -// the dispatch object. If the constructor has __creates__ set to True (default), -// then a call to PythonOwns() will switch the hard ref to a weak ref, preventing -// accidental circular references. - if (disp_inited != base_infos.size()) - code << " new ((void*)&inst->_internal_self) CPyCppyy::DispatchPtr{self, true};\n"; - code << " }"; - -// provide an accessor to re-initialize after round-tripping from C++ (internal) - code << "\n static PyObject* _get_dispatch(" << derivedName << "* inst) {\n" - " PyGILState_STATE state = PyGILState_Ensure();\n" - " PyObject* res = (PyObject*)inst->_internal_self;\n" - " Py_XINCREF(res);\n" - " PyGILState_Release(state);\n" - " return res;\n }"; - -// finish class declaration - code << "};\n}"; - -// finally, compile the code - if (!Cppyy::Compile(code.str())) { - err << "failed to compile the dispatcher code"; - return false; - } - -// keep track internally of the actual C++ type (this is used in -// CPPConstructor to call the dispatcher's one instead of the base) - Cppyy::TCppScope_t disp = Cppyy::GetScope("__cppyy_internal::"+derivedName); - if (!disp) { - err << "failed to retrieve the internal dispatcher"; - return false; - } - klass->fCppType = disp; - -// at this point, the dispatcher only lives in C++, as opposed to regular classes -// that are part of the hierarchy in Python, so create it, which will cache it for -// later use by e.g. the MemoryRegulator - unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross); - PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp, flags); - if (flags) ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross; - ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython; - -// finally, to expose protected members, copy them over from the C++ dispatcher base -// to the Python dictionary (the C++ dispatcher's Python proxy is not a base of the -// Python class to keep the inheritance tree intact) - for (const auto& name : protected_names) { - PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict); -#if PY_VERSION_HEX < 0x30d00f0 - PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); -#else - PyObject* pyf = nullptr; - PyMapping_GetOptionalItemString(disp_dct, (char*)name.c_str(), &pyf); -#endif - if (pyf) { - PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf); - Py_DECREF(pyf); - } - Py_DECREF(disp_dct); - } - - Py_XDECREF(disp_proxy); - - return true; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.h b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.h deleted file mode 100644 index f8d4dc57131c7..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CPYCPPYY_DISPATCHER_H -#define CPYCPPYY_DISPATCHER_H - -// Standard -#include - -namespace CPyCppyy { - -class CPPScope; - -// helper that inserts dispatchers for virtual methods -bool InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, std::ostringstream& err); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DISPATCHER_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx deleted file mode 100644 index 8a3648fad8576..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx +++ /dev/null @@ -1,1114 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "DeclareExecutors.h" -#include "CPPInstance.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - typedef std::map ExecFactories_t; - static ExecFactories_t gExecFactories; - - extern PyObject* gNullPtrObject; - - extern std::set gIteratorTypes; -} - - -//- helpers ------------------------------------------------------------------ -namespace { - -#ifdef WITH_THREAD - class GILControl { - public: - GILControl() : fSave(PyEval_SaveThread()) { } - ~GILControl() { - PyEval_RestoreThread(fSave); - } - private: - PyThreadState* fSave; - }; -#endif - -} // unnamed namespace - -#ifdef WITH_THREAD -#define CPPYY_IMPL_GILCALL(rtype, tcode) \ -static inline rtype GILCall##tcode( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt)\ -{ \ - if (!ReleasesGIL(ctxt)) \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ - GILControl gc{}; \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ -} -#else -#define CPPYY_IMPL_GILCALL(rtype, tcode) \ -static inline rtype GILCall##tcode( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt)\ -{ \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ -} -#endif - -CPPYY_IMPL_GILCALL(void, V) -CPPYY_IMPL_GILCALL(unsigned char, B) -CPPYY_IMPL_GILCALL(char, C) -CPPYY_IMPL_GILCALL(short, H) -CPPYY_IMPL_GILCALL(int, I) -CPPYY_IMPL_GILCALL(long, L) -CPPYY_IMPL_GILCALL(PY_LONG_LONG, LL) -CPPYY_IMPL_GILCALL(float, F) -CPPYY_IMPL_GILCALL(double, D) -CPPYY_IMPL_GILCALL(PY_LONG_DOUBLE, LD) -CPPYY_IMPL_GILCALL(void*, R) - -static inline Cppyy::TCppObject_t GILCallO(Cppyy::TCppMethod_t method, - Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt, Cppyy::TCppType_t klass) -{ -#ifdef WITH_THREAD - if (!ReleasesGIL(ctxt)) -#endif - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass); -#ifdef WITH_THREAD - GILControl gc{}; - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass); -#endif -} - -static inline Cppyy::TCppObject_t GILCallConstructor( - Cppyy::TCppMethod_t method, Cppyy::TCppType_t klass, CPyCppyy::CallContext* ctxt) -{ -#ifdef WITH_THREAD - if (!ReleasesGIL(ctxt)) -#endif - return Cppyy::CallConstructor(method, klass, ctxt->GetEncodedSize(), ctxt->GetArgs()); -#ifdef WITH_THREAD - GILControl gc{}; - return Cppyy::CallConstructor(method, klass, ctxt->GetEncodedSize(), ctxt->GetArgs()); -#endif -} - -static inline PyObject* CPyCppyy_PyText_FromLong(long cl) -{ -// python chars are range(256) - if (cl < -256 || cl > 255) { - PyErr_SetString(PyExc_ValueError, "char conversion out of range"); - return nullptr; - } - int c = (int)cl; - if (c < 0) return CPyCppyy_PyText_FromFormat("%c", 256 - std::abs(c)); - return CPyCppyy_PyText_FromFormat("%c", c); -} - -static inline PyObject* CPyCppyy_PyText_FromULong(unsigned long uc) -{ -// TODO: range check here? - if (255 < uc) { - PyErr_SetString(PyExc_ValueError, "char conversion out of range"); - return nullptr; - } - int c = (int)uc; - return CPyCppyy_PyText_FromFormat("%c", c); -} - -static inline PyObject* CPyCppyy_PyBool_FromLong(long b) -{ - PyObject* result = (bool)b ? Py_True : Py_False; - Py_INCREF(result); - return result; -} - - -//- base executor implementation --------------------------------------------- -CPyCppyy::Executor::~Executor() -{ - /* empty */ -} - -//- executors for built-ins -------------------------------------------------- -PyObject* CPyCppyy::BoolExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python bool return value - bool retval = GILCallB(method, self, ctxt); - PyObject* result = retval ? Py_True : Py_False; - Py_INCREF(result); - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BoolConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python bool return value - return CPyCppyy_PyBool_FromLong(*((bool*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong((int)GILCallC(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CharConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong(*((char*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UCharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong((unsigned char)GILCallB(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UCharConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char from the pointer return - return CPyCppyy_PyText_FromLong(*((unsigned char*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::WCharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single wide char - wchar_t res = (wchar_t)GILCallL(method, self, ctxt); - return PyUnicode_FromWideChar(&res, 1); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Char16Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char16 - char16_t res = (char16_t)GILCallL(method, self, ctxt); - return PyUnicode_DecodeUTF16((const char*)&res, sizeof(char16_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Char32Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char32 - char32_t res = (char32_t)GILCallL(method, self, ctxt); - return PyUnicode_DecodeUTF32((const char*)&res, sizeof(char32_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::IntExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((int)GILCallI(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Int8Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((int8_t)GILCallC(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UInt8Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((uint8_t)GILCallB(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ShortExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((short)GILCallH(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long return value - return PyLong_FromLong((long)GILCallL(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ULongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unsigned long return value - return PyLong_FromUnsignedLong((unsigned long)GILCallLL(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongLongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long long return value - PY_LONG_LONG result = GILCallLL(method, self, ctxt); - return PyLong_FromLongLong(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ULongLongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unsigned long long return value - PY_ULONG_LONG result = (PY_ULONG_LONG)GILCallLL(method, self, ctxt); - return PyLong_FromUnsignedLongLong(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::FloatExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallF(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::DoubleExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallD(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongDoubleExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallLD(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::RefExecutor::SetAssignable(PyObject* pyobject) -{ -// prepare "buffer" for by-ref returns, used with __setitem__ - if (pyobject) { - Py_INCREF(pyobject); - fAssignable = pyobject; - return true; - } - - fAssignable = nullptr; - return false; -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_REFEXEC(name, type, stype, F1, F2) \ -PyObject* CPyCppyy::name##RefExecutor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - type* ref = (type*)GILCallR(method, self, ctxt); \ - if (!ref) { /* can happen if wrapper compilation fails */ \ - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");\ - return nullptr; \ - } \ - if (!fAssignable) \ - return F1((stype)*ref); \ - else { \ - *ref = (type)F2(fAssignable); \ - Py_DECREF(fAssignable); \ - fAssignable = nullptr; \ - if (*ref == (type)-1 && PyErr_Occurred()) \ - return nullptr; \ - Py_INCREF(Py_None); \ - return Py_None; \ - } \ -} - -CPPYY_IMPL_REFEXEC(Bool, bool, long, CPyCppyy_PyBool_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(Char, char, long, CPyCppyy_PyText_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UChar, unsigned char, unsigned long, CPyCppyy_PyText_FromULong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Int8, int8_t, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UInt8, uint8_t, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Short, short, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UShort, unsigned short, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Int, int, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UInt, unsigned int, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Long, long, long, PyLong_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(ULong, unsigned long, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(LongLong, PY_LONG_LONG, PY_LONG_LONG, PyLong_FromLongLong, PyLong_AsLongLong) -CPPYY_IMPL_REFEXEC(ULongLong, PY_ULONG_LONG, PY_ULONG_LONG, PyLong_FromUnsignedLongLong, PyLongOrInt_AsULong64) -CPPYY_IMPL_REFEXEC(Float, float, double, PyFloat_FromDouble, PyFloat_AsDouble) -CPPYY_IMPL_REFEXEC(Double, double, double, PyFloat_FromDouble, PyFloat_AsDouble) -CPPYY_IMPL_REFEXEC(LongDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, PyFloat_FromDouble, PyFloat_AsDouble) - -template -static inline PyObject* PyComplex_FromComplex(const std::complex& c) { - return PyComplex_FromDoubles(c.real(), c.imag()); -} - -template -static inline std::complex PyComplex_AsComplex(PyObject* pycplx) { - Py_complex cplx = PyComplex_AsCComplex(pycplx); - return std::complex(cplx.real, cplx.imag); -} - -CPPYY_IMPL_REFEXEC(ComplexD, std::complex, - std::complex, PyComplex_FromComplex, PyComplex_AsComplex) - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLStringRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return python string return value - std::string* result = (std::string*)GILCallR(method, self, ctxt); - if (!fAssignable) { - return CPyCppyy_PyText_FromStringAndSize(result->c_str(), result->size()); - } - - *result = std::string( - CPyCppyy_PyText_AsString(fAssignable), CPyCppyy_PyText_GET_SIZE(fAssignable)); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return None - GILCallV(method, self, ctxt); - if (PyErr_Occurred()) return nullptr; - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - char* result = (char*)GILCallR(method, self, ctxt); - if (!result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; - } - - return CPyCppyy_PyText_FromString(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - char** result = (char**)GILCallR(method, self, ctxt); - if (!result || !*result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; - } - - return CPyCppyy_PyText_FromString(*result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::WCStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - wchar_t* result = (wchar_t*)GILCallR(method, self, ctxt); - if (!result) { - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); - } - - return PyUnicode_FromWideChar(result, wcslen(result)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CString16Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - char16_t* result = (char16_t*)GILCallR(method, self, ctxt); - if (!result) { - char16_t w = u'\0'; - return PyUnicode_DecodeUTF16((const char*)&w, 0, nullptr, nullptr); - } - - return PyUnicode_DecodeUTF16((const char*)result, - std::char_traits::length(result)*sizeof(char16_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CString32Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - char32_t* result = (char32_t*)GILCallR(method, self, ctxt); - if (!result) { - char32_t w = U'\0'; - return PyUnicode_DecodeUTF32((const char*)&w, 0, nullptr, nullptr); - } - - return PyUnicode_DecodeUTF32((const char*)result, - std::char_traits::length(result)*sizeof(char32_t), nullptr, nullptr); -} - - -//- pointer/array executors -------------------------------------------------- -PyObject* CPyCppyy::VoidArrayExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long return value - intptr_t* result = (intptr_t*)GILCallR(method, self, ctxt); - if (!result) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - return CreatePointerView(result, fShape); -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_ARRAY_EXEC(name, type, suffix) \ -PyObject* CPyCppyy::name##ArrayExecutor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - return CreateLowLevelView##suffix((type*)GILCallR(method, self, ctxt), fShape); \ -} - -CPPYY_IMPL_ARRAY_EXEC(Bool, bool, ) -CPPYY_IMPL_ARRAY_EXEC(SChar, signed char, ) -CPPYY_IMPL_ARRAY_EXEC(UChar, unsigned char, ) -CPPYY_IMPL_ARRAY_EXEC(Byte, std::byte, ) -CPPYY_IMPL_ARRAY_EXEC(Int8, int8_t, _i8) -CPPYY_IMPL_ARRAY_EXEC(UInt8, uint8_t, _i8) -CPPYY_IMPL_ARRAY_EXEC(Short, short, ) -CPPYY_IMPL_ARRAY_EXEC(UShort, unsigned short, ) -CPPYY_IMPL_ARRAY_EXEC(Int, int, ) -CPPYY_IMPL_ARRAY_EXEC(UInt, unsigned int, ) -CPPYY_IMPL_ARRAY_EXEC(Long, long, ) -CPPYY_IMPL_ARRAY_EXEC(ULong, unsigned long, ) -CPPYY_IMPL_ARRAY_EXEC(LLong, long long, ) -CPPYY_IMPL_ARRAY_EXEC(ULLong, unsigned long long, ) -CPPYY_IMPL_ARRAY_EXEC(Float, float, ) -CPPYY_IMPL_ARRAY_EXEC(Double, double, ) -CPPYY_IMPL_ARRAY_EXEC(LDouble, long double, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexF, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexD, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexI, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexL, std::complex, ) - - -//- special cases ------------------------------------------------------------ -#define CPPYY_COMPLEX_EXEC(code, type) \ -PyObject* CPyCppyy::Complex##code##Executor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - static Cppyy::TCppScope_t scopeid = Cppyy::GetScope("std::complex<"#type">");\ - std::complex* result = \ - (std::complex*)GILCallO(method, self, ctxt, scopeid); \ - if (!result) { \ - PyErr_SetString(PyExc_ValueError, "NULL result where temporary expected");\ - return nullptr; \ - } \ - \ - PyObject* pyres = PyComplex_FromDoubles(result->real(), result->imag()); \ - ::operator delete(result); /* Cppyy::CallO calls ::operator new */ \ - return pyres; \ -} - -CPPYY_COMPLEX_EXEC(D, double) - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - -// TODO: make use of GILLCallS (?!) - static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetScope("std::string"); - std::string* result = (std::string*)GILCallO(method, self, ctxt, sSTLStringScope); - if (!result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; - } - - PyObject* pyresult = - CPyCppyy_PyText_FromStringAndSize(result->c_str(), result->size()); - delete result; // Cppyy::CallO allocates and constructs a string, so it must be properly destroyed - - return pyresult; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLWStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - static Cppyy::TCppScope_t sSTLWStringScope = Cppyy::GetScope("std::wstring"); - std::wstring* result = (std::wstring*)GILCallO(method, self, ctxt, sSTLWStringScope); - if (!result) { - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); - } - - PyObject* pyresult = PyUnicode_FromWideChar(result->c_str(), result->size()); - delete result; // Cppyy::CallO allocates and constructs a string, so it must be properly destroyed - - return pyresult; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstancePtrExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python proxy object return value - return BindCppObject((void*)GILCallR(method, self, ctxt), fClass); -} - -//---------------------------------------------------------------------------- -CPyCppyy::InstanceExecutor::InstanceExecutor(Cppyy::TCppType_t klass) : - fClass(klass), fFlags(CPPInstance::kIsValue | CPPInstance::kIsOwner) -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execution will bring a temporary in existence - Cppyy::TCppObject_t value = GILCallO(method, self, ctxt, fClass); - - if (!value) { - if (!PyErr_Occurred()) // callee may have set a python error itself - PyErr_SetString(PyExc_ValueError, "nullptr result where temporary expected"); - return nullptr; - } - -// the result can then be bound - PyObject* pyobj = BindCppObjectNoCast(value, fClass, fFlags); - if (!pyobj) - return nullptr; - -// python ref counting will now control this object's life span; it will be -// deleted b/c it is marked as a by-value object owned by python (from fFlags) - return pyobj; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::IteratorExecutor::IteratorExecutor(Cppyy::TCppType_t klass) : - InstanceExecutor(klass) -{ - fFlags |= CPPInstance::kNoMemReg | CPPInstance::kNoWrapConv; // adds to flags from base class -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// executor binds the result to the left-hand side, overwriting if an old object - PyObject* result = BindCppObject((void*)GILCallR(method, self, ctxt), fClass); - if (!result || !fAssignable) - return result; - else { - // this generic code is quite slow compared to its C++ equivalent ... - PyObject* assign = PyObject_GetAttr(result, PyStrings::gAssign); - if (!assign) { - PyErr_Clear(); - PyObject* descr = PyObject_Str(result); - if (descr && CPyCppyy_PyText_CheckExact(descr)) { - PyErr_Format(PyExc_TypeError, "cannot assign to return object (%s)", - CPyCppyy_PyText_AsString(descr)); - } else { - PyErr_SetString(PyExc_TypeError, "cannot assign to result"); - } - Py_XDECREF(descr); - Py_DECREF(result); - Py_DECREF(fAssignable); fAssignable = nullptr; - return nullptr; - } - - PyObject* res2 = PyObject_CallFunction(assign, const_cast("O"), fAssignable); - - Py_DECREF(assign); - Py_DECREF(result); - Py_DECREF(fAssignable); fAssignable = nullptr; - - if (res2) { - Py_DECREF(res2); // typically, *this from operator=() - Py_RETURN_NONE; - } - - return nullptr; - } -} - -//---------------------------------------------------------------------------- -static inline PyObject* SetInstanceCheckError(PyObject* pyobj) { - PyObject* pystr = PyObject_Str(pyobj); - if (pystr) { - PyErr_Format(PyExc_TypeError, - "C++ object expected, got %s", CPyCppyy_PyText_AsString(pystr)); - Py_DECREF(pystr); - } else - PyErr_SetString(PyExc_TypeError, "C++ object expected"); - return nullptr; -} - -PyObject* CPyCppyy::InstancePtrPtrExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python C++ proxy object -// return ptr value - if (fAssignable && !CPPInstance_Check(fAssignable)) - return SetInstanceCheckError(fAssignable); - - void** result = (void**)GILCallR(method, self, ctxt); - if (!fAssignable) - return BindCppObject((void*)result, fClass, - CPPInstance::kIsPtrPtr | CPPInstance::kIsReference); - - CPPInstance* cppinst = (CPPInstance*)fAssignable; - *result = cppinst->GetObject(); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstancePtrRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python C++ proxy object -// ignoring ref) return ptr value - if (fAssignable && !CPPInstance_Check(fAssignable)) - return SetInstanceCheckError(fAssignable); - - void** result = (void**)GILCallR(method, self, ctxt); - if (!fAssignable) - return BindCppObject(*result, fClass); - - CPPInstance* cppinst = (CPPInstance*)fAssignable; - *result = cppinst->GetObject(); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceArrayExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct TupleOfInstances from -// return value - return BindCppObjectArray((void*)GILCallR(method, self, ctxt), fClass, {fSize}); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ConstructorExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t klass, CallContext* ctxt) -{ -// package return address in PyObject* for caller to handle appropriately (see -// CPPConstructor for the actual build of the PyObject) - return (PyObject*)GILCallConstructor(method, (Cppyy::TCppType_t)klass, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::PyObjectExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return python object - return (PyObject*)GILCallR(method, self, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::FunctionPointerExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return std::function from func ptr - -// A function pointer in clang is represented by a Type, not a FunctionDecl and it's -// not possible to get the latter from the former: the backend will need to support -// both. Since that is far in the future, we'll use a std::function instead. - void* address = (void*)GILCallR(method, self, ctxt); - if (address) - return Utility::FuncPtr2StdFunction(fRetType, fSignature, address); - -// There's currently now way to return a typed function nullptr, but given that -// this is a return value (thus can not be set) and overloading function based -// on function pointer return type is likely uncommon ... - Py_INCREF(gNullPtrObject); - return gNullPtrObject; -} - -//- factories ---------------------------------------------------------------- -CPyCppyy::Executor* CPyCppyy::CreateExecutor(const std::string& fullType, cdims_t dims) -{ -// The matching of the fulltype to an executor factory goes through up to 4 levels: -// 1) full, qualified match -// 2) drop '&' as by ref/full type is often pretty much the same python-wise -// 3) C++ classes, either by ref/ptr or by value -// 4) additional special case for enums -// -// If all fails, void is used, which will cause the return type to be ignored on use - -// an exactly matching executor is best - ExecFactories_t::iterator h = gExecFactories.find(fullType); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// resolve typedefs etc. - const std::string& resolvedType = Cppyy::ResolveName(fullType); - -// a full, qualified matching executor is preferred - if (resolvedType != fullType) { - h = gExecFactories.find(resolvedType); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedType); - std::string realType = TypeManip::clean_type(resolvedType, false); - -// accept unqualified type (as python does not know about qualifiers) - h = gExecFactories.find(realType + cpd); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - realType = TypeManip::remove_const(realType); - h = gExecFactories.find(realType + cpd); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// simple array types - if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - h = gExecFactories.find(realType + " ptr"); - if (h != gExecFactories.end()) - return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) ? dims_t(cpd.size()) : dims); - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd == "[]") { - h = gExecFactories.find(realType + "*"); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// C++ classes and special cases - Executor* result = 0; - if (Cppyy::TCppType_t klass = Cppyy::GetScope(realType)) { - if (Utility::IsSTLIterator(realType) || gIteratorTypes.find(fullType) != gIteratorTypes.end()) { - if (cpd == "") - return new IteratorExecutor(klass); - } - - if (cpd == "") - result = new InstanceExecutor(klass); - else if (cpd == "&") - result = new InstanceRefExecutor(klass); - else if (cpd == "**" || cpd == "*[]" || cpd == "&*") - result = new InstancePtrPtrExecutor(klass); - else if (cpd == "*&") - result = new InstancePtrRefExecutor(klass); - else if (cpd == "[]") { - Py_ssize_t asize = TypeManip::array_size(resolvedType); - if (0 < asize) - result = new InstanceArrayExecutor(klass, asize); - else - result = new InstancePtrRefExecutor(klass); - } else - result = new InstancePtrExecutor(klass); - } else if (resolvedType.find("(*)") != std::string::npos || - (resolvedType.find("::*)") != std::string::npos)) { - // this is a function pointer - // TODO: find better way of finding the type - auto pos1 = resolvedType.find('('); - auto pos2 = resolvedType.find("*)"); - auto pos3 = resolvedType.rfind(')'); - result = new FunctionPointerExecutor( - resolvedType.substr(0, pos1), resolvedType.substr(pos2+2, pos3-pos2-1)); - } else { - // unknown: void* may work ("user knows best"), void will fail on use of return value - h = (cpd == "") ? gExecFactories.find("void") : gExecFactories.find("void ptr"); - } - - if (!result && h != gExecFactories.end()) - // executor factory available, use it to create executor - result = (h->second)(dims); - - return result; // may still be null -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void CPyCppyy::DestroyExecutor(Executor* p) -{ - if (p && p->HasState()) - delete p; // state-less executors are always shared -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterExecutor(const std::string& name, ef_t fac) -{ -// register a custom executor - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) - return false; - - gExecFactories[name] = fac; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterExecutorAlias(const std::string& name, const std::string& target) -{ -// register a custom executor that is a reference to an existing converter - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) - return false; - - auto t = gExecFactories.find(target); - if (t == gExecFactories.end()) - return false; - - gExecFactories[name] = t->second; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::UnregisterExecutor(const std::string& name) -{ -// remove a custom executor - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) { - gExecFactories.erase(f); - return true; - } - return false; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void* CPyCppyy::CallVoidP(Cppyy::TCppMethod_t meth, Cppyy::TCppObject_t obj, CallContext* ctxt) -{ - return GILCallR(meth, obj, ctxt); -} - - -//---------------------------------------------------------------------------- -namespace { - -using namespace CPyCppyy; - -#define WSTRING1 "std::basic_string" -#define WSTRING2 "std::basic_string,std::allocator>" - -//-- aliasing special case: C complex (is binary compatible with C++ std::complex) -#ifndef _WIN32 -#define CCOMPLEX_D "_Complex double" -#define CCOMPLEX_F "_Complex float" -#else -#define CCOMPLEX_D "_C_double_complex" -#define CCOMPLEX_F "_C_float_complex" -#endif - -struct InitExecFactories_t { -public: - InitExecFactories_t() { - // load all executor factories in the global map 'gExecFactories' - CPyCppyy::ExecFactories_t& gf = gExecFactories; - - // factories for built-ins - gf["bool"] = (ef_t)+[](cdims_t) { static BoolExecutor e{}; return &e; }; - gf["bool&"] = (ef_t)+[](cdims_t) { return new BoolRefExecutor{}; }; - gf["const bool&"] = (ef_t)+[](cdims_t) { static BoolConstRefExecutor e{}; return &e; }; - gf["char"] = (ef_t)+[](cdims_t) { static CharExecutor e{}; return &e; }; - gf["signed char"] = gf["char"]; - gf["unsigned char"] = (ef_t)+[](cdims_t) { static UCharExecutor e{}; return &e; }; - gf["char&"] = (ef_t)+[](cdims_t) { return new CharRefExecutor{}; }; - gf["signed char&"] = gf["char&"]; - gf["unsigned char&"] = (ef_t)+[](cdims_t) { return new UCharRefExecutor{}; }; - gf["const char&"] = (ef_t)+[](cdims_t) { static CharConstRefExecutor e{}; return &e; }; - gf["const signed char&"] = gf["const char&"]; - gf["const unsigned char&"] = (ef_t)+[](cdims_t) { static UCharConstRefExecutor e{}; return &e; }; - gf["wchar_t"] = (ef_t)+[](cdims_t) { static WCharExecutor e{}; return &e; }; - gf["char16_t"] = (ef_t)+[](cdims_t) { static Char16Executor e{}; return &e; }; - gf["char32_t"] = (ef_t)+[](cdims_t) { static Char32Executor e{}; return &e; }; - gf["int8_t"] = (ef_t)+[](cdims_t) { static Int8Executor e{}; return &e; }; - gf["int8_t&"] = (ef_t)+[](cdims_t) { return new Int8RefExecutor{}; }; - gf["const int8_t&"] = (ef_t)+[](cdims_t) { static Int8RefExecutor e{}; return &e; }; - gf["uint8_t"] = (ef_t)+[](cdims_t) { static UInt8Executor e{}; return &e; }; - gf["uint8_t&"] = (ef_t)+[](cdims_t) { return new UInt8RefExecutor{}; }; - gf["const uint8_t&"] = (ef_t)+[](cdims_t) { static UInt8RefExecutor e{}; return &e; }; - gf["short"] = (ef_t)+[](cdims_t) { static ShortExecutor e{}; return &e; }; - gf["short&"] = (ef_t)+[](cdims_t) { return new ShortRefExecutor{}; }; - gf["int"] = (ef_t)+[](cdims_t) { static IntExecutor e{}; return &e; }; - gf["int&"] = (ef_t)+[](cdims_t) { return new IntRefExecutor{}; }; - gf["unsigned short"] = gf["int"]; - gf["unsigned short&"] = (ef_t)+[](cdims_t) { return new UShortRefExecutor{}; }; - gf["unsigned long"] = (ef_t)+[](cdims_t) { static ULongExecutor e{}; return &e; }; - gf["unsigned long&"] = (ef_t)+[](cdims_t) { return new ULongRefExecutor{}; }; - gf["unsigned int"] = gf["unsigned long"]; - gf["unsigned int&"] = (ef_t)+[](cdims_t) { return new UIntRefExecutor{}; }; - gf["long"] = (ef_t)+[](cdims_t) { static LongExecutor e{}; return &e; }; - gf["long&"] = (ef_t)+[](cdims_t) { return new LongRefExecutor{}; }; - gf["unsigned long"] = (ef_t)+[](cdims_t) { static ULongExecutor e{}; return &e; }; - gf["unsigned long&"] = (ef_t)+[](cdims_t) { return new ULongRefExecutor{}; }; - gf["long long"] = (ef_t)+[](cdims_t) { static LongLongExecutor e{}; return &e; }; - gf["long long&"] = (ef_t)+[](cdims_t) { return new LongLongRefExecutor{}; }; - gf["unsigned long long"] = (ef_t)+[](cdims_t) { static ULongLongExecutor e{}; return &e; }; - gf["unsigned long long&"] = (ef_t)+[](cdims_t) { return new ULongLongRefExecutor{}; }; - - gf["float"] = (ef_t)+[](cdims_t) { static FloatExecutor e{}; return &e; }; - gf["float&"] = (ef_t)+[](cdims_t) { return new FloatRefExecutor{}; }; - gf["double"] = (ef_t)+[](cdims_t) { static DoubleExecutor e{}; return &e; }; - gf["double&"] = (ef_t)+[](cdims_t) { return new DoubleRefExecutor{}; }; - gf["long double"] = (ef_t)+[](cdims_t) { static LongDoubleExecutor e{}; return &e; }; // TODO: lost precision - gf["long double&"] = (ef_t)+[](cdims_t) { return new LongDoubleRefExecutor{}; }; - gf["std::complex"] = (ef_t)+[](cdims_t) { static ComplexDExecutor e{}; return &e; }; - gf["std::complex&"] = (ef_t)+[](cdims_t) { return new ComplexDRefExecutor{}; }; - gf["void"] = (ef_t)+[](cdims_t) { static VoidExecutor e{}; return &e; }; - - // pointer/array factories - gf["void ptr"] = (ef_t)+[](cdims_t d) { return new VoidArrayExecutor{d}; }; - gf["bool ptr"] = (ef_t)+[](cdims_t d) { return new BoolArrayExecutor{d}; }; - gf["unsigned char ptr"] = (ef_t)+[](cdims_t d) { return new UCharArrayExecutor{d}; }; - gf["const unsigned char ptr"] = gf["unsigned char ptr"]; - gf["std::byte ptr"] = (ef_t)+[](cdims_t d) { return new ByteArrayExecutor{d}; }; - gf["const std::byte ptr"] = gf["std::byte ptr"]; - gf["byte ptr"] = gf["std::byte ptr"]; - gf["const byte ptr"] = gf["std::byte ptr"]; - gf["int8_t ptr"] = (ef_t)+[](cdims_t d) { return new Int8ArrayExecutor{d}; }; - gf["uint8_t ptr"] = (ef_t)+[](cdims_t d) { return new UInt8ArrayExecutor{d}; }; - gf["short ptr"] = (ef_t)+[](cdims_t d) { return new ShortArrayExecutor{d}; }; - gf["unsigned short ptr"] = (ef_t)+[](cdims_t d) { return new UShortArrayExecutor{d}; }; - gf["int ptr"] = (ef_t)+[](cdims_t d) { return new IntArrayExecutor{d}; }; - gf["unsigned int ptr"] = (ef_t)+[](cdims_t d) { return new UIntArrayExecutor{d}; }; - gf["long ptr"] = (ef_t)+[](cdims_t d) { return new LongArrayExecutor{d}; }; - gf["unsigned long ptr"] = (ef_t)+[](cdims_t d) { return new ULongArrayExecutor{d}; }; - gf["long long ptr"] = (ef_t)+[](cdims_t d) { return new LLongArrayExecutor{d}; }; - gf["unsigned long long ptr"] = (ef_t)+[](cdims_t d) { return new ULLongArrayExecutor{d}; }; - gf["float ptr"] = (ef_t)+[](cdims_t d) { return new FloatArrayExecutor{d}; }; - gf["double ptr"] = (ef_t)+[](cdims_t d) { return new DoubleArrayExecutor{d}; }; - gf["long double ptr"] = (ef_t)+[](cdims_t d) { return new LDoubleArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexFArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexDArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexIArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexLArrayExecutor{d}; }; - - // aliases - gf["internal_enum_type_t"] = gf["int"]; - gf["internal_enum_type_t&"] = gf["int&"]; - gf["internal_enum_type_t ptr"] = gf["int ptr"]; - gf["std::byte"] = gf["uint8_t"]; - gf["byte"] = gf["uint8_t"]; - gf["std::byte&"] = gf["uint8_t&"]; - gf["byte&"] = gf["uint8_t&"]; - gf["const std::byte&"] = gf["const uint8_t&"]; - gf["const byte&"] = gf["const uint8_t&"]; - gf["std::int8_t"] = gf["int8_t"]; - gf["std::int8_t&"] = gf["int8_t&"]; - gf["const std::int8_t&"] = gf["const int8_t&"]; - gf["std::int8_t ptr"] = gf["int8_t ptr"]; - gf["std::uint8_t"] = gf["uint8_t"]; - gf["std::uint8_t&"] = gf["uint8_t&"]; - gf["const std::uint8_t&"] = gf["const uint8_t&"]; - gf["std::uint8_t ptr"] = gf["uint8_t ptr"]; -#ifdef _WIN32 - gf["__int64"] = gf["long long"]; - gf["__int64&"] = gf["long long&"]; - gf["__int64 ptr"] = gf["long long ptr"]; - gf["unsigned __int64"] = gf["unsigned long long"]; - gf["unsigned __int64&"] = gf["unsigned long long&"]; - gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; -#endif - gf[CCOMPLEX_D] = gf["std::complex"]; - gf[CCOMPLEX_D "&"] = gf["std::complex&"]; - gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; - gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; - - // We always need these executors when cppyy is based on an unpatched - // ROOT, because the "long long" types are always converted to Long64_t - // and ULong64_t already at the ROOT Meta level. - // See https://github.com/root-project/root/issues/15872#issuecomment-2174092763 - gf["Long64_t"] = gf["long long"]; - gf["Long64_t&"] = gf["long long&"]; - gf["Long64_t ptr"] = gf["long long ptr"]; - gf["ULong64_t"] = gf["unsigned long long"]; - gf["ULong64_t&"] = gf["unsigned long long&"]; - gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; - - // factories for special cases - gf["const char*"] = (ef_t)+[](cdims_t) { static CStringExecutor e{}; return &e; }; - gf["char*"] = gf["const char*"]; - gf["const char*&"] = (ef_t)+[](cdims_t) { static CStringRefExecutor e{}; return &e; }; - gf["char*&"] = gf["const char*&"]; - gf["const signed char*"] = gf["const char*"]; - //gf["signed char*"] = gf["char*"]; - gf["signed char ptr"] = (ef_t)+[](cdims_t d) { return new SCharArrayExecutor{d}; }; - gf["wchar_t*"] = (ef_t)+[](cdims_t) { static WCStringExecutor e{}; return &e;}; - gf["char16_t*"] = (ef_t)+[](cdims_t) { static CString16Executor e{}; return &e;}; - gf["char32_t*"] = (ef_t)+[](cdims_t) { static CString32Executor e{}; return &e;}; - gf["std::string"] = (ef_t)+[](cdims_t) { static STLStringExecutor e{}; return &e; }; - gf["string"] = gf["std::string"]; - gf["std::string&"] = (ef_t)+[](cdims_t) { return new STLStringRefExecutor{}; }; - gf["string&"] = gf["std::string&"]; - gf["std::wstring"] = (ef_t)+[](cdims_t) { static STLWStringExecutor e{}; return &e; }; - gf[WSTRING1] = gf["std::wstring"]; - gf[WSTRING2] = gf["std::wstring"]; - gf["__init__"] = (ef_t)+[](cdims_t) { static ConstructorExecutor e{}; return &e; }; - gf["PyObject*"] = (ef_t)+[](cdims_t) { static PyObjectExecutor e{}; return &e; }; - gf["_object*"] = gf["PyObject*"]; - gf["FILE*"] = gf["void ptr"]; - } -} initExecvFactories_; - -} // unnamed namespace diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h deleted file mode 100644 index e043bf164fa55..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef CPYCPPYY_EXECUTORS_H -#define CPYCPPYY_EXECUTORS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -struct CallContext; - -class CPYCPPYY_CLASS_EXPORT Executor { -public: - virtual ~Executor(); - virtual PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) = 0; - virtual bool HasState() { return false; } -}; - -// special case needed for CPPSetItem -class RefExecutor : public Executor { -public: - RefExecutor() : fAssignable(nullptr) {} - virtual bool SetAssignable(PyObject*); - bool HasState() override { return true; } - -protected: - PyObject* fAssignable; -}; - -// create/destroy executor from fully qualified type (public API) -CPYCPPYY_EXPORT Executor* CreateExecutor(const std::string& fullType, cdims_t = 0); -CPYCPPYY_EXPORT void DestroyExecutor(Executor* p); -typedef Executor* (*ef_t) (cdims_t); -CPYCPPYY_EXPORT bool RegisterExecutor(const std::string& name, ef_t fac); -CPYCPPYY_EXPORT bool RegisterExecutorAlias(const std::string& name, const std::string& target); -CPYCPPYY_EXPORT bool UnregisterExecutor(const std::string& name); - -// helper for the actual call -CPYCPPYY_EXPORT void* CallVoidP(Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_EXECUTORS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx deleted file mode 100644 index 8e302f74154b8..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx +++ /dev/null @@ -1,1234 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "LowLevelViews.h" -#include "Converters.h" -#include "CustomPyTypes.h" -#include "PyStrings.h" - -// Standard -#include -#include -#include -#include - - -//= memoryview-like object =================================================== -// This is largely setup just like Python builtin memory view objects, with -// the exceptions that there is no need of a "base" object (it views on C++ -// memory, not a Python object with a buffer interface), it uses the CPyCppyy -// converters, and typed results and assignments are supported. Code reused -// under PSF License Version 2. - - -//- helpers ------------------------------------------------------------------ -static inline void set_strides(Py_buffer& view, size_t itemsize, bool isfix) { - if (isfix) { - Py_ssize_t stride = itemsize; - for (Py_ssize_t idim = view.ndim-1; 0 <= idim; --idim) { - view.strides[idim] = stride; - stride *= view.shape[idim]; - } - } else { - view.strides[view.ndim-1] = itemsize; - for (Py_ssize_t idim = 0; idim < view.ndim-1; ++idim) - view.strides[idim] = view.itemsize; - } -} - - -//= CPyCppyy low level view construction/destruction ========================= -static CPyCppyy::LowLevelView* ll_new(PyTypeObject* subtype, PyObject*, PyObject*) -{ -// Create a new low level ptr type - CPyCppyy::LowLevelView* pyobj = (CPyCppyy::LowLevelView*)subtype->tp_alloc(subtype, 0); - if (!pyobj) - return nullptr; - - memset(&pyobj->fBufInfo, 0, sizeof(Py_buffer)); - (intptr_t&)pyobj->fBufInfo.internal |= CPyCppyy::LowLevelView::kIsCppArray; - pyobj->fBuf = nullptr; - pyobj->fConverter = nullptr; - pyobj->fElemCnv = nullptr; - - return pyobj; -} - -//---------------------------------------------------------------------------- -static void ll_dealloc(CPyCppyy::LowLevelView* pyobj) -{ -// Destruction requires the deletion of the converter (if any) - PyMem_Free(pyobj->fBufInfo.shape); - PyMem_Free(pyobj->fBufInfo.strides); - if ((intptr_t)pyobj->fBufInfo.internal & CPyCppyy::LowLevelView::kIsOwner) { - if ((intptr_t)pyobj->fBufInfo.internal & CPyCppyy::LowLevelView::kIsCppArray) - delete [] pyobj->fBuf; - else - free(pyobj->fBuf); - } - - if (pyobj->fElemCnv != pyobj->fConverter &&\ - pyobj->fElemCnv && pyobj->fElemCnv->HasState()) - delete pyobj->fElemCnv; - - if (pyobj->fConverter && pyobj->fConverter->HasState()) - delete pyobj->fConverter; - - Py_TYPE(pyobj)->tp_free((PyObject*)pyobj); -} - - -//---------------------------------------------------------------------------- -#define CPYCPPYY_LL_FLAG_GETSET(name, flag, doc) \ -static PyObject* ll_get##name(CPyCppyy::LowLevelView* pyobj) \ -{ \ - return PyBool_FromLong((long)((intptr_t)pyobj->fBufInfo.internal & flag));\ -} \ - \ -static int ll_set##name(CPyCppyy::LowLevelView* pyobj, PyObject* value, void*)\ -{ \ - long settrue = PyLong_AsLong(value); \ - if (settrue == -1 && PyErr_Occurred()) { \ - PyErr_SetString(PyExc_ValueError, #doc" should be either True or False");\ - return -1; \ - } \ - \ - if ((bool)settrue) \ - (intptr_t&)pyobj->fBufInfo.internal |= flag; \ - else \ - (intptr_t&)pyobj->fBufInfo.internal &= ~flag; \ - \ - return 0; \ -} - -CPYCPPYY_LL_FLAG_GETSET(ownership, CPyCppyy::LowLevelView::kIsOwner, __python_owns__) -CPYCPPYY_LL_FLAG_GETSET(cpparray, CPyCppyy::LowLevelView::kIsCppArray, __cpp_array__) - -//--------------------------------------------------------------------------- -static PyObject* ll_typecode(CPyCppyy::LowLevelView* self, void*) -{ - return CPyCppyy_PyText_FromString((char*)self->fBufInfo.format); -} - - -//- Copy memoryview buffers ================================================= - -// The functions in this section take a source and a destination buffer -// with the same logical structure: format, itemsize, ndim and shape -// are identical, with ndim > 0. - -// Check for the presence of suboffsets in the first dimension. -#define HAVE_PTR(suboffsets, dim) (suboffsets && suboffsets[dim] >= 0) -// Adjust ptr if suboffsets are present. -#define ADJUST_PTR(ptr, suboffsets, dim) \ - (HAVE_PTR(suboffsets, dim) ? *((char**)ptr) + suboffsets[dim] : ptr) - -// Assumptions: ndim >= 1. The macro tests for a corner case that should -// perhaps be explicitly forbidden in the PEP. -#define HAVE_SUBOFFSETS_IN_LAST_DIM(view) \ - (view->suboffsets && view->suboffsets[dest->ndim-1] >= 0) - -//--------------------------------------------------------------------------- -static inline int last_dim_is_contiguous(const Py_buffer *dest, const Py_buffer *src) -{ - assert(dest->ndim > 0 && src->ndim > 0); - return (!HAVE_SUBOFFSETS_IN_LAST_DIM(dest) && - !HAVE_SUBOFFSETS_IN_LAST_DIM(src) && - dest->strides[dest->ndim-1] == dest->itemsize && - src->strides[src->ndim-1] == src->itemsize); -} - -//--------------------------------------------------------------------------- -static inline bool equiv_shape(const Py_buffer* dest, const Py_buffer* src) -{ -// Two shapes are equivalent if they are either equal or identical up -// to a zero element at the same position. For example, in NumPy arrays -// the shapes [1, 0, 5] and [1, 0, 7] are equivalent. - if (dest->ndim != src->ndim) - return false; - - for (int i = 0; i < dest->ndim; i++) { - if (dest->shape[i] != src->shape[i]) - return 0; - if (dest->shape[i] == 0) - break; - } - - return true; -} - -//--------------------------------------------------------------------------- -static bool equiv_structure(const Py_buffer* dest, const Py_buffer* src) -{ -// Check that the logical structure of the destination and source buffers -// is identical. - if (strcmp(dest->format, src->format) != 0 || dest->itemsize != src->itemsize || - !equiv_shape(dest, src)) { - PyErr_SetString(PyExc_ValueError, - "low level pointer assignment: lvalue and rvalue have different structures"); - return false; - } - - return true; -} - -//--------------------------------------------------------------------------- -static void copy_base(const Py_ssize_t* shape, Py_ssize_t itemsize, - char* dptr, const Py_ssize_t* dstrides, const Py_ssize_t* dsuboffsets, - char* sptr, const Py_ssize_t* sstrides, const Py_ssize_t* ssuboffsets, - char* mem) -{ -// Base case for recursive multi-dimensional copying. Contiguous arrays are -// copied with very little overhead. Assumptions: ndim == 1, mem == nullptr or -// sizeof(mem) == shape[0] * itemsize. - if (!mem) { // contiguous - Py_ssize_t size = shape[0] * itemsize; - if (dptr + size < sptr || sptr + size < dptr) - memcpy(dptr, sptr, size); // no overlapping - else - memmove(dptr, sptr, size); - } - else { - char *p; - Py_ssize_t i; - for (i=0, p=mem; i < shape[0]; p+=itemsize, sptr+=sstrides[0], i++) { - char *xsptr = ADJUST_PTR(sptr, ssuboffsets, 0); - memcpy(p, xsptr, itemsize); - } - for (i=0, p=mem; i < shape[0]; p+=itemsize, dptr+=dstrides[0], i++) { - char *xdptr = ADJUST_PTR(dptr, dsuboffsets, 0); - memcpy(xdptr, p, itemsize); - } - } - -} - -//--------------------------------------------------------------------------- -static int copy_single(Py_buffer* dest, Py_buffer* src) -{ -// Faster copying of one-dimensional arrays. - char* mem = nullptr; - - assert(dest->ndim == 1); - - if (!equiv_structure(dest, src)) - return -1; - - if (!last_dim_is_contiguous(dest, src)) { - mem = (char*)PyMem_Malloc(dest->shape[0] * dest->itemsize); - if (!mem) { - PyErr_NoMemory(); - return -1; - } - } - - copy_base(dest->shape, dest->itemsize, - (char*)dest->buf, dest->strides, dest->suboffsets, - (char*)src->buf, src->strides, src->suboffsets, - mem); - - if (mem) - PyMem_Free(mem); - - return 0; -} - - -//- Indexing and slicing ---------------------------------------------------- -static char* lookup_dimension(Py_buffer& view, char* ptr, int dim, Py_ssize_t index) -{ - Py_ssize_t nitems; // items in the given dimension - - assert(view.shape); - assert(view.strides); - - nitems = view.shape[dim]; - if (index < 0) { - if (nitems != CPyCppyy::UNKNOWN_SIZE) - index += nitems; - else { - PyErr_Format(PyExc_IndexError, - "negative index not supported on dimension %d with unknown size", dim + 1); - return nullptr; - } - } - - if (view.strides[dim] == CPyCppyy::UNKNOWN_SIZE) { - PyErr_Format(PyExc_IndexError, - "multi index not supported on dimension %d with unknown stride", dim + 1); - return nullptr; - } - - if (nitems != CPyCppyy::UNKNOWN_SIZE && (index < 0 || index >= nitems)) { - PyErr_Format(PyExc_IndexError, - "index out of bounds on dimension %d", dim + 1); - return nullptr; - } - - ptr += view.strides[dim] * index; - ptr = ADJUST_PTR(ptr, view.suboffsets, dim); - - return ptr; -} - -// Get the pointer to the item at index. -//--------------------------------------------------------------------------- -static inline void* ptr_from_index(CPyCppyy::LowLevelView* llview, Py_ssize_t index) -{ - Py_buffer& view = llview->fBufInfo; - return lookup_dimension(view, (char*)llview->get_buf(), 0, index); -} - -// Get the pointer to the item at tuple. -//--------------------------------------------------------------------------- -static void* ptr_from_tuple(CPyCppyy::LowLevelView* llview, PyObject* tup) -{ - Py_buffer& view = llview->fBufInfo; - - Py_ssize_t nindices = PyTuple_GET_SIZE(tup); - if (nindices > view.ndim) { - PyErr_Format(PyExc_TypeError, - "cannot index %d-dimension view with %zd-element tuple", view.ndim, nindices); - return nullptr; - } - - char* ptr = (char*)llview->get_buf(); - for (Py_ssize_t dim = 0; dim < nindices; dim++) { - Py_ssize_t index; - index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(tup, dim), - PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return nullptr; - - ptr = lookup_dimension(view, ptr, (int)dim, index); - if (!ptr) - return nullptr; - - if (!((intptr_t)view.internal & CPyCppyy::LowLevelView::kIsFixed) && dim != view.ndim-1) - ptr = *(char**)ptr; - } - return ptr; -} - - -//= mapping methods ========================================================= -static Py_ssize_t ll_length(CPyCppyy::LowLevelView* self) -{ - if (!self->get_buf()) - return 0; - return self->fBufInfo.ndim == 0 ? 1 : self->fBufInfo.shape[0]; -} - -//--------------------------------------------------------------------------- -static inline int init_slice(Py_buffer* base, PyObject* _key, int dim) -{ - Py_ssize_t start, stop, step, slicelength; - -#if PY_VERSION_HEX < 0x03000000 - PySliceObject* key = (PySliceObject*)_key; -#else - PyObject* key = _key; -#endif - - if (PySlice_GetIndicesEx(key, base->shape[dim], &start, &stop, &step, &slicelength) < 0) - return -1; - - if (!base->suboffsets || dim == 0) { - adjust_buf: - base->buf = (char *)base->buf + base->strides[dim] * start; - } - else { - Py_ssize_t n = dim-1; - while (n >= 0 && base->suboffsets[n] < 0) - n--; - if (n < 0) - goto adjust_buf; // all suboffsets are negative - base->suboffsets[n] = base->suboffsets[n] + base->strides[dim] * start; - } - base->shape[dim] = slicelength; - base->strides[dim] = base->strides[dim] * step; - - return 0; -} - -//--------------------------------------------------------------------------- -static bool is_multislice(PyObject* key) -{ - if (!PyTuple_Check(key)) - return false; - - Py_ssize_t size = PyTuple_GET_SIZE(key); - if (size == 0) - return false; - - for (Py_ssize_t i = 0; i < size; i++) { - PyObject *x = PyTuple_GET_ITEM(key, i); - if (!PySlice_Check(x)) - return false; - } - return true; -} - -//--------------------------------------------------------------------------- -static Py_ssize_t is_multiindex(PyObject* key) -{ - if (!PyTuple_Check(key)) - return 0; - - Py_ssize_t size = PyTuple_GET_SIZE(key); - for (Py_ssize_t i = 0; i < size; i++) { - PyObject *x = PyTuple_GET_ITEM(key, i); - if (!PyIndex_Check(x)) - return 0; - } - return 1; -} - - -// Return the item at index. In a one-dimensional view, this is an object -// with the type specified by view->format. Otherwise, the item is a sub-view. -// The function is used in ll_subscript() and ll_as_sequence. -//--------------------------------------------------------------------------- -static PyObject* ll_item(CPyCppyy::LowLevelView* self, Py_ssize_t index) -{ - Py_buffer& view = self->fBufInfo; - - if (!self->get_buf()) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return nullptr; - } - - if (view.ndim == 0) { - PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); - return nullptr; - } - - void* ptr = ptr_from_index(self, index); - if (ptr) { - bool isfix = (intptr_t)view.internal & CPyCppyy::LowLevelView::kIsFixed; - if (self->fBufInfo.ndim == 1 || !isfix) - return self->fConverter->FromMemory(ptr); - return self->fConverter->FromMemory((void*)&ptr); - } - - return nullptr; // error already set by lookup_dimension -} - -// Return the item at position *key* (a tuple of indices). -//--------------------------------------------------------------------------- -static PyObject* ll_item_multi(CPyCppyy::LowLevelView* self, PyObject *tup) -{ - Py_buffer& view = self->fBufInfo; - Py_ssize_t nindices = PyTuple_GET_SIZE(tup); - - if (nindices < view.ndim) { - // TODO: implement - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return nullptr; - } - - void* ptr = ptr_from_tuple(self, tup); - -// if there's an error, it was already set by lookup_dimension - return ptr ? self->fElemCnv->FromMemory(ptr) : nullptr; -} - - -// llp[obj] returns an object holding the data for one element if obj -// fully indexes the lowlevelptr or another lowlevelptr object if it -// does not. -// -// 0-d lowlevelptr objects can be referenced using llp[...] or llp[()] -// but not with anything else. -//--------------------------------------------------------------------------- -static PyObject* ll_subscript(CPyCppyy::LowLevelView* self, PyObject* key) -{ - Py_buffer& view = self->fBufInfo; - - if (view.ndim == 0) { - if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0) { - return self->fConverter->FromMemory(self->get_buf()); - } - else if (key == Py_Ellipsis) { - Py_INCREF(self); - return (PyObject*)self; - } - else { - PyErr_SetString(PyExc_TypeError, - "invalid indexing of 0-dim memory"); - return nullptr; - } - } - - if (PyIndex_Check(key)) { - Py_ssize_t index = PyNumber_AsSsize_t(key, PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return nullptr; - return ll_item(self, index); - } - else if (PySlice_Check(key)) { - if (view.ndim == 1) { - Py_ssize_t start, stop, step, slicelen; - if (PySlice_Unpack(key, &start, &stop, &step) < 0) - return nullptr; - - slicelen = PySlice_AdjustIndices(view.shape[0], &start, &stop, step); - if (slicelen <= 0) - slicelen = view.shape[0]; - - char* buf = (char*)self->get_buf(); - char* slice_buf = new char[slicelen*view.itemsize]; - size_t isize = view.itemsize; - for (size_t i=0, cur=0; i < (size_t)slicelen; cur += step, ++i) { - for (size_t j=0; j < isize; ++j) - slice_buf[i*isize+j] = buf[(start+cur)*isize + j]; - } - - CPyCppyy::LowLevelView* ll = self->fCreator(slice_buf, {1, slicelen}); - if (!ll) - delete [] slice_buf; - else - (intptr_t&)ll->fBufInfo.internal |= CPyCppyy::LowLevelView::kIsOwner; - - return (PyObject*)ll; - - } else { - // TODO: handle slicing. This should be simpler than the memoryview - // case as there is no Python object holding the buffer. - PyErr_SetString(PyExc_NotImplementedError, - "multi-dimensional slicing is not implemented"); - return nullptr; - } - } - else if (is_multiindex(key)) { - return ll_item_multi(self, key); - } - else if (is_multislice(key)) { - PyErr_SetString(PyExc_NotImplementedError, - "multi-dimensional slicing is not implemented"); - return nullptr; - } - - PyErr_SetString(PyExc_TypeError, "invalid slice key"); - return nullptr; -} - -//--------------------------------------------------------------------------- -static int ll_ass_sub(CPyCppyy::LowLevelView* self, PyObject* key, PyObject* value) -{ - Py_buffer& view = self->fBufInfo; - Py_buffer src; - - if (view.readonly) { - PyErr_SetString(PyExc_TypeError, "cannot modify read-only memory"); - return -1; - } - - if (value == nullptr) { - PyErr_SetString(PyExc_TypeError, "cannot delete memory"); - return -1; - } - - if (view.ndim == 0) { - if (key == Py_Ellipsis || - (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0)) { - return self->fConverter->ToMemory(value, self->get_buf()) ? 0 : -1; - } - else { - PyErr_SetString(PyExc_TypeError, - "invalid indexing of 0-dim memory"); - return -1; - } - } - - if (PyIndex_Check(key)) { - Py_ssize_t index; - if (1 < view.ndim) { - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return -1; - } - index = PyNumber_AsSsize_t(key, PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return -1; - void* ptr = ptr_from_index(self, index); - if (ptr == nullptr) - return -1; - return self->fConverter->ToMemory(value, ptr) ? 0 : -1; - } - - // one-dimensional: fast path - if (PySlice_Check(key) && view.ndim == 1) { - Py_buffer dest; // sliced view - Py_ssize_t arrays[3]; - int ret = -1; - - // rvalue must be an exporter - if (PyObject_GetBuffer(value, &src, PyBUF_FULL_RO) < 0) { - if (src.obj) PyBuffer_Release(&src); - return ret; - } - - dest = view; - dest.shape = &arrays[0]; dest.shape[0] = view.shape[0]; - dest.strides = &arrays[1]; dest.strides[0] = view.strides[0]; - if (view.suboffsets) { - dest.suboffsets = &arrays[2]; dest.suboffsets[0] = view.suboffsets[0]; - } - - if (init_slice(&dest, key, 0) < 0) - return -1; - dest.len = dest.shape[0] * dest.itemsize; - - ret = copy_single(&dest, &src); - PyBuffer_Release(&src); - return ret; - } - - if (is_multiindex(key)) { - // TODO: implement - if (PyTuple_GET_SIZE(key) < view.ndim) { - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return -1; - } - void* ptr = ptr_from_tuple(self, key); - if (ptr == nullptr) - return -1; - return self->fElemCnv->ToMemory(value, ptr) ? 0 : -1; - } - - if (PySlice_Check(key) || is_multislice(key)) { - // TODO: implement - PyErr_SetString(PyExc_NotImplementedError, - "LowLevelView slice assignments are currently restricted " - "to ndim = 1"); - return -1; - } - - PyErr_SetString(PyExc_TypeError, "invalid slice key"); - return -1; -} - -#if PY_VERSION_HEX < 0x03000000 -//--------------------------------------------------------------------------- -static Py_ssize_t ll_oldgetbuf(CPyCppyy::LowLevelView* self, Py_ssize_t seg, void** pptr) -{ - if (seg != 0) { - PyErr_SetString(PyExc_TypeError, "accessing non-existent segment"); - return -1; - } - - *pptr = self->get_buf(); - return self->fBufInfo.len; -} - -//--------------------------------------------------------------------------- -static Py_ssize_t ll_getsegcount(PyObject*, Py_ssize_t* lenp) -{ - if (lenp) *lenp = 1; - return 1; -} -#endif - -//--------------------------------------------------------------------------- -static int ll_getbuf(CPyCppyy::LowLevelView* self, Py_buffer* view, int flags) -{ -// Simplified from memoryobject, as we're always dealing with C arrays. - -// start with full copy - *view = self->fBufInfo; - - if (!(flags & PyBUF_FORMAT)) { - /* NULL indicates that the buffer's data type has been cast to 'B'. - view->itemsize is the _previous_ itemsize. If shape is present, - the equality product(shape) * itemsize = len still holds at this - point. The equality calcsize(format) = itemsize does _not_ hold - from here on! */ - view->format = NULL; - } - - if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { - PyErr_SetString(PyExc_BufferError, - "underlying buffer is not Fortran contiguous"); - return -1; - } - - if (!(flags & PyBUF_FORMAT)) { - /* PyBUF_SIMPLE or PyBUF_WRITABLE: at this point buf is C-contiguous, - so base->buf = ndbuf->data. */ - if (view->format != NULL) { - /* PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT do - not make sense. */ - PyErr_Format(PyExc_BufferError, - "cannot cast to unsigned bytes if the format flag is present"); - return -1; - } - /* product(shape) * itemsize = len and calcsize(format) = itemsize - do _not_ hold from here on! */ - view->ndim = 1; - view->shape = NULL; - } - - view->obj = (PyObject*)self; - Py_INCREF(view->obj); - - return 0; -} - - -//= iterator protocol ======================================================= -static PyObject* ll_iter(PyObject* self) { -// The index iterator indexes through getitem, just like python would do by -// default, except that it checks the size externally to raise StopIteration, -// rather than geitem failing. - - using namespace CPyCppyy; - - indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); - if (!ii) return nullptr; - - Py_INCREF(self); - ii->ii_container = self; - ii->ii_pos = 0; - ii->ii_len = ll_length((LowLevelView*)self); - - PyObject_GC_Track(ii); - return (PyObject*)ii; -} - - -//- mapping methods --------------------------------------------------------- -static PyMappingMethods ll_as_mapping = { - (lenfunc) ll_length, // mp_length - (binaryfunc) ll_subscript, // mp_subscript - (objobjargproc)ll_ass_sub, // mp_ass_subscript -}; - -//- sequence methods -------------------------------------------------------- -static PySequenceMethods ll_as_sequence = { - (lenfunc)ll_length, // sq_length - 0, // sq_concat - 0, // sq_repeat - (ssizeargfunc)ll_item, // sq_item - 0, // sq_slice - 0, // sq_ass_item - 0, // sq_ass_slice - 0, // sq_contains - 0, // sq_inplace_concat - 0, // sq_inplace_repeat -}; - -//- buffer methods ---------------------------------------------------------- -static PyBufferProcs ll_as_buffer = { -#if PY_VERSION_HEX < 0x03000000 - (readbufferproc)ll_oldgetbuf, // bf_getreadbuffer - (writebufferproc)ll_oldgetbuf, // bf_getwritebuffer - (segcountproc)ll_getsegcount, // bf_getsegcount - 0, // bf_getcharbuffer -#endif - (getbufferproc)ll_getbuf, // bf_getbuffer - 0, // bf_releasebuffer -}; - - - -//--------------------------------------------------------------------------- -static PyObject* ll_shape(CPyCppyy::LowLevelView* self) -{ - Py_buffer& view = self->fBufInfo; - - PyObject* shape = PyTuple_New(view.ndim); - for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) - PyTuple_SET_ITEM(shape, idim, PyInt_FromSsize_t(view.shape[idim])); - - return shape; -} - -//--------------------------------------------------------------------------- -static PyObject* ll_reshape(CPyCppyy::LowLevelView* self, PyObject* shape) -{ -// Allow the user to fix up the actual (type-strided) size of the buffer. - if (!PyTuple_Check(shape)) { - if (shape) { - PyObject* pystr = PyObject_Str(shape); - if (pystr) { - PyErr_Format(PyExc_TypeError, "tuple object expected, received %s", - CPyCppyy_PyText_AsStringChecked(pystr)); - Py_DECREF(pystr); - return nullptr; - } - } - PyErr_SetString(PyExc_TypeError, "tuple object expected"); - return nullptr; - } - - Py_buffer& view = self->fBufInfo; - -// verify size match - Py_ssize_t oldsz = 0; - for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) { - Py_ssize_t nlen = view.shape[idim]; - if (nlen == CPyCppyy::UNKNOWN_SIZE || nlen == INT_MAX/view.itemsize /* fake 'max' */) { - oldsz = -1; // meaning, unable to check size match - break; - } - oldsz += view.shape[idim]; - } - - if (0 < oldsz) { - Py_ssize_t newsz = 0; - for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) - newsz += PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); - if (oldsz != newsz) { - PyObject* tas = PyObject_Str(shape); - PyErr_Format(PyExc_ValueError, - "cannot reshape array of size %ld into shape %s", (long)oldsz, CPyCppyy_PyText_AsString(tas)); - Py_DECREF(tas); - return nullptr; - } - } - -// reshape - size_t itemsize = view.strides[view.ndim-1]; - if (view.ndim != PyTuple_GET_SIZE(shape)) { - PyMem_Free(view.shape); - PyMem_Free(view.strides); - - view.ndim = (int)PyTuple_GET_SIZE(shape); - view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - } - - for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) { - Py_ssize_t nlen = PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); - if (nlen == -1 && PyErr_Occurred()) - return nullptr; - - if (idim == 0) view.len = nlen * view.itemsize; - - view.shape[idim] = nlen; - } - - set_strides(view, itemsize, false /* by definition not fixed */); - - Py_RETURN_NONE; -} - - -//--------------------------------------------------------------------------- -static PyObject* ll_array(CPyCppyy::LowLevelView* self, PyObject* args, PyObject* kwds) -{ -// Construct a numpy array from the lowlevelview (w/o copy if possible); this -// uses the Python methods to avoid depending on numpy directly - -// Expect as most a dtype from the arguments; - static PyObject* npmod = PyImport_ImportModule("numpy"); // ref-count kept - if (!npmod) - return nullptr; - - bool docopy = false; - if (kwds) { - PyObject* pycp = PyObject_GetItem(kwds, CPyCppyy::PyStrings::gCopy); - if (!pycp) { - PyErr_SetString(PyExc_TypeError, "__array__ only supports the \"copy\" keyword"); - return nullptr; - } - - docopy = PyObject_IsTrue(pycp); - Py_DECREF(pycp); - } - - if (!docopy) { // view requested - // expect possible dtype from the arguments, otherwise take it from the type code - PyObject* dtype; - if (!args || PyTuple_GET_SIZE(args) != 1) { - PyObject* npdtype = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gDType); - PyObject* typecode = ll_typecode(self, nullptr); - dtype = PyObject_CallFunctionObjArgs(npdtype, typecode, nullptr); - Py_DECREF(typecode); - Py_DECREF(npdtype); - } else { - dtype = PyTuple_GET_ITEM(args, 0); - Py_INCREF(dtype); - } - - if (!dtype) - return nullptr; - - PyObject* npfrombuf = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gFromBuffer); - PyObject* view = PyObject_CallFunctionObjArgs(npfrombuf, (PyObject*)self, dtype, nullptr); - Py_DECREF(dtype); - Py_DECREF(npfrombuf); - - return view; - - } else { // copy requested - PyObject* npcopy = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gCopy); - PyObject* newarr = PyObject_CallFunctionObjArgs(npcopy, (PyObject*)self, nullptr); - Py_DECREF(npcopy); - - return newarr; - } - -// never get here - return nullptr; -} - - -//--------------------------------------------------------------------------- -static PyObject* ll_as_string(CPyCppyy::LowLevelView* self) -{ -// Interpret memory as a null-terminated char string. - Py_buffer& view = self->fBufInfo; - - if (strcmp(view.format, "b") != 0 || view.ndim != 1) { - PyErr_Format(PyExc_TypeError, - "as_string only supported for 1-dim char strings (format: %s, dim: %d)", - view.format, (int)view.ndim); - return nullptr; - } - - char* buf = (char*)self->get_buf(); - size_t sz = strnlen(buf, (size_t)view.shape[0]); - return CPyCppyy_PyText_FromStringAndSize(buf, sz); -} - -//--------------------------------------------------------------------------- -static PyMethodDef ll_methods[] = { - {(char*)"reshape", (PyCFunction)ll_reshape, METH_O, - (char*)"change the shape (not layout) of the low level view"}, - {(char*)"as_string", (PyCFunction)ll_as_string, METH_NOARGS, - (char*)"interpret memory as a null-terminated char string and return Python str"}, - {(char*)"__array__", (PyCFunction)ll_array, METH_VARARGS | METH_KEYWORDS, - (char*)"return a numpy array from the low level view"}, - {(char*)nullptr, nullptr, 0, nullptr} -}; - -//--------------------------------------------------------------------------- -static PyGetSetDef ll_getset[] = { - {(char*)"__python_owns__", (getter)ll_getownership, (setter)ll_setownership, - (char*)"If true, python manages the life time of this buffer", nullptr}, - {(char*)"__cpp_array__", (getter)ll_getcpparray, (setter)ll_setcpparray, - (char*)"If true, this array was allocated with C++\'s new[]", nullptr}, - {(char*)"format", (getter)ll_typecode, nullptr, nullptr, nullptr}, - {(char*)"typecode", (getter)ll_typecode, nullptr, nullptr, nullptr}, - {(char*)"shape", (getter)ll_shape, (setter)ll_reshape, nullptr, nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr } -}; - - -namespace CPyCppyy { - -//= CPyCppyy low level view type ============================================ -PyTypeObject LowLevelView_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.LowLevelView", // tp_name - sizeof(CPyCppyy::LowLevelView),// tp_basicsize - 0, // tp_itemsize - (destructor)ll_dealloc, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // itp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - &ll_as_sequence, // tp_as_sequence - &ll_as_mapping, // tp_as_mapping - 0, // tp_hash - 0, // tp_call - 0, // tp_str - 0, // tp_getattro - 0, // tp_setattro - &ll_as_buffer, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"memory view on C++ pointer", // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - (getiterfunc)ll_iter, // tp_iter - 0, // tp_iternext - ll_methods, // tp_methods - 0, // tp_members - ll_getset, // tp_getset - 0, // tp_base - 0, // tp_dict - 0, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)ll_new, // tp_new - 0, // tp_free - 0, // tp_is_gc - 0, // tp_bases - 0, // tp_mro - 0, // tp_cache - 0, // tp_subclasses - 0 // tp_weaklist -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy - -namespace { - -template struct typecode_traits {}; -template<> struct typecode_traits { - static constexpr const char* format = "?"; static constexpr const char* name = "bool"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "char"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "SCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "B"; static constexpr const char* name = "UCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "B"; static constexpr const char* name = "UCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "char*"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "const char*"; }; -template<> struct typecode_traits { - static constexpr const char* format = "h"; static constexpr const char* name = "short"; }; -template<> struct typecode_traits { - static constexpr const char* format = "H"; static constexpr const char* name = "unsigned short"; }; -template<> struct typecode_traits { - static constexpr const char* format = "i"; static constexpr const char* name = "int"; }; -template<> struct typecode_traits { - static constexpr const char* format = "I"; static constexpr const char* name = "unsigned int"; }; -template<> struct typecode_traits { - static constexpr const char* format = "l"; static constexpr const char* name = "long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "L"; static constexpr const char* name = "unsigned long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "q"; static constexpr const char* name = "long long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "Q"; static constexpr const char* name = "unsigned long long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "f"; static constexpr const char* name = "float"; }; -template<> struct typecode_traits { - static constexpr const char* format = "d"; static constexpr const char* name = "double"; }; -template<> struct typecode_traits { - static constexpr const char* format = "D"; static constexpr const char* name = "long double"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zf"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zd"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zi"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zl"; static constexpr const char* name = "std::complex"; }; - -} // unnamed namespace - - -//--------------------------------------------------------------------------- -bool CPyCppyy::LowLevelView::resize(size_t sz) -{ - Py_buffer& bi = this->fBufInfo; - if (bi.ndim == 1 && bi.shape) { - bi.len = sz * bi.itemsize; - bi.shape[0] = sz; - return true; - } - - return false; -} - -//--------------------------------------------------------------------------- -template -static inline CPyCppyy::LowLevelView* CreateLowLevelViewT( - T* address, CPyCppyy::cdims_t shape, const char* format = nullptr, const char* name = nullptr, Py_ssize_t itemsize = -1) -{ - using namespace CPyCppyy; - Py_ssize_t nx = (shape.ndim() != UNKNOWN_SIZE) ? shape[0] : INT_MAX/sizeof(T); - if (nx == UNKNOWN_SIZE) nx = INT_MAX/sizeof(T); - PyObject* args = PyTuple_New(0); - LowLevelView* llp = - (LowLevelView*)LowLevelView_Type.tp_new(&LowLevelView_Type, args, nullptr); - Py_DECREF(args); - - Py_buffer& view = llp->fBufInfo; - view.buf = address; - view.obj = nullptr; - view.readonly = 0; - view.format = (char*)(format ? format : typecode_traits::format); - view.ndim = int(shape.ndim() != UNKNOWN_SIZE ? shape.ndim() : 1); - view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.shape[0] = nx; // view.len / view.itemsize - view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.suboffsets = nullptr; - (intptr_t&)view.internal = CPyCppyy::LowLevelView::kIsCppArray; // assumed - bool isfix = shape.ndim() != UNKNOWN_SIZE; - if (isfix) { - for (int i = 0; i < shape.ndim(); ++i) - isfix = isfix && (shape[i] != UNKNOWN_SIZE); - if (isfix) (intptr_t&)view.internal |= CPyCppyy::LowLevelView::kIsFixed; - } - - llp->fElemCnv = CreateConverter(name ? name : typecode_traits::name); - if (view.ndim == 1) { - // simple 1-dim array of the declared type - view.len = nx * sizeof(T); - view.itemsize = (itemsize > 0 ? (size_t)itemsize : sizeof(T)); - llp->fConverter = llp->fElemCnv; - } else { - // multi-dim array; sub-views are projected by using more LLViews - const size_t elemsize = isfix ? sizeof(T) : sizeof(void*); - view.len = nx * elemsize; - view.itemsize = elemsize; - for (Py_ssize_t idim = 1; idim < view.ndim; ++idim) - view.shape[idim] = shape[idim]; - - // peel off one dimension and create a new LLView converter - std::string tname{name ? name : typecode_traits::name}; - tname.append("[]"); // make sure to ask for another array - // TODO: although this will work, it means that "naive" loops are expensive - llp->fConverter = CreateConverter(tname, shape.sub()); - } - - set_strides(view, sizeof(T), isfix); - - return llp; -} - -//--------------------------------------------------------------------------- -template -static inline CPyCppyy::LowLevelView* CreateLowLevelViewT( - T** address, CPyCppyy::cdims_t shape, const char* format = nullptr, const char* name = nullptr) -{ - using namespace CPyCppyy; - LowLevelView* llp = (LowLevelView*)CreateLowLevelViewT((T*)address, shape, format, name); - llp->set_buf((void**)address); - return llp; -} - -//--------------------------------------------------------------------------- -#define CPPYY_RET_W_CREATOR(type, fname) \ - PyObject* (*c)(type, cdims_t) = &fname; \ - ll->fCreator = (LowLevelView::Creator_t)c; \ - return (PyObject*)ll - -#define CPPYY_IMPL_VIEW_CREATOR(type) \ -PyObject* CPyCppyy::CreateLowLevelView(type* address, cdims_t shape) { \ - LowLevelView* ll = CreateLowLevelViewT(address, shape); \ - CPPYY_RET_W_CREATOR(type*, CreateLowLevelView); \ -} \ -PyObject* CPyCppyy::CreateLowLevelView(type** address, cdims_t shape) { \ - LowLevelView* ll = CreateLowLevelViewT(address, shape); \ - CPPYY_RET_W_CREATOR(type**, CreateLowLevelView); \ -} - -CPPYY_IMPL_VIEW_CREATOR(bool); -CPPYY_IMPL_VIEW_CREATOR(signed char); -CPPYY_IMPL_VIEW_CREATOR(unsigned char); -CPPYY_IMPL_VIEW_CREATOR(std::byte); -CPPYY_IMPL_VIEW_CREATOR(short); -CPPYY_IMPL_VIEW_CREATOR(unsigned short); -CPPYY_IMPL_VIEW_CREATOR(int); -CPPYY_IMPL_VIEW_CREATOR(unsigned int); -CPPYY_IMPL_VIEW_CREATOR(long); -CPPYY_IMPL_VIEW_CREATOR(unsigned long); -CPPYY_IMPL_VIEW_CREATOR(long long); -CPPYY_IMPL_VIEW_CREATOR(unsigned long long); -CPPYY_IMPL_VIEW_CREATOR(float); -CPPYY_IMPL_VIEW_CREATOR(double); -CPPYY_IMPL_VIEW_CREATOR(long double); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); - -PyObject* CPyCppyy::CreateLowLevelView(char* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape); - CPPYY_RET_W_CREATOR(char*, CreateLowLevelView); -} - -PyObject* CPyCppyy::CreateLowLevelView(char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape); - CPPYY_RET_W_CREATOR(char**, CreateLowLevelView); -} - -PyObject* CPyCppyy::CreateLowLevelViewString(char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, nullptr, sizeof(char)); - CPPYY_RET_W_CREATOR(char**, CreateLowLevelViewString); -} - -PyObject* CPyCppyy::CreateLowLevelViewString(const char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, nullptr, sizeof(char)); - CPPYY_RET_W_CREATOR(const char**, CreateLowLevelViewString); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(int8_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); - CPPYY_RET_W_CREATOR(int8_t*, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(int8_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); - CPPYY_RET_W_CREATOR(int8_t**, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(uint8_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "B", "uint8_t"); - CPPYY_RET_W_CREATOR(uint8_t*, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(uint8_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "B", "uint8_t"); - CPPYY_RET_W_CREATOR(uint8_t**, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(int16_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "h", "int16_t"); - CPPYY_RET_W_CREATOR(int16_t*, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(int16_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "h", "int16_t"); - CPPYY_RET_W_CREATOR(int16_t**, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(uint16_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "H", "uint16_t"); - CPPYY_RET_W_CREATOR(uint16_t*, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(uint16_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "H", "uint16_t"); - CPPYY_RET_W_CREATOR(uint16_t**, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(int32_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "i", "int32_t"); - CPPYY_RET_W_CREATOR(int32_t*, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(int32_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "i", "int32_t"); - CPPYY_RET_W_CREATOR(int32_t**, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(uint32_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "I", "uint32_t"); - CPPYY_RET_W_CREATOR(uint32_t*, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(uint32_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "I", "uint32_t"); - CPPYY_RET_W_CREATOR(uint32_t**, CreateLowLevelView_i32); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h deleted file mode 100644 index b3b6c8daa16ca..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef CPYCPPYY_LOWLEVELVIEWS_H -#define CPYCPPYY_LOWLEVELVIEWS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -class Converter; - -class LowLevelView { -public: - enum EFlags { - kDefault = 0x0000, - kIsCppArray = 0x0001, // allocated with new[] - kIsFixed = 0x0002, // fixed size array (assumed flat) - kIsOwner = 0x0004 }; // Python owns - -public: - PyObject_HEAD - Py_buffer fBufInfo; - void** fBuf; - Converter* fConverter; - Converter* fElemCnv; - - typedef LowLevelView* (*Creator_t)(void*, cdims_t); - Creator_t fCreator; // for slicing, which requires copying - -public: - void* get_buf() { return fBuf ? *fBuf : fBufInfo.buf; } - void set_buf(void** buf) { fBuf = buf; fBufInfo.buf = get_buf(); } - - bool resize(size_t sz); -}; - -#define CPPYY_DECL_VIEW_CREATOR(type) \ - PyObject* CreateLowLevelView(type*, cdims_t shape); \ - PyObject* CreateLowLevelView(type**, cdims_t shape) - -CPPYY_DECL_VIEW_CREATOR(bool); -CPPYY_DECL_VIEW_CREATOR(char); -CPPYY_DECL_VIEW_CREATOR(signed char); -CPPYY_DECL_VIEW_CREATOR(unsigned char); -CPPYY_DECL_VIEW_CREATOR(std::byte); -PyObject* CreateLowLevelView_i8(int8_t*, cdims_t shape); -PyObject* CreateLowLevelView_i8(int8_t**, cdims_t shape); -PyObject* CreateLowLevelView_i8(uint8_t*, cdims_t shape); -PyObject* CreateLowLevelView_i8(uint8_t**, cdims_t shape); -PyObject* CreateLowLevelView_i16(int16_t*, cdims_t shape); -PyObject* CreateLowLevelView_i16(int16_t**, cdims_t shape); -PyObject* CreateLowLevelView_i16(uint16_t*, cdims_t shape); -PyObject* CreateLowLevelView_i16(uint16_t**, cdims_t shape); -PyObject* CreateLowLevelView_i32(int32_t*, cdims_t shape); -PyObject* CreateLowLevelView_i32(int32_t**, cdims_t shape); -PyObject* CreateLowLevelView_i32(uint32_t*, cdims_t shape); -PyObject* CreateLowLevelView_i32(uint32_t**, cdims_t shape); - -CPPYY_DECL_VIEW_CREATOR(short); -CPPYY_DECL_VIEW_CREATOR(unsigned short); -CPPYY_DECL_VIEW_CREATOR(int); -CPPYY_DECL_VIEW_CREATOR(unsigned int); -CPPYY_DECL_VIEW_CREATOR(long); -CPPYY_DECL_VIEW_CREATOR(unsigned long); -CPPYY_DECL_VIEW_CREATOR(long long); -CPPYY_DECL_VIEW_CREATOR(unsigned long long); -CPPYY_DECL_VIEW_CREATOR(float); -CPPYY_DECL_VIEW_CREATOR(double); -CPPYY_DECL_VIEW_CREATOR(long double); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); - -PyObject* CreateLowLevelViewString(char**, cdims_t shape); -PyObject* CreateLowLevelViewString(const char**, cdims_t shape); - -inline PyObject* CreatePointerView(void* ptr, cdims_t shape = 0) { - return CreateLowLevelView((uintptr_t*)ptr, shape); -} - -//- low level view type and type verification -------------------------------- -extern PyTypeObject LowLevelView_Type; - -template -inline bool LowLevelView_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &LowLevelView_Type); -} - -template -inline bool LowLevelView_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &LowLevelView_Type; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_LOWLEVELVIEWS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx deleted file mode 100644 index 52d8261bc1fda..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx +++ /dev/null @@ -1,274 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "MemoryRegulator.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" - -// Standard -#include -#include -#include - - -//= pseudo-None type for masking out objects on the python side =============== -static PyTypeObject CPyCppyy_NoneType; - -//----------------------------------------------------------------------------- -static Py_ssize_t AlwaysNullLength(PyObject*) -{ - return 0; -} - -//----------------------------------------------------------------------------- -static PyMappingMethods CPyCppyy_NoneType_mapping = { - AlwaysNullLength, - (binaryfunc) 0, - (objobjargproc) 0 -}; - -// silence warning about some cast operations -#if defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || (__GNUC_MINOR__ >= 3)))) && !__INTEL_COMPILER -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif - - -//----------------------------------------------------------------------------- -namespace { - -// Py_SET_REFCNT was only introduced in Python 3.9 -#if PY_VERSION_HEX < 0x03090000 -inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { - assert(refcnt >= 0); -#if SIZEOF_VOID_P > 4 - ob->ob_refcnt = (PY_UINT32_T)refcnt; -#else - ob->ob_refcnt = refcnt; -#endif -} -#endif - -struct InitCPyCppyy_NoneType_t { - InitCPyCppyy_NoneType_t() { - // create a CPyCppyy NoneType (for references that went dodo) from NoneType - memset(&CPyCppyy_NoneType, 0, sizeof(CPyCppyy_NoneType)); - - ((PyObject&)CPyCppyy_NoneType).ob_type = &PyType_Type; - Py_SET_REFCNT((PyObject*)&CPyCppyy_NoneType, 1); - ((PyVarObject&)CPyCppyy_NoneType).ob_size = 0; - - CPyCppyy_NoneType.tp_name = const_cast("CPyCppyy_NoneType"); - CPyCppyy_NoneType.tp_flags = Py_TPFLAGS_HAVE_RICHCOMPARE; - - CPyCppyy_NoneType.tp_traverse = (traverseproc)0; - CPyCppyy_NoneType.tp_clear = (inquiry)0; - CPyCppyy_NoneType.tp_dealloc = (destructor)&InitCPyCppyy_NoneType_t::DeAlloc; - CPyCppyy_NoneType.tp_repr = Py_TYPE(Py_None)->tp_repr; - CPyCppyy_NoneType.tp_richcompare = (richcmpfunc)&InitCPyCppyy_NoneType_t::RichCompare; -#if PY_VERSION_HEX < 0x03000000 - // tp_compare has become tp_reserved (place holder only) in p3 - CPyCppyy_NoneType.tp_compare = (cmpfunc)&InitCPyCppyy_NoneType_t::Compare; -#endif - CPyCppyy_NoneType.tp_hash = (hashfunc)&InitCPyCppyy_NoneType_t::PtrHash; - - CPyCppyy_NoneType.tp_as_mapping = &CPyCppyy_NoneType_mapping; - - PyType_Ready(&CPyCppyy_NoneType); - } - - static void DeAlloc(PyObject* pyobj) { Py_TYPE(pyobj)->tp_free(pyobj); } - static int PtrHash(PyObject* pyobj) { return (int)ptrdiff_t(pyobj); } - - static PyObject* RichCompare(PyObject*, PyObject* other, int opid) { - return PyObject_RichCompare(other, Py_None, opid); - } - - static int Compare(PyObject*, PyObject* other) { -#if PY_VERSION_HEX < 0x03000000 - return PyObject_Compare(other, Py_None); -#else - // TODO the following isn't correct as it doesn't order, but will do for now ... - return !PyObject_RichCompareBool(other, Py_None, Py_EQ); -#endif - } -}; - -} // unnamed namespace - -// Memory regulation hooks -CPyCppyy::MemHook_t CPyCppyy::MemoryRegulator::registerHook = nullptr; -CPyCppyy::MemHook_t CPyCppyy::MemoryRegulator::unregisterHook = nullptr; - - -//- ctor/dtor ---------------------------------------------------------------- -CPyCppyy::MemoryRegulator::MemoryRegulator() -{ -// setup NoneType for referencing and create weakref cache - static InitCPyCppyy_NoneType_t initCPyCppyy_NoneType; -} - - -//- public members ----------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::RecursiveRemove( - Cppyy::TCppObject_t cppobj, Cppyy::TCppType_t klass) -{ -// if registered by the framework, called whenever a cppobj gets destroyed - if (!cppobj) - return false; - - PyObject* pyscope = GetScopeProxy(klass); - if (!CPPScope_Check(pyscope)) { - Py_XDECREF(pyscope); - return false; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)pyscope)->fImp.fCppObjects; - if (!cppobjs) { // table may have been deleted on shutdown - Py_DECREF(pyscope); - return false; - } - -// see whether we're tracking this object - CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); - - if (ppo != cppobjs->end()) { - // get the tracked object - CPPInstance* pyobj = (CPPInstance*)ppo->second; - - // erase the object from tracking - pyobj->fFlags &= ~CPPInstance::kIsRegulated; - cppobjs->erase(ppo); - - // nullify the object - if (!CPyCppyy_NoneType.tp_traverse) { - // take a reference as we're copying its function pointers - Py_INCREF(Py_TYPE(pyobj)); - - // all object that arrive here are expected to be of the same type ("instance") - CPyCppyy_NoneType.tp_traverse = Py_TYPE(pyobj)->tp_traverse; - CPyCppyy_NoneType.tp_clear = Py_TYPE(pyobj)->tp_clear; - CPyCppyy_NoneType.tp_free = Py_TYPE(pyobj)->tp_free; - CPyCppyy_NoneType.tp_flags |= Py_TYPE(pyobj)->tp_flags; - } else if (CPyCppyy_NoneType.tp_traverse != Py_TYPE(pyobj)->tp_traverse) { - // TODO: SystemError? - std::cerr << "in CPyCppyy::MemoryRegulater, unexpected object of type: " - << Py_TYPE(pyobj)->tp_name << std::endl; - - // drop object and leave before too much damage is done - Py_DECREF(pyscope); - return false; - } - - // notify any other weak referents by playing dead - Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, 0); - PyObject_ClearWeakRefs((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, refcnt); - - // cleanup object internals - pyobj->CppOwns(); // held object is out of scope now anyway - op_dealloc_nofree(pyobj); // normal object cleanup, while keeping memory - - // reset type object - Py_INCREF((PyObject*)(void*)&CPyCppyy_NoneType); - Py_DECREF(Py_TYPE(pyobj)); - ((PyObject*)pyobj)->ob_type = &CPyCppyy_NoneType; - - Py_DECREF(pyscope); - return true; - } - -// unregulated cppobj - Py_DECREF(pyscope); - return false; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::RegisterPyObject( - CPPInstance* pyobj, Cppyy::TCppObject_t cppobj) -{ -// start tracking proxied by - if (!(pyobj && cppobj)) - return false; - - if (registerHook) { - auto res = registerHook(cppobj, pyobj->ObjectIsA(false)); - if (!res.second) return res.first; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)Py_TYPE(pyobj))->fImp.fCppObjects; - if (!cppobjs) - return false; - -// if an address was already associated with a different object, then stop following -// the old and force insert the new proxy for following - const auto& res = cppobjs->insert(std::make_pair(cppobj, (PyObject*)pyobj)); - if (!res.second) { - ((CPPInstance*)res.first->second)->fFlags &= ~CPPInstance::kIsRegulated; - (*cppobjs)[cppobj] = (PyObject*)pyobj; - } - - pyobj->fFlags |= CPPInstance::kIsRegulated; - return true; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass) -{ -// called when the proxy is garbage collected or is about delete the C++ instance - if (!(pyobj && pyclass)) - return false; - - Cppyy::TCppObject_t cppobj = pyobj->IsSmart() ? pyobj->GetObjectRaw() : pyobj->GetObject(); - if (!cppobj) - return false; - - if (unregisterHook) { - auto res = unregisterHook(cppobj, ((CPPClass*)pyclass)->fCppType); - if (!res.second) return res.first; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; - if (!cppobjs) - return false; - -// erase if tracked - if (cppobjs->erase(cppobj)) { - pyobj->fFlags &= ~CPPInstance::kIsRegulated; - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::MemoryRegulator::RetrievePyObject(Cppyy::TCppObject_t cppobj, PyObject* pyclass) -{ -// lookup to see if a C++ address is already known, return old proxy if tracked - if (!(cppobj && pyclass)) - return nullptr; - - CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; - if (!cppobjs) - return nullptr; - - CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); - if (ppo != cppobjs->end()) { - Py_INCREF(ppo->second); - return ppo->second; - } - - return nullptr; -} - - -//----------------------------------------------------------------------------- -void CPyCppyy::MemoryRegulator::SetRegisterHook(MemHook_t h) { -// Set custom register hook; called when a regulated object is to be tracked - registerHook = h; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::MemoryRegulator::SetUnregisterHook(MemHook_t h) { -// Set custom unregister hook; called when a regulated object is to be untracked - unregisterHook = h; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.h b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.h deleted file mode 100644 index 2cd449a37fb5c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CPYCPPYY_MEMORYREGULATOR_H -#define CPYCPPYY_MEMORYREGULATOR_H - -#include -#include - -namespace CPyCppyy { - -class CPPInstance; - -typedef std::function(Cppyy::TCppObject_t, Cppyy::TCppType_t)> MemHook_t; - -class MemoryRegulator { -private: - static MemHook_t registerHook, unregisterHook; - -public: - MemoryRegulator(); - -// callback from C++-side frameworks - static bool RecursiveRemove(Cppyy::TCppObject_t cppobj, Cppyy::TCppType_t klass); - -// called when a new python proxy object is created - static bool RegisterPyObject(CPPInstance* pyobj, void* cppobj); - -// called when a the python proxy object is about to be garbage collected or when it is -// about to delete the proxied C++ object, if owned - static bool UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass); - -// new reference to python object matching cppobj, or 0 on failure - static PyObject* RetrievePyObject(Cppyy::TCppObject_t cppobj, PyObject* pyclass); - -// set hooks for custom memory regulation - static void SetRegisterHook(MemHook_t h); - static void SetUnregisterHook(MemHook_t h); -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_MEMORYREGULATOR_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx deleted file mode 100644 index 6aad4bf43a22e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx +++ /dev/null @@ -1,985 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "ProxyWrappers.h" -#include "CPPClassMethod.h" -#include "CPPConstructor.h" -#include "CPPDataMember.h" -#include "CPPExcInstance.h" -#include "CPPFunction.h" -#include "CPPGetSetItem.h" -#include "CPPInstance.h" -#include "CPPMethod.h" -#include "CPPOperator.h" -#include "CPPOverload.h" -#include "CPPScope.h" -#include "MemoryRegulator.h" -#include "PyStrings.h" -#include "Pythonize.h" -#include "TemplateProxy.h" -#include "TupleOfInstances.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include - - -//- data _______________________________________________________________________ -namespace CPyCppyy { - extern PyObject* gThisModule; - extern PyObject* gPyTypeMap; - extern std::set gPinnedTypes; -} - -// to prevent having to walk scopes, track python classes by C++ class -typedef std::map PyClassMap_t; -static PyClassMap_t gPyClasses; - - -//- helpers -------------------------------------------------------------------- - -namespace CPyCppyy { - -typedef struct { - PyObject_HEAD - PyObject *dict; -} proxyobject; - -// helper for creating new C++ proxy python types -static PyObject* CreateNewCppProxyClass(Cppyy::TCppScope_t klass, PyObject* pybases) -{ -// Create a new python shadow class with the required hierarchy and meta-classes. - PyObject* pymetabases = PyTuple_New(PyTuple_GET_SIZE(pybases)); - for (int i = 0; i < PyTuple_GET_SIZE(pybases); ++i) { - PyObject* btype = (PyObject*)Py_TYPE(PyTuple_GetItem(pybases, i)); - Py_INCREF(btype); - PyTuple_SET_ITEM(pymetabases, i, btype); - } - - std::string name = Cppyy::GetFinalName(klass); - -// create meta-class, add a dummy __module__ to pre-empt the default setting - PyObject* args = Py_BuildValue((char*)"sO{}", (name+"_meta").c_str(), pymetabases); - PyDict_SetItem(PyTuple_GET_ITEM(args, 2), PyStrings::gModule, Py_True); - Py_DECREF(pymetabases); - - PyObject* pymeta = (PyObject*)CPPScopeMeta_New(klass, args); - Py_DECREF(args); - if (!pymeta) - return nullptr; - -// alright, and now we really badly want to get rid of the dummy ... - PyObject* dictproxy = PyObject_GetAttr(pymeta, PyStrings::gDict); - PyDict_DelItem(((proxyobject*)dictproxy)->dict, PyStrings::gModule); - -// create actual class - args = Py_BuildValue((char*)"sO{}", name.c_str(), pybases); - PyObject* pyclass = - ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); - - Py_DECREF(args); - Py_DECREF(pymeta); - - return pyclass; -} - -static inline -void AddPropertyToClass(PyObject* pyclass, - Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) -{ - CPyCppyy::CPPDataMember* property = CPyCppyy::CPPDataMember_New(scope, idata); - PyObject* pname = CPyCppyy_PyText_InternFromString(const_cast(property->GetName().c_str())); - -// allow access at the instance level - PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); - -// allow access at the class level (always add after setting instance level) - if (Cppyy::IsStaticData(scope, idata)) - PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, (PyObject*)property); - -// cleanup - Py_DECREF(pname); - Py_DECREF(property); -} - -static inline -void AddScopeToParent(PyObject* parent, const std::string& name, PyObject* newscope) -{ - PyObject* pyname = CPyCppyy_PyText_InternFromString((char*)name.c_str()); - if (CPPScope_Check(parent)) PyType_Type.tp_setattro(parent, pyname, newscope); - else PyObject_SetAttr(parent, pyname, newscope); - Py_DECREF(pyname); -} - -static inline -PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { -// get an attribute without causing getattr lookups - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - return attr; - } - return nullptr; -} - -} // namespace CPyCppyy - - -//- public functions --------------------------------------------------------- -namespace CPyCppyy { - -static inline void sync_templates( - PyObject* pyclass, const std::string& mtCppName, const std::string& mtName) -{ - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - PyObject* pyname = CPyCppyy_PyText_FromString(const_cast(mtName.c_str())); - PyObject* attr = PyObject_GetItem(dct, pyname); - if (!attr) PyErr_Clear(); - Py_DECREF(dct); - if (!TemplateProxy_Check(attr)) { - TemplateProxy* pytmpl = TemplateProxy_New(mtCppName, mtName, pyclass); - if (CPPOverload_Check(attr)) pytmpl->MergeOverload((CPPOverload*)attr); - PyType_Type.tp_setattro(pyclass, pyname, (PyObject*)pytmpl); - Py_DECREF(pytmpl); - } - Py_XDECREF(attr); - Py_DECREF(pyname); -} - -static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, const unsigned int flags) -{ -// Collect methods and data for the given scope, and add them to the given python -// proxy object. - -// some properties that'll affect building the dictionary - bool isNamespace = Cppyy::IsNamespace(scope); - bool isAbstract = Cppyy::IsAbstract(scope); - bool hasConstructor = false; - Cppyy::TCppMethod_t potGetItem = (Cppyy::TCppMethod_t)0; - -// load all public methods and data members - typedef std::vector Callables_t; - typedef std::map CallableCache_t; - CallableCache_t cache; - -// bypass custom __getattr__ for efficiency - getattrofunc oldgetattro = Py_TYPE(pyclass)->tp_getattro; - Py_TYPE(pyclass)->tp_getattro = PyType_Type.tp_getattro; - -// functions in namespaces are properly found through lazy lookup, so do not -// create them until needed (the same is not true for data members) - const Cppyy::TCppIndex_t nMethods = isNamespace ? 0 : Cppyy::GetNumMethods(scope); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) { - Cppyy::TCppMethod_t method = Cppyy::GetMethod(scope, imeth); - - // do not expose non-public methods as the Cling wrappers as those won't compile - if (!Cppyy::IsPublicMethod(method)) - continue; - - // process the method based on its name - std::string mtCppName = Cppyy::GetMethodName(method); - - // special case trackers - bool setupSetItem = false; - bool isConstructor = Cppyy::IsConstructor(method); - bool isTemplate = isConstructor ? false : Cppyy::IsMethodTemplate(scope, imeth); - bool isStubbedOperator = false; - - // filter empty names (happens for namespaces, is bug?) - if (mtCppName == "") - continue; - - // filter C++ destructors - if (mtCppName[0] == '~') - continue; - - // translate operators - std::string mtName = Utility::MapOperatorName( - mtCppName, Cppyy::GetMethodNumArgs(method), &isStubbedOperator); - if (mtName.empty()) - continue; - - // operator[]/() returning a reference type will be used for __setitem__ - bool isCall = mtName == "__call__"; - if (isCall || mtName == "__getitem__") { - const std::string& qual_return = Cppyy::ResolveName(Cppyy::GetMethodResultType(method)); - const std::string& cpd = TypeManip::compound(qual_return); - if (!cpd.empty() && cpd[cpd.size()-1] == '&' && \ - qual_return.find("const", 0, 5) == std::string::npos) { - if (isCall && !potGetItem) potGetItem = method; - setupSetItem = true; // will add methods as overloads - } else if (isCall && 1 < Cppyy::GetMethodNumArgs(method)) { - // not a non-const by-ref return, thus better __getitem__ candidate; the - // requirement for multiple arguments is that there is otherwise no benefit - // over the use of normal __getitem__ (this allows multi-indexing arguments, - // which is clean in Python, but not allowed in C++) - potGetItem = method; - } - } - - // template members; handled by adding a dispatcher to the class - bool storeOnTemplate = - isTemplate ? true : (!isConstructor && Cppyy::ExistsMethodTemplate(scope, mtCppName)); - if (storeOnTemplate) { - sync_templates(pyclass, mtCppName, mtName); - // continue processing to actually add the method so that the proxy can find - // it on the class when called explicitly - } - - // construct the holder - PyCallable* pycall = nullptr; - if (Cppyy::IsStaticMethod(method)) // class method - pycall = new CPPClassMethod(scope, method); - else if (isNamespace) // free function - pycall = new CPPFunction(scope, method); - else if (isConstructor) { // ctor - mtName = "__init__"; - hasConstructor = true; - if (!isAbstract) { - if (flags & CPPScope::kIsMultiCross) { - pycall = new CPPMultiConstructor(scope, method); - } else - pycall = new CPPConstructor(scope, method); - } else - pycall = new CPPAbstractClassConstructor(scope, method); - } else if (isStubbedOperator) { - pycall = new CPPOperator(scope, method, mtName); - } else // member function - pycall = new CPPMethod(scope, method); - - if (storeOnTemplate) { - // template proxy was already created in sync_templates call above, so - // add only here, not to the cache of collected methods - PyObject* attr = PyObject_GetAttrString(pyclass, const_cast(mtName.c_str())); - if (isTemplate) ((TemplateProxy*)attr)->AdoptTemplate(pycall); - else ((TemplateProxy*)attr)->AdoptMethod(pycall); - Py_DECREF(attr); - - // for operator[]/() that returns by ref, also add __setitem__ - if (setupSetItem) { - TemplateProxy* pysi = (TemplateProxy*)GetAttrDirect(pyclass, PyStrings::gSetItem); - if (!TemplateProxy_Check(pysi)) { - CPPOverload* precursor = (CPPOverload_Check(pysi)) ? (CPPOverload*)pysi : nullptr; - if (pysi && !precursor) Py_DECREF(pysi); // something unknown, just drop it - pysi = TemplateProxy_New(mtCppName, "__setitem__", pyclass); - if (precursor) pysi->MergeOverload(precursor); - Py_XDECREF(precursor); - PyObject_SetAttrString(pyclass, const_cast("__setitem__"), (PyObject*)pysi); - } - if (isTemplate) pysi->AdoptTemplate(new CPPSetItem(scope, method)); - else pysi->AdoptMethod(new CPPSetItem(scope, method)); - Py_XDECREF(pysi); - } - - } else { - // lookup method dispatcher and store method - Callables_t& md = (*(cache.insert( - std::make_pair(mtName, Callables_t())).first)).second; - md.push_back(pycall); - - // special case for operator[]/() that returns by ref, use for getitem/call and setitem - if (setupSetItem) { - Callables_t& setitem = (*(cache.insert( - std::make_pair(std::string("__setitem__"), Callables_t())).first)).second; - setitem.push_back(new CPPSetItem(scope, method)); - } - } - } - -// add proxies for un-instantiated/non-overloaded templated methods - const Cppyy::TCppIndex_t nTemplMethods = isNamespace ? 0 : Cppyy::GetNumTemplatedMethods(scope); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nTemplMethods; ++imeth) { - const std::string mtCppName = Cppyy::GetTemplatedMethodName(scope, imeth); - // the number of arguments isn't known until instantiation and as far as C++ is concerned, all - // same-named operators are simply overloads; so will pre-emptively add both names if with and - // without arguments differ, letting the normal overload mechanism resolve on call - bool isConstructor = Cppyy::IsTemplatedConstructor(scope, imeth); - - // first add with no arguments - std::string mtName0 = isConstructor ? "__init__" : Utility::MapOperatorName(mtCppName, false); - sync_templates(pyclass, mtCppName, mtName0); - - // then add when taking arguments, if this method is different - if (!isConstructor) { - std::string mtName1 = Utility::MapOperatorName(mtCppName, true); - if (mtName0 != mtName1) - sync_templates(pyclass, mtCppName, mtName1); - } - } - -// add a pseudo-default ctor, if none defined - if (!hasConstructor) { - PyCallable* defctor = nullptr; - if (isAbstract) - defctor = new CPPAbstractClassConstructor(scope, (Cppyy::TCppMethod_t)0); - else if (isNamespace) - defctor = new CPPNamespaceConstructor(scope, (Cppyy::TCppMethod_t)0); - else if (!Cppyy::IsComplete(Cppyy::GetScopedFinalName(scope))) { - ((CPPScope*)pyclass)->fFlags |= CPPScope::kIsInComplete; - defctor = new CPPIncompleteClassConstructor(scope, (Cppyy::TCppMethod_t)0); - } else - defctor = new CPPAllPrivateClassConstructor(scope, (Cppyy::TCppMethod_t)0); - cache["__init__"].push_back(defctor); - } - -// map __call__ to __getitem__ if also mapped to __setitem__ - if (potGetItem) { - Callables_t& getitem = (*(cache.insert( - std::make_pair(std::string("__getitem__"), Callables_t())).first)).second; - getitem.push_back(new CPPGetItem(scope, potGetItem)); - } - -// add the methods to the class dictionary - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - for (CallableCache_t::iterator imd = cache.begin(); imd != cache.end(); ++imd) { - // in order to prevent removing templated editions of this method (which were set earlier, - // above, as a different proxy object), we'll check and add this method flagged as a generic - // one (to be picked up by the templated one as appropriate) if a template exists - PyObject* pyname = CPyCppyy_PyText_FromString(const_cast(imd->first.c_str())); - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(pyname); - if (TemplateProxy_Check(attr)) { - // template exists, supply it with the non-templated method overloads - for (auto cit : imd->second) - ((TemplateProxy*)attr)->AdoptMethod(cit); - } else { - if (!attr) PyErr_Clear(); - // normal case, add a new method - CPPOverload* method = CPPOverload_New(imd->first, imd->second); - PyObject* pymname = CPyCppyy_PyText_InternFromString(const_cast(method->GetName().c_str())); - PyType_Type.tp_setattro(pyclass, pymname, (PyObject*)method); - Py_DECREF(pymname); - Py_DECREF(method); - } - - Py_XDECREF(attr); // could have been found in base class or non-existent - } - Py_DECREF(dct); - - // collect data members (including enums) - const Cppyy::TCppIndex_t nDataMembers = Cppyy::GetNumDatamembers(scope); - for (Cppyy::TCppIndex_t idata = 0; idata < nDataMembers; ++idata) { - // allow only public members - if (!Cppyy::IsPublicData(scope, idata)) - continue; - - // enum datamembers (this in conjunction with previously collected enums above) - if (Cppyy::IsEnumData(scope, idata) && Cppyy::IsStaticData(scope, idata)) { - // some implementation-specific data members have no address: ignore them - if (!Cppyy::GetDatamemberOffset(scope, idata)) - continue; - - // two options: this is a static variable, or it is the enum value, the latter - // already exists, so check for it and move on if set - PyObject* eset = PyObject_GetAttrString(pyclass, - const_cast(Cppyy::GetDatamemberName(scope, idata).c_str())); - if (eset) { - Py_DECREF(eset); - continue; - } - - PyErr_Clear(); - - // it could still be that this is an anonymous enum, which is not in the list - // provided by the class - if (strstr(Cppyy::GetDatamemberType(scope, idata).c_str(), "(anonymous)") != 0 || - strstr(Cppyy::GetDatamemberType(scope, idata).c_str(), "(unnamed)") != 0) { - AddPropertyToClass(pyclass, scope, idata); - continue; - } - } - - // properties (aka public (static) data members) - AddPropertyToClass(pyclass, scope, idata); - } - -// restore custom __getattr__ - Py_TYPE(pyclass)->tp_getattro = oldgetattro; - -// all ok, done - return 0; -} - -//---------------------------------------------------------------------------- -static void CollectUniqueBases(Cppyy::TCppType_t klass, std::deque& uqb) -{ -// collect bases in acceptable mro order, while removing duplicates (this may -// break the overload resolution in esoteric cases, but otherwise the class can -// not be used at all, as CPython will refuse the mro). - size_t nbases = Cppyy::GetNumBases(klass); - - std::deque bids; - for (size_t ibase = 0; ibase < nbases; ++ibase) { - const std::string& name = Cppyy::GetBaseName(klass, ibase); - int decision = 2; - Cppyy::TCppType_t tp = Cppyy::GetScope(name); - if (!tp) continue; // means this base with not be available Python-side - for (size_t ibase2 = 0; ibase2 < uqb.size(); ++ibase2) { - if (uqb[ibase2] == name) { // not unique ... skip - decision = 0; - break; - } - - if (Cppyy::IsSubtype(tp, bids[ibase2])) { - // mro requirement: sub-type has to follow base - decision = 1; - break; - } - } - - if (decision == 1) { - uqb.push_front(name); - bids.push_front(tp); - } else if (decision == 2) { - uqb.push_back(name); - bids.push_back(tp); - } - // skipped if decision == 0 (not unique) - } -} - -static PyObject* BuildCppClassBases(Cppyy::TCppType_t klass) -{ -// Build a tuple of python proxy classes of all the bases of the given 'klass'. - std::deque uqb; - CollectUniqueBases(klass, uqb); - -// allocate a tuple for the base classes, special case for first base - size_t nbases = uqb.size(); - - PyObject* pybases = PyTuple_New(nbases ? nbases : 1); - if (!pybases) - return nullptr; - -// build all the bases - if (nbases == 0) { - Py_INCREF((PyObject*)(void*)&CPPInstance_Type); - PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPInstance_Type); - } else { - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { - PyObject* pyclass = CreateScopeProxy(uqb[ibase]); - if (!pyclass) { - Py_DECREF(pybases); - return nullptr; - } - - PyTuple_SET_ITEM(pybases, ibase, pyclass); - } - - // special case, if true python types enter the hierarchy, make sure that - // the first base seen is still the CPPInstance_Type - if (!PyObject_IsSubclass(PyTuple_GET_ITEM(pybases, 0), (PyObject*)&CPPInstance_Type)) { - PyObject* newpybases = PyTuple_New(nbases+1); - Py_INCREF((PyObject*)(void*)&CPPInstance_Type); - PyTuple_SET_ITEM(newpybases, 0, (PyObject*)(void*)&CPPInstance_Type); - for (int ibase = 0; ibase < (int)nbases; ++ibase) { - PyObject* pyclass = PyTuple_GET_ITEM(pybases, ibase); - Py_INCREF(pyclass); - PyTuple_SET_ITEM(newpybases, ibase+1, pyclass); - } - Py_DECREF(pybases); - pybases = newpybases; - } - } - - return pybases; -} - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::GetScopeProxy(Cppyy::TCppScope_t scope) -{ -// Retrieve scope proxy from the known ones. - PyClassMap_t::iterator pci = gPyClasses.find(scope); - if (pci != gPyClasses.end()) { - PyObject* pyclass = CPyCppyy_GetWeakRef(pci->second); - if (pyclass) - return pyclass; - } - - return nullptr; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(Cppyy::TCppScope_t scope, const unsigned flags) -{ -// Convenience function with a lookup first through the known existing proxies. - PyObject* pyclass = GetScopeProxy(scope); - if (pyclass) - return pyclass; - - return CreateScopeProxy(Cppyy::GetScopedFinalName(scope), nullptr, flags); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(PyObject*, PyObject* args) -{ -// Build a python shadow class for the named C++ class. - std::string cname = CPyCppyy_PyText_AsString(PyTuple_GetItem(args, 0)); - if (PyErr_Occurred()) - return nullptr; - - return CreateScopeProxy(cname); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, const unsigned flags) -{ -// Build a python shadow class for the named C++ class or namespace. - -// determine complete scope name, if a python parent has been given - std::string scName = ""; - if (parent) { - if (CPPScope_Check(parent)) - scName = Cppyy::GetScopedFinalName(((CPPScope*)parent)->fCppType); - else { - PyObject* parname = PyObject_GetAttr(parent, PyStrings::gName); - if (!parname) { - PyErr_Format(PyExc_SystemError, "given scope has no name for %s", name.c_str()); - return nullptr; - } - - // should be a string - scName = CPyCppyy_PyText_AsString(parname); - Py_DECREF(parname); - if (PyErr_Occurred()) - return nullptr; - } - - // accept this parent scope and use it's name for prefixing - Py_INCREF(parent); - } - -// retrieve C++ class (this verifies name, and is therefore done first) - const std::string& lookup = scName.empty() ? name : (scName+"::"+name); - Cppyy::TCppScope_t klass = Cppyy::GetScope(lookup); - - if (!(bool)klass && Cppyy::IsTemplate(lookup)) { - // a "naked" templated class is requested: return callable proxy for instantiations - PyObject* pytcl = PyObject_GetAttr(gThisModule, PyStrings::gTemplate); - PyObject* pytemplate = PyObject_CallFunction( - pytcl, const_cast("s"), const_cast(lookup.c_str())); - Py_DECREF(pytcl); - - // cache the result - AddScopeToParent(parent ? parent : gThisModule, name, pytemplate); - - // done, next step should be a call into this template - Py_XDECREF(parent); - return pytemplate; - } - - if (!(bool)klass) { - // could be an enum, which are treated separately in CPPScope (TODO: maybe they - // should be handled here instead anyway??) - if (Cppyy::IsEnum(lookup)) - return nullptr; - - // final possibility is a typedef of a builtin; these are mapped on the python side - std::string resolved = Cppyy::ResolveName(lookup); - if (gPyTypeMap) { - PyObject* tc = PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed - if (tc && PyCallable_Check(tc)) { - PyObject* nt = PyObject_CallFunction(tc, (char*)"ss", name.c_str(), scName.c_str()); - if (nt) { - if (parent) { - AddScopeToParent(parent, name, nt); - Py_DECREF(parent); - } - return nt; - } - PyErr_Clear(); - } - } - - // all options have been exhausted: it doesn't exist as such - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", lookup.c_str()); - Py_XDECREF(parent); - return nullptr; - } - -// locate class by ID, if possible, to prevent parsing scopes/templates anew - PyObject* pyscope = GetScopeProxy(klass); - if (pyscope) { - if (parent) { - AddScopeToParent(parent, name, pyscope); - Py_DECREF(parent); - } - return pyscope; - } - -// now have a class ... get the actual, fully scoped class name, so that typedef'ed -// classes are created in the right place - const std::string& actual = Cppyy::GetScopedFinalName(klass); - if (actual != lookup) { - pyscope = CreateScopeProxy(actual); - if (!pyscope) PyErr_Clear(); - } - -// locate the parent, if necessary, for memoizing the class if not specified - std::string::size_type last = 0; - if (!parent) { - // TODO: move this to TypeManip, which already has something similar in - // the form of 'extract_namespace' - // need to deal with template parameters that can have scopes themselves - int tpl_open = 0; - for (std::string::size_type pos = 0; pos < name.size(); ++pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '<') - ++tpl_open; - else if (c == '>') - --tpl_open; - - // by only checking for "::" the last part (class name) is dropped - else if (tpl_open == 0 && \ - c == ':' && pos+1 < name.size() && name[ pos+1 ] == ':') { - // found a new scope part - const std::string& part = name.substr(last, pos-last); - - PyObject* next = PyObject_GetAttrString( - parent ? parent : gThisModule, const_cast(part.c_str())); - - if (!next) { // lookup failed, try to create it - PyErr_Clear(); - next = CreateScopeProxy(part, parent); - } - Py_XDECREF(parent); - - if (!next) // create failed, give up - return nullptr; - - // found scope part - parent = next; - - // done with part (note that pos is moved one ahead here) - last = pos+2; ++pos; - } - } - - if (parent && !CPPScope_Check(parent)) { - // Special case: parent found is not one of ours (it's e.g. a pure Python module), so - // continuing would fail badly. One final lookup, then out of here ... - std::string unscoped = name.substr(last, std::string::npos); - PyObject* ret = PyObject_GetAttrString(parent, unscoped.c_str()); - Py_DECREF(parent); - return ret; - } - } - -// use the module as a fake scope if no outer scope found - if (!parent) { - Py_INCREF(gThisModule); - parent = gThisModule; - } - -// if the scope was earlier found as actual, then we're done already, otherwise -// build a new scope proxy - if (!pyscope) { - // construct the base classes - PyObject* pybases = BuildCppClassBases(klass); - if (pybases != 0) { - // create a fresh Python class, given bases, name, and empty dictionary - pyscope = CreateNewCppProxyClass(klass, pybases); - Py_DECREF(pybases); - } - - // fill the dictionary, if successful - if (pyscope) { - if (BuildScopeProxyDict(klass, pyscope, flags)) { - // something failed in building the dictionary - Py_DECREF(pyscope); - pyscope = nullptr; - } - } - - // store a ref from cppyy scope id to new python class - if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { - gPyClasses[klass] = PyWeakref_NewRef(pyscope, nullptr); - - if (!(((CPPScope*)pyscope)->fFlags & CPPScope::kIsNamespace)) { - // add python-style features to classes only - if (!Pythonize(pyscope, Cppyy::GetScopedFinalName(klass))) { - Py_DECREF(pyscope); - pyscope = nullptr; - } - } else { - // add to sys.modules to allow importing from this namespace - PyObject* pyfullname = PyObject_GetAttr(pyscope, PyStrings::gModule); - CPyCppyy_PyText_AppendAndDel(&pyfullname, CPyCppyy_PyText_FromString(".")); - CPyCppyy_PyText_AppendAndDel(&pyfullname, PyObject_GetAttr(pyscope, PyStrings::gName)); - PyObject* modules = PySys_GetObject(const_cast("modules")); - if (modules && PyDict_Check(modules)) - PyDict_SetItem(modules, pyfullname, pyscope); - Py_DECREF(pyfullname); - } - } - } - -// store on parent if found/created and complete - if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) - AddScopeToParent(parent, name, pyscope); - Py_DECREF(parent); - -// all done - return pyscope; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyObject* parent) -{ -// To allow use of C++ exceptions in lieue of Python exceptions, they need to -// derive from BaseException, which can not mix with the normal CPPInstance and -// use of the meta-class. Instead, encapsulate them in a forwarding class that -// derives from Pythons Exception class - -// start with creation of CPPExcInstance type base classes - std::deque uqb; - CollectUniqueBases(((CPPScope*)pyscope)->fCppType, uqb); - size_t nbases = uqb.size(); - -// Support for multiple bases actually can not actually work as-is: the reason -// for deriving from BaseException is to guarantee the layout needed for storing -// traces. If some other base is std::exception (as e.g. boost::bad_any_cast) or -// also derives from std::exception, then there are two trace locations. OTOH, -// if the other class is a non-exception type, then the exception class does not -// need to derive from it because it can never be caught as that type forwarding -// to the proxy will work as expected, through, which is good enough). -// -// The code below restricts the hierarchy to a single base class, picking the -// "best" by filtering std::exception and non-exception bases. - - PyObject* pybases = PyTuple_New(1); - if (nbases == 0) { - Py_INCREF((PyObject*)(void*)&CPPExcInstance_Type); - PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPExcInstance_Type); - } else { - PyObject* best_base = nullptr; - - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { - // retrieve bases through their enclosing scope to guarantee treatment as - // exception classes and proper caching - const std::string& finalname = Cppyy::GetScopedFinalName(Cppyy::GetScope(uqb[ibase])); - const std::string& parentname = TypeManip::extract_namespace(finalname); - PyObject* base_parent = CreateScopeProxy(parentname); - if (!base_parent) { - Py_DECREF(pybases); - return nullptr; - } - - PyObject* excbase = PyObject_GetAttrString(base_parent, - parentname.empty() ? finalname.c_str() : finalname.substr(parentname.size()+2, std::string::npos).c_str()); - Py_DECREF(base_parent); - if (!excbase) { - Py_DECREF(pybases); - return nullptr; - } - - if (PyType_IsSubtype((PyTypeObject*)excbase, &CPPExcInstance_Type)) { - Py_XDECREF(best_base); - best_base = excbase; - if (finalname != "std::exception") - break; - } else { - // just skip: there will be at least one exception derived base class - Py_DECREF(excbase); - } - } - - PyTuple_SET_ITEM(pybases, 0, best_base); - } - - PyObject* args = Py_BuildValue((char*)"OO{}", pyname, pybases); - -// meta-class attributes (__cpp_name__, etc.) can not be resolved lazily so add -// them directly instead in case they are needed - PyObject* dct = PyTuple_GET_ITEM(args, 2); - PyDict_SetItem(dct, PyStrings::gUnderlying, pyscope); - PyDict_SetItem(dct, PyStrings::gName, PyObject_GetAttr(pyscope, PyStrings::gName)); - PyDict_SetItem(dct, PyStrings::gCppName, PyObject_GetAttr(pyscope, PyStrings::gCppName)); - PyDict_SetItem(dct, PyStrings::gModule, PyObject_GetAttr(pyscope, PyStrings::gModule)); - -// create the actual exception class - PyObject* exc_pyscope = PyType_Type.tp_new(&PyType_Type, args, nullptr); - Py_DECREF(args); - Py_DECREF(pybases); - -// cache the result for future lookups and return - PyType_Type.tp_setattro(parent, pyname, exc_pyscope); - return exc_pyscope; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address, - Cppyy::TCppType_t klass, const unsigned flags) -{ -// only known or knowable objects will be bound (null object is ok) - if (!klass) { - PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); - return nullptr; - } - -// retrieve python class - PyObject* pyclass = CreateScopeProxy(klass); - if (!pyclass) - return nullptr; // error has been set in CreateScopeProxy - - bool noReg = flags & (CPPInstance::kNoMemReg|CPPInstance::kNoWrapConv); - bool isRef = flags & CPPInstance::kIsReference; - void* r_address = isRef ? (address ? *(void**)address : nullptr) : address; - -// check whether the object to be bound is a smart pointer that needs embedding - PyObject* smart_type = (!(flags & CPPInstance::kNoWrapConv) && \ - (((CPPClass*)pyclass)->fFlags & CPPScope::kIsSmart)) ? pyclass : nullptr; - if (smart_type) { - Cppyy::TCppType_t underlying = ((CPPSmartClass*)smart_type)->fUnderlyingType; - - // Down-cast the underlying object to its actual (most derived) class, just - // as BindCppObject does for raw pointers. Two conditions must hold: - // * the reported actual class must really be a subtype of the declared one. - // Cppyy::GetActualClass() can return a *base* class (e.g. when the actual - // class has no dictionary of its own and inherits IsA() from a base with - // ClassDef, ROOT reports that base) -- such an up-cast must be rejected. - // * the cast must require no pointer adjustment, because the smart pointer's - // dereferencer always yields a pointer to the underlying (declared) class, - // so a non-zero offset can not be applied consistently on later member - // access (e.g. with multiple inheritance). - if (address && !isRef) { - void* deref = Cppyy::CallR( - ((CPPSmartClass*)smart_type)->fDereferencer, address, 0, nullptr); - if (deref) { - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(underlying, deref); - if (clActual && clActual != underlying && - Cppyy::IsSubtype(clActual, underlying) && - Cppyy::GetBaseOffset(clActual, underlying, deref, -1 /* down-cast */) == 0) - underlying = clActual; - } - } - - pyclass = CreateScopeProxy(underlying); - if (!pyclass) { - // simply restore and expose as the actual smart pointer class - pyclass = smart_type; - smart_type = nullptr; - } - } - -// TODO: make sure that a consistent address is used (may have to be done in BindCppObject) - if (address && !(flags & CPPInstance::kIsValue) /* always fresh */ && !noReg) { - PyObject* oldPyObject = MemoryRegulator::RetrievePyObject(r_address, pyclass); - - // embedded smart pointers are registered with the class of the underlying type, b/c - // there is no Python proxy for the smart pointer itself in that case; check whether - // the result found matches the smart-ness and if so, whether the smart pointer found - // is the same as the one requested (note: ptr-ptr requires old object to be a - // reference to enable re-use) - if (oldPyObject) { - CPPInstance* o_pyobj = ((CPPInstance*)oldPyObject); - - if ((bool)smart_type == o_pyobj->IsSmart() && \ - r_address == (smart_type ? o_pyobj->GetObjectRaw() : o_pyobj->GetObject()) && \ - (!(flags & CPPInstance::kIsPtrPtr) || (o_pyobj->fFlags & CPPInstance::kIsReference))) { - Py_DECREF(pyclass); - return oldPyObject; - } else { // reference or naked v.s. smart pointer mismatch - Py_DECREF(oldPyObject); - } - } - } - -// instantiate an object of this class - PyObject* args = PyTuple_New(0); - CPPInstance* pyobj = - (CPPInstance*)((PyTypeObject*)pyclass)->tp_new((PyTypeObject*)pyclass, args, nullptr); - Py_DECREF(args); - -// bind, register and return if successful - if (pyobj != 0) { // fill proxy value? - unsigned objflags = flags & \ - (CPPInstance::kIsReference | CPPInstance::kIsPtrPtr | CPPInstance::kIsValue | CPPInstance::kIsOwner | CPPInstance::kIsActual); - pyobj->Set(address, (CPPInstance::EFlags)objflags); - - if (smart_type) - pyobj->SetSmart(smart_type); - - // do not register null pointers, references (regulated objects can be referenced, but - // referenced objects can not be regulated), or direct usage of smart pointers or iterators - if (address && !isRef && !noReg) - MemoryRegulator::RegisterPyObject(pyobj, smart_type ? pyobj->GetObjectRaw() : pyobj->GetObject()); - } - -// successful completion; wrap exception options to make them raiseable, normal return otherwise - if (((CPPClass*)pyclass)->fFlags & CPPScope::kIsException) { - PyObject* exc_obj = CPPExcInstance_Type.tp_new(&CPPExcInstance_Type, nullptr, nullptr); - ((CPPExcInstance*)exc_obj)->fCppInstance = (PyObject*)pyobj; - Py_DECREF(pyclass); - return exc_obj; - } - - Py_DECREF(pyclass); - return (PyObject*)pyobj; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObject(Cppyy::TCppObject_t address, - Cppyy::TCppType_t klass, const unsigned flags) -{ -// if the object is a null pointer, return a typed one (as needed for overloading) - if (!address) - return BindCppObjectNoCast(address, klass, flags); - -// only known or knowable objects will be bound - if (!klass) { - PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); - return nullptr; - } - - bool isRef = flags & CPPInstance::kIsReference; - -// downcast to real class for object returns, unless pinned -// TODO: should the memory regulator for klass be searched first, so that if -// successful, no down-casting is attempted? -// TODO: optimize for final classes - unsigned new_flags = flags; - if (gPinnedTypes.empty() || gPinnedTypes.find(klass) == gPinnedTypes.end()) { - if (!isRef) { - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, address); - - if (clActual) { - if (clActual != klass) { - intptr_t offset = Cppyy::GetBaseOffset( - clActual, klass, address, -1 /* down-cast */, true /* report errors */); - if (offset != -1) { // may fail if clActual not fully defined - address = (void*)((intptr_t)address + offset); - klass = clActual; - } - } - new_flags |= CPPInstance::kIsActual; - } - } else { - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, *(void**)address); - klass = clActual; - } - } - -// actual binding (returned object may be zero w/ a python exception set) - return BindCppObjectNoCast(address, klass, new_flags); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims) -{ -// TODO: this function exists for symmetry; need to figure out if it's useful - return TupleOfInstances_New(address, klass, dims); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h deleted file mode 100644 index cfa4360294d08..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CPYCPPYY_PROXYWRAPPERS_H -#define CPYCPPYY_PROXYWRAPPERS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -// construct a Python shadow class for the named C++ class -PyObject* GetScopeProxy(Cppyy::TCppScope_t); -PyObject* CreateScopeProxy(Cppyy::TCppScope_t, const unsigned flags = 0); -PyObject* CreateScopeProxy(PyObject*, PyObject* args); -PyObject* CreateScopeProxy( - const std::string& scope_name, PyObject* parent = nullptr, const unsigned flags = 0); - -// C++ exceptions form a special case b/c they have to derive from BaseException -PyObject* CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyObject* parent); - -// bind a C++ object into a Python proxy object (flags are CPPInstance::Default) -PyObject* BindCppObjectNoCast(Cppyy::TCppObject_t object, - Cppyy::TCppType_t klass, const unsigned flags = 0); -PyObject* BindCppObject(Cppyy::TCppObject_t object, - Cppyy::TCppType_t klass, const unsigned flags = 0); -PyObject* BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PROXYWRAPPERS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h deleted file mode 100644 index d2a072c5b389e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CPYCPPYY_PYCALLABLE_H -#define CPYCPPYY_PYCALLABLE_H - -#include - -// Bindings -#include "CPyCppyy/Reflex.h" -#include "CallContext.h" - - -namespace CPyCppyy { - -class CPPInstance; - -class PyCallable { -public: - virtual ~PyCallable() {} - -public: - virtual PyObject* GetSignature(bool show_formalargs = true) = 0; - virtual PyObject* GetSignatureNames() = 0; - virtual PyObject* GetSignatureTypes() = 0; - virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; - virtual PyObject* GetTypeName() { return GetPrototype(false); } - virtual PyObject* GetDocString() { return GetPrototype(); } - virtual PyObject* Reflex(Cppyy::Reflex::RequestId_t request, - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL) { - PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", request, format); - return nullptr; - }; - - virtual int GetPriority() = 0; - virtual bool IsGreedy() = 0; - - virtual int GetMaxArgs() = 0; - virtual PyObject* GetCoVarNames() = 0; - virtual PyObject* GetArgDefault(int /* iarg */, bool silent=true) = 0; - virtual bool IsConst() { return false; } - - virtual PyObject* GetScopeProxy() = 0; - virtual Cppyy::TCppFuncAddr_t GetFunctionAddress() = 0; - - virtual PyCallable* Clone() = 0; - - virtual int GetArgMatchScore(PyObject* /* args_tuple */) { return INT_MAX; } - -public: - virtual PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) = 0; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYCALLABLE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx deleted file mode 100644 index f478ab33f98f4..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx +++ /dev/null @@ -1,141 +0,0 @@ -// Standard -#include - -// Bindings -#include "CPyCppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/PyException.h" -#undef CPYCPPYY_INTERNAL - - -//______________________________________________________________________________ -// C++ exception for throwing python exceptions -// ============================================ -// Purpose: A C++ exception class for throwing python exceptions -// through C++ code. -// Created: Apr, 2004, Scott Snyder, from the version in D0's python_util. -// -// Note: Don't be tempted to declare the virtual functions defined here -// as inline. -// If you do, you may not be able to properly throw these -// exceptions across shared libraries. - - -//- constructors/destructor -------------------------------------------------- -CPyCppyy::PyException::PyException() -{ -#ifdef WITH_THREAD - PyGILState_STATE state = PyGILState_Ensure(); -#endif - -#if PY_VERSION_HEX >= 0x030c0000 - PyObject *pyvalue = PyErr_GetRaisedException(); - PyObject *pytype = pyvalue ? (PyObject *)Py_TYPE(pyvalue) : nullptr; - PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; -#else - PyObject* pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - PyObject* traceback = pytrace; // to keep the original unchanged - Py_XINCREF(traceback); -#endif - - if (pytype && pyvalue) { - const char* tname = PyExceptionClass_Name(pytype); - if (tname) { - char* dot = strrchr((char*)tname, '.'); - if (dot) tname = dot+1; - fMsg += tname; - fMsg += ": "; - } - - PyObject* msg = PyObject_Str(pyvalue); - if (msg) { - fMsg += CPyCppyy_PyText_AsString(msg); - Py_DECREF(msg); - } - } - - std::string locName; - std::string locFile; - int locLine = 0; - - while (traceback && traceback != Py_None) { - PyObject* frame = PyObject_GetAttrString(traceback, "tb_frame"); - PyObject* code = PyObject_GetAttrString(frame, "f_code"); - Py_DECREF(frame); - - PyObject* filename = PyObject_GetAttrString(code, "co_filename"); - Py_DECREF(code); - - PyObject* filenameStr = PyObject_Str(filename); - locFile = CPyCppyy_PyText_AsString(filenameStr); - Py_DECREF(filenameStr); - Py_DECREF(filename); - - PyObject* name = PyObject_GetAttrString(code, "co_name"); - PyObject* nameStr = PyObject_Str(name); - locName = CPyCppyy_PyText_AsString(nameStr); - Py_DECREF(nameStr); - Py_DECREF(name); - - PyObject* lineno = PyObject_GetAttrString(traceback, "tb_lineno"); - locLine = PyLong_AsLong(lineno); - Py_DECREF(lineno); - - if (locFile == "") { // these are not that useful, skipping - PyObject* nextTraceback = PyObject_GetAttrString(traceback, "tb_next"); - Py_DECREF(traceback); - traceback = nextTraceback; - continue; - } - - break; - } - - Py_XDECREF(traceback); - -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(pyvalue); -#else - PyErr_Restore(pytype, pyvalue, pytrace); -#endif - - if (fMsg.empty()) - fMsg = "python exception"; - - if (!locFile.empty()) { - - // only keeping the filename, not the full path - locFile = locFile.substr(locFile.find_last_of("/\\") + 1); - - fMsg += " (at " + locFile + ":" + std::to_string(locLine); - - if (locName != "") - fMsg += " in " + locName; - - fMsg += ")"; - } - -#ifdef WITH_THREAD - PyGILState_Release(state); -#endif -} - -CPyCppyy::PyException::~PyException() noexcept -{ -// destructor -} - - -//- public members ----------------------------------------------------------- -const char* CPyCppyy::PyException::what() const noexcept -{ -// Return reason for throwing this exception: a python exception was raised. - return fMsg.c_str(); -} - -void CPyCppyy::PyException::clear() const noexcept -{ -// clear Python error, to allow full error handling C++ side - PyErr_Clear(); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx deleted file mode 100644 index 2cea5722b9967..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx +++ /dev/null @@ -1,245 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "PyStrings.h" - - -//- data _____________________________________________________________________ -PyObject* CPyCppyy::PyStrings::gAssign = nullptr; -PyObject* CPyCppyy::PyStrings::gBases = nullptr; -PyObject* CPyCppyy::PyStrings::gBase = nullptr; -PyObject* CPyCppyy::PyStrings::gContains = nullptr; -PyObject* CPyCppyy::PyStrings::gCopy = nullptr; -PyObject* CPyCppyy::PyStrings::gCppBool = nullptr; -PyObject* CPyCppyy::PyStrings::gCppName = nullptr; -PyObject* CPyCppyy::PyStrings::gAnnotations = nullptr; -PyObject* CPyCppyy::PyStrings::gCastCpp = nullptr; -PyObject* CPyCppyy::PyStrings::gCType = nullptr; -PyObject* CPyCppyy::PyStrings::gDeref = nullptr; -PyObject* CPyCppyy::PyStrings::gPreInc = nullptr; -PyObject* CPyCppyy::PyStrings::gPostInc = nullptr; -PyObject* CPyCppyy::PyStrings::gDict = nullptr; -PyObject* CPyCppyy::PyStrings::gEmptyString = nullptr; -PyObject* CPyCppyy::PyStrings::gEq = nullptr; -PyObject* CPyCppyy::PyStrings::gFollow = nullptr; -PyObject *CPyCppyy::PyStrings::gHasValue = nullptr; -PyObject* CPyCppyy::PyStrings::gGetItem = nullptr; -PyObject* CPyCppyy::PyStrings::gGetNoCheck = nullptr; -PyObject* CPyCppyy::PyStrings::gSetItem = nullptr; -PyObject* CPyCppyy::PyStrings::gInit = nullptr; -PyObject* CPyCppyy::PyStrings::gIter = nullptr; -PyObject* CPyCppyy::PyStrings::gLen = nullptr; -PyObject* CPyCppyy::PyStrings::gLifeLine = nullptr; -PyObject* CPyCppyy::PyStrings::gModule = nullptr; -PyObject* CPyCppyy::PyStrings::gMRO = nullptr; -PyObject* CPyCppyy::PyStrings::gName = nullptr; -PyObject* CPyCppyy::PyStrings::gNe = nullptr; -PyObject* CPyCppyy::PyStrings::gRepr = nullptr; -PyObject* CPyCppyy::PyStrings::gCppRepr = nullptr; -PyObject* CPyCppyy::PyStrings::gStr = nullptr; -PyObject* CPyCppyy::PyStrings::gCppStr = nullptr; -PyObject* CPyCppyy::PyStrings::gTypeCode = nullptr; -PyObject* CPyCppyy::PyStrings::gCTypesType = nullptr; - -PyObject* CPyCppyy::PyStrings::gUnderlying = nullptr; -PyObject* CPyCppyy::PyStrings::gRealInit = nullptr; - -PyObject* CPyCppyy::PyStrings::gAdd = nullptr; -PyObject* CPyCppyy::PyStrings::gSub = nullptr; -PyObject* CPyCppyy::PyStrings::gMul = nullptr; -PyObject* CPyCppyy::PyStrings::gDiv = nullptr; - -PyObject* CPyCppyy::PyStrings::gLShift = nullptr; -PyObject* CPyCppyy::PyStrings::gLShiftC = nullptr; - -PyObject* CPyCppyy::PyStrings::gAt = nullptr; -PyObject* CPyCppyy::PyStrings::gBegin = nullptr; -PyObject* CPyCppyy::PyStrings::gEnd = nullptr; -PyObject* CPyCppyy::PyStrings::gFirst = nullptr; -PyObject* CPyCppyy::PyStrings::gSecond = nullptr; -PyObject* CPyCppyy::PyStrings::gSize = nullptr; -PyObject* CPyCppyy::PyStrings::gTemplate = nullptr; -PyObject* CPyCppyy::PyStrings::gVectorAt = nullptr; -PyObject* CPyCppyy::PyStrings::gInsert = nullptr; -PyObject* CPyCppyy::PyStrings::gValueType = nullptr; -PyObject* CPyCppyy::PyStrings::gValueSize = nullptr; - -PyObject* CPyCppyy::PyStrings::gCppReal = nullptr; -PyObject* CPyCppyy::PyStrings::gCppImag = nullptr; - -PyObject* CPyCppyy::PyStrings::gThisModule = nullptr; - -PyObject* CPyCppyy::PyStrings::gDispInit = nullptr; -PyObject* CPyCppyy::PyStrings::gDispGet = nullptr; - -PyObject* CPyCppyy::PyStrings::gExPythonize = nullptr; -PyObject* CPyCppyy::PyStrings::gPythonize = nullptr; - -PyObject* CPyCppyy::PyStrings::gArray = nullptr; -PyObject* CPyCppyy::PyStrings::gDType = nullptr; -PyObject* CPyCppyy::PyStrings::gFromBuffer = nullptr; - - -//----------------------------------------------------------------------------- -#define CPPYY_INITIALIZE_STRING(var, str) \ - if (!(PyStrings::var = CPyCppyy_PyText_InternFromString((char*)#str))) \ - return false - -bool CPyCppyy::CreatePyStrings() { -// Build cache of commonly used python strings (the cache is python intern, so -// all strings are shared python-wide, not just in cppyy). - CPPYY_INITIALIZE_STRING(gAssign, __assign__); - CPPYY_INITIALIZE_STRING(gBases, __bases__); - CPPYY_INITIALIZE_STRING(gBase, __base__); - CPPYY_INITIALIZE_STRING(gContains, contains); - CPPYY_INITIALIZE_STRING(gCopy, copy); -#if PY_VERSION_HEX < 0x03000000 - CPPYY_INITIALIZE_STRING(gCppBool, __cpp_nonzero__); -#else - CPPYY_INITIALIZE_STRING(gCppBool, __cpp_bool__); -#endif - CPPYY_INITIALIZE_STRING(gCppName, __cpp_name__); - CPPYY_INITIALIZE_STRING(gAnnotations, __annotations__); - CPPYY_INITIALIZE_STRING(gCastCpp, __cast_cpp__); - CPPYY_INITIALIZE_STRING(gCType, __ctype__); - CPPYY_INITIALIZE_STRING(gDeref, __deref__); - CPPYY_INITIALIZE_STRING(gPreInc, __preinc__); - CPPYY_INITIALIZE_STRING(gPostInc, __postinc__); - CPPYY_INITIALIZE_STRING(gDict, __dict__); - if (!(PyStrings::gEmptyString = CPyCppyy_PyText_FromString((char*)""))) - return false; - CPPYY_INITIALIZE_STRING(gEq, __eq__); - CPPYY_INITIALIZE_STRING(gFollow, __follow__); - CPPYY_INITIALIZE_STRING(gHasValue, has_value); - CPPYY_INITIALIZE_STRING(gGetItem, __getitem__); - CPPYY_INITIALIZE_STRING(gGetNoCheck, _getitem__unchecked); - CPPYY_INITIALIZE_STRING(gSetItem, __setitem__); - CPPYY_INITIALIZE_STRING(gInit, __init__); - CPPYY_INITIALIZE_STRING(gIter, __iter__); - CPPYY_INITIALIZE_STRING(gLen, __len__); - CPPYY_INITIALIZE_STRING(gLifeLine, __lifeline); - CPPYY_INITIALIZE_STRING(gModule, __module__); - CPPYY_INITIALIZE_STRING(gMRO, __mro__); - CPPYY_INITIALIZE_STRING(gName, __name__); - CPPYY_INITIALIZE_STRING(gNe, __ne__); - CPPYY_INITIALIZE_STRING(gRepr, __repr__); - CPPYY_INITIALIZE_STRING(gCppRepr, __cpp_repr); - CPPYY_INITIALIZE_STRING(gStr, __str__); - CPPYY_INITIALIZE_STRING(gCppStr, __cpp_str); - CPPYY_INITIALIZE_STRING(gTypeCode, typecode); - CPPYY_INITIALIZE_STRING(gCTypesType, _type_); - - CPPYY_INITIALIZE_STRING(gUnderlying, __underlying); - CPPYY_INITIALIZE_STRING(gRealInit, __real_init); - - CPPYY_INITIALIZE_STRING(gAdd, __add__); - CPPYY_INITIALIZE_STRING(gSub, __sub__); - CPPYY_INITIALIZE_STRING(gMul, __mul__); - CPPYY_INITIALIZE_STRING(gDiv, CPPYY__div__); - - CPPYY_INITIALIZE_STRING(gLShift, __lshift__); - CPPYY_INITIALIZE_STRING(gLShiftC, __lshiftc__); - - CPPYY_INITIALIZE_STRING(gAt, at); - CPPYY_INITIALIZE_STRING(gBegin, begin); - CPPYY_INITIALIZE_STRING(gEnd, end); - CPPYY_INITIALIZE_STRING(gFirst, first); - CPPYY_INITIALIZE_STRING(gSecond, second); - CPPYY_INITIALIZE_STRING(gSize, size); - CPPYY_INITIALIZE_STRING(gTemplate, Template); - CPPYY_INITIALIZE_STRING(gVectorAt, _vector__at); - CPPYY_INITIALIZE_STRING(gInsert, insert); - CPPYY_INITIALIZE_STRING(gValueType, value_type); - CPPYY_INITIALIZE_STRING(gValueSize, value_size); - - CPPYY_INITIALIZE_STRING(gCppReal, __cpp_real); - CPPYY_INITIALIZE_STRING(gCppImag, __cpp_imag); - - CPPYY_INITIALIZE_STRING(gThisModule, cppyy); - - CPPYY_INITIALIZE_STRING(gDispInit, _init_dispatchptr); - CPPYY_INITIALIZE_STRING(gDispGet, _get_dispatch); - - CPPYY_INITIALIZE_STRING(gExPythonize, __cppyy_explicit_pythonize__); - CPPYY_INITIALIZE_STRING(gPythonize, __cppyy_pythonize__); - - CPPYY_INITIALIZE_STRING(gArray, __array__); - CPPYY_INITIALIZE_STRING(gDType, dtype); - CPPYY_INITIALIZE_STRING(gFromBuffer, frombuffer); - - return true; -} - - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::DestroyPyStrings() { -// Remove all cached python strings. - Py_DECREF(PyStrings::gBases); PyStrings::gBases = nullptr; - Py_DECREF(PyStrings::gBase); PyStrings::gBase = nullptr; - Py_DECREF(PyStrings::gContains); PyStrings::gContains = nullptr; - Py_DECREF(PyStrings::gCopy); PyStrings::gCopy = nullptr; - Py_DECREF(PyStrings::gCppBool); PyStrings::gCppBool = nullptr; - Py_DECREF(PyStrings::gCppName); PyStrings::gCppName = nullptr; - Py_DECREF(PyStrings::gAnnotations); PyStrings::gAnnotations = nullptr; - Py_DECREF(PyStrings::gCType); PyStrings::gCType = nullptr; - Py_DECREF(PyStrings::gDeref); PyStrings::gDeref = nullptr; - Py_DECREF(PyStrings::gPreInc); PyStrings::gPreInc = nullptr; - Py_DECREF(PyStrings::gPostInc); PyStrings::gPostInc = nullptr; - Py_DECREF(PyStrings::gDict); PyStrings::gDict = nullptr; - Py_DECREF(PyStrings::gEmptyString); PyStrings::gEmptyString = nullptr; - Py_DECREF(PyStrings::gEq); PyStrings::gEq = nullptr; - Py_DECREF(PyStrings::gFollow); PyStrings::gFollow = nullptr; - Py_DECREF(PyStrings::gHasValue); PyStrings::gHasValue = nullptr; - Py_DECREF(PyStrings::gGetItem); PyStrings::gGetItem = nullptr; - Py_DECREF(PyStrings::gGetNoCheck); PyStrings::gGetNoCheck = nullptr; - Py_DECREF(PyStrings::gSetItem); PyStrings::gSetItem = nullptr; - Py_DECREF(PyStrings::gInit); PyStrings::gInit = nullptr; - Py_DECREF(PyStrings::gIter); PyStrings::gIter = nullptr; - Py_DECREF(PyStrings::gLen); PyStrings::gLen = nullptr; - Py_DECREF(PyStrings::gLifeLine); PyStrings::gLifeLine = nullptr; - Py_DECREF(PyStrings::gModule); PyStrings::gModule = nullptr; - Py_DECREF(PyStrings::gMRO); PyStrings::gMRO = nullptr; - Py_DECREF(PyStrings::gName); PyStrings::gName = nullptr; - Py_DECREF(PyStrings::gNe); PyStrings::gNe = nullptr; - Py_DECREF(PyStrings::gTypeCode); PyStrings::gTypeCode = nullptr; - Py_DECREF(PyStrings::gCTypesType); PyStrings::gCTypesType = nullptr; - - Py_DECREF(PyStrings::gUnderlying); PyStrings::gUnderlying = nullptr; - Py_DECREF(PyStrings::gRealInit); PyStrings::gRealInit = nullptr; - - Py_DECREF(PyStrings::gAdd); PyStrings::gAdd = nullptr; - Py_DECREF(PyStrings::gSub); PyStrings::gSub = nullptr; - Py_DECREF(PyStrings::gMul); PyStrings::gMul = nullptr; - Py_DECREF(PyStrings::gDiv); PyStrings::gDiv = nullptr; - - Py_DECREF(PyStrings::gLShift); PyStrings::gLShift = nullptr; - Py_DECREF(PyStrings::gLShiftC); PyStrings::gLShiftC = nullptr; - - Py_DECREF(PyStrings::gAt); PyStrings::gAt = nullptr; - Py_DECREF(PyStrings::gBegin); PyStrings::gBegin = nullptr; - Py_DECREF(PyStrings::gEnd); PyStrings::gEnd = nullptr; - Py_DECREF(PyStrings::gFirst); PyStrings::gFirst = nullptr; - Py_DECREF(PyStrings::gSecond); PyStrings::gSecond = nullptr; - Py_DECREF(PyStrings::gSize); PyStrings::gSize = nullptr; - Py_DECREF(PyStrings::gTemplate); PyStrings::gTemplate = nullptr; - Py_DECREF(PyStrings::gVectorAt); PyStrings::gVectorAt = nullptr; - Py_DECREF(PyStrings::gInsert); PyStrings::gInsert = nullptr; - Py_DECREF(PyStrings::gValueType); PyStrings::gValueType = nullptr; - Py_DECREF(PyStrings::gValueSize); PyStrings::gValueSize = nullptr; - - Py_DECREF(PyStrings::gCppReal); PyStrings::gCppReal = nullptr; - Py_DECREF(PyStrings::gCppImag); PyStrings::gCppImag = nullptr; - - Py_DECREF(PyStrings::gThisModule); PyStrings::gThisModule = nullptr; - - Py_DECREF(PyStrings::gDispInit); PyStrings::gDispInit = nullptr; - Py_DECREF(PyStrings::gDispGet); PyStrings::gDispGet = nullptr; - - Py_DECREF(PyStrings::gExPythonize); PyStrings::gExPythonize = nullptr; - Py_DECREF(PyStrings::gPythonize); PyStrings::gPythonize = nullptr; - - Py_DECREF(PyStrings::gArray); PyStrings::gArray = nullptr; - Py_DECREF(PyStrings::gDType); PyStrings::gDType = nullptr; - Py_DECREF(PyStrings::gFromBuffer); PyStrings::gFromBuffer = nullptr; - - Py_RETURN_NONE; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h deleted file mode 100644 index 77c454d507f34..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef CPYCPPYY_PYSTRINGS_H -#define CPYCPPYY_PYSTRINGS_H - -namespace CPyCppyy { - -// python strings kept for performance reasons - -namespace PyStrings { - - extern PyObject* gAssign; - extern PyObject* gBases; - extern PyObject* gBase; - extern PyObject* gContains; - extern PyObject* gCopy; - extern PyObject* gCppBool; - extern PyObject* gCppName; - extern PyObject* gAnnotations; - extern PyObject* gCastCpp; - extern PyObject* gCType; - extern PyObject* gDeref; - extern PyObject* gPreInc; - extern PyObject* gPostInc; - extern PyObject* gDict; - extern PyObject* gEmptyString; - extern PyObject* gEq; - extern PyObject* gFollow; - extern PyObject *gHasValue; - extern PyObject* gGetItem; - extern PyObject* gGetNoCheck; - extern PyObject* gSetItem; - extern PyObject* gInit; - extern PyObject* gIter; - extern PyObject* gLen; - extern PyObject* gLifeLine; - extern PyObject* gModule; - extern PyObject* gMRO; - extern PyObject* gName; - extern PyObject* gNe; - extern PyObject* gRepr; - extern PyObject* gCppRepr; - extern PyObject* gStr; - extern PyObject* gCppStr; - extern PyObject* gTypeCode; - extern PyObject* gCTypesType; - - extern PyObject* gUnderlying; - extern PyObject* gRealInit; - - extern PyObject* gAdd; - extern PyObject* gSub; - extern PyObject* gMul; - extern PyObject* gDiv; - - extern PyObject* gLShift; - extern PyObject* gLShiftC; - - extern PyObject* gAt; - extern PyObject* gBegin; - extern PyObject* gEnd; - extern PyObject* gFirst; - extern PyObject* gSecond; - extern PyObject* gSize; - extern PyObject* gTemplate; - extern PyObject* gVectorAt; - extern PyObject* gInsert; - extern PyObject* gValueType; - extern PyObject* gValueSize; - - extern PyObject* gCppReal; - extern PyObject* gCppImag; - - extern PyObject* gThisModule; - - extern PyObject* gDispInit; - extern PyObject* gDispGet; - - extern PyObject* gExPythonize; - extern PyObject* gPythonize; - - extern PyObject* gArray; - extern PyObject* gDType; - extern PyObject* gFromBuffer; - -} // namespace PyStrings - -bool CreatePyStrings(); -PyObject* DestroyPyStrings(); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYSTRINGS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx deleted file mode 100644 index 90fc2b941eb88..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ /dev/null @@ -1,2233 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Pythonize.h" -#include "Converters.h" -#include "CPPInstance.h" -#include "CPPFunction.h" -#include "CPPOverload.h" -#include "CustomPyTypes.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include - - -//- data and local helpers --------------------------------------------------- -namespace CPyCppyy { - extern PyObject* gThisModule; - std::map> &pythonizations(); -} - -namespace { - -// for convenience -using namespace CPyCppyy; - -//----------------------------------------------------------------------------- -bool HasAttrDirect(PyObject* pyclass, PyObject* pyname, bool mustBeCPyCppyy = false) { -// prevents calls to Py_TYPE(pyclass)->tp_getattr, which is unnecessary for our -// purposes here and could tickle problems w/ spurious lookups into ROOT meta - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - if (attr) { - bool ret = !mustBeCPyCppyy || CPPOverload_Check(attr); - Py_DECREF(attr); - return ret; - } - } - PyErr_Clear(); - return false; -} - -bool HasAttrInMRO(PyObject *pyclass, PyObject *pyname) -{ - // Check base classes in the MRO (skipping the class itself) for a CPyCppyy overload. - PyObject *mro = ((PyTypeObject *)pyclass)->tp_mro; - if (mro && PyTuple_Check(mro)) { - for (Py_ssize_t i = 1; i < PyTuple_GET_SIZE(mro); ++i) { - if (HasAttrDirect(PyTuple_GET_ITEM(mro, i), pyname, /*mustBeCPyCppyy=*/true)) - return true; - } - } - return false; -} - -PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { -// get an attribute without causing getattr lookups - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - return attr; - } - return nullptr; -} - -//----------------------------------------------------------------------------- -inline bool IsTemplatedSTLClass(const std::string& name, const std::string& klass) { -// Scan the name of the class and determine whether it is a template instantiation. - auto pos = name.find(klass); - return (pos == 0 || pos == 5) && name.find("::", name.rfind(">")) == std::string::npos; -} - -// to prevent compiler warnings about const char* -> char* -inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth) -{ -// Helper; call method with signature: obj->meth(). - Py_INCREF(obj); - PyObject* result = PyObject_CallMethod(obj, const_cast(meth), const_cast("")); - Py_DECREF(obj); - return result; -} - -//----------------------------------------------------------------------------- -inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, PyObject* arg1) -{ -// Helper; call method with signature: obj->meth(arg1). - Py_INCREF(obj); - PyObject* result = PyObject_CallMethod( - obj, const_cast(meth), const_cast("O"), arg1); - Py_DECREF(obj); - return result; -} - -//----------------------------------------------------------------------------- -PyObject* PyStyleIndex(PyObject* self, PyObject* index) -{ -// Helper; converts python index into straight C index. - Py_ssize_t idx = PyInt_AsSsize_t(index); - if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) - return nullptr; - - Py_ssize_t size = PySequence_Size(self); - if (idx >= size || (idx < 0 && idx < -size)) { - PyErr_SetString(PyExc_IndexError, "index out of range"); - return nullptr; - } - - PyObject* pyindex = nullptr; - if (idx >= 0) { - Py_INCREF(index); - pyindex = index; - } else - pyindex = PyLong_FromSsize_t(size+idx); - - return pyindex; -} - -//----------------------------------------------------------------------------- -inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, Py_ssize_t& stop, Py_ssize_t& step) -{ -// Helper; modify slice range to match the container. - if ((step > 0 && stop <= start) || (step < 0 && start <= stop)) - return false; - - if (start < 0) start = 0; - if (start >= nlen) start = nlen-1; - if (step >= nlen) step = nlen; - - stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1); - return true; -} - -//----------------------------------------------------------------------------- -inline PyObject* CallSelfIndex(CPPInstance* self, PyObject* idx, PyObject* pymeth) -{ -// Helper; call method with signature: meth(pyindex). - Py_INCREF((PyObject*)self); - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) { - Py_DECREF((PyObject*)self); - return nullptr; - } - - PyObject* result = PyObject_CallMethodOneArg((PyObject*)self, pymeth, pyindex); - Py_DECREF(pyindex); - Py_DECREF((PyObject*)self); - return result; -} - -//- "smart pointer" behavior --------------------------------------------------- -static PyObject* smart_follow(PyObject* self, PyObject* name, PyObject* method) -{ - if (!CPyCppyy_PyText_Check(name)) - PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string"); - - PyObject* pyptr = PyObject_CallMethodNoArgs(self, method); - if (!pyptr) - return nullptr; - -// prevent a potential infinite loop - if (Py_TYPE(pyptr) == Py_TYPE(self)) { - PyObject* val1 = PyObject_Str((PyObject*)Py_TYPE(self)); - PyObject* val2 = PyObject_Str(name); - PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'", - CPyCppyy_PyText_AsString(val1), CPyCppyy_PyText_AsString(val2)); - Py_DECREF(val2); - Py_DECREF(val1); - - Py_DECREF(pyptr); - return nullptr; - } - - PyObject* result = PyObject_GetAttr(pyptr, name); - Py_DECREF(pyptr); - return result; -} - -PyObject* DeRefGetAttr(PyObject* self, PyObject* name) -{ -// Follow operator*() if present (available in python as __deref__), so that -// smart pointers behave as expected. - if (name == PyStrings::gTypeCode || name == PyStrings::gCTypesType) { - // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice, - // whereas as-is, they can accidentally dereference the result of end() on some STL - // containers. Obviously, this is a dumb hack that should be resolved more fundamentally. - PyErr_SetString(PyExc_AttributeError, CPyCppyy_PyText_AsString(name)); - return nullptr; - } - - - return smart_follow(self, name, PyStrings::gDeref); -} - -//----------------------------------------------------------------------------- -PyObject *ValueGetAttr(PyObject *self, PyObject *name) -{ - // std::optional and std::expected require has_value() before operator*(). - PyObject *has_value = PyObject_CallMethodNoArgs(self, PyStrings::gHasValue); - if (!has_value) - return nullptr; - - const int contains_value = PyObject_IsTrue(has_value); - Py_DECREF(has_value); - if (contains_value < 0) - return nullptr; - if (!contains_value) { - PyErr_SetObject(PyExc_AttributeError, name); - return nullptr; - } - - return DeRefGetAttr(self, name); -} - -//----------------------------------------------------------------------------- -PyObject* FollowGetAttr(PyObject* self, PyObject* name) -{ -// Follow operator->() if present (available in python as __follow__), so that -// smart pointers behave as expected. - return smart_follow(self, name, PyStrings::gFollow); -} - -//- pointer checking bool converter ------------------------------------------- -PyObject* NullCheckBool(PyObject* self) -{ - if (!CPPInstance_Check(self)) { - PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); - return nullptr; - } - - if (!((CPPInstance*)self)->GetObject()) - Py_RETURN_FALSE; - - return PyObject_CallMethodNoArgs(self, PyStrings::gCppBool); -} - -//- vector behavior as primitives ---------------------------------------------- -#if PY_VERSION_HEX < 0x03040000 -#define PyObject_LengthHint _PyObject_LengthHint -#endif - -// TODO: can probably use the below getters in the InitializerListConverter -struct ItemGetter { - ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); } - virtual ~ItemGetter() { Py_DECREF(fPyObject); } - virtual Py_ssize_t size() = 0; - virtual PyObject* get() = 0; - PyObject* fPyObject; -}; - -struct CountedItemGetter : public ItemGetter { - CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {} - Py_ssize_t fCur; -}; - -struct TupleItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); } - PyObject* get() override { - if (fCur < PyTuple_GET_SIZE(fPyObject)) { - PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++); - Py_INCREF(item); - return item; - } - PyErr_SetString(PyExc_StopIteration, "end of tuple"); - return nullptr; - } -}; - -struct ListItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); } - PyObject* get() override { - if (fCur < PyList_GET_SIZE(fPyObject)) { - PyObject* item = PyList_GET_ITEM(fPyObject, fCur++); - Py_INCREF(item); - return item; - } - PyErr_SetString(PyExc_StopIteration, "end of list"); - return nullptr; - } -}; - -struct SequenceItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { - Py_ssize_t sz = PySequence_Size(fPyObject); - if (sz < 0) { - PyErr_Clear(); - return PyObject_LengthHint(fPyObject, 8); - } - return sz; - } - PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); } -}; - -struct IterItemGetter : public ItemGetter { - using ItemGetter::ItemGetter; - Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); } - PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); } -}; - -static ItemGetter* GetGetter(PyObject* args) -{ -// Create an ItemGetter to loop over the iterable argument, if any. - ItemGetter* getter = nullptr; - - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* fi = PyTuple_GET_ITEM(args, 0); - if (CPyCppyy_PyText_Check(fi) || PyBytes_Check(fi)) - return nullptr; // do not accept string to fill std::vector - - // TODO: this only tests for new-style buffers, which is too strict, but a - // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the - // main use case is numpy, which offers the new interface) - if (PyObject_CheckBuffer(fi)) - return nullptr; - - if (PyTuple_CheckExact(fi)) - getter = new TupleItemGetter(fi); - else if (PyList_CheckExact(fi)) - getter = new ListItemGetter(fi); - else if (PySequence_Check(fi)) - getter = new SequenceItemGetter(fi); - else { - PyObject* iter = PyObject_GetIter(fi); - if (iter) { - getter = new IterItemGetter{iter}; - Py_DECREF(iter); - } - else PyErr_Clear(); - } - } - - return getter; -} - -namespace { - -void compileSpanHelpers() -{ - static bool compiled = false; - - if (compiled) - return; - - compiled = true; - - auto code = R"( -namespace __cppyy_internal { - -template -struct ptr_iterator { - T *cur; - T *end; - - ptr_iterator(T *c, T *e) : cur(c), end(e) {} - - T &operator*() const { return *cur; } - ptr_iterator &operator++() - { - ++cur; - return *this; - } - bool operator==(const ptr_iterator &other) const { return cur == other.cur; } - bool operator!=(const ptr_iterator &other) const { return !(*this == other); } -}; - -template -ptr_iterator make_iter(T *begin, T *end) -{ - return {begin, end}; -} - -} // namespace __cppyy_internal - -// Note: for const span, T is const-qualified here -template -auto __cppyy_internal_begin(T &s) noexcept -{ - return __cppyy_internal::make_iter(s.data(), s.data() + s.size()); -} - -// Note: for const span, T is const-qualified here -template -auto __cppyy_internal_end(T &s) noexcept -{ - // end iterator = begin iterator with cur == end - return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size()); -} - )"; - Cppyy::Compile(code, /*silent*/ true); -} - -PyObject *spanBegin() -{ - static PyObject *pyFunc = nullptr; - if (!pyFunc) { - compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); - pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin"); - if (!pyFunc) { - PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " - "'__cppyy_internal_begin' for std::span pythonization"); - } - } - return pyFunc; -} - -PyObject *spanEnd() -{ - static PyObject *pyFunc = nullptr; - if (!pyFunc) { - compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); - pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end"); - if (!pyFunc) { - PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " - "'__cppyy_internal_end' for std::span pythonization"); - } - } - return pyFunc; -} - -} // namespace - -static PyObject *SpanBegin(PyObject *self, PyObject *) -{ - auto begin = spanBegin(); - if (!begin) - return nullptr; - return PyObject_CallOneArg(begin, self); -} - -static PyObject *SpanEnd(PyObject *self, PyObject *) -{ - auto end = spanEnd(); - if (!end) - return nullptr; - return PyObject_CallOneArg(end, self); -} - -static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) -{ - Py_ssize_t sz = getter->size(); - if (sz < 0) - return false; - -// reserve memory as applicable - if (0 < sz) { - PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz); - Py_DECREF(res); - } else // i.e. sz == 0, so empty container: done - return true; - - bool fill_ok = true; - -// two main options: a list of lists (or tuples), or a list of objects; the former -// are emplace_back'ed, the latter push_back'ed - PyObject* fi = PySequence_GetItem(PyTuple_GET_ITEM(args, 0), 0); - if (!fi) PyErr_Clear(); - if (fi && (PyTuple_CheckExact(fi) || PyList_CheckExact(fi))) { - // use emplace_back to construct the vector entries one by one - PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back"); - PyObject* vtype = GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueType); - bool value_is_vector = false; - if (vtype && CPyCppyy_PyText_Check(vtype)) { - // if the value_type is a vector, then allow for initialization from sequences - if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos) - value_is_vector = true; - } else - PyErr_Clear(); - Py_XDECREF(vtype); - - if (eb_call) { - PyObject* eb_args; - for (int i = 0; /* until break */; ++i) { - PyObject* item = getter->get(); - if (item) { - if (value_is_vector && PySequence_Check(item)) { - eb_args = PyTuple_New(1); - PyTuple_SET_ITEM(eb_args, 0, item); - } else if (PyTuple_CheckExact(item)) { - eb_args = item; - } else if (PyList_CheckExact(item)) { - Py_ssize_t isz = PyList_GET_SIZE(item); - eb_args = PyTuple_New(isz); - for (Py_ssize_t j = 0; j < isz; ++j) { - PyObject* iarg = PyList_GET_ITEM(item, j); - Py_INCREF(iarg); - PyTuple_SET_ITEM(eb_args, j, iarg); - } - Py_DECREF(item); - } else { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i); - fill_ok = false; - break; - } - PyObject* ebres = PyObject_CallObject(eb_call, eb_args); - Py_DECREF(eb_args); - if (!ebres) { - fill_ok = false; - break; - } - Py_DECREF(ebres); - } else { - if (PyErr_Occurred()) { - if (!(PyErr_ExceptionMatches(PyExc_IndexError) || - PyErr_ExceptionMatches(PyExc_StopIteration))) - fill_ok = false; - else { PyErr_Clear(); } - } - break; - } - } - Py_DECREF(eb_call); - } - } else { - // use push_back to add the vector entries one by one - PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back"); - if (pb_call) { - for (;;) { - PyObject* item = getter->get(); - if (item) { - PyObject* pbres = PyObject_CallFunctionObjArgs(pb_call, item, nullptr); - Py_DECREF(item); - if (!pbres) { - fill_ok = false; - break; - } - Py_DECREF(pbres); - } else { - if (PyErr_Occurred()) { - if (!(PyErr_ExceptionMatches(PyExc_IndexError) || - PyErr_ExceptionMatches(PyExc_StopIteration))) - fill_ok = false; - else { PyErr_Clear(); } - } - break; - } - } - Py_DECREF(pb_call); - } - } - Py_XDECREF(fi); - - return fill_ok; -} - -PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python) - ItemGetter* getter = GetGetter(args); - - if (getter) { - bool fill_ok = FillVector(self, args, getter); - delete getter; - - if (!fill_ok) - return nullptr; - - Py_INCREF(self); - return self; - } - -// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over -// a buffer here is slow, so use insert() instead - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* fi = PyTuple_GET_ITEM(args, 0); - if (PyObject_CheckBuffer(fi) && !(CPyCppyy_PyText_Check(fi) || PyBytes_Check(fi))) { - PyObject* vend = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (vend) { - // when __iadd__ is overriden, the operation does not end with - // calling the __iadd__ method, but also assigns the result to the - // lhs of the iadd. For example, performing vec += arr, Python - // first calls our override, and then does vec = vec.iadd(arr). - PyObject *it = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, vend, fi, nullptr); - Py_DECREF(vend); - - if (!it) - return nullptr; - - Py_DECREF(it); - // Assign the result of the __iadd__ override to the std::vector - Py_INCREF(self); - return self; - } - } - } - - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_TypeError, "argument is not iterable"); - return nullptr; // error already set -} - - -PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized vector constructor to allow construction from containers; allowing -// such construction from initializer_list instead would possible, but can be -// error-prone. This use case is common enough for std::vector to implement it -// directly, except for arrays (which can be passed wholesale) and strings (which -// won't convert properly as they'll be seen as buffers) - - ItemGetter* getter = GetGetter(args); - - if (getter) { - // construct an empty vector, then back-fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) { - delete getter; - return nullptr; - } - - bool fill_ok = FillVector(self, args, getter); - delete getter; - - if (!fill_ok) { - Py_DECREF(result); - return nullptr; - } - - return result; - } - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - -//--------------------------------------------------------------------------- -PyObject* VectorData(PyObject* self, PyObject*) -{ - PyObject* pydata = CallPyObjMethod(self, "__real_data"); - if (!LowLevelView_Check(pydata) && !CPPInstance_Check(pydata)) - return pydata; - - PyObject* pylen = PyObject_CallMethodNoArgs(self, PyStrings::gSize); - if (!pylen) { - PyErr_Clear(); - return pydata; - } - - long clen = PyInt_AsLong(pylen); - Py_DECREF(pylen); - - if (CPPInstance_Check(pydata)) { - ((CPPInstance*)pydata)->CastToArray(clen); - return pydata; - } - - ((LowLevelView*)pydata)->resize((size_t)clen); - return pydata; -} - - -// This function implements __array__, added to std::vector python proxies and causes -// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize) -// The recursive nature of this function, passes each subarray (pydata) to the next call and only -// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the -// first 1D array to be returned. See https://github.com/root-project/root/issues/17729 -// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added. -#if 0 -//--------------------------------------------------------------------------- -PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) -{ - PyObject* pydata = VectorData(self, nullptr); - PyObject* arrcall = PyObject_GetAttr(pydata, PyStrings::gArray); - PyObject* newarr = PyObject_Call(arrcall, args, kwargs); - Py_DECREF(arrcall); - Py_DECREF(pydata); - return newarr; -} -#endif - -//----------------------------------------------------------------------------- -static PyObject* vector_iter(PyObject* v) { - vectoriterobject* vi = PyObject_GC_New(vectoriterobject, &VectorIter_Type); - if (!vi) return nullptr; - - vi->ii_container = v; - -// tell the iterator code to set a life line if this container is a temporary - vi->vi_flags = vectoriterobject::kDefault; -#if PY_VERSION_HEX >= 0x030e0000 - if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#else - if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#endif - vi->vi_flags = vectoriterobject::kNeedLifeLine; - - Py_INCREF(v); - - PyObject* pyvalue_type = PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueType); - if (pyvalue_type) { - PyObject* pyvalue_size = GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); - if (pyvalue_size) { - vi->vi_stride = PyLong_AsLong(pyvalue_size); - Py_DECREF(pyvalue_size); - } else { - PyErr_Clear(); - vi->vi_stride = 0; - } - - if (CPyCppyy_PyText_Check(pyvalue_type)) { - std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type); - value_type = Cppyy::ResolveName(value_type); - vi->vi_klass = Cppyy::GetScope(value_type); - if (!vi->vi_klass) { - // look for a special case of pointer to a class type (which is a builtin, but it - // is more useful to treat it polymorphically by allowing auto-downcasts) - const std::string& clean_type = TypeManip::clean_type(value_type, false, false); - Cppyy::TCppScope_t c = Cppyy::GetScope(clean_type); - if (c && TypeManip::compound(value_type) == "*") { - vi->vi_klass = c; - vi->vi_flags = vectoriterobject::kIsPolymorphic; - } - } - if (vi->vi_klass) { - vi->vi_converter = nullptr; - if (!vi->vi_flags) { - if (value_type.back() != '*') // meaning, object stored by-value - vi->vi_flags = vectoriterobject::kNeedLifeLine; - } - } else - vi->vi_converter = CPyCppyy::CreateConverter(value_type); - if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type); - - } else if (CPPScope_Check(pyvalue_type)) { - vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType; - vi->vi_converter = nullptr; - if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass); - if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine; - } - - PyObject* pydata = CallPyObjMethod(v, "__real_data"); - if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0) - vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr; - Py_XDECREF(pydata); - - } else { - PyErr_Clear(); - vi->vi_data = nullptr; - vi->vi_stride = 0; - vi->vi_converter = nullptr; - vi->vi_klass = 0; - vi->vi_flags = 0; - } - - Py_XDECREF(pyvalue_type); - - vi->ii_pos = 0; - vi->ii_len = PySequence_Size(v); - - PyObject_GC_Track(vi); - return (PyObject*)vi; -} - -PyObject* VectorGetItem(CPPInstance* self, PySliceObject* index) -{ -// Implement python's __getitem__ for std::vector<>s. - if (PySlice_Check(index)) { - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); - PyObject* nseq = PyObject_CallObject(pyclass, nullptr); - - Py_ssize_t start, stop, step; - PySlice_GetIndices((CPyCppyy_PySliceCast)index, PyObject_Length((PyObject*)self), &start, &stop, &step); - - const Py_ssize_t nlen = PySequence_Size((PyObject*)self); - if (!AdjustSlice(nlen, start, stop, step)) - return nseq; - - const Py_ssize_t sign = step < 0 ? -1 : 1; - for (Py_ssize_t i = start; i*sign < stop*sign; i += step) { - PyObject* pyidx = PyInt_FromSsize_t(i); - PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, PyStrings::gGetNoCheck, pyidx); - CallPyObjMethod(nseq, "push_back", item); - Py_DECREF(item); - Py_DECREF(pyidx); - } - - return nseq; - } - - return CallSelfIndex(self, (PyObject*)index, PyStrings::gGetNoCheck); -} - - -static Cppyy::TCppType_t sVectorBoolTypeID = (Cppyy::TCppType_t)0; - -PyObject* VectorBoolGetItem(CPPInstance* self, PyObject* idx) -{ -// std::vector is a special-case in C++, and its return type depends on -// the compiler: treat it special here as well - if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { - PyErr_Format(PyExc_TypeError, - "require object of type std::vector, but %s given", - Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str()); - return nullptr; - } - - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - if (PySlice_Check(idx)) { - PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); - PyObject* nseq = PyObject_CallObject(pyclass, nullptr); - - Py_ssize_t start, stop, step; - PySlice_GetIndices((CPyCppyy_PySliceCast)idx, PyObject_Length((PyObject*)self), &start, &stop, &step); - const Py_ssize_t nlen = PySequence_Size((PyObject*)self); - if (!AdjustSlice(nlen, start, stop, step)) - return nseq; - - const Py_ssize_t sign = step < 0 ? -1 : 1; - for (Py_ssize_t i = start; i*sign < stop*sign; i += step) { - PyObject* pyidx = PyInt_FromSsize_t(i); - PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, PyStrings::gGetItem, pyidx); - CallPyObjMethod(nseq, "push_back", item); - Py_DECREF(item); - Py_DECREF(pyidx); - } - - return nseq; - } - - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) - return nullptr; - - int index = (int)PyLong_AsLong(pyindex); - Py_DECREF(pyindex); - -// get hold of the actual std::vector (no cast, as vector is never a base) - std::vector* vb = (std::vector*)self->GetObject(); - -// finally, return the value - if (bool((*vb)[index])) - Py_RETURN_TRUE; - Py_RETURN_FALSE; -} - -PyObject* VectorBoolSetItem(CPPInstance* self, PyObject* args) -{ -// std::vector is a special-case in C++, and its return type depends on -// the compiler: treat it special here as well - if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { - PyErr_Format(PyExc_TypeError, - "require object of type std::vector, but %s given", - Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str()); - return nullptr; - } - - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - int bval = 0; PyObject* idx = nullptr; - if (!PyArg_ParseTuple(args, const_cast("Oi:__setitem__"), &idx, &bval)) - return nullptr; - - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) - return nullptr; - - int index = (int)PyLong_AsLong(pyindex); - Py_DECREF(pyindex); - -// get hold of the actual std::vector (no cast, as vector is never a base) - std::vector* vb = (std::vector*)self->GetObject(); - -// finally, set the value - (*vb)[index] = (bool)bval; - - Py_RETURN_NONE; -} - - -//- array behavior as primitives ---------------------------------------------- -PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// std::array is normally only constructed using aggregate initialization, which -// is a concept that does not exist in python, so use this custom constructor to -// to fill the array using setitem - - if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) { - // construct the empty array, then fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* items = PyTuple_GET_ITEM(args, 0); - Py_ssize_t fillsz = PySequence_Size(items); - if (PySequence_Size(self) != fillsz) { - PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected", - fillsz, PySequence_Size(self)); - Py_DECREF(result); - return nullptr; - } - - PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); - for (Py_ssize_t i = 0; i < fillsz; ++i) { - PyObject* item = PySequence_GetItem(items, i); - PyObject* index = PyInt_FromSsize_t(i); - PyObject* sires = PyObject_CallFunctionObjArgs(si_call, index, item, nullptr); - Py_DECREF(index); - Py_DECREF(item); - if (!sires) { - Py_DECREF(si_call); - Py_DECREF(result); - return nullptr; - } else - Py_DECREF(sires); - } - Py_DECREF(si_call); - - return result; - } else - PyErr_Clear(); - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - - -//- map behavior as primitives ------------------------------------------------ -static PyObject* MapFromPairs(PyObject* self, PyObject* pairs) -{ -// construct an empty map, then fill it with the key, value pairs - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); - for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) { - PyObject* pair = PySequence_GetItem(pairs, i); - PyObject* sires = nullptr; - if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) { - PyObject* key = PySequence_GetItem(pair, 0); - PyObject* value = PySequence_GetItem(pair, 1); - sires = PyObject_CallFunctionObjArgs(si_call, key, value, nullptr); - Py_DECREF(value); - Py_DECREF(key); - } - Py_DECREF(pair); - if (!sires) { - Py_DECREF(si_call); - Py_DECREF(result); - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)"); - return nullptr; - } else - Py_DECREF(sires); - } - Py_DECREF(si_call); - - return result; -} - -PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized map constructor to allow construction from mapping containers and -// from tuples of pairs ("initializer_list style"). - -// PyMapping_Check is not very discriminatory, as it basically only checks for the -// existence of __getitem__, hence the most common cases of tuple and list are -// dropped straight-of-the-bat (the PyMapping_Items call will fail on them). - if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \ - !(PyTuple_Check(PyTuple_GET_ITEM(args, 0)) || PyList_Check(PyTuple_GET_ITEM(args, 0)))) { - PyObject* assoc = PyTuple_GET_ITEM(args, 0); -#if PY_VERSION_HEX < 0x03000000 - // to prevent warning about literal string, expand macro - PyObject* items = PyObject_CallMethod(assoc, (char*)"items", nullptr); -#else - // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict - PyObject* items = PyMapping_Items(assoc); -#endif - if (items && PySequence_Check(items)) { - PyObject* result = MapFromPairs(self, items); - Py_DECREF(items); - return result; - } - - Py_XDECREF(items); - PyErr_Clear(); - - // okay to fall through as long as 'self' has not been created (is done in MapFromPairs) - } - -// tuple of pairs case (some mapping types are sequences) - if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) - return MapFromPairs(self, PyTuple_GET_ITEM(args, 0)); - -// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - -#if __cplusplus <= 202002L -PyObject* STLContainsWithFind(PyObject* self, PyObject* obj) -{ -// Implement python's __contains__ for std::map/std::set - PyObject* result = nullptr; - - PyObject* iter = CallPyObjMethod(self, "find", obj); - if (CPPInstance_Check(iter)) { - PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (CPPInstance_Check(end)) { - if (!PyObject_RichCompareBool(iter, end, Py_EQ)) { - Py_INCREF(Py_True); - result = Py_True; - } - } - Py_XDECREF(end); - } - Py_XDECREF(iter); - - if (!result) { - PyErr_Clear(); // e.g. wrong argument type, which should always lead to False - Py_INCREF(Py_False); - result = Py_False; - } - - return result; -} -#endif - - -//- set behavior as primitives ------------------------------------------------ -PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized set constructor to allow construction from Python sets. - if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) { - PyObject* pyset = PyTuple_GET_ITEM(args, 0); - - // construct an empty set, then fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* iter = PyObject_GetIter(pyset); - if (iter) { - PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert"); - - IterItemGetter getter{iter}; - Py_DECREF(iter); - - PyObject* item = getter.get(); - while (item) { - PyObject* insres = PyObject_CallFunctionObjArgs(ins_call, item, nullptr); - Py_DECREF(item); - if (!insres) { - Py_DECREF(ins_call); - Py_DECREF(result); - return nullptr; - } else - Py_DECREF(insres); - item = getter.get(); - } - Py_DECREF(ins_call); - } - - return result; - } - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - - -//- STL container iterator support -------------------------------------------- -static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash -static const ptrdiff_t PS_FLAG_ADDR = 11; // id. -static const ptrdiff_t PS_COLL_ADDR = 13; // id. - -PyObject* LLSequenceIter(PyObject* self) -{ -// Implement python's __iter__ for low level views used through STL-type begin()/end() - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); - - if (LowLevelView_Check(iter)) { - // builtin pointer iteration: can only succeed if a size is available - Py_ssize_t sz = PySequence_Size(self); - if (sz == -1) { - Py_DECREF(iter); - return nullptr; - } - PyObject* lliter = Py_TYPE(iter)->tp_iter(iter); - ((indexiterobject*)lliter)->ii_len = sz; - Py_DECREF(iter); - return lliter; - } - - if (iter) { - Py_DECREF(iter); - PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views"); - } - - return nullptr; -} - -PyObject* STLSequenceIter(PyObject* self) -{ -// Implement python's __iter__ for std::iterator<>s - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); - if (iter) { - PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (end) { - if (CPPInstance_Check(iter)) { - // use the data member cache to store extra state on the iterator object, - // without it being visible on the Python side - auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache(); - dmc.push_back(std::make_pair(PS_END_ADDR, end)); - - // set a flag, indicating first iteration (reset in __next__) - Py_INCREF(Py_False); - dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False)); - - // make sure the iterated over collection remains alive for the duration - Py_INCREF(self); - dmc.push_back(std::make_pair(PS_COLL_ADDR, self)); - } else { - // could store "end" on the object's dictionary anyway, but if end() returns - // a user-customized object, then its __next__ is probably custom, too - Py_DECREF(end); - } - } - } - return iter; -} - -//- generic iterator support over a sequence with operator[] and size --------- -//----------------------------------------------------------------------------- -static PyObject* index_iter(PyObject* c) { - indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); - if (!ii) return nullptr; - - Py_INCREF(c); - ii->ii_container = c; - ii->ii_pos = 0; - ii->ii_len = PySequence_Size(c); - - PyObject_GC_Track(ii); - return (PyObject*)ii; -} - - -//- safe indexing for STL-like vector w/o iterator dictionaries --------------- -/* replaced by indexiterobject iteration, but may still have some future use ... -PyObject* CheckedGetItem(PyObject* self, PyObject* obj) -{ -// Implement a generic python __getitem__ for STL-like classes that are missing the -// reflection info for their iterators. This is then used for iteration by means of -// consecutive indices, it such index is of integer type. - Py_ssize_t size = PySequence_Size(self); - Py_ssize_t idx = PyInt_AsSsize_t(obj); - if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) { - // argument conversion problem: let method itself resolve anew and report - PyErr_Clear(); - return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); - } - - bool inbounds = false; - if (idx < 0) idx += size; - if (0 <= idx && 0 <= size && idx < size) - inbounds = true; - - if (inbounds) - return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); - else - PyErr_SetString( PyExc_IndexError, "index out of range" ); - - return nullptr; -}*/ - - -//- pair as sequence to allow tuple unpacking -------------------------------- -PyObject* PairUnpack(PyObject* self, PyObject* pyindex) -{ -// For std::map<> iteration, unpack std::pair<>s into tuples for the loop. - long idx = PyLong_AsLong(pyindex); - if (idx == -1 && PyErr_Occurred()) - return nullptr; - - if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - if ((int)idx == 0) - return PyObject_GetAttr(self, PyStrings::gFirst); - else if ((int)idx == 1) - return PyObject_GetAttr(self, PyStrings::gSecond); - -// still here? Trigger stop iteration - PyErr_SetString(PyExc_IndexError, "out of bounds"); - return nullptr; -} - -//- simplistic len() functions ----------------------------------------------- -PyObject* ReturnTwo(CPPInstance*, PyObject*) { - return PyInt_FromLong(2); -} - - -//- shared/unique_ptr behavior ----------------------------------------------- -PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// since the shared/unique pointer will take ownership, we need to relinquish it - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) { - CPPInstance* cppinst = (CPPInstance*)PyTuple_GET_ITEM(args, 0); - if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns(); - } - return result; - } - return nullptr; -} - - -//- string behavior as primitives -------------------------------------------- -#if PY_VERSION_HEX >= 0x03000000 -// TODO: this is wrong, b/c it doesn't order -static int PyObject_Compare(PyObject* one, PyObject* other) { - return !PyObject_RichCompareBool(one, other, Py_EQ); -} -#endif -static inline -PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) { - if (native) - return PyBytes_FromStringAndSize(s.data(), s.size()); - return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size()); -} - -static inline -PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) { - PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); - if (pyobj && native) { - PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); - Py_DECREF(pyobj); - pyobj = pybytes; - } - return pyobj; -} - -#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \ -static inline \ -PyObject* name##StringGetData(PyObject* self, bool native=true) \ -{ \ - if (CPyCppyy::CPPInstance_Check(self)) { \ - type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ - if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \ - } \ - PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ - return nullptr; \ -} \ - \ -PyObject* name##StringStr(PyObject* self) \ -{ \ - PyObject* pyobj = name##StringGetData(self, false); \ - if (!pyobj) { \ - /* do a native conversion to make printing possible (debatable) */ \ - PyErr_Clear(); \ - PyObject* pybytes = name##StringGetData(self, true); \ - if (pybytes) { /* should not fail */ \ - pyobj = PyObject_Str(pybytes); \ - Py_DECREF(pybytes); \ - } \ - } \ - return pyobj; \ -} \ - \ -PyObject* name##StringBytes(PyObject* self) \ -{ \ - return name##StringGetData(self, true); \ -} \ - \ -PyObject* name##StringRepr(PyObject* self) \ -{ \ - PyObject* data = name##StringGetData(self, true); \ - if (data) { \ - PyObject* repr = PyObject_Repr(data); \ - Py_DECREF(data); \ - return repr; \ - } \ - return nullptr; \ -} \ - \ -PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - if (data) { \ - PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \ - Py_DECREF(data); \ - return result; \ - } \ - return nullptr; \ -} \ - \ -PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - if (data) { \ - PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \ - Py_DECREF(data); \ - return result; \ - } \ - return nullptr; \ -} - -// Only define STLStringCompare: -#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \ -CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \ -PyObject* name##StringCompare(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - int result = 0; \ - if (data) { \ - result = PyObject_Compare(data, obj); \ - Py_DECREF(data); \ - } \ - if (PyErr_Occurred()) \ - return nullptr; \ - return PyInt_FromLong(result); \ -} - -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) - -static inline std::string* GetSTLString(CPPInstance* self) { - if (!CPPInstance_Check(self)) { - PyErr_SetString(PyExc_TypeError, "std::string object expected"); - return nullptr; - } - - std::string* obj = (std::string*)self->GetObject(); - if (!obj) - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - - return obj; -} - -PyObject* STLStringDecode(CPPInstance* self, PyObject* args, PyObject* kwds) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr}; - const char* encoding = nullptr; const char* errors = nullptr; - if (!PyArg_ParseTupleAndKeywords(args, kwds, - const_cast("s|s"), keywords, &encoding, &errors)) - return nullptr; - - return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors); -} - -#if __cplusplus <= 202302L -PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - const char* needle = CPyCppyy_PyText_AsString(pyobj); - if (!needle) - return nullptr; - - if (obj->find(needle) != std::string::npos) { - Py_RETURN_TRUE; - } - - Py_RETURN_FALSE; -} -#endif - -PyObject* STLStringReplace(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - -// both str and std::string have a method "replace", but the Python version only -// accepts strings and takes no keyword arguments, whereas the C++ version has no -// overload that takes a string - - if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) { - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size()); - PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace"); - Py_DECREF(pystr); - PyObject* result = PyObject_CallObject(meth, args); - Py_DECREF(meth); - return result; - } - - PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace"); - if (cppreplace) { - PyObject* result = PyObject_Call(cppreplace, args, nullptr); - Py_DECREF(cppreplace); - return result; - } - - PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'"); - return nullptr; -} - -#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \ -PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \ -{ \ - std::string* obj = GetSTLString(self); \ - if (!obj) \ - return nullptr; \ - \ - PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\ - if (cppmeth) { \ - PyObject* result = PyObject_Call(cppmeth, args, nullptr); \ - Py_DECREF(cppmeth); \ - if (result) { \ - if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\ - Py_DECREF(result); \ - return PyInt_FromLong(-1); \ - } \ - return result; \ - } \ - PyErr_Clear(); \ - } \ - \ - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\ - PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \ - Py_DECREF(pystr); \ - PyObject* result = PyObject_CallObject(pymeth, args); \ - Py_DECREF(pymeth); \ - return result; \ -} - -// both str and std::string have method "find" and "rfin"; try the C++ version first -// and fall back on the Python one in case of failure -CPYCPPYY_STRING_FINDMETHOD( Find, __cpp_find, find) -CPYCPPYY_STRING_FINDMETHOD(RFind, __cpp_rfind, rfind) - -PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size()); - PyObject* attr = PyObject_GetAttr(pystr, attr_name); - Py_DECREF(pystr); - return attr; -} - - -#if 0 -PyObject* UTF8Repr(PyObject* self) -{ -// force C++ string types conversion to Python str per Python __repr__ requirements - PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppRepr); - if (!res || CPyCppyy_PyText_Check(res)) - return res; - PyObject* str_res = PyObject_Str(res); - Py_DECREF(res); - return str_res; -} - -PyObject* UTF8Str(PyObject* self) -{ -// force C++ string types conversion to Python str per Python __str__ requirements - PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppStr); - if (!res || CPyCppyy_PyText_Check(res)) - return res; - PyObject* str_res = PyObject_Str(res); - Py_DECREF(res); - return str_res; -} -#endif - -Py_hash_t STLStringHash(PyObject* self) -{ -// std::string objects hash to the same values as Python strings to allow -// matches in dictionaries etc. - PyObject* data = STLStringGetData(self, false); - Py_hash_t h = CPyCppyy_PyText_Type.tp_hash(data); - Py_DECREF(data); - return h; -} - - -//- string_view behavior as primitive ---------------------------------------- -PyObject* StringViewInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// if constructed from a Python unicode object, the constructor will convert it -// to a temporary byte string, which is likely to go out of scope too soon; so -// buffer it as needed - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject *strbuf = nullptr, *newArgs = nullptr; - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* arg0 = PyTuple_GET_ITEM(args, 0); - if (PyUnicode_Check(arg0)) { - // convert to the expected bytes array to control the temporary - strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict"); - newArgs = PyTuple_New(1); - Py_INCREF(strbuf); - PyTuple_SET_ITEM(newArgs, 0, strbuf); - } else if (PyBytes_Check(arg0)) { - // tie the life time of the provided string to the string_view - Py_INCREF(arg0); - strbuf = arg0; - } - } - - PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr); - - Py_XDECREF(newArgs); - Py_DECREF(realInit); - - // if construction was successful and a string buffer was used, add a - // life line to it from the string_view bound object - if (result && self && strbuf) - PyObject_SetAttr(self, PyStrings::gLifeLine, strbuf); - Py_XDECREF(strbuf); - - return result; - } - return nullptr; -} - - -//- STL iterator behavior ---------------------------------------------------- -PyObject* STLIterNext(PyObject* self) -{ -// Python iterator protocol __next__ for STL forward iterators. - bool mustIncrement = true; - PyObject* last = nullptr; - if (CPPInstance_Check(self)) { - auto& dmc = ((CPPInstance*)self)->GetDatamemberCache(); - for (auto& p: dmc) { - if (p.first == PS_END_ADDR) { - last = p.second; - Py_INCREF(last); - } else if (p.first == PS_FLAG_ADDR) { - mustIncrement = p.second == Py_True; - if (!mustIncrement) { - Py_DECREF(p.second); - Py_INCREF(Py_True); - p.second = Py_True; - } - } - } - } - - PyObject* next = nullptr; - if (last) { - // handle special case of empty container (i.e. self is end) - if (!PyObject_RichCompareBool(last, self, Py_EQ)) { - bool iter_valid = true; - if (mustIncrement) { - // prefer preinc, but allow post-inc; in both cases, it is "self" that has - // the updated state to dereference - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gPreInc); - if (!iter) { - PyErr_Clear(); - static PyObject* dummy = PyInt_FromLong(1l); - iter = PyObject_CallMethodOneArg(self, PyStrings::gPostInc, dummy); - } - iter_valid = iter && PyObject_RichCompareBool(last, self, Py_NE); - Py_XDECREF(iter); - } - - if (iter_valid) { - next = PyObject_CallMethodNoArgs(self, PyStrings::gDeref); - if (!next) PyErr_Clear(); - } - } - Py_DECREF(last); - } - - if (!next) PyErr_SetString(PyExc_StopIteration, ""); - return next; -} - - -//- STL complex behavior -------------------------------------------------- -#define COMPLEX_METH_GETSET(name, cppname) \ -static PyObject* name##ComplexGet(PyObject* self, void*) { \ - return PyObject_CallMethodNoArgs(self, cppname); \ -} \ -static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \ - PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \ - if (result) { \ - Py_DECREF(result); \ - return 0; \ - } \ - return -1; \ -} \ -PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr}; - -COMPLEX_METH_GETSET(real, PyStrings::gCppReal) -COMPLEX_METH_GETSET(imag, PyStrings::gCppImag) - -static PyObject* ComplexComplex(PyObject* self) { - PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); - if (!real) return nullptr; - double r = PyFloat_AsDouble(real); - Py_DECREF(real); - if (r == -1. && PyErr_Occurred()) - return nullptr; - - PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); - if (!imag) return nullptr; - double i = PyFloat_AsDouble(imag); - Py_DECREF(imag); - if (i == -1. && PyErr_Occurred()) - return nullptr; - - return PyComplex_FromDoubles(r, i); -} - -static PyObject* ComplexRepr(PyObject* self) { - PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); - if (!real) return nullptr; - double r = PyFloat_AsDouble(real); - Py_DECREF(real); - if (r == -1. && PyErr_Occurred()) - return nullptr; - - PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); - if (!imag) return nullptr; - double i = PyFloat_AsDouble(imag); - Py_DECREF(imag); - if (i == -1. && PyErr_Occurred()) - return nullptr; - - std::ostringstream s; - s << '(' << r << '+' << i << "j)"; - return CPyCppyy_PyText_FromString(s.str().c_str()); -} - -static PyObject* ComplexDRealGet(CPPInstance* self, void*) -{ - return PyFloat_FromDouble(((std::complex*)self->GetObject())->real()); -} - -static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*) -{ - double d = PyFloat_AsDouble(value); - if (d == -1.0 && PyErr_Occurred()) - return -1; - ((std::complex*)self->GetObject())->real(d); - return 0; -} - -PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr}; - - -static PyObject* ComplexDImagGet(CPPInstance* self, void*) -{ - return PyFloat_FromDouble(((std::complex*)self->GetObject())->imag()); -} - -static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*) -{ - double d = PyFloat_AsDouble(value); - if (d == -1.0 && PyErr_Occurred()) - return -1; - ((std::complex*)self->GetObject())->imag(d); - return 0; -} - -PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr}; - -static PyObject* ComplexDComplex(CPPInstance* self) -{ - double r = ((std::complex*)self->GetObject())->real(); - double i = ((std::complex*)self->GetObject())->imag(); - return PyComplex_FromDoubles(r, i); -} - - -} // unnamed namespace - - -//- public functions --------------------------------------------------------- -namespace CPyCppyy { - std::set gIteratorTypes; -} - -static inline -bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector& v) -{ - PyObject* args = PyTuple_New(2); - Py_INCREF(pyclass); PyTuple_SET_ITEM(args, 0, pyclass); - Py_INCREF(pyname); PyTuple_SET_ITEM(args, 1, pyname); - - bool pstatus = true; - for (auto pythonizor : v) { - PyObject* result = PyObject_CallObject(pythonizor, args); - if (!result) { - pstatus = false; // TODO: detail the error handling - break; - } - Py_DECREF(result); - } - Py_DECREF(args); - - return pstatus; -} - -bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) -{ -// Add pre-defined pythonizations (for STL and ROOT) to classes based on their -// signature and/or class name. - if (!pyclass) - return false; - - CPPScope* klass = (CPPScope*)pyclass; - -//- method name based pythonization ------------------------------------------ - -// for smart pointer style classes that are otherwise not known as such; would -// prefer operator-> as that returns a pointer (which is simpler since it never -// has to deal with ref-assignment), but operator* plays better with STL iters -// and algorithms -// optional and expected use operator* to access a contained value, rather than -// to provide pointer-like access to an object. - const bool has_value_semantics = IsTemplatedSTLClass(name, "optional") || IsTemplatedSTLClass(name, "expected"); - if (HasAttrDirect(pyclass, PyStrings::gDeref) && !Cppyy::IsSmartPtr(klass->fCppType)) { - if (has_value_semantics) - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)ValueGetAttr, METH_O); - else - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)DeRefGetAttr, METH_O); - } else if (HasAttrDirect(pyclass, PyStrings::gFollow) && !Cppyy::IsSmartPtr(klass->fCppType)) - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)FollowGetAttr, METH_O); - -// for pre-check of nullptr for boolean types - if (HasAttrDirect(pyclass, PyStrings::gCppBool)) { -#if PY_VERSION_HEX >= 0x03000000 - const char* pybool_name = "__bool__"; -#else - const char* pybool_name = "__nonzero__"; -#endif - Utility::AddToClass(pyclass, pybool_name, (PyCFunction)NullCheckBool, METH_NOARGS); - } - - // for STL containers, and user classes modeled after them. Guard the alias to - // __len__ by verifying that size() returns an integer type and the class has - // begin()/end() or operator[] (i.e. is container-like). This prevents bool() - // returning False for valid objects whose size() returns non-integer types like - // std::optional. Skip if size() has multiple overloads, as that - // indicates it is not the simple container-style size() one would map to __len__. - if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/true) || HasAttrInMRO(pyclass, PyStrings::gSize)) { - bool sizeIsInteger = false; - PyObject *pySizeMethod = PyObject_GetAttr(pyclass, PyStrings::gSize); - if (pySizeMethod && CPPOverload_Check(pySizeMethod)) { - auto *ol = (CPPOverload *)pySizeMethod; - if (ol->HasMethods() && ol->fMethodInfo->fMethods.size() == 1) { - PyObject *pyrestype = - ol->fMethodInfo->fMethods[0]->Reflex(Cppyy::Reflex::RETURN_TYPE, Cppyy::Reflex::AS_STRING); - if (pyrestype) { - sizeIsInteger = Cppyy::IsIntegerType(CPyCppyy_PyText_AsString(pyrestype)); - Py_DECREF(pyrestype); - } - } - } - Py_XDECREF(pySizeMethod); - - if (sizeIsInteger) { - bool hasIterators = (HasAttrDirect(pyclass, PyStrings::gBegin) || HasAttrInMRO(pyclass, PyStrings::gBegin)) && - (HasAttrDirect(pyclass, PyStrings::gEnd) || HasAttrInMRO(pyclass, PyStrings::gEnd)); - bool hasSubscript = HasAttrDirect(pyclass, PyStrings::gGetItem) || HasAttrInMRO(pyclass, PyStrings::gGetItem); - if (hasIterators || hasSubscript) { - Utility::AddToClass(pyclass, "__len__", "size"); - } - } - } - - if (HasAttrDirect(pyclass, PyStrings::gContains)) { - Utility::AddToClass(pyclass, "__contains__", "contains"); - } - - if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below - !((PyTypeObject*)pyclass)->tp_iter) { - if (HasAttrDirect(pyclass, PyStrings::gBegin) && HasAttrDirect(pyclass, PyStrings::gEnd)) { - // obtain the name of the return type - const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin"); - if (!v.empty()) { - // check return type; if not explicitly an iterator, add it to the "known" return - // types to add the "next" method on use - Cppyy::TCppMethod_t meth = Cppyy::GetMethod(klass->fCppType, v[0]); - const std::string& resname = Cppyy::GetMethodResultType(meth); - bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end(); - if (!isIterator && Cppyy::GetScope(resname)) { - if (resname.find("iterator") == std::string::npos) - gIteratorTypes.insert(resname); - isIterator = true; - } - - if (isIterator) { - // install iterator protocol a la STL - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)STLSequenceIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)STLSequenceIter, METH_NOARGS); - } else { - // still okay if this is some pointer type of builtin persuasion (general class - // won't work: the return type needs to understand the iterator protocol) - std::string resolved = Cppyy::ResolveName(resname); - if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) { - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)LLSequenceIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)LLSequenceIter, METH_NOARGS); - } - } - } - } - if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved - HasAttrDirect(pyclass, PyStrings::gGetItem) && PyObject_HasAttr(pyclass, PyStrings::gLen)) { - // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise - // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some - // cases but would mess up if operator[] is meant to implement an associative container. So, - // this has to be implemented as an iterator protocol. - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)index_iter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)index_iter, METH_NOARGS); - } - } - -// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows -// comparisons to None; if no operator is available, a hook is installed for lazy -// lookups in the global and/or class namespace - if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \ - Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) { - PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gEq); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); - klass->fOperators->fEq = cppol; - // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side - // override in the base class - static PyObject* top_eq = nullptr; - if (!top_eq) { - PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); - top_eq = PyObject_GetAttr(top_cls, PyStrings::gEq); - Py_DECREF(top_eq); // make it borrowed - Py_DECREF(top_cls); - } - PyObject_SetAttr(pyclass, PyStrings::gEq, top_eq); - } - - if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \ - Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) { - PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gNe); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); - klass->fOperators->fNe = cppol; - // re-insert the forwarding __ne__ (same reason as above for __eq__) - static PyObject* top_ne = nullptr; - if (!top_ne) { - PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); - top_ne = PyObject_GetAttr(top_cls, PyStrings::gNe); - Py_DECREF(top_ne); // make it borrowed - Py_DECREF(top_cls); - } - PyObject_SetAttr(pyclass, PyStrings::gNe, top_ne); - } - -#if 0 - if (HasAttrDirect(pyclass, PyStrings::gRepr, true)) { - // guarantee that the result of __repr__ is a Python string - Utility::AddToClass(pyclass, "__cpp_repr", "__repr__"); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)UTF8Repr, METH_NOARGS); - } - - if (HasAttrDirect(pyclass, PyStrings::gStr, true)) { - // guarantee that the result of __str__ is a Python string - Utility::AddToClass(pyclass, "__cpp_str", "__str__"); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)UTF8Str, METH_NOARGS); - } -#endif - - if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 && - name.compare(0, 6, "tuple<", 6) != 0) { - // create a pseudo-constructor to allow initializer-style object creation - Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType; - Cppyy::TCppIndex_t ndata = Cppyy::GetNumDatamembers(kls); - if (ndata) { - std::string rname = name; - TypeManip::cppscope_to_legalname(rname); - - std::ostringstream initdef; - initdef << "namespace __cppyy_internal {\n" - << "void init_" << rname << "(" << name << "** self"; - bool codegen_ok = true; - std::vector arg_types, arg_names, arg_defaults; - arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata); - for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) { - if (Cppyy::IsStaticData(kls, i) || !Cppyy::IsPublicData(kls, i)) - continue; - - const std::string& txt = Cppyy::GetDatamemberType(kls, i); - const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt); - const std::string& cpd = TypeManip::compound(res); - std::string res_clean = TypeManip::clean_type(res, false, true); - - if (res_clean == "internal_enum_type_t") - res_clean = txt; // restore (properly scoped name) - - if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) { - if (!cpd.empty()) arg_types.push_back(res_clean+cpd); - else arg_types.push_back("const "+res_clean+"&"); - arg_names.push_back(Cppyy::GetDatamemberName(kls, i)); - if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean)) - arg_defaults.push_back("0"); - else { - Cppyy::TCppScope_t klsid = Cppyy::GetScope(res_clean); - if (Cppyy::IsDefaultConstructable(klsid)) arg_defaults.push_back(res_clean+"{}"); - } - } else { - codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc? - break; - } - } - - if (codegen_ok && !arg_types.empty()) { - bool defaults_ok = arg_defaults.size() == arg_types.size(); - for (std::vector::size_type i = 0; i < arg_types.size(); ++i) { - initdef << ", " << arg_types[i] << " " << arg_names[i]; - if (defaults_ok) initdef << " = " << arg_defaults[i]; - } - initdef << ") {\n *self = new " << name << "{"; - for (std::vector::size_type i = 0; i < arg_names.size(); ++i) { - if (i != 0) initdef << ", "; - initdef << arg_names[i]; - } - initdef << "};\n} }"; - - if (Cppyy::Compile(initdef.str(), true /* silent */)) { - Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal"); - const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname); - if (mix.size()) { - if (!Utility::AddToClass(pyclass, "__init__", - new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0])))) - PyErr_Clear(); - } - } - } - } - } - - -//- class name based pythonization ------------------------------------------- - - if (IsTemplatedSTLClass(name, "span")) { - // libstdc++ (GCC >= 15) implements std::span::iterator using a private - // nested tag type, which makes the iterator non-instantiable by - // CallFunc-generated wrappers (the return type cannot be named without - // violating access rules). - // - // To preserve correct Python iteration semantics, we replace begin()/end() - // for std::span to return a custom pointer-based iterator instead. This - // avoids relying on std::span::iterator while still providing a real C++ - // iterator object that CPyCppyy can also wrap and expose via - // __iter__/__next__. - Utility::AddToClass(pyclass, "begin", (PyCFunction)SpanBegin, METH_NOARGS); - Utility::AddToClass(pyclass, "end", (PyCFunction)SpanEnd, METH_NOARGS); - } - - if (IsTemplatedSTLClass(name, "vector")) { - - // std::vector is a special case in C++ - if (!sVectorBoolTypeID) sVectorBoolTypeID = (Cppyy::TCppType_t)Cppyy::GetScope("std::vector"); - if (klass->fCppType == sVectorBoolTypeID) { - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorBoolGetItem, METH_O); - Utility::AddToClass(pyclass, "__setitem__", (PyCFunction)VectorBoolSetItem); - } else { - // constructor that takes python collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)VectorInit, METH_VARARGS | METH_KEYWORDS); - - // data with size - Utility::AddToClass(pyclass, "__real_data", "data"); - PyErr_Clear(); // AddToClass might have failed for data - Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); - - // The addition of the __array__ utility to std::vector Python proxies causes a - // bug where the resulting array is a single dimension, causing loss of data when - // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization - // was added with the upgrade in 6.32, and is only defined and used recursively, - // the safe option is to disable this function and no longer add it. -#if 0 - // numpy array conversion - Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, METH_VARARGS | METH_KEYWORDS /* unused */); -#endif - - // checked getitem - if (HasAttrDirect(pyclass, PyStrings::gLen)) { - Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__"); - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorGetItem, METH_O); - } - - // vector-optimized iterator protocol - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)vector_iter; - - // optimized __iadd__ - Utility::AddToClass(pyclass, "__iadd__", (PyCFunction)VectorIAdd, METH_VARARGS | METH_KEYWORDS); - - // helpers for iteration - const std::string& vtype = Cppyy::ResolveName(name+"::value_type"); - if (vtype.rfind("value_type") == std::string::npos) { // actually resolved? - PyObject* pyvalue_type = CPyCppyy_PyText_FromString(vtype.c_str()); - PyObject_SetAttr(pyclass, PyStrings::gValueType, pyvalue_type); - Py_DECREF(pyvalue_type); - } - - size_t typesz = Cppyy::SizeOf(name+"::value_type"); - if (typesz) { - PyObject* pyvalue_size = PyLong_FromSsize_t(typesz); - PyObject_SetAttr(pyclass, PyStrings::gValueSize, pyvalue_size); - Py_DECREF(pyvalue_size); - } - } - } - - else if (IsTemplatedSTLClass(name, "array")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)ArrayInit, METH_VARARGS | METH_KEYWORDS); - } - - else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)MapInit, METH_VARARGS | METH_KEYWORDS); -#if __cplusplus <= 202002L - // From C++20, std::map and std::unordered_map already implement a contains() method. - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O); -#endif - } - - else if (IsTemplatedSTLClass(name, "set")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, METH_VARARGS | METH_KEYWORDS); - -#if __cplusplus <= 202002L - // From C++20, std::set already implements a contains() method. - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O); -#endif - } - - else if (IsTemplatedSTLClass(name, "pair")) { - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)PairUnpack, METH_O); - Utility::AddToClass(pyclass, "__len__", (PyCFunction)ReturnTwo, METH_NOARGS); - } - - if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) { - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)SmartPtrInit, METH_VARARGS | METH_KEYWORDS); - } - - else if (!((PyTypeObject*)pyclass)->tp_iter && \ - (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) { - ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext; - Utility::AddToClass(pyclass, CPPYY__next__, (PyCFunction)STLIterNext, METH_NOARGS); - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)PyObject_SelfIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, METH_NOARGS); - } - - else if (name == "string" || name == "std::string") { // TODO: ask backend as well - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, METH_NOARGS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, METH_O); -#if __cplusplus <= 202302L - // From C++23, std::sting already implements a contains() method. - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, METH_O); -#endif - Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_find", "find"); - Utility::AddToClass(pyclass, "find", (PyCFunction)STLStringFind, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_rfind", "rfind"); - Utility::AddToClass(pyclass, "rfind", (PyCFunction)STLStringRFind, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_replace", "replace"); - Utility::AddToClass(pyclass, "replace", (PyCFunction)STLStringReplace, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)STLStringGetAttr, METH_O); - - // to allow use of std::string in dictionaries and findable with str - ((PyTypeObject*)pyclass)->tp_hash = (hashfunc)STLStringHash; - } - - else if (name == "basic_string_view >" || name == "std::basic_string_view") { - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, METH_O); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); - } - -// The first condition was already present in upstream CPyCppyy. The other two -// are special to ROOT, because its reflection layer gives us the types without -// the "std::" namespace. On some platforms, that applies only to the template -// arguments, and on others also to the "basic_string". - else if (name == "std::basic_string,std::allocator >" - || name == "basic_string,allocator >" - || name == "std::basic_string,allocator >" - ) { - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, METH_NOARGS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLWStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLWStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLWStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLWStringIsNotEqual, METH_O); - } - - else if (name == "complex" || name == "std::complex") { - Utility::AddToClass(pyclass, "__cpp_real", "real"); - PyObject_SetAttrString(pyclass, "real", PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDReal)); - Utility::AddToClass(pyclass, "__cpp_imag", "imag"); - PyObject_SetAttrString(pyclass, "imag", PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDImag)); - Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexDComplex, METH_NOARGS); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, METH_NOARGS); - } - - else if (IsTemplatedSTLClass(name, "complex")) { - Utility::AddToClass(pyclass, "__cpp_real", "real"); - PyObject_SetAttrString(pyclass, "real", PyDescr_NewGetSet((PyTypeObject*)pyclass, &realComplex)); - Utility::AddToClass(pyclass, "__cpp_imag", "imag"); - PyObject_SetAttrString(pyclass, "imag", PyDescr_NewGetSet((PyTypeObject*)pyclass, &imagComplex)); - Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexComplex, METH_NOARGS); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, METH_NOARGS); - } - -// direct user access; there are two calls here: -// - explicit pythonization: won't fall through to the base classes and is preferred if present -// - normal pythonization: only called if explicit isn't present, falls through to base classes - bool bUserOk = true; PyObject* res = nullptr; - PyObject* pyname = CPyCppyy_PyText_FromString(name.c_str()); - if (HasAttrDirect(pyclass, PyStrings::gExPythonize)) { - res = PyObject_CallMethodObjArgs(pyclass, PyStrings::gExPythonize, pyclass, pyname, nullptr); - bUserOk = (bool)res; - } else { - PyObject* func = PyObject_GetAttr(pyclass, PyStrings::gPythonize); - if (func) { - res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr); - Py_DECREF(func); - bUserOk = (bool)res; - } else - PyErr_Clear(); - } - if (!bUserOk) { - Py_DECREF(pyname); - return false; - } else { - Py_XDECREF(res); - // pyname handed to args tuple below - } - -// call registered pythonizors, if any: first run the namespace-specific pythonizors, then -// the global ones (the idea is to allow writing a pythonizor that see all classes) - bool pstatus = true; - std::string outer_scope = TypeManip::extract_namespace(name); - auto &pyzMap = pythonizations(); - if (!outer_scope.empty()) { - auto p = pyzMap.find(outer_scope); - if (p != pyzMap.end()) { - PyObject* subname = CPyCppyy_PyText_FromString( - name.substr(outer_scope.size()+2, std::string::npos).c_str()); - pstatus = run_pythonizors(pyclass, subname, p->second); - Py_DECREF(subname); - } - } - - if (pstatus) { - auto p = pyzMap.find(""); - if (p != pyzMap.end()) - pstatus = run_pythonizors(pyclass, pyname, p->second); - } - - Py_DECREF(pyname); - -// phew! all done ... - return pstatus; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h deleted file mode 100644 index cbdec834a24cd..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CPYCPPYY_PYTHONIZE_H -#define CPYCPPYY_PYTHONIZE_H - -// Standard -#include - - -namespace CPyCppyy { - -// make the named C++ class more python-like -bool Pythonize(PyObject* pyclass, const std::string& name); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYTHONIZE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h deleted file mode 100644 index 453d8eb6ee388..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h +++ /dev/null @@ -1,81 +0,0 @@ -// Partial reproduction of ROOT's TException.h - -/************************************************************************* - * Copyright (C) 1995-2000, 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 CPYCPPYY_SIGNALTRYCATCH_H -#define CPYCPPYY_SIGNALTRYCATCH_H - -#include -#include "CPyCppyy/CommonDefs.h" - -#ifndef _WIN32 -#define NEED_SIGJMP 1 -#endif - -// By default, the ExceptionContext_t class is expected in the namespace -// CppyyLegacy, If it is expected in no namespace, one can explicitly define -// NO_CPPYY_LEGACY_NAMESPACE at build time (e.g. if one wants to use ROOT). - -#ifndef NO_CPPYY_LEGACY_NAMESPACE -namespace CppyyLegacy { -#endif -struct ExceptionContext_t { -#ifdef NEED_SIGJMP - sigjmp_buf fBuf; -#else - jmp_buf fBuf; -#endif -}; -#ifndef NO_CPPYY_LEGACY_NAMESPACE -} - -using CppyyExceptionContext_t = CppyyLegacy::ExceptionContext_t; -#else -using CppyyExceptionContext_t = ExceptionContext_t; -#endif - -#ifdef NEED_SIGJMP -# define CLING_EXCEPTION_SETJMP(buf) sigsetjmp(buf,1) -#else -# define CLING_EXCEPTION_SETJMP(buf) setjmp(buf) -#endif - -#define CLING_EXCEPTION_RETRY \ - { \ - static CppyyExceptionContext_t R__curr, *R__old = gException; \ - int R__code; \ - gException = &R__curr; \ - R__code = CLING_EXCEPTION_SETJMP(gException->fBuf); if (R__code) { }; { - -#define CLING_EXCEPTION_TRY \ - { \ - static CppyyExceptionContext_t R__curr, *R__old = gException; \ - int R__code; \ - gException = &R__curr; \ - if ((R__code = CLING_EXCEPTION_SETJMP(gException->fBuf)) == 0) { - -#define CLING_EXCEPTION_CATCH(n) \ - gException = R__old; \ - } else { \ - int n = R__code; \ - gException = R__old; - -#define CLING_EXCEPTION_ENDTRY \ - } \ - gException = R__old; \ - } - -// extern, defined in ROOT Core -#ifdef _MSC_VER -extern __declspec(dllimport) CppyyExceptionContext_t *gException; -#else -extern CppyyExceptionContext_t *gException; -#endif - -#endif diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx deleted file mode 100644 index dc6453d1529d8..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx +++ /dev/null @@ -1,984 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "TemplateProxy.h" -#include "CPPClassMethod.h" -#include "CPPConstructor.h" -#include "CPPFunction.h" -#include "CPPMethod.h" -#include "CPPOverload.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "Utility.h" - -// Standard -#include - - -namespace CPyCppyy { - -//---------------------------------------------------------------------------- -TemplateInfo::TemplateInfo() : fPyClass(nullptr), fNonTemplated(nullptr), - fTemplated(nullptr), fLowPriority(nullptr), fDoc(nullptr) -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -TemplateInfo::~TemplateInfo() -{ - Py_XDECREF(fPyClass); - - Py_XDECREF(fDoc); - Py_DECREF(fNonTemplated); - Py_DECREF(fTemplated); - Py_DECREF(fLowPriority); - - for (const auto& p : fDispatchMap) { - for (const auto& c : p.second) { - Py_DECREF(c.second); - } - } -} - - -//---------------------------------------------------------------------------- -void TemplateProxy::MergeOverload(CPPOverload* mp) { -// Store overloads of this templated method. - bool isGreedy = false; - for (auto pc : mp->fMethodInfo->fMethods) { - if (pc->IsGreedy()) { - isGreedy = true; - break; - } - } - - CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated; - cppol->MergeOverload(mp); -} - -void TemplateProxy::AdoptMethod(PyCallable* pc) { -// Store overload of this templated method. - CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated; - cppol->AdoptMethod(pc); -} - -void TemplateProxy::AdoptTemplate(PyCallable* pc) -{ -// Store known template methods. - fTI->fTemplated->AdoptMethod(pc); -} - -//---------------------------------------------------------------------------- -PyObject* TemplateProxy::Instantiate(const std::string& fname, - CPyCppyy_PyArgs_t args, size_t nargsf, Utility::ArgPreference pref, int* pcnt) -{ -// Instantiate (and cache) templated methods, return method if any - std::string proto = ""; - -#if PY_VERSION_HEX >= 0x03080000 -// adjust arguments for self if this is a rebound (global) function - bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (!isNS && CPyCppyy_PyArgs_GET_SIZE(args, nargsf) && \ - (!fSelf || - (fSelf == Py_None && !Cppyy::IsStaticTemplate(((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { - args += 1; - nargsf -= 1; - } -#endif - - Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - if (argc != 0) { - PyObject* tpArgs = PyTuple_New(argc); - for (Py_ssize_t i = 0; i < argc; ++i) { - PyObject* itemi = CPyCppyy_PyArgs_GET_ITEM(args, i); - - bool bArgSet = false; - - // special case for arrays - PyObject* pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode); - if (pytc) { - Py_buffer bufinfo; - memset(&bufinfo, 0, sizeof(Py_buffer)); - std::string ptrdef; - if (PyObject_GetBuffer(itemi, &bufinfo, PyBUF_FORMAT) == 0) { - for (int j = 0; j < bufinfo.ndim; ++j) ptrdef += "*"; - PyBuffer_Release(&bufinfo); - } else { - ptrdef += "*"; - PyErr_Clear(); - } - - PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true); - if (pyptrname) { - PyTuple_SET_ITEM(tpArgs, i, pyptrname); - bArgSet = true; - // string added, but not counted towards nStrings - } - Py_DECREF(pytc); pytc = nullptr; - } else - PyErr_Clear(); - - // if not arg set, try special case for ctypes - if (!bArgSet) pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType); - - if (!bArgSet && pytc) { - PyObject* pyactname = Utility::CT2CppName(pytc, "&", false); - if (!pyactname) { - // _type_ of a pointer to c_type is that type, which will have a type - PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType); - Py_DECREF(pytc); - pytc = newpytc; - if (pytc) { - pyactname = Utility::CT2CppName(pytc, "*", false); - } else - PyErr_Clear(); - } - Py_XDECREF(pytc); pytc = nullptr; - if (pyactname) { - PyTuple_SET_ITEM(tpArgs, i, pyactname); - bArgSet = true; - // string added, but not counted towards nStrings - } - } else - PyErr_Clear(); - - if (!bArgSet && (Py_TYPE(itemi) == &TemplateProxy_Type)) { - TemplateProxy *tp = (TemplateProxy*)itemi; - PyObject *tmpl_name = CPyCppyy_PyText_FromFormat("decltype(%s%s)", tp->fTI->fCppName.c_str(), tp->fTemplateArgs ? CPyCppyy_PyText_AsString(tp->fTemplateArgs) : ""); - PyTuple_SET_ITEM(tpArgs, i, tmpl_name); - bArgSet = true; - } - if (!bArgSet) { - // normal case (may well fail) - PyErr_Clear(); - PyObject* tp = (PyObject*)Py_TYPE(itemi); - Py_INCREF(tp); - PyTuple_SET_ITEM(tpArgs, i, tp); - } - } - -#if PY_VERSION_HEX >= 0x03080000 - PyObject* pyargs = PyTuple_New(argc); - for (Py_ssize_t i = 0; i < argc; ++i) { - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(args, i); - Py_INCREF(item); - PyTuple_SET_ITEM(pyargs, i, item); - } -#else - Py_INCREF(args); - PyObject* pyargs = args; -#endif - const std::string& name_v1 = \ - Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt); - - Py_DECREF(pyargs); - Py_DECREF(tpArgs); - - // Propagate the error that occurs if we can't construct the C++ name - // from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - - if (name_v1.size()) - proto = name_v1.substr(1, name_v1.size()-2); - } - -// the following causes instantiation as necessary - Cppyy::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType; - Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fname, proto); - if (cppmeth) { // overload stops here - // A successful instantiation needs to be cached to pre-empt future instantiations. There - // are two names involved, the original asked (which may be partial) and the received. - // - // Caching scheme: if the match is exact, simply add the overload to the pre-existing - // one, or create a new overload for later lookups. If the match is not exact, do the - // same, but also create an alias. Only add exact matches to the set of known template - // instantiations, to prevent piling on from different partial instantiations. - // - // TODO: this caches the lookup method before the call, meaning that failing overloads - // can add already existing overloads to the set of methods. - - std::string resname = Cppyy::GetMethodFullName(cppmeth); - - // An initializer_list is preferred for the argument types, but should not leak into - // the argument types. If it did, replace with vector and lookup anew. - if (resname.find("initializer_list") != std::string::npos) { - auto pos = proto.find("initializer_list"); - while (pos != std::string::npos) { - proto.replace(pos, 16, "vector"); - pos = proto.find("initializer_list", pos + 6); - } - - Cppyy::TCppMethod_t m2 = Cppyy::GetMethodTemplate(scope, fname, proto); - if (m2 && m2 != cppmeth) { - // replace if the new method with vector was found; otherwise just continue - // with the previously found method with initializer_list. - cppmeth = m2; - resname = Cppyy::GetMethodFullName(cppmeth); - } - } - - bool bExactMatch = fname == resname; - - // lookup on existing name in case this was an overload, not a caching, failure - PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict); - PyObject* pycachename = CPyCppyy_PyText_InternFromString(fname.c_str()); - PyObject* pyol = PyObject_GetItem(dct, pycachename); - if (!pyol) PyErr_Clear(); - bool bIsCppOL = CPPOverload_Check(pyol); - bool bIsCppTP = TemplateProxy_Check(pyol); - - // find the full name if the requested one was partial - PyObject* exact = nullptr; - PyObject* pyresname = CPyCppyy_PyText_FromString(resname.c_str()); - if (!bExactMatch) { - exact = PyObject_GetItem(dct, pyresname); - if (!exact) PyErr_Clear(); - } - Py_DECREF(dct); - - bool bIsConstructor = false, bNeedsRebind = true; - - PyCallable* meth = nullptr; - if (Cppyy::IsNamespace(scope)) { - meth = new CPPFunction(scope, cppmeth); - bNeedsRebind = false; - } else if (Cppyy::IsStaticMethod(cppmeth)) { - meth = new CPPClassMethod(scope, cppmeth); - bNeedsRebind = false; - } else if (Cppyy::IsConstructor(cppmeth)) { - bIsConstructor = true; - meth = new CPPConstructor(scope, cppmeth); - } else - meth = new CPPMethod(scope, cppmeth); - - // Case 1/2: method simply did not exist before - if (!pyol) { - // actual overload to use (now owns meth) - pyol = (PyObject*)CPPOverload_New(fname, meth); - if (bIsConstructor) { - // TODO: this is an ugly hack :( - ((CPPOverload*)pyol)->fMethodInfo->fFlags |= \ - CallContext::kIsCreator | CallContext::kIsConstructor; - } - - // add to class dictionary - PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol); - } - - // Case 3/4: pre-existing method that was either not found b/c the full - // templated name was constructed in this call or it failed as overload - else if (bIsCppOL) { - // TODO: see above, since the call hasn't happened yet, this overload may - // already exist and fail again. - ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership - } - - // Case 5: must be a template proxy, meaning that current template name is not - // a template overload - else if (bIsCppTP) { - ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone()); - Py_DECREF(pyol); - pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership - } - // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy - // we do not cache it - // as this might be a pythonization (monkey-patched func/method) - else { - Py_DECREF(pyol); - pyol = (PyObject*)CPPOverload_New(fname, meth); - } - - // Special Case if name was aliased (e.g. typedef in template instantiation) - if (!exact && !bExactMatch) { - PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol); - } - - // cleanup - Py_XDECREF(exact); - Py_DECREF(pyresname); - Py_DECREF(pycachename); - - // retrieve fresh (for boundedness) and call - PyObject* pymeth = - CPPOverload_Type.tp_descr_get(pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type); - Py_DECREF(pyol); - return pymeth; - } - - PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"", fname.c_str(), proto.c_str()); - return nullptr; -} - - -//= CPyCppyy template proxy construction/destruction ========================= -static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) -{ -// Create a new empty template method proxy. - TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type); - pytmpl->fSelf = nullptr; - pytmpl->fTemplateArgs = nullptr; - pytmpl->fWeakrefList = nullptr; - new (&pytmpl->fTI) TP_TInfo_t{}; - pytmpl->fTI = std::make_shared(); - - PyObject_GC_Track(pytmpl); - return pytmpl; -} - -//---------------------------------------------------------------------------- -static Py_hash_t tpp_hash(TemplateProxy* self) -{ - return (Py_hash_t)self; -} - -//---------------------------------------------------------------------------- -static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) -{ - if (op == Py_EQ || op == Py_NE) { - if (!TemplateProxy_CheckExact(other)) - Py_RETURN_FALSE; - - if (self->fTI == ((TemplateProxy*)other)->fTI) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; - } - - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; -} - -//---------------------------------------------------------------------------- -static int tpp_clear(TemplateProxy* pytmpl) -{ -// Garbage collector clear of held python member objects. - Py_CLEAR(pytmpl->fSelf); - Py_CLEAR(pytmpl->fTemplateArgs); - - return 0; -} - -//---------------------------------------------------------------------------- -static void tpp_dealloc(TemplateProxy* pytmpl) -{ -// Destroy the given template method proxy. - if (pytmpl->fWeakrefList) - PyObject_ClearWeakRefs((PyObject*)pytmpl); - PyObject_GC_UnTrack(pytmpl); - tpp_clear(pytmpl); - pytmpl->fTI.~TP_TInfo_t(); - PyObject_GC_Del(pytmpl); -} - -//---------------------------------------------------------------------------- -static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) -{ -// Garbage collector traverse of held python member objects. - Py_VISIT(pytmpl->fSelf); - Py_VISIT(pytmpl->fTemplateArgs); - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) -{ - if (pytmpl->fTI->fDoc) { - Py_INCREF(pytmpl->fTI->fDoc); - return pytmpl->fTI->fDoc; - } - -// Forward to method proxies to doc all overloads - PyObject* doc = nullptr; - if (pytmpl->fTI->fNonTemplated->HasMethods()) - doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated, "__doc__"); - if (pytmpl->fTI->fTemplated->HasMethods()) { - PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__"); - if (doc && doc2) { - CPyCppyy_PyText_AppendAndDel(&doc, CPyCppyy_PyText_FromString("\n")); - CPyCppyy_PyText_AppendAndDel(&doc, doc2); - } else if (!doc && doc2) { - doc = doc2; - } - } - if (pytmpl->fTI->fLowPriority->HasMethods()) { - PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__"); - if (doc && doc2) { - CPyCppyy_PyText_AppendAndDel(&doc, CPyCppyy_PyText_FromString("\n")); - CPyCppyy_PyText_AppendAndDel(&doc, doc2); - } else if (!doc && doc2) { - doc = doc2; - } - } - - if (doc) - return doc; - - return CPyCppyy_PyText_FromString(TemplateProxy_Type.tp_doc); -} - -static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *) -{ - Py_XDECREF(pytmpl->fTI->fDoc); - Py_INCREF(val); - pytmpl->fTI->fDoc = val; - return 0; -} - -//---------------------------------------------------------------------------- - -//= CPyCppyy template proxy callable behavior ================================ - -#define TPPCALL_RETURN \ -{ errors.clear(); \ - return result; } - -static inline std::string targs2str(TemplateProxy* pytmpl) -{ - if (!pytmpl || !pytmpl->fTemplateArgs) return ""; - return CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); -} - -static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, uint64_t sighash, CPPOverload* pymeth) -{ -// Memoize a method in the dispatch map after successful call; replace old if need be (may be -// with the same CPPOverload, just with more methods). - bool bInserted = false; - auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""]; - - Py_INCREF(pymeth); - for (auto& p : v) { - if (p.first == sighash) { - Py_DECREF(p.second); - p.second = pymeth; - bInserted = true; - } - } - if (!bInserted) v.push_back(std::make_pair(sighash, pymeth)); -} - -static inline PyObject* SelectAndForward(TemplateProxy* pytmpl, CPPOverload* pymeth, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, - bool implicitOkay, bool use_targs, uint64_t sighash, std::vector& errors) -{ -// Forward a call to known overloads, if any. - if (pymeth->HasMethods()) { - PyObject* pycall = CPPOverload_Type.tp_descr_get( - (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); - - if (!implicitOkay) - ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit; - - // now call the method with the arguments (loops internally) - PyObject* result = CPyCppyy_tp_call(pycall, args, nargsf, kwds); - Py_DECREF(pycall); - if (result) { - UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } - - return nullptr; -} - -static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, bool impOK, uint64_t sighash) -{ -// Actual call of a given overload: takes care of handlign of "self" and -// dereferences the overloaded method after use. - - PyObject* result; - if (!impOK && CPPOverload_Check(pymeth)) - ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit; - bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) { - // this is a global method added a posteriori to the class - PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds}; - AdjustSelf(cargs); - result = CPyCppyy_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds); - } else { - if (!pytmpl->fSelf && CPPOverload_Check(pymeth)) - ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr; - result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds); - } - - if (result) { - Py_XDECREF(((CPPOverload*)pymeth)->fSelf); ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind - UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth); - } - - Py_DECREF(pymeth); pymeth = nullptr; - return result; -} - -#if PY_VERSION_HEX >= 0x03080000 -static PyObject* tpp_vectorcall( - TemplateProxy* pytmpl, PyObject* const *args, size_t nargsf, PyObject* kwds) -#else -static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) -#endif -{ -// Dispatcher to the actual member method, several uses possible; in order: -// -// case 1: explicit template previously selected through subscript -// -// case 2: select known non-template overload -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) // non-template -// -// case 3: select known template overload -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) // all known templates -// -// case 4: auto-instantiation from types of arguments -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) -// -// Note: explicit instantiation needs to use [] syntax: -// -// obj.method[type, type, ...](a0, a1, ...) -// -// case 5: low priority methods, such as ones that take void* arguments -// - -// TODO: should previously instantiated templates be considered first? - -#if PY_VERSION_HEX < 0x03080000 - size_t nargsf = PyTuple_GET_SIZE(args); -#endif - - PyObject *pymeth = nullptr, *result = nullptr; - -// short-cut through memoization map - Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - uint64_t sighash = HashSignature(args, argc); - - CPPOverload* ol = nullptr; - if (!pytmpl->fTemplateArgs) { - // look for known signatures ... - auto& v = pytmpl->fTI->fDispatchMap[""]; - for (const auto& p : v) { - if (p.first == sighash) { - ol = p.second; - break; - } - } - - if (ol != nullptr) { - if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) { - result = CPyCppyy_tp_call((PyObject*)ol, args, nargsf, kwds); - } else { - pymeth = CPPOverload_Type.tp_descr_get( - (PyObject*)ol, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); - result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds); - Py_DECREF(pymeth); pymeth = nullptr; - } - if (result) - return result; - } - } - -// container for collecting errors - std::vector errors; - if (ol) Utility::FetchError(errors); - -// case 1: explicit template previously selected through subscript - if (pytmpl->fTemplateArgs) { - // instantiate explicitly - PyObject* pyfullname = CPyCppyy_PyText_FromString(pytmpl->fTI->fCppName.c_str()); - CPyCppyy_PyText_Append(&pyfullname, pytmpl->fTemplateArgs); - - // first, lookup by full name, if previously stored - bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS) - pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname); - else // by-passes custom scope getattr that searches into Cling - pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname); - - // attempt call if found (this may fail if there are specializations) - if (CPPOverload_Check(pymeth)) { - // since the template args are fully explicit, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } else if (pymeth && PyCallable_Check(pymeth)) { - // something different (user provided?) - result = CPyCppyy_PyObject_Call(pymeth, args, nargsf, kwds); - Py_DECREF(pymeth); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } else if (!pymeth) - PyErr_Clear(); - - // not cached or failed call; try instantiation - pymeth = pytmpl->Instantiate( - CPyCppyy_PyText_AsString(pyfullname), args, nargsf, Utility::kNone); - if (pymeth) { - // attempt actual call; same as above, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - } - - // no drop through if failed (if implicit was desired, don't provide template args) - Utility::FetchError(errors); - PyObject* topmsg = CPyCppyy_PyText_FromFormat( - "Could not find \"%s\" (set cppyy.set_debug() for C++ errors):", CPyCppyy_PyText_AsString(pyfullname)); - Py_DECREF(pyfullname); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); - - return nullptr; - } - -// case 2: select known non-template overload - result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf, kwds, - true /* implicitOkay */, false /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// case 3: select known template overload - result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds, - false /* implicitOkay */, true /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// case 4: auto-instantiation from types of arguments - for (auto pref : {Utility::kReference, Utility::kPointer, Utility::kValue}) { - // TODO: no need to loop if there are no non-instance arguments; also, should any - // failed lookup be removed? - int pcnt = 0; - pymeth = pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt); - if (pymeth) { - // attempt actual call; even if argument based, allow implicit conversions, for example for non-template arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); - if (result) TPPCALL_RETURN; - } - Utility::FetchError(errors); - if (!pcnt) break; // preference never used; no point trying others - } - -// case 5: low priority methods, such as ones that take void* arguments - result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf, kwds, - false /* implicitOkay */, false /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// error reporting is fraud, given the numerous steps taken, but more details seems better - if (!errors.empty()) { - PyObject* topmsg = CPyCppyy_PyText_FromString("Template method resolution failed:"); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); - } else { - PyErr_Format(PyExc_TypeError, "cannot resolve method template call for \'%s\'", - pytmpl->fTI->fCppName.c_str()); - } - - return nullptr; -} - -//---------------------------------------------------------------------------- -static TemplateProxy* tpp_descr_get(TemplateProxy* pytmpl, PyObject* pyobj, PyObject*) -{ -// create and use a new template proxy (language requirement) - TemplateProxy* newPyTmpl = (TemplateProxy*)TemplateProxy_Type.tp_alloc(&TemplateProxy_Type, 0); - -// new method is to be bound to current object (may be nullptr) - if (pyobj) { - Py_INCREF(pyobj); - newPyTmpl->fSelf = pyobj; - } else { - Py_INCREF(Py_None); - newPyTmpl->fSelf = Py_None; - } - - Py_XINCREF(pytmpl->fTemplateArgs); - newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs; - -// copy name, class, etc. pointers - new (&newPyTmpl->fTI) std::shared_ptr{pytmpl->fTI}; - -#if PY_VERSION_HEX >= 0x03080000 - newPyTmpl->fVectorCall = pytmpl->fVectorCall; -#endif - - return newPyTmpl; -} - - -//---------------------------------------------------------------------------- -static PyObject* tpp_subscript(TemplateProxy* pytmpl, PyObject* args) -{ -// Explicit template member lookup/instantiation; works by re-bounding. This method can -// not cache overloads as instantiations need not be unique for the argument types due -// to template specializations. - TemplateProxy* typeBoundMethod = tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr); - Py_XDECREF(typeBoundMethod->fTemplateArgs); - typeBoundMethod->fTemplateArgs = CPyCppyy_PyText_FromString( - Utility::ConstructTemplateArgs(nullptr, args).c_str()); -// Propagate the error that occurs if we can't construct the C++ name -// from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - return (PyObject*)typeBoundMethod; -} - -//----------------------------------------------------------------------------- -static PyObject* tpp_getuseffi(CPPOverload*, void*) -{ - return PyInt_FromLong(0); // dummy (__useffi__ unused) -} - -//----------------------------------------------------------------------------- -static int tpp_setuseffi(CPPOverload*, PyObject*, void*) -{ - return 0; // dummy (__useffi__ unused) -} - -//----------------------------------------------------------------------------- -static PyObject* tpp_gettemplateargs(TemplateProxy* self, void*) { - if (!self->fTemplateArgs) { - Py_RETURN_NONE; - } - - Py_INCREF(self->fTemplateArgs); - return self->fTemplateArgs; -} - -//----------------------------------------------------------------------------- -static int tpp_settemplateargs(TemplateProxy*, PyObject*, void*) { - PyErr_SetString(PyExc_AttributeError, "__template_args__ is read-only"); - return -1; -} - -//---------------------------------------------------------------------------- -static PyMappingMethods tpp_as_mapping = { - nullptr, (binaryfunc)tpp_subscript, nullptr -}; - -static PyGetSetDef tpp_getset[] = { - {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr}, - {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi, - (char*)"unused", nullptr}, - {(char*)"__template_args__", (getter)tpp_gettemplateargs, (setter)tpp_settemplateargs, - (char*)"the template arguments for this method", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}, -}; - - -//---------------------------------------------------------------------------- -void TemplateProxy::Set(const std::string& cppname, const std::string& pyname, PyObject* pyclass) -{ -// Initialize the proxy for the given 'pyclass.' - fSelf = nullptr; - fTemplateArgs = nullptr; - - fTI->fCppName = cppname; - Py_XINCREF(pyclass); - fTI->fPyClass = pyclass; - - std::vector dummy; - fTI->fNonTemplated = CPPOverload_New(pyname, dummy); - fTI->fTemplated = CPPOverload_New(pyname, dummy); - fTI->fLowPriority = CPPOverload_New(pyname, dummy); - -#if PY_VERSION_HEX >= 0x03080000 - fVectorCall = (vectorcallfunc)tpp_vectorcall; -#endif -} - - -//= CPyCppyy method proxy access to internals ================================ -static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) -{ -// Select and call a specific C++ overload, based on its signature. - const char* sigarg = nullptr; - const char* tmplarg = nullptr; - PyObject* sigarg_tuple = nullptr; - int want_const = -1; - - Cppyy::TCppScope_t scope = (Cppyy::TCppScope_t) 0; - Cppyy::TCppMethod_t cppmeth = (Cppyy::TCppMethod_t) 0; - std::string proto; - - if (PyArg_ParseTuple(args, const_cast("s|i:__overload__"), &sigarg, &want_const)) { - want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; - - // check existing overloads in order - PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const); - if (ol) return ol; - - proto = Utility::ConstructTemplateArgs(nullptr, args); - // Propagate the error that occurs if we can't construct the C++ name - // from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - cppmeth = Cppyy::GetMethodTemplate( - scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2)); - } else if (PyArg_ParseTuple(args, const_cast("ss:__overload__"), &sigarg, &tmplarg)) { - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - std::string full_name = std::string(pytmpl->fTI->fCppName) + "<" + tmplarg + ">"; - - cppmeth = Cppyy::GetMethodTemplate(scope, full_name, sigarg); - } else if (PyArg_ParseTuple(args, const_cast("O|i:__overload__"), &sigarg_tuple, &want_const)) { - PyErr_Clear(); - want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; - - // check existing overloads in order - PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - - proto.reserve(128); - proto.push_back('<'); - Py_ssize_t n = PyTuple_Size(sigarg_tuple); - for (int i = 0; i < n; i++) { - PyObject *pItem = PyTuple_GetItem(sigarg_tuple, i); - if(!CPyCppyy_PyText_Check(pItem)) { - PyErr_Format(PyExc_LookupError, "argument types should be in string format"); - return (PyObject*) nullptr; - } - proto.append(CPyCppyy_PyText_AsString(pItem)); - if (i < n - 1) - proto.push_back(','); - } - proto.push_back('>'); - - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - cppmeth = Cppyy::GetMethodTemplate( - scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2)); - } else { - PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); - return nullptr; - } - -// else attempt instantiation - if (!cppmeth) { - return nullptr; - } - - PyErr_Clear(); - - // TODO: the next step should be consolidated with Instantiate() - PyCallable* meth = nullptr; - if (Cppyy::IsNamespace(scope)) { - meth = new CPPFunction(scope, cppmeth); - } else if (Cppyy::IsStaticMethod(cppmeth)) { - meth = new CPPClassMethod(scope, cppmeth); - } else if (Cppyy::IsConstructor(cppmeth)) { - meth = new CPPConstructor(scope, cppmeth); - } else - meth = new CPPMethod(scope, cppmeth); - - return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName+proto, meth); -} - -static PyMethodDef tpp_methods[] = { - {(char*)"__overload__", (PyCFunction)tpp_overload, METH_VARARGS, - (char*)"select overload for dispatch" }, - {(char*)nullptr, nullptr, 0, nullptr } -}; - - -//= CPyCppyy template proxy type ============================================= -PyTypeObject TemplateProxy_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.TemplateProxy", // tp_name - sizeof(TemplateProxy), // tp_basicsize - 0, // tp_itemsize - (destructor)tpp_dealloc, // tp_dealloc -#if PY_VERSION_HEX >= 0x03080000 - offsetof(TemplateProxy, fVectorCall), -#else - 0, // tp_vectorcall_offset / tp_print -#endif - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - &tpp_as_mapping, // tp_as_mapping - (hashfunc)tpp_hash, // tp_hash -#if PY_VERSION_HEX >= 0x03080000 - (ternaryfunc)PyVectorcall_Call, // tp_call -#else - (ternaryfunc)tpp_call, // tp_call -#endif - 0, // tp_str - 0, // tp_getattro - 0, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC -#if PY_VERSION_HEX >= 0x03080000 - | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR -#endif - , // tp_flags - (char*)"cppyy template proxy (internal)", // tp_doc - (traverseproc)tpp_traverse, // tp_traverse - (inquiry)tpp_clear, // tp_clear - (richcmpfunc)tpp_richcompare, // tp_richcompare - offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - tpp_methods, // tp_methods - 0, // tp_members - tpp_getset, // tp_getset - 0, // tp_base - 0, // tp_dict - (descrgetfunc)tpp_descr_get, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)tpp_new, // tp_new - 0, // tp_free - 0, // tp_is_gc - 0, // tp_bases - 0, // tp_mro - 0, // tp_cache - 0, // tp_subclasses - 0 // tp_weaklist -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.h b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.h deleted file mode 100644 index a286fe7147e1f..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef CPYCPPYY_TEMPLATEPROXY_H -#define CPYCPPYY_TEMPLATEPROXY_H - -// Bindings -#include "CPPScope.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include - - -namespace CPyCppyy { - -class PyCallable; -class CPPOverload; - -/** Template proxy object to return functions and methods - */ - -typedef std::pair TP_DispatchEntry_t; -typedef std::map> TP_DispatchMap_t; - -class TemplateInfo { -public: - TemplateInfo(); - TemplateInfo(const TemplateInfo&) = delete; - TemplateInfo& operator=(const TemplateInfo&) = delete; - ~TemplateInfo(); - -public: - std::string fCppName; - PyObject* fPyClass; - CPPOverload* fNonTemplated; // holder for non-template overloads - CPPOverload* fTemplated; // holder for templated overloads - CPPOverload* fLowPriority; // low priority overloads such as void*/void** - - TP_DispatchMap_t fDispatchMap; - PyObject* fDoc; -}; - -typedef std::shared_ptr TP_TInfo_t; - -class TemplateProxy { -private: - friend TemplateProxy* TemplateProxy_New( - const std::string& cppname, const std::string& pyname, PyObject* pyclass); - void Set(const std::string& cppname, const std::string& pyname, PyObject* pyclass); - -public: // public, as the python C-API works with C structs - PyObject_HEAD - PyObject* fSelf; // must be first (same layout as CPPOverload) - PyObject* fTemplateArgs; - PyObject* fWeakrefList; -#if PY_VERSION_HEX >= 0x03080000 - vectorcallfunc fVectorCall; -#endif - TP_TInfo_t fTI; - -public: - void MergeOverload(CPPOverload* mp); - void AdoptMethod(PyCallable* pc); - void AdoptTemplate(PyCallable* pc); - PyObject* Instantiate(const std::string& fname, - CPyCppyy_PyArgs_t tmplArgs, size_t nargsf, Utility::ArgPreference, int* pcnt = nullptr); - -private: // private, as the python C-API will handle creation - TemplateProxy() = delete; -}; - - -//- template proxy type and type verification -------------------------------- -extern PyTypeObject TemplateProxy_Type; - -template -inline bool TemplateProxy_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TemplateProxy_Type); -} - -template -inline bool TemplateProxy_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TemplateProxy_Type; -} - -//- creation ----------------------------------------------------------------- -inline TemplateProxy* TemplateProxy_New( - const std::string& cppname, const std::string& pyname, PyObject* pyclass) -{ -// Create and initialize a new template method proxy for the class. - if (!CPPScope_Check(pyclass)) return nullptr; - - TemplateProxy* pytmpl = - (TemplateProxy*)TemplateProxy_Type.tp_new(&TemplateProxy_Type, nullptr, nullptr); - pytmpl->Set(cppname, pyname, pyclass); - return pytmpl; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TEMPLATEPROXY_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx deleted file mode 100644 index 67361fa69fc8b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx +++ /dev/null @@ -1,255 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "TupleOfInstances.h" -#include "ProxyWrappers.h" - - -namespace { - -typedef struct { - PyObject_HEAD - Cppyy::TCppType_t ia_klass; - void* ia_array_start; - Py_ssize_t ia_pos; - Py_ssize_t ia_len; - Py_ssize_t ia_stride; -} ia_iterobject; - -static PyObject* ia_iternext(ia_iterobject* ia) { - if (ia->ia_len != (Py_ssize_t)-1 && ia->ia_pos >= ia->ia_len) { - ia->ia_pos = 0; // debatable, but since the iterator is cached, this - return nullptr; // allows for multiple conversions to e.g. a tuple - } else if (ia->ia_stride == 0 && ia->ia_pos != 0) { - PyErr_SetString(PyExc_ReferenceError, "no stride available for indexing"); - return nullptr; - } - PyObject* result = CPyCppyy::BindCppObjectNoCast( - (char*)ia->ia_array_start + ia->ia_pos*ia->ia_stride, ia->ia_klass); - ia->ia_pos += 1; - return result; -} - -static int ia_traverse(ia_iterobject*, visitproc, void*) { - return 0; -} - -static PyObject* ia_getsize(ia_iterobject* ia, void*) { - return PyInt_FromSsize_t(ia->ia_len); -} - -static int ia_setsize(ia_iterobject* ia, PyObject* pysize, void*) { - Py_ssize_t size = PyInt_AsSsize_t(pysize); - if (size == (Py_ssize_t)-1 && PyErr_Occurred()) - return -1; - ia->ia_len = size; - return 0; -} - -static PyGetSetDef ia_getset[] = { - {(char*)"size", (getter)ia_getsize, (setter)ia_setsize, - (char*)"set size of array to which this iterator refers", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -static Py_ssize_t ia_length(ia_iterobject* ia) -{ - return ia->ia_len; -} - -static PyObject* ia_subscript(ia_iterobject* ia, PyObject* pyidx) -{ -// Subscripting the iterator allows direct access through indexing on arrays -// that do not have a defined length. This way, the return from accessing such -// an array as a data member can both be used in a loop and directly. - Py_ssize_t idx = PyInt_AsSsize_t(pyidx); - if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) - return nullptr; - - if (ia->ia_len != (Py_ssize_t)-1 && (idx < 0 || ia->ia_len <= idx)) { - PyErr_SetString(PyExc_IndexError, "index out of range"); - return nullptr; - } - - return CPyCppyy::BindCppObjectNoCast( - (char*)ia->ia_array_start + ia->ia_pos*ia->ia_stride, ia->ia_klass); -} - -static PyMappingMethods ia_as_mapping = { - (lenfunc) ia_length, // mp_length - (binaryfunc) ia_subscript, // mp_subscript - (objobjargproc)nullptr, // mp_ass_subscript -}; - -} // unnamed namespace - - -namespace CPyCppyy { - -PyTypeObject InstanceArrayIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.instancearrayiter", // tp_name - sizeof(ia_iterobject), // tp_basicsize - 0, - (destructor)PyObject_GC_Del, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, - &ia_as_mapping, // tp_as_mapping - 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)ia_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)ia_iternext, // tp_iternext - 0, 0, - ia_getset, // tp_getset - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - - -//= support for C-style arrays of objects ==================================== -PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims) -{ -// recursively set up tuples of instances on all dimensions - if (dims.ndim() == UNKNOWN_SIZE || dims[0] == UNKNOWN_SIZE /* unknown shape or size */) { - // no known length ... return an iterable object and let the user figure it out - ia_iterobject* ia = PyObject_GC_New(ia_iterobject, &InstanceArrayIter_Type); - if (!ia) return nullptr; - - ia->ia_klass = klass; - ia->ia_array_start = address; - ia->ia_pos = 0; - ia->ia_len = -1; - ia->ia_stride = Cppyy::SizeOf(klass); - - PyObject_GC_Track(ia); - return (PyObject*)ia; - } else if (1 < dims.ndim()) { - // not the innermost dimension, descend one level - size_t block_size = 0; - for (Py_ssize_t i = 1; i < dims.ndim(); ++i) block_size += (size_t)dims[i]; - block_size *= Cppyy::SizeOf(klass); - - Py_ssize_t nelems = dims[0]; - PyObject* tup = PyTuple_New(nelems); - for (Py_ssize_t i = 0; i < nelems; ++i) { - PyTuple_SetItem(tup, i, TupleOfInstances_New( - (char*)address + i*block_size, klass, dims.sub())); - } - return tup; - } else { - // innermost dimension: construct tuple - int nelems = (int)dims[0]; - size_t block_size = Cppyy::SizeOf(klass); - if (block_size == 0) { - PyErr_Format(PyExc_TypeError, - "can not determine size of type \"%s\" for array indexing", - Cppyy::GetScopedFinalName(klass).c_str()); - return nullptr; - } - - // TODO: the extra copy is inefficient, but it appears that the only way to - // initialize a subclass of a tuple is through a sequence - PyObject* tup = PyTuple_New(nelems); - for (int i = 0; i < nelems; ++i) { - // TODO: there's an assumption here that there is no padding, which is bound - // to be incorrect in certain cases - PyTuple_SetItem(tup, i, - BindCppObjectNoCast((char*)address + i*block_size, klass)); - // Note: objects are bound as pointers, yet since the pointer value stays in - // place, updates propagate just as if they were bound by-reference - } - - PyObject* args = PyTuple_New(1); - Py_INCREF(tup); PyTuple_SET_ITEM(args, 0, tup); - PyObject* arr = PyTuple_Type.tp_new(&TupleOfInstances_Type, args, nullptr); - - Py_DECREF(args); - // tup ref eaten by SET_ITEM on args - - return arr; - } - -// never get here - return nullptr; -} - -//= CPyCppyy custom tuple-like array type ==================================== -PyTypeObject TupleOfInstances_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.InstancesArray", // tp_name - 0, // tp_basicsize - 0, // tp_itemsize - 0, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - 0, // tp_as_mapping - 0, // tp_hash - 0, // tp_call - 0, // tp_str - 0, // tp_getattro - 0, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"array of C++ instances", // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - 0, // tp_methods - 0, // tp_members - 0, // tp_getset - &PyTuple_Type, // tp_base - 0, // tp_dict - 0, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - 0, // tp_new - 0, // tp_free - 0, // tp_is_gc - 0, // tp_bases - 0, // tp_mro - 0, // tp_cache - 0, // tp_subclasses - 0 // tp_weaklist -#if PY_VERSION_HEX >= 0x02030000 - , 0 // tp_del -#endif -#if PY_VERSION_HEX >= 0x02060000 - , 0 // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x03040000 - , 0 // tp_finalize -#endif -#if PY_VERSION_HEX >= 0x03080000 - , 0 // tp_vectorcall -#endif - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h deleted file mode 100644 index de95f29b2f8ba..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef CPYCPPYY_TUPLEOFINSTANCES_H -#define CPYCPPYY_TUPLEOFINSTANCES_H - -// Bindings -#include "Dimensions.h" - - -namespace CPyCppyy { - -/** Representation of C-style array of instances - @author WLAV - @date 02/10/2014 - @version 1.0 - */ - -//- custom tuple type that can pass through C-style arrays ------------------- -extern PyTypeObject TupleOfInstances_Type; -extern PyTypeObject InstanceArrayIter_Type; - -template -inline bool TupleOfInstances_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TupleOfInstances_Type); -} - -template -inline bool TupleOfInstances_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TupleOfInstances_Type; -} - -PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TUPLEOFINSTANCES_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx deleted file mode 100644 index 98bc183d25d55..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx +++ /dev/null @@ -1,274 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "TypeManip.h" - -// Standard -#include -#include -#include - - -//- helpers ------------------------------------------------------------------ -static inline -bool is_varchar(char c) { - return isalnum((int)c) || c == '_' || c == ')' || c == '(' /* for (anonymous)/(unnamed) */; -} - -static inline -std::string::size_type find_qualifier_index(const std::string& name) -{ -// Find the first location that is not part of the class name proper. - std::string::size_type i = name.size() - 1; - bool arr_open = false; - for ( ; 0 < i; --i) { - std::string::value_type c = name[i]; - if (!arr_open && (is_varchar(c) || c == '>')) { - if (c == 't' && 6 < i && !is_varchar(name[i-5]) && name.substr(i-4, 5) == "const") - i -= 4; // this skips 'const' on a pointer type - else - break; - } else if (c == ']') { - arr_open = true; - } else if (c == '[') { - arr_open = false; - } - } - - return i+1; -} - -static inline void erase_const(std::string& name) -{ -// Find and remove all occurrence of 'const'. - if (name.empty()) - return; - - std::string::size_type spos = std::string::npos; - std::string::size_type start = 0; - while ((spos = name.find("const", start)) != std::string::npos) { - // make sure not to erase 'const' as part of the name: if it is - // connected, before or after, to a variable name, then keep it - std::string::size_type after = spos+5; - if (after < name.size() && is_varchar(name[after])) { - start = after; - continue; - } else if (after == name.size()) { - if (spos > 0 && is_varchar(name[spos - 1])) - break; - } - - std::string::size_type i = 5; - while (name[spos+i] == ' ') ++i; - name.swap(name.erase(spos, i)); - } -} - -static inline void rstrip(std::string& name) -{ -// Remove space from the right side of name. - std::string::size_type i = name.size(); - for ( ; 0 < i; --i) { - if (!isspace(name[i-1])) - break; - } - - if (i != name.size()) - name = name.substr(0, i); -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::remove_const(const std::string& cppname) -{ -// Remove 'const' qualifiers from the given C++ name. - std::string::size_type tmplt_start = cppname.find('<'); - std::string::size_type type_stop = cppname.rfind('>'); - if (cppname.find("::", type_stop+1) != std::string::npos) // e.g. klass::some_typedef - type_stop = cppname.find(' ', type_stop+1); - if (tmplt_start != std::string::npos && cppname[tmplt_start+1] != '<') { - // only replace const qualifying cppname, not in template parameters - std::string pre = cppname.substr(0, tmplt_start); - erase_const(pre); - std::string post = ""; - if (type_stop != std::string::npos && type_stop != cppname.size()-1) { - post = cppname.substr(type_stop+1, std::string::npos); - erase_const(post); - } - - return pre + cppname.substr(tmplt_start, type_stop+1-tmplt_start) + post; - } - - std::string clean_name = cppname; - erase_const(clean_name); - return clean_name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::clean_type( - const std::string& cppname, bool template_strip, bool const_strip) -{ -// Strip C++ name from all qualifiers and compounds. - std::string::size_type i = find_qualifier_index(cppname); - std::string name = cppname.substr(0, i); - rstrip(name); - - if (name.back() == ']') { // array type? - // TODO: this fails templates instantiated on arrays (not common) - name = name.substr(0, name.find('[')); - } else if (template_strip && name.back() == '>') { - name = name.substr(0, name.find('<')); - } - - if (const_strip) { - if (template_strip) - erase_const(name); - else - name = remove_const(name); - } - - return name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::template_base(const std::string& cppname) -{ -// If this is a template, return the underlying template name w/o arguments - if (cppname.empty() || cppname.back() != '>') - return cppname; - - int tpl_open = 0; - for (std::string::size_type pos = cppname.size()-1; 0 < pos; --pos) { - std::string::value_type c = cppname[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && cppname[pos+1] != '<') - ++tpl_open; - - if (tpl_open == 0) - return cppname.substr(0, pos); - } - - return cppname; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::compound(const std::string& name) -{ -// Break down the compound of a fully qualified type name. - std::string cleanName = remove_const(name); - auto idx = find_qualifier_index(cleanName); - - const std::string& cpd = cleanName.substr(idx, std::string::npos); - -// for easy identification of fixed size arrays - if (!cpd.empty() && cpd.back() == ']') { - if (cpd.front() == '[') - return "[]"; // fixed array any; dimensions handled separately - - std::ostringstream scpd; - scpd << cpd.substr(0, cpd.find('[')) << "[]"; - return scpd.str(); - } - - return cpd; -} - -//---------------------------------------------------------------------------- -void CPyCppyy::TypeManip::cppscope_to_pyscope(std::string& cppscope) -{ -// Change '::' in C++ scope into '.' as in a Python scope. - std::string::size_type pos = 0; - while ((pos = cppscope.find("::", pos)) != std::string::npos) { - cppscope.replace(pos, 2, "."); - pos += 1; - } -} - -//---------------------------------------------------------------------------- -void CPyCppyy::TypeManip::cppscope_to_legalname(std::string& cppscope) -{ -// Change characters illegal in a variable name into '_' to form a legal name. - for (char& c : cppscope) { - for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*'}) - if (c == needle) c = '_'; - } -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::extract_namespace(const std::string& name) -{ -// Find the namespace the named class lives in, take care of templates - if (name.empty()) - return name; - - int tpl_open = 0; - for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && name[pos+1] != '<') - ++tpl_open; - - // collect name up to "::" - else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos-1); - } - } - -// no namespace; assume outer scope - return ""; -} - -//---------------------------------------------------------------------------- -std::vector CPyCppyy::TypeManip::extract_arg_types(const std::string& sig) -{ -// break out the argument types from the signature string - std::vector result; - - if (sig.empty() || sig == "()") - return result; - - int tpl_open = 0; - std::string::size_type start = 1; - for (std::string::size_type pos = 1; pos < sig.size()-1; ++pos) { - std::string::value_type c = sig[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && sig[pos+1] != '<') - ++tpl_open; - - // collect type name up to ',' or end ')' - else if (tpl_open == 0 && c == ',') { - // found the extend of the scope ... done - result.push_back(sig.substr(start, pos-start)); - start = pos+1; - } - } - -// add last type - result.push_back(sig.substr(start, sig.rfind(")")-start)); - - return result; -} - -//---------------------------------------------------------------------------- -Py_ssize_t CPyCppyy::TypeManip::array_size(const std::string& name) -{ -// Extract the array size from a given type name (assumes 1D arrays) - std::string cleanName = remove_const(name); - if (cleanName[cleanName.size()-1] == ']') { - std::string::size_type idx = cleanName.rfind('['); - if (idx != std::string::npos) { - const std::string asize = cleanName.substr(idx+1, cleanName.size()-2); - return strtoul(asize.c_str(), nullptr, 0); - } - } - - return -1; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.h b/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.h deleted file mode 100644 index 4fb8bd0cd6c57..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CPYCPPYY_TYPEMANIP_H -#define CPYCPPYY_TYPEMANIP_H - -#include -#include - - -namespace CPyCppyy { - -namespace TypeManip { - - std::string remove_const(const std::string& cppname); - std::string clean_type(const std::string& cppname, - bool template_strip = true, bool const_strip = true); - std::string template_base(const std::string& cppname); - std::string compound(const std::string& name); - - void cppscope_to_pyscope(std::string& cppscope); - void cppscope_to_legalname(std::string& cppscope); - std::string extract_namespace(const std::string& name); - - std::vector extract_arg_types(const std::string& sig); - Py_ssize_t array_size(const std::string& name); - -} // namespace TypeManip - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TYPEMANIP_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx deleted file mode 100644 index c878e050c01bc..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx +++ /dev/null @@ -1,1326 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Utility.h" -#include "CPPFunction.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "ProxyWrappers.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "CustomPyTypes.h" -#include "TemplateProxy.h" -#include "TypeManip.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include - - -//- data _____________________________________________________________________ -#if PY_VERSION_HEX < 0x030b0000 -bool CPyCppyy::gDictLookupActive = false; -#endif - -typedef std::map TC2POperatorMapping_t; -static TC2POperatorMapping_t gC2POperatorMapping; -static std::set gOpSkip; -static std::set gOpRemove; - -namespace CPyCppyy { -// special objects - extern PyObject* gNullPtrObject; - extern PyObject* gDefaultObject; -} - -namespace { - - using namespace CPyCppyy::Utility; - - struct InitOperatorMapping_t { - public: - InitOperatorMapping_t() { - // Initialize the global map of operator names C++ -> python. - - gOpSkip.insert("[]"); // __s/getitem__, depends on return type - gOpSkip.insert("+"); // __add__, depends on # of args (see __pos__) - gOpSkip.insert("-"); // __sub__, id. (eq. __neg__) - gOpSkip.insert("*"); // __mul__ or __deref__ - gOpSkip.insert("++"); // __postinc__ or __preinc__ - gOpSkip.insert("--"); // __postdec__ or __predec__ - - gOpRemove.insert("new"); // this and the following not handled at all - gOpRemove.insert("new[]"); - gOpRemove.insert("delete"); - gOpRemove.insert("delete[]"); - - gC2POperatorMapping["[]"] = "__getitem__"; - gC2POperatorMapping["()"] = "__call__"; - gC2POperatorMapping["%"] = "__mod__"; - gC2POperatorMapping["**"] = "__pow__"; - gC2POperatorMapping["<<"] = "__lshift__"; - gC2POperatorMapping[">>"] = "__rshift__"; - gC2POperatorMapping["&"] = "__and__"; - gC2POperatorMapping["&&"] = "__dand__"; - gC2POperatorMapping["|"] = "__or__"; - gC2POperatorMapping["||"] = "__dor__"; - gC2POperatorMapping["^"] = "__xor__"; - gC2POperatorMapping["~"] = "__invert__"; - gC2POperatorMapping[","] = "__comma__"; - gC2POperatorMapping["+="] = "__iadd__"; - gC2POperatorMapping["-="] = "__isub__"; - gC2POperatorMapping["*="] = "__imul__"; - gC2POperatorMapping["/="] = CPPYY__idiv__; - gC2POperatorMapping["%="] = "__imod__"; - gC2POperatorMapping["**="] = "__ipow__"; - gC2POperatorMapping["<<="] = "__ilshift__"; - gC2POperatorMapping[">>="] = "__irshift__"; - gC2POperatorMapping["&="] = "__iand__"; - gC2POperatorMapping["|="] = "__ior__"; - gC2POperatorMapping["^="] = "__ixor__"; - gC2POperatorMapping["=="] = "__eq__"; - gC2POperatorMapping["!="] = "__ne__"; - gC2POperatorMapping[">"] = "__gt__"; - gC2POperatorMapping["<"] = "__lt__"; - gC2POperatorMapping[">="] = "__ge__"; - gC2POperatorMapping["<="] = "__le__"; - - // the following type mappings are "exact" - gC2POperatorMapping["const char*"] = "__str__"; - gC2POperatorMapping["char*"] = "__str__"; - gC2POperatorMapping["const char *"] = gC2POperatorMapping["const char*"]; - gC2POperatorMapping["char *"] = gC2POperatorMapping["char*"]; - gC2POperatorMapping["int"] = "__int__"; - gC2POperatorMapping["long"] = CPPYY__long__; - gC2POperatorMapping["double"] = "__float__"; - - // the following type mappings are "okay"; the assumption is that they - // are not mixed up with the ones above or between themselves (and if - // they are, that it is done consistently) - gC2POperatorMapping["short"] = "__int__"; - gC2POperatorMapping["unsigned short"] = "__int__"; - gC2POperatorMapping["unsigned int"] = CPPYY__long__; - gC2POperatorMapping["unsigned long"] = CPPYY__long__; - gC2POperatorMapping["long long"] = CPPYY__long__; - gC2POperatorMapping["unsigned long long"] = CPPYY__long__; - gC2POperatorMapping["float"] = "__float__"; - - gC2POperatorMapping["->"] = "__follow__"; // not an actual python operator - gC2POperatorMapping["="] = "__assign__"; // id. - -#if PY_VERSION_HEX < 0x03000000 - gC2POperatorMapping["bool"] = "__cpp_nonzero__"; -#else - gC2POperatorMapping["bool"] = "__cpp_bool__"; -#endif - } - } initOperatorMapping_; - - inline std::string full_scope(const std::string& tpname) { - return tpname[0] == ':' ? tpname : "::"+tpname; - } - -} // unnamed namespace - - -//- public functions --------------------------------------------------------- -unsigned long CPyCppyy::PyLongOrInt_AsULong(PyObject* pyobject) -{ -// Convert to C++ unsigned long, with bounds checking, allow int -> ulong. - if (PyFloat_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long"); - return (unsigned long)-1; - } else if (pyobject == CPyCppyy::gDefaultObject) { - return (unsigned long)0; - } - - unsigned long ul = PyLong_AsUnsignedLong(pyobject); - if (ul == (unsigned long)-1 && PyErr_Occurred() && PyInt_Check(pyobject)) { - PyErr_Clear(); - long i = PyInt_AS_LONG(pyobject); - if (0 <= i) { - ul = (unsigned long)i; - } else { - PyErr_SetString(PyExc_ValueError, - "can\'t convert negative value to unsigned long"); - return (unsigned long)-1; - } - } - - return ul; -} - -//---------------------------------------------------------------------------- -PY_ULONG_LONG CPyCppyy::PyLongOrInt_AsULong64(PyObject* pyobject) -{ -// Convert to C++ unsigned long long, with bounds checking. - if (PyFloat_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long long"); - return -1; - } else if (pyobject == CPyCppyy::gDefaultObject) { - return (unsigned long)0; - } - - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(pyobject); - if (PyErr_Occurred() && PyInt_Check(pyobject)) { - PyErr_Clear(); - long i = PyInt_AS_LONG(pyobject); - if (0 <= i) { - ull = (PY_ULONG_LONG)i; - } else { - PyErr_SetString(PyExc_ValueError, - "can\'t convert negative value to unsigned long long"); - } - } - - return ull; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass( - PyObject* pyclass, const char* label, PyCFunction cfunc, int flags) -{ -// Add the given function to the class under name 'label'. - -// use list for clean-up (.so's are unloaded only at interpreter shutdown) - static std::list s_pymeths; - - s_pymeths.push_back(PyMethodDef()); - PyMethodDef* pdef = &s_pymeths.back(); - pdef->ml_name = const_cast(label); - pdef->ml_meth = cfunc; - pdef->ml_flags = flags; - pdef->ml_doc = nullptr; - - PyObject* func = PyCFunction_New(pdef, nullptr); - PyObject* name = CPyCppyy_PyText_InternFromString(pdef->ml_name); - PyObject* method = CustomInstanceMethod_New(func, nullptr, pyclass); - bool isOk = PyType_Type.tp_setattro(pyclass, name, method) == 0; - Py_DECREF(method); - Py_DECREF(name); - Py_DECREF(func); - - if (PyErr_Occurred()) - return false; - - if (!isOk) { - PyErr_Format(PyExc_TypeError, "could not add method %s", label); - return false; - } - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass(PyObject* pyclass, const char* label, const char* func) -{ -// Add the given function to the class under name 'label'. - PyObject* pyfunc = PyObject_GetAttrString(pyclass, const_cast(func)); - if (!pyfunc) - return false; - - PyObject* pylabel = CPyCppyy_PyText_InternFromString(const_cast(label)); - bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, pyfunc) == 0; - Py_DECREF(pylabel); - - Py_DECREF(pyfunc); - return isOk; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc) -{ -// Add the given function to the class under name 'label'. - CPPOverload* method = - (CPPOverload*)PyObject_GetAttrString(pyclass, const_cast(label)); - - if (!method || !CPPOverload_Check(method)) { - // not adding to existing CPPOverload; add callable directly to the class - if (PyErr_Occurred()) - PyErr_Clear(); - Py_XDECREF((PyObject*)method); - method = CPPOverload_New(label, pyfunc); - PyObject* pylabel = CPyCppyy_PyText_InternFromString(const_cast(label)); - bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, (PyObject*)method) == 0; - Py_DECREF(pylabel); - Py_DECREF(method); - return isOk; - } - - method->AdoptMethod(pyfunc); - - Py_DECREF(method); - return true; -} - - -//---------------------------------------------------------------------------- -static inline -CPyCppyy::PyCallable* BuildOperator(const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope, bool reverse=false) -{ -// Helper to find a function with matching signature in 'funcs'. - std::string opname = "operator"; - opname += op; - - Cppyy::TCppIndex_t idx = Cppyy::GetGlobalOperator(scope, lcname, rcname, opname); - if (idx == (Cppyy::TCppIndex_t)-1) - return nullptr; - - Cppyy::TCppMethod_t meth = Cppyy::GetMethod(scope, idx); - if (!reverse) - return new CPyCppyy::CPPFunction(scope, meth); - return new CPyCppyy::CPPReverseBinary(scope, meth); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindUnaryOperator(PyObject* pyclass, const char* op) -{ -// Find a callable matching named operator (op) and klass arguments in the global -// namespace or the klass' namespace. - - if (!CPPScope_Check(pyclass)) - return nullptr; - - CPPClass* klass = (CPPClass*)pyclass; - const std::string& lcname = Cppyy::GetScopedFinalName(klass->fCppType); - Cppyy::TCppScope_t scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)); - return FindBinaryOperator(lcname, "", op, scope, false); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator(PyObject* left, PyObject* right, - const char* op, Cppyy::TCppScope_t scope) -{ -// Find a callable matching the named operator (op) and the (left, right) -// arguments in the global or these objects' namespaces. - - bool reverse = false; - if (!CPPInstance_Check(left)) { - if (CPPInstance_Check(right)) - reverse = true; - else - return nullptr; - } - -// retrieve the class names to match the signature of any found global functions - const std::string& lcname = ClassName(left); - const std::string& rcname = ClassName(right); - return FindBinaryOperator(lcname, rcname, op, scope, reverse); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator( - const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope, bool reverse) -{ -// Find a global function with a matching signature; search __gnu_cxx, std::__1, -// and __cppyy_internal pro-actively (as there's AFAICS no way to unearth 'using' -// information). - - if (rcname == "" || lcname == "") - return nullptr; - - PyCallable* pyfunc = 0; - - if (!scope) { - // TODO: the following should remain sync with what clingwrapper does in its - // type remapper; there must be a better way? - if (lcname == "str" || lcname == "unicode" || lcname == "complex") - scope = Cppyy::GetScope("std"); - else scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)); - } - if (scope) - pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); - - if (!pyfunc && scope != Cppyy::gGlobalScope) // search in global scope anyway - pyfunc = BuildOperator(lcname, rcname, op, Cppyy::gGlobalScope, reverse); - - if (!pyfunc) { - // For GNU on clang, search the internal __gnu_cxx namespace for binary operators (is - // typically the case for STL iterators operator==/!=. - // TODO: only look in __gnu_cxx for iterators (and more generally: do lookups in the - // namespace where the class is defined - static Cppyy::TCppScope_t gnucxx = Cppyy::GetScope("__gnu_cxx"); - if (gnucxx) - pyfunc = BuildOperator(lcname, rcname, op, gnucxx, reverse); - } - - if (!pyfunc) { - // Same for clang (on Mac only?). TODO: find proper pre-processor magic to only use those - // specific namespaces that are actually around; although to be sure, this isn't expensive. - static Cppyy::TCppScope_t std__1 = Cppyy::GetScope("std::__1"); - - if (std__1 -#ifdef __APPLE__ - && lcname.find("__wrap_iter") == std::string::npos // wrapper call does not compile -#endif - ) { - pyfunc = BuildOperator(lcname, rcname, op, std__1, reverse); - } - } - - if (!pyfunc) { - // One more, mostly for Mac, but again not sure whether this is not a general issue. Some - // operators are declared as friends only in classes, so then they're not found in the - // global namespace, so this helper let's the compiler resolve the operator. - static Cppyy::TCppScope_t s_intern = Cppyy::GetScope("__cppyy_internal"); - if (s_intern) { - std::stringstream fname, proto; - if (strncmp(op, "==", 2) == 0) { fname << "is_equal<"; } - else if (strncmp(op, "!=", 2) == 0) { fname << "is_not_equal<"; } - else { fname << "not_implemented<"; } - fname << lcname << ", " << rcname << ">"; - proto << "const " << lcname << "&, const " << rcname; - Cppyy::TCppMethod_t method = Cppyy::GetMethodTemplate(s_intern, fname.str(), proto.str()); - if (method) pyfunc = new CPPFunction(s_intern, method); - } - } - - return pyfunc; -} - -//---------------------------------------------------------------------------- -static inline std::string AnnotationAsText(PyObject* pyobj) -{ - if (!CPyCppyy_PyText_Check(pyobj)) { - PyObject* pystr = PyObject_GetAttr(pyobj, CPyCppyy::PyStrings::gName); - if (!pystr) { - PyErr_Clear(); - pystr = PyObject_Str(pyobj); - } - - std::string str = CPyCppyy_PyText_AsString(pystr); - Py_DECREF(pystr); - return str; - } - return CPyCppyy_PyText_AsString(pyobj); -} - -static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, - CPyCppyy::Utility::ArgPreference pref, int* pcnt = nullptr) -{ -// Determine the appropriate C++ type for a given Python type; this is a helper because -// it can recurse if the type is list or tuple and needs matching on std::vector. - using namespace CPyCppyy; - using namespace CPyCppyy::Utility; - - if (tn == (PyObject*)&PyInt_Type) { - if (arg) { -#if PY_VERSION_HEX < 0x03000000 - long l = PyInt_AS_LONG(arg); - tmpl_name.append((l < INT_MIN || INT_MAX < l) ? "long" : "int"); -#else - PY_LONG_LONG ll = PyLong_AsLongLong(arg); - if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); - if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - tmpl_name.append("int"); // still out of range, will fail later - } else - tmpl_name.append("unsigned long long"); // since already failed long long - } else - tmpl_name.append((ll < INT_MIN || INT_MAX < ll) ? \ - ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") : "int"); -#endif - } else - tmpl_name.append("int"); - - return true; - } - -#if PY_VERSION_HEX < 0x03000000 - if (tn == (PyObject*)&PyLong_Type) { - if (arg) { - PY_LONG_LONG ll = PyLong_AsLongLong(arg); - if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); - if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - tmpl_name.append("long"); // still out of range, will fail later - } else - tmpl_name.append("unsigned long long"); // since already failed long long - } else - tmpl_name.append((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long"); - } else - tmpl_name.append("long"); - - return true; - } -#endif - - if (tn == (PyObject*)&PyFloat_Type) { - // special case for floats (Python-speak for double) if from argument (only) - tmpl_name.append(arg ? "double" : "float"); - return true; - } - -#if PY_VERSION_HEX < 0x03000000 - if (tn == (PyObject*)&PyString_Type) { -#else - if (tn == (PyObject*)&PyUnicode_Type) { -#endif - tmpl_name.append("std::string"); - return true; - } - - if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { - if (arg && PySequence_Size(arg)) { - std::string subtype{"std::initializer_list<"}; - PyObject* item = PySequence_GetItem(arg, 0); - ArgPreference subpref = pref == kValue ? kValue : kPointer; - bool ret = AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref); - if (ret) { - tmpl_name.append(subtype); - tmpl_name.append(">"); - } - Py_DECREF(item); - // Error occurred in inner call to AddTypeName, which means it has - // also set a TypeError. We return and let the error propagate. - if (!ret) { - return false; - } - } - - return true; - } - - if (CPPScope_Check(tn)) { - tmpl_name.append(full_scope(Cppyy::GetScopedFinalName(((CPPClass*)tn)->fCppType))); - if (arg) { - // try to specialize the type match for the given object - CPPInstance* pyobj = (CPPInstance*)arg; - if (CPPInstance_Check(pyobj)) { - if (pyobj->fFlags & CPPInstance::kIsRValue) - tmpl_name.append("&&"); - else { - if (pcnt) *pcnt += 1; - if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) - tmpl_name.push_back('*'); - else if (pref != kValue) - tmpl_name.push_back('&'); - } - } - } - - return true; - } - - if (tn == (PyObject*)&CPPOverload_Type) { - PyObject* tpName = arg ? \ - PyObject_GetAttr(arg, PyStrings::gCppName) : \ - CPyCppyy_PyText_FromString("void* (*)(...)"); - tmpl_name.append(CPyCppyy_PyText_AsString(tpName)); - Py_DECREF(tpName); - - return true; - } - - if (arg && PyCallable_Check(arg)) { - // annotated/typed Python function - PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); - if (annot) { - if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { - PyObject* ret = PyDict_GetItemString(annot, "return"); - if (ret) { - // dict is ordered, with the last value being the return type - std::ostringstream tpn; - tpn << (CPPScope_Check(ret) ? ClassName(ret) : AnnotationAsText(ret)) - << " (*)("; - - PyObject* values = PyDict_Values(annot); - for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values)-1); ++i) { - if (i) tpn << ", "; - PyObject* item = PyList_GET_ITEM(values, i); - tpn << (CPPScope_Check(item) ? full_scope(ClassName(item)) : AnnotationAsText(item)); - } - Py_DECREF(values); - - tpn << ')'; - tmpl_name.append(tpn.str()); - - Py_DECREF(annot); - return true; - - } else - PyErr_Clear(); - } - Py_DECREF(annot); - } else - PyErr_Clear(); - - // ctypes function pointer - PyObject* argtypes = nullptr; - PyObject* ret = nullptr; - if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) && (ret = PyObject_GetAttrString(arg, "restype"))) { - std::ostringstream tpn; - PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); - tpn << CT2CppNameS(pytc, false) - << " (*)("; - Py_DECREF(pytc); - - for (Py_ssize_t i = 0; i < PySequence_Length(argtypes); ++i) { - if (i) tpn << ", "; - PyObject* item = PySequence_GetItem(argtypes, i); - pytc = PyObject_GetAttr(item, PyStrings::gCTypesType); - tpn << CT2CppNameS(pytc, false); - Py_DECREF(pytc); - Py_DECREF(item); - } - - tpn << ')'; - tmpl_name.append(tpn.str()); - - Py_DECREF(ret); - Py_DECREF(argtypes); - - return true; - - } else { - PyErr_Clear(); - Py_XDECREF(ret); - Py_XDECREF(argtypes); - } - - // callable C++ type (e.g. std::function) - PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); - if (tpName) { - const char* cname = CPyCppyy_PyText_AsString(tpName); - tmpl_name.append(CPPScope_Check(arg) ? full_scope(cname) : cname); - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { - PyObject* tpName = PyObject_GetAttr(tn, nn); - if (tpName) { - tmpl_name.append(CPyCppyy_PyText_AsString(tpName)); - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { - // last ditch attempt, works for things like int values; since this is a - // source of errors otherwise, it is limited to specific types and not - // generally used (str(obj) can print anything ...) - PyObject* pystr = PyObject_Str(tn); - tmpl_name.append(CPyCppyy_PyText_AsString(pystr)); - Py_DECREF(pystr); - return true; - } - -// Give up with a TypeError - -// Try to get a readable representation of the argument - PyObject *repr = PyObject_Repr(tn); - const char *repr_cstr = repr ? PyUnicode_AsUTF8(repr) : ""; - - PyErr_Format(PyExc_TypeError, "could not construct C++ name from template argument %s", repr_cstr); - - Py_XDECREF(repr); - return false; -} - -std::string CPyCppyy::Utility::ConstructTemplateArgs( - PyObject* pyname, PyObject* tpArgs, PyObject* args, ArgPreference pref, int argoff, int* pcnt) -{ -// Helper to construct the "" part of a templated name (either -// for a class or method lookup - bool justOne = !PyTuple_CheckExact(tpArgs); - -// Note: directly appending to string is a lot faster than stringstream - std::string tmpl_name; - tmpl_name.reserve(128); - if (pyname) - tmpl_name.append(CPyCppyy_PyText_AsString(pyname)); - tmpl_name.push_back('<'); - - if (pcnt) *pcnt = 0; // count number of times 'pref' is used - - Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); - for (int i = argoff; i < nArgs; ++i) { - // add type as string to name - PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); - if (CPyCppyy_PyText_Check(tn)) { - tmpl_name.append(CPyCppyy_PyText_AsString(tn)); - // some common numeric types (separated out for performance: checking for - // __cpp_name__ and/or __name__ is rather expensive) - } else { - if (!AddTypeName(tmpl_name, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, pcnt)) { - return ""; - } - } - - // add a comma, as needed (no space as internally, final names don't have them) - if (i != nArgs-1) - tmpl_name.push_back(','); - } - -// close template name - tmpl_name.push_back('>'); - - return tmpl_name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp) -{ -// helper to convert ctypes' `_type_` info to the equivalent C++ name - const char* name = ""; - if (CPyCppyy_PyText_Check(pytc)) { - char tc = ((char*)CPyCppyy_PyText_AsString(pytc))[0]; - switch (tc) { - case '?': name = "bool"; break; - case 'c': name = "char"; break; - case 'b': name = "char"; break; - case 'B': name = "unsigned char"; break; - case 'h': name = "short"; break; - case 'H': name = "unsigned short"; break; - case 'i': name = "int"; break; - case 'I': name = "unsigned int"; break; - case 'l': name = "long"; break; - case 'L': name = "unsigned long"; break; - case 'q': name = "long long"; break; - case 'Q': name = "unsigned long long"; break; - case 'f': name = "float"; break; - case 'd': name = "double"; break; - case 'g': name = "long double"; break; - case 'z': name = "const char*"; break; - default: name = (allow_voidp ? "void*" : nullptr); break; - } - } - - return name; -} - -//---------------------------------------------------------------------------- -static inline bool check_scope(const std::string& name) -{ - return (bool)Cppyy::GetScope(CPyCppyy::TypeManip::clean_type(name, false)); -} - -void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, - const std::vector& argtypes, std::ostringstream& code) -{ -// Generate function setup to be used in callbacks (wrappers and overrides). - int nArgs = (int)argtypes.size(); - -// return value and argument type converters - bool isVoid = retType == "void"; - if (!isVoid) - code << " CPYCPPYY_STATIC std::unique_ptr> " - "retconv{CPyCppyy::CreateConverter(\"" - << retType << "\"), CPyCppyy::DestroyConverter};\n"; - std::vector arg_is_ptr; - if (nArgs) { - arg_is_ptr.resize(nArgs); - code << " CPYCPPYY_STATIC std::vector>> argcvs;\n" - << " if (argcvs.empty()) {\n" - << " argcvs.reserve(" << nArgs << ");\n"; - for (int i = 0; i < nArgs; ++i) { - arg_is_ptr[i] = false; - code << " argcvs.emplace_back(CPyCppyy::CreateConverter(\""; - const std::string& at = argtypes[i]; - const std::string& res_at = Cppyy::ResolveName(at); - const std::string& cpd = TypeManip::compound(res_at); - if (!cpd.empty() && check_scope(res_at)) { - // in case of a pointer, the original argument needs to be used to ensure - // the pointer-value remains comparable - // - // in case of a reference, there is no extra indirection on the C++ side as - // would be when converting a data member, so adjust the converter - arg_is_ptr[i] = cpd.back() == '*'; - if (arg_is_ptr[i] || cpd.back() == '&') { - code << res_at.substr(0, res_at.size()-1); - } else code << at; - } else - code << at; - code << "\"), CPyCppyy::DestroyConverter);\n"; - } - code << " }\n"; - } - -// declare return value (TODO: this does not work for most non-builtin values) - if (!isVoid) - code << " " << retType << " ret{};\n"; - -// acquire GIL - code << " PyGILState_STATE state = PyGILState_Ensure();\n"; - -// build argument tuple if needed - if (nArgs) { - code << " std::vector pyargs;\n"; - code << " pyargs.reserve(" << nArgs << ");\n" - << " try {\n"; - for (int i = 0; i < nArgs; ++i) { - code << " pyargs.emplace_back(argcvs[" << i << "]->FromMemory((void*)"; - if (!arg_is_ptr[i]) code << '&'; - code << "arg" << i << "));\n" - << " if (!pyargs.back()) throw " << i << ";\n"; - } - code << " } catch(int) {\n" - << " for (auto pyarg : pyargs) Py_XDECREF(pyarg);\n" - << " CPyCppyy::PyException pyexc; PyGILState_Release(state); throw pyexc;\n" - << " }\n"; - } -} - -void CPyCppyy::Utility::ConstructCallbackReturn(const std::string& retType, int nArgs, std::ostringstream& code) -{ -// Generate code for return value conversion and error handling. - bool isVoid = retType == "void"; - bool isPtr = Cppyy::ResolveName(retType).back() == '*'; - - if (nArgs) - code << " for (auto pyarg : pyargs) Py_DECREF(pyarg);\n"; - code << " bool cOk = (bool)pyresult;\n" - " if (pyresult) {\n"; - if (isPtr) { - // If the return type is a CPPInstance, owned by Python, and the ref-count down - // to 1, the return will hold a dangling pointer, so set it to nullptr instead. - code << " if (!CPyCppyy::Instance_IsLively(pyresult))\n" - " ret = nullptr;\n" - " else {\n"; - } - code << (isVoid ? "" : " cOk = retconv->ToMemory(pyresult, (void*)&ret);\n") - << " Py_DECREF(pyresult);\n }\n"; - if (isPtr) code << " }\n"; - code << " if (!cOk) {" // assume error set when converter failed -// TODO: On Windows, throwing a C++ exception here makes the code hang; leave -// the error be which allows at least one layer of propagation -#ifdef _WIN32 - " /* do nothing */ }\n" -#else - " CPyCppyy::PyException pyexc; PyGILState_Release(state); throw pyexc; }\n" -#endif - " PyGILState_Release(state);\n" - " return"; - code << (isVoid ? ";\n }\n" : " ret;\n }\n"); -} - - -//---------------------------------------------------------------------------- -static std::map sStdFuncLookup; -static std::map sStdFuncMakerLookup; -PyObject* CPyCppyy::Utility::FuncPtr2StdFunction( - const std::string& retType, const std::string& signature, void* address) -{ -// Convert a function pointer to an equivalent std::function<> object. - static int maker_count = 0; - - auto pf = sStdFuncLookup.find(address); - if (pf != sStdFuncLookup.end()) { - Py_INCREF(pf->second); - return pf->second; - } - - PyObject* maker = nullptr; - - auto pm = sStdFuncMakerLookup.find(retType+signature); - if (pm == sStdFuncMakerLookup.end()) { - std::ostringstream fname; - fname << "ptr2func" << ++maker_count; - - std::ostringstream code; - code << "namespace __cppyy_internal { std::function<" - << retType << signature << "> " << fname.str() - << "(intptr_t faddr) { return (" << retType << "(*)" << signature << ")faddr;} }"; - - if (!Cppyy::Compile(code.str())) { - PyErr_SetString(PyExc_TypeError, "conversion to std::function failed"); - return nullptr; - } - - PyObject* pyscope = CreateScopeProxy("__cppyy_internal"); - maker = PyObject_GetAttrString(pyscope, fname.str().c_str()); - Py_DECREF(pyscope); - if (!maker) - return nullptr; - - // cache the new maker (TODO: does it make sense to use weakrefs?) - sStdFuncMakerLookup[retType+signature] = maker; - } else - maker = pm->second; - - PyObject* args = PyTuple_New(1); - PyTuple_SET_ITEM(args, 0, PyLong_FromLongLong((intptr_t)address)); - PyObject* func = PyObject_Call(maker, args, NULL); - Py_DECREF(args); - - if (func) { // prevent moving this func object, since then it can not be reused - ((CPPInstance*)func)->fFlags |= CPPInstance::kIsLValue; - Py_INCREF(func); // TODO: use weak? The C++ maker doesn't go away either - sStdFuncLookup[address] = func; - } - - return func; -} - - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::InitProxy(PyObject* module, PyTypeObject* pytype, const char* name) -{ -// Initialize a proxy class for use by python, and add it to the module. - -// finalize proxy type - if (PyType_Ready(pytype) < 0) - return false; - -// add proxy type to the given module - Py_INCREF(pytype); // PyModule_AddObject steals reference - if (PyModule_AddObject(module, (char*)name, (PyObject*)pytype) < 0) { - Py_DECREF(pytype); - return false; - } - -// declare success - return true; -} - -//---------------------------------------------------------------------------- -std::map const &CPyCppyy::Utility::TypecodeMap() -{ - // See https://docs.python.org/3/library/array.html#array.array - static std::map typecodeMap{ - {"char", 'b'}, - {"unsigned char", 'B'}, -#if PY_VERSION_HEX < 0x03100000 - {"wchar_t", 'u'}, -#endif -#if PY_VERSION_HEX >= 0x030d0000 - {"Py_UCS4", 'w'}, -#endif - {"short", 'h'}, - {"unsigned short", 'H'}, - {"int", 'i'}, - {"unsigned int", 'I'}, - {"long", 'l'}, - {"unsigned long", 'L'}, - {"long long", 'q'}, - {"unsigned long long", 'Q'}, - {"float", 'f'}, - {"double", 'd'} - }; - return typecodeMap; -} - -//---------------------------------------------------------------------------- -Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, bool check) -{ -// Retrieve a linear buffer pointer from the given pyobject. - -// special case: don't handle character strings here (yes, they're buffers, but not quite) - if (PyBytes_Check(pyobject) || PyUnicode_Check(pyobject)) - return 0; - -// special case: bytes array - if ((!check || tc == '*' || tc == 'B') && PyByteArray_CheckExact(pyobject)) { - buf = PyByteArray_AS_STRING(pyobject); - return PyByteArray_GET_SIZE(pyobject); - } - -// new-style buffer interface - if (PyObject_CheckBuffer(pyobject)) { - if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) - return 0; // PyObject_GetBuffer() crashes on some platforms for some zero-sized seqeunces - PyErr_Clear(); - Py_buffer bufinfo; - memset(&bufinfo, 0, sizeof(Py_buffer)); - if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { - if (tc == '*' || strchr(bufinfo.format, tc) - // if `long int` and `int` are the same size (on Windows and 32bit Linux, - // for example), `ctypes` isn't too picky about the type format, so make - // sure both integer types pass the type check - || (sizeof(long int) == sizeof(int) && ((tc == 'I' && strchr(bufinfo.format, 'L')) || - (tc == 'i' && strchr(bufinfo.format, 'l')))) - // complex float is 'Zf' in bufinfo.format, but 'z' in single char - || (tc == 'z' && strstr(bufinfo.format, "Zf")) - // allow 'signed char' ('b') from array to pass through '?' (bool as from struct) - || (tc == '?' && strchr(bufinfo.format, 'b')) - ) { - buf = bufinfo.buf; - - if (check && bufinfo.itemsize != size) { - PyErr_Format(PyExc_TypeError, - "buffer itemsize (%ld) does not match expected size (%d)", bufinfo.itemsize, size); - PyBuffer_Release(&bufinfo); - return 0; - } - - Py_ssize_t buflen = 0; - if (buf && bufinfo.ndim == 0) - buflen = bufinfo.len/bufinfo.itemsize; - else if (buf && bufinfo.ndim == 1) - buflen = bufinfo.shape ? bufinfo.shape[0] : bufinfo.len/bufinfo.itemsize; - PyBuffer_Release(&bufinfo); - if (buflen) - return buflen; - } else { - // have buf, but format mismatch: bail out now, otherwise the old - // code will return based on itemsize match - PyBuffer_Release(&bufinfo); - return 0; - } - } else if (bufinfo.obj) - PyBuffer_Release(&bufinfo); - PyErr_Clear(); - } - -// attempt to retrieve pointer through old-style buffer interface - PyBufferProcs* bufprocs = Py_TYPE(pyobject)->tp_as_buffer; - - PySequenceMethods* seqmeths = Py_TYPE(pyobject)->tp_as_sequence; - if (seqmeths != 0 && bufprocs != 0 -#if PY_VERSION_HEX < 0x03000000 - && bufprocs->bf_getwritebuffer != 0 - && (*(bufprocs->bf_getsegcount))(pyobject, 0) == 1 -#else - && bufprocs->bf_getbuffer != 0 -#endif - ) { - - // get the buffer -#if PY_VERSION_HEX < 0x03000000 - Py_ssize_t buflen = (*(bufprocs->bf_getwritebuffer))(pyobject, 0, &buf); -#else - Py_buffer bufinfo; - (*(bufprocs->bf_getbuffer))(pyobject, &bufinfo, PyBUF_WRITABLE); - buf = (char*)bufinfo.buf; - Py_ssize_t buflen = bufinfo.len; - PyBuffer_Release(&bufinfo); -#endif - - if (buf && check == true) { - // determine buffer compatibility (use "buf" as a status flag) - PyObject* pytc = tc != '*' ? PyObject_GetAttr(pyobject, PyStrings::gTypeCode) : nullptr; - if (pytc != 0) { // for array objects - char cpytc = CPyCppyy_PyText_AsString(pytc)[0]; - if (!(cpytc == tc || (tc == '?' && cpytc == 'b'))) - buf = 0; // no match - Py_DECREF(pytc); - } else if (seqmeths->sq_length && - (int)(buflen/(*(seqmeths->sq_length))(pyobject)) == size) { - // this is a gamble ... may or may not be ok, but that's for the user - PyErr_Clear(); - } else if (buflen == size) { - // also a gamble, but at least 1 item will fit into the buffer, so very likely ok ... - PyErr_Clear(); - } else { - buf = 0; // not compatible - - // clarify error message - auto error = FetchPyError(); - PyObject* pyvalue2 = CPyCppyy_PyText_FromFormat( - (char*)"%s and given element size (%ld) do not match needed (%d)", - CPyCppyy_PyText_AsString(error.fValue.get()), - seqmeths->sq_length ? (long)(buflen/(*(seqmeths->sq_length))(pyobject)) : (long)buflen, - size); - error.fValue.reset(pyvalue2); - RestorePyError(error); - } - } - - if (!buf) return 0; - return buflen/(size ? size : 1); - } - - return 0; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::Utility::MapOperatorName(const std::string& name, bool bTakesParams, bool* stubbed) -{ -// Map the given C++ operator name on the python equivalent. - if (8 < name.size() && name.substr(0, 8) == "operator") { - std::string op = name.substr(8, std::string::npos); - - // stripping ... - std::string::size_type start = 0, end = op.size(); - while (start < end && isspace(op[start])) ++start; - while (start < end && isspace(op[end-1])) --end; - op = op.substr(start, end - start); - - // certain operators should be removed completely (e.g. operator delete & friends) - if (gOpRemove.find(op) != gOpRemove.end()) - return ""; - - // check first if none, to prevent spurious deserializing downstream - TC2POperatorMapping_t::iterator pop = gC2POperatorMapping.find(op); - if (pop == gC2POperatorMapping.end() && gOpSkip.find(op) == gOpSkip.end()) { - op = Cppyy::ResolveName(op); - pop = gC2POperatorMapping.find(op); - } - - // map C++ operator to python equivalent, or made up name if no equivalent exists - if (pop != gC2POperatorMapping.end()) { - return pop->second; - - } else if (op == "*") { - // dereference v.s. multiplication of two instances - if (!bTakesParams) return "__deref__"; - if (stubbed) *stubbed = true; - return "__mul__"; - - } else if (op == "/") { - // no unary, but is stubbed - return CPPYY__div__; - - } else if (op == "+") { - // unary positive v.s. addition of two instances - if (!bTakesParams) return "__pos__"; - if (stubbed) *stubbed = true; - return "__add__"; - - } else if (op == "-") { - // unary negative v.s. subtraction of two instances - if (!bTakesParams) return "__neg__"; - if (stubbed) *stubbed = true; - return "__sub__"; - - } else if (op == "++") { - // prefix v.s. postfix increment - return bTakesParams ? "__postinc__" : "__preinc__"; - - } else if (op == "--") { - // prefix v.s. postfix decrement - return bTakesParams ? "__postdec__" : "__predec__"; - } - - } - -// might get here, as not all operator methods are handled (new, delete, etc.) - return name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::Utility::ClassName(PyObject* pyobj) -{ -// Retrieve the class name from the given Python instance. - std::string clname = ""; - PyObject* pyclass = (PyObject*)Py_TYPE(pyobj); - PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gCppName); - if (!pyname) { - PyErr_Clear(); - pyname = PyObject_GetAttr(pyclass, PyStrings::gName); - } - - if (pyname) { - clname = CPyCppyy_PyText_AsString(pyname); - Py_DECREF(pyname); - } else - PyErr_Clear(); - return clname; -} - -//---------------------------------------------------------------------------- -static std::set sIteratorTypes; -bool CPyCppyy::Utility::IsSTLIterator(const std::string& classname) -{ -// attempt to recognize STL iterators (TODO: probably belongs in the backend), using -// a couple of common container classes with different iterator protocols (note that -// mapping iterators are handled separately in the pythonizations) as exemplars (the -// actual, resolved, names will be compiler-specific) that are picked b/c they are -// baked into the CoreLegacy dictionary - if (sIteratorTypes.empty()) { - std::string tt = "::"; - for (auto c : {"std::vector", "std::list", "std::deque"}) { - for (auto i : {"iterator", "const_iterator"}) { - const std::string& itname = Cppyy::ResolveName(c+tt+i); - auto pos = itname.find('<'); - if (pos != std::string::npos) - sIteratorTypes.insert(itname.substr(0, pos)); - } - } - } - - auto pos = classname.find('<'); - if (pos != std::string::npos) - return sIteratorTypes.find(classname.substr(0, pos)) != sIteratorTypes.end(); - return false; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::Utility::PyOperators::~PyOperators() -{ - Py_XDECREF(fEq); - Py_XDECREF(fNe); - Py_XDECREF(fLAdd); Py_XDECREF(fRAdd); - Py_XDECREF(fSub); - Py_XDECREF(fLMul); Py_XDECREF(fRMul); - Py_XDECREF(fDiv); - Py_XDECREF(fHash); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Utility::PyErr_Occurred_WithGIL() -{ -// Re-acquire the GIL before calling PyErr_Occurred() in case it has been -// released; note that the p2.2 code assumes that there are no callbacks in -// C++ to python (or at least none returning errors). -#if PY_VERSION_HEX >= 0x02030000 - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* e = PyErr_Occurred(); - PyGILState_Release(gstate); -#else - if (PyThreadState_GET()) - return PyErr_Occurred(); - PyObject* e = 0; -#endif - - return e; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::Utility::PyError_t CPyCppyy::Utility::FetchPyError() -{ - // create a PyError_t RAII object that will capture and store the exception data - CPyCppyy::Utility::PyError_t error{}; -#if PY_VERSION_HEX >= 0x030c0000 - error.fValue.reset(PyErr_GetRaisedException()); -#else - PyObject *pytype = nullptr; - PyObject *pyvalue = nullptr; - PyObject *pytrace = nullptr; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - error.fType.reset(pytype); - error.fValue.reset(pyvalue); - error.fTrace.reset(pytrace); -#endif - return error; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::Utility::RestorePyError(CPyCppyy::Utility::PyError_t &error) -{ -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(error.fValue.release()); -#else - PyErr_Restore(error.fType.release(), error.fValue.release(), error.fTrace.release()); -#endif -} - - -//---------------------------------------------------------------------------- -size_t CPyCppyy::Utility::FetchError(std::vector& errors, bool is_cpp) -{ -// Fetch the current python error, if any, and store it for future use. - if (PyErr_Occurred()) { - errors.emplace_back(FetchPyError()); - errors.back().fIsCpp = is_cpp; - } - return errors.size(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, PyObject* topmsg, PyObject* defexc) -{ -// Use the collected exceptions to build up a detailed error log. - if (errors.empty()) { - // should not happen ... - PyErr_SetString(defexc, CPyCppyy_PyText_AsString(topmsg)); - Py_DECREF(topmsg); - return; - } - -// if a _single_ exception was thrown from C++, assume it has priority (see below) - PyError_t* unique_from_cpp = nullptr; - for (auto& e : errors) { - if (e.fIsCpp) { - if (!unique_from_cpp) - unique_from_cpp = &e; - else { - // two C++ exceptions, resort to default behavior - unique_from_cpp = nullptr; - break; - } - } - } - - if (unique_from_cpp) { - // report only this error; the idea here is that all other errors come from - // the bindings (e.g. argument conversion errors), while the exception from - // C++ means that it originated from an otherwise successful call - - // bind the original C++ object, rather than constructing from topmsg, as it - // is expected to have informative state - RestorePyError(*unique_from_cpp); - } else { - // try to consolidate Python exceptions, otherwise select default - PyObject* exc_type = nullptr; - for (auto& e : errors) { -#if PY_VERSION_HEX >= 0x030c0000 - PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); -#else - PyObject* pytype = e.fType.get(); -#endif - if (!exc_type) exc_type = pytype; - else if (exc_type != pytype) { - exc_type = defexc; - break; - } - } - - // add the details to the topmsg - PyObject* separator = CPyCppyy_PyText_FromString("\n "); - for (auto& e : errors) { - PyObject *pyvalue = e.fValue.get(); - CPyCppyy_PyText_Append(&topmsg, separator); - if (CPyCppyy_PyText_Check(pyvalue)) { - CPyCppyy_PyText_Append(&topmsg, pyvalue); - } else if (pyvalue) { - PyObject* excstr = PyObject_Str(pyvalue); - if (!excstr) { - PyErr_Clear(); - excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); - } - CPyCppyy_PyText_AppendAndDel(&topmsg, excstr); - } else { - CPyCppyy_PyText_AppendAndDel(&topmsg, - CPyCppyy_PyText_FromString("unknown exception")); - } - } - - Py_DECREF(separator); - - // set the python exception - PyErr_SetString(exc_type, CPyCppyy_PyText_AsString(topmsg)); - } - - Py_DECREF(topmsg); -} - - -//---------------------------------------------------------------------------- -static bool includesDone = false; -bool CPyCppyy::Utility::IncludePython() -{ -// setup Python API for callbacks - if (!includesDone) { - bool okay = Cppyy::Compile( - // basic API (converters etc.) - "#include \"CPyCppyy/API.h\"\n" - - // utilities from the CPyCppyy public API - "#include \"CPyCppyy/DispatchPtr.h\"\n" - "#include \"CPyCppyy/PyException.h\"\n" - ); - includesDone = okay; - } - - return includesDone; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h deleted file mode 100644 index dfa2e5478f6a0..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef CPYCPPYY_UTILITY_H -#define CPYCPPYY_UTILITY_H - -// Standard -#include -#include -#include -#include - - -namespace CPyCppyy { - -class PyCallable; - -#if PY_VERSION_HEX < 0x030b0000 -extern bool gDictLookupActive; -#endif - -// additional converter functions -unsigned long PyLongOrInt_AsULong(PyObject* pyobject); -PY_ULONG_LONG PyLongOrInt_AsULong64(PyObject* pyobject); - -namespace Utility { - -// convenience functions for adding methods to classes -bool AddToClass(PyObject* pyclass, const char* label, PyCFunction cfunc, - int flags = METH_VARARGS); -bool AddToClass(PyObject* pyclass, const char* label, const char* func); -bool AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc); - -// helpers for dynamically constructing operators -PyCallable* FindUnaryOperator(PyObject* pyclass, const char* op); -PyCallable* FindBinaryOperator(PyObject* left, PyObject* right, - const char* op, Cppyy::TCppScope_t scope = 0); -PyCallable* FindBinaryOperator(const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope = 0, bool reverse = false); - -// helper for template classes and methods -enum ArgPreference { kNone, kPointer, kReference, kValue }; -std::string ConstructTemplateArgs( - PyObject* pyname, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr); -std::string CT2CppNameS(PyObject* pytc, bool allow_voidp); -inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp) -{ - const std::string& name = CT2CppNameS(pytc, allow_voidp); - if (!name.empty()) { - if (name == "const char*") cpd = ""; -#if PY_VERSION_HEX < 0x03000000 - return PyString_FromString((std::string{name}+cpd).c_str()); -#else - return PyUnicode_FromString((std::string{name}+cpd).c_str()); -#endif - } - return nullptr; -} - -// helper for generating callbacks -void ConstructCallbackPreamble(const std::string& retType, - const std::vector& argtypes, std::ostringstream& code); -void ConstructCallbackReturn(const std::string& retType, int nArgs, std::ostringstream& code); - -// helper for function pointer conversions -PyObject* FuncPtr2StdFunction(const std::string& retType, const std::string& signature, void* address); - -// initialize proxy type objects -bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); - -std::map const &TypecodeMap(); - -// retrieve the memory buffer from pyobject, return buflength, tc (optional) is python -// array.array type code, size is type size, buf will point to buffer, and if check is -// true, some heuristics will be applied to check buffer compatibility with the type -Py_ssize_t GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, bool check = true); - -// data/operator mappings -std::string MapOperatorName(const std::string& name, bool bTakesParames, bool* stubbed = nullptr); - -struct PyOperators { - PyOperators() : fEq(nullptr), fNe(nullptr), fLt(nullptr), fLe(nullptr), fGt(nullptr), fGe(nullptr), - fLAdd(nullptr), fRAdd(nullptr), fSub(nullptr), fLMul(nullptr), fRMul(nullptr), fDiv(nullptr), - fHash(nullptr) {} - ~PyOperators(); - - PyObject* fEq; - PyObject* fNe; - PyObject *fLt, *fLe; - PyObject *fGt, *fGe; - PyObject *fLAdd, *fRAdd; - PyObject* fSub; - PyObject *fLMul, *fRMul; - PyObject* fDiv; - PyObject* fHash; -}; - -// meta information -std::string ClassName(PyObject* pyobj); -bool IsSTLIterator(const std::string& classname); - -// for threading: save call to PyErr_Occurred() -PyObject* PyErr_Occurred_WithGIL(); - -// helpers for collecting/maintaining python exception data -struct PyError_t { - struct PyObjectDeleter { - void operator()(PyObject *obj) { Py_XDECREF(obj); } - }; -#if PY_VERSION_HEX < 0x030c0000 - std::unique_ptr fType; - std::unique_ptr fTrace; -#endif - std::unique_ptr fValue; - bool fIsCpp = false; -}; - -PyError_t FetchPyError(); -void RestorePyError(PyError_t &error); - -size_t FetchError(std::vector&, bool is_cpp = false); -void SetDetailedException( - std::vector&& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); - -// setup Python API for callbacks -bool IncludePython(); - -} // namespace Utility - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_UTILITY_H diff --git a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt deleted file mode 100644 index 8b142b4d30a60..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -add_library(cppyy_backend STATIC clingwrapper/src/clingwrapper.cxx) -target_link_libraries(cppyy_backend Core) -if(NOT MSVC) - target_compile_options(cppyy_backend PRIVATE -fPIC) -endif() diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/LICENSE.txt b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/LICENSE.txt deleted file mode 100644 index 838eb22c670ef..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/LICENSE.txt +++ /dev/null @@ -1,55 +0,0 @@ -Copyright (c) 2017-2021, The Regents of the University of California, -through Lawrence Berkeley National Laboratory (subject to receipt of -any required approvals from the U.S. Dept. of Energy). All rights -reserved. Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the following -conditions are met: - -(1) Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -(2) Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -(3) Neither the name of the University of California, Lawrence Berkeley -National Laboratory, U.S. Dept. of Energy nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - -You are under no obligation whatsoever to provide any bug fixes, -patches, or upgrades to the features, functionality or performance of -the source code ("Enhancements") to anyone; however, if you choose to -make your Enhancements available either publicly, or directly to -Lawrence Berkeley National Laboratory, without imposing a separate -written license agreement for such Enhancements, then you hereby grant -the following license: a non-exclusive, royalty-free perpetual license -to install, use, modify, prepare derivative works, incorporate into -other computer software, distribute, and sublicense such Enhancements -or derivative works thereof, in binary and source code form. - - -Additional copyright holders ----------------------------- - -In addition to LBNL/UC Berkeley, this package contains files copyrighted by -one or more of the following people and organizations, and licensed under -the same conditions (except for some compatible licenses as retained in the -source code): - - CERN - Antonio Cuni - Aditi Dutta - Shaheed Haque - Aaron Jomy - Baidyanath Kundu diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h deleted file mode 100644 index edd7ca522c864..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef CPYCPPYY_CALLCONTEXT_H -#define CPYCPPYY_CALLCONTEXT_H - -// Standard -#include - -//Bindings -#include "cpp_cppyy.h" - -//ROOT -#include "Rtypes.h" - -namespace CPyCppyy { - -// general place holder for function parameters - struct Parameter { - union Value { - bool fBool; - int8_t fInt8; - uint8_t fUInt8; - short fShort; - unsigned short fUShort; - int fInt; - unsigned int fUInt; - long fLong; - intptr_t fIntPtr; - unsigned long fULong; - long long fLLong; - unsigned long long fULLong; - int64_t fInt64; - uint64_t fUInt64; - float fFloat; - double fDouble; - long double fLDouble; - void* fVoidp; - } fValue; - void* fRef; - char fTypeCode; - }; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CALLCONTEXT_H diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx deleted file mode 100644 index 86954bba721cd..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ /dev/null @@ -1,2668 +0,0 @@ -#ifndef WIN32 -#ifndef _CRT_SECURE_NO_WARNINGS -// silence warnings about getenv, strncpy, etc. -#define _CRT_SECURE_NO_WARNINGS -#endif -#endif - -// Bindings -#include "precommondefs.h" -#include "cpp_cppyy.h" -#include "callcontext.h" - -// ROOT -#include "TBaseClass.h" -#include "TClass.h" -#include "TClassRef.h" -#include "TClassTable.h" -#include "TClassEdit.h" -#include "TCollection.h" -#include "TDataMember.h" -#include "TDataType.h" -#include "TEnum.h" -#include "TEnumConstant.h" -#include "TEnv.h" -#include "TError.h" -#include "TException.h" -#include "TFunction.h" -#include "TFunctionTemplate.h" -#include "TGlobal.h" -#include "THashList.h" -#include "TInterpreter.h" -#include "TList.h" -#include "TListOfDataMembers.h" -#include "TListOfEnums.h" -#include "TMethod.h" -#include "TMethodArg.h" -#include "TROOT.h" -#include "TSystem.h" -#include "TThread.h" - -// Standard -#include -#include // for std::count, std::remove -#include -#include -#include -#include -#include -#include -#include -#include // for getenv -#include -#include - -#if defined(__arm64__) -#include -#include -#define CLING_CATCH_UNCAUGHT_ \ -ARMUncaughtException guard; \ -if (setjmp(gExcJumBuf) == 0) { -#define _CLING_CATCH_UNCAUGHT \ -} else { \ - if (!std::getenv("CPPYY_UNCAUGHT_QUIET")) \ - std::cerr << "Warning: uncaught exception in JIT is rethrown; resources may leak" \ - << " (suppress with \"CPPYY_UNCAUGHT_QUIET=1\")" << std::endl;\ - std::rethrow_exception(std::current_exception()); \ -} -#else -#define CLING_CATCH_UNCAUGHT_ -#define _CLING_CATCH_UNCAUGHT -#endif - -#if 0 -// force std::string and allocator instantation, otherwise Clang 13+ fails to JIT -// symbols that rely on some private helpers (e.g. _M_use_local_data) when used in -// in conjunction with the PCH; hat tip: -// https://github.com/sxs-collaboration/spectre/pull/5222/files#diff-093aadf224e5fee0d33ae1810f2f1c23304fb5ca398ba6b96c4e7918e0811729 -#if defined(__GLIBCXX__) && __GLIBCXX__ >= 20220506 -template class std::allocator; -template class std::basic_string; -template class std::basic_string; -#endif - -using namespace CppyyLegacy; -#endif - -// temp -#include -typedef CPyCppyy::Parameter Parameter; -// --temp - -#if 0 -#if defined(__arm64__) -namespace { - -// Trap uncaught exceptions and longjump back to the point of JIT wrapper entry -jmp_buf gExcJumBuf; - -void arm_uncaught_exception() { - longjmp(gExcJumBuf, 1); -} - -class ARMUncaughtException { - std::terminate_handler m_Handler; -public: - ARMUncaughtException() { m_Handler = std::set_terminate(arm_uncaught_exception); } - ~ARMUncaughtException() { std::set_terminate(m_Handler); } -}; - -} // unnamed namespace -#endif // __arm64__ -#endif - -// small number that allows use of stack for argument passing -const int SMALL_ARGS_N = 8; - -// convention to pass flag for direct calls (similar to Python's vector calls) -#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) -static inline size_t CALL_NARGS(size_t nargs) { - return nargs & ~DIRECT_CALL; -} - -// data for life time management --------------------------------------------- -typedef std::vector ClassRefs_t; -static ClassRefs_t g_classrefs(1); -static const ClassRefs_t::size_type GLOBAL_HANDLE = 1; -static const ClassRefs_t::size_type STD_HANDLE = GLOBAL_HANDLE + 1; - -typedef std::map Name2ClassRefIndex_t; -static Name2ClassRefIndex_t g_name2classrefidx; - -static std::map resolved_enum_types; - -namespace { - -static inline -Cppyy::TCppType_t find_memoized_scope(const std::string& name) -{ - auto icr = g_name2classrefidx.find(name); - if (icr != g_name2classrefidx.end()) - return (Cppyy::TCppType_t)icr->second; - return (Cppyy::TCppType_t)0; -} - -static inline -std::string find_memoized_resolved_name(const std::string& name) -{ -// resolved class types - Cppyy::TCppType_t klass = find_memoized_scope(name); - if (klass) return Cppyy::GetScopedFinalName(klass); - -// resolved enum types - auto res = resolved_enum_types.find(name); - if (res != resolved_enum_types.end()) - return res->second; - -// unknown ... - return ""; -} - -class CallWrapper { -public: - typedef const void* DeclId_t; - -public: - CallWrapper(TFunction* f) : fDecl(f->GetDeclId()), fName(f->GetName()), fTF(new TFunction(*f)) {} - CallWrapper(DeclId_t fid, const std::string& n) : fDecl(fid), fName(n), fTF(nullptr) {} - ~CallWrapper() { - delete fTF; - } - -public: - TInterpreter::CallFuncIFacePtr_t fFaceptr; - DeclId_t fDecl; - std::string fName; - TFunction* fTF; -}; - -} - -static std::vector gWrapperHolder; - -static inline -CallWrapper* new_CallWrapper(TFunction* f) -{ - CallWrapper* wrap = new CallWrapper(f); - gWrapperHolder.push_back(wrap); - return wrap; -} - -static inline -CallWrapper* new_CallWrapper(CallWrapper::DeclId_t fid, const std::string& n) -{ - CallWrapper* wrap = new CallWrapper(fid, n); - gWrapperHolder.push_back(wrap); - return wrap; -} - -typedef std::vector GlobalVars_t; -typedef std::map GlobalVarsIndices_t; - -static GlobalVars_t g_globalvars; -static GlobalVarsIndices_t g_globalidx; - -static std::set gSTLNames; - - -// data ---------------------------------------------------------------------- -Cppyy::TCppScope_t Cppyy::gGlobalScope = GLOBAL_HANDLE; - -// builtin types (including a few common STL templates as long as they live in -// the global namespace b/c of choices upstream) -static std::set g_builtins = - {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", "unsigned short", - "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", - "float", "double", "long double", "void", - "allocator", "array", "basic_string", "complex", "initializer_list", "less", "list", - "map", "pair", "set", "vector"}; - -// smart pointer types -static std::set gSmartPtrTypes = - {"auto_ptr", "std::auto_ptr", "shared_ptr", "std::shared_ptr", - "unique_ptr", "std::unique_ptr", "weak_ptr", "std::weak_ptr"}; - -// to filter out ROOT names -static std::set gInitialNames; -static std::set gRootSOs; - -// configuration -static bool gEnableFastPath = true; - - -// global initialization ----------------------------------------------------- -namespace { - -// names copied from TUnixSystem -#ifdef WIN32 -const int SIGBUS = 0; // simple placeholders for ones that don't exist -const int SIGSYS = 0; -const int SIGPIPE = 0; -const int SIGQUIT = 0; -const int SIGWINCH = 0; -const int SIGALRM = 0; -const int SIGCHLD = 0; -const int SIGURG = 0; -const int SIGUSR1 = 0; -const int SIGUSR2 = 0; -#endif - -static struct Signalmap_t { - int fCode; - const char *fSigName; -} gSignalMap[kMAXSIGNALS] = { // the order of the signals should be identical - { SIGBUS, "bus error" }, // to the one in TSysEvtHandler.h - { SIGSEGV, "segmentation violation" }, - { SIGSYS, "bad argument to system call" }, - { SIGPIPE, "write on a pipe with no one to read it" }, - { SIGILL, "illegal instruction" }, - { SIGABRT, "abort" }, - { SIGQUIT, "quit" }, - { SIGINT, "interrupt" }, - { SIGWINCH, "window size change" }, - { SIGALRM, "alarm clock" }, - { SIGCHLD, "death of a child" }, - { SIGURG, "urgent data arrived on an I/O channel" }, - { SIGFPE, "floating point exception" }, - { SIGTERM, "termination signal" }, - { SIGUSR1, "user-defined signal 1" }, - { SIGUSR2, "user-defined signal 2" } -}; - -static void inline do_trace(int sig) { - std::cerr << " *** Break *** " << (sig < kMAXSIGNALS ? gSignalMap[sig].fSigName : "") << std::endl; - gSystem->StackTrace(); -} - -class TExceptionHandlerImp : public TExceptionHandler { -public: - void HandleException(Int_t sig) override { - if (TROOT::Initialized()) { - if (gException) { - gInterpreter->RewindDictionary(); - gInterpreter->ClearFileBusy(); - } - - if (!std::getenv("CPPYY_CRASH_QUIET")) - do_trace(sig); - - // jump back, if catch point set - Throw(sig); - } - - do_trace(sig); - gSystem->Exit(128 + sig); - } -}; - -class ApplicationStarter { -public: - ApplicationStarter() { - // initialize ROOT early to guarantee proper order of shutdown later on (gROOT is a - // macro that resolves to the ::CppyyLegacy::GetROOT() function call) - (void)gROOT; - - // setup dummy holders for global and std namespaces - assert(g_classrefs.size() == GLOBAL_HANDLE); - g_name2classrefidx[""] = GLOBAL_HANDLE; - g_classrefs.push_back(TClassRef("")); - - // aliases for std (setup already in pythonify) - g_name2classrefidx["std"] = STD_HANDLE; - g_name2classrefidx["::std"] = g_name2classrefidx["std"]; - g_classrefs.push_back(TClassRef("std")); - - // add a dummy global to refer to as null at index 0 - g_globalvars.push_back(nullptr); - g_globalidx[nullptr] = 0; - - // fill out the builtins - std::set bi{g_builtins}; - for (const auto& name : bi) { - for (const char* a : {"*", "&", "*&", "[]", "*[]"}) - g_builtins.insert(name+a); - } - - // disable fast path if requested - if (std::getenv("CPPYY_DISABLE_FASTPATH")) gEnableFastPath = false; - - // fill the set of STL names - const char* stl_names[] = {"allocator", "auto_ptr", "bad_alloc", "bad_cast", - "bad_exception", "bad_typeid", "basic_filebuf", "basic_fstream", "basic_ifstream", - "basic_ios", "basic_iostream", "basic_istream", "basic_istringstream", - "basic_ofstream", "basic_ostream", "basic_ostringstream", "basic_streambuf", - "basic_string", "basic_stringbuf", "basic_stringstream", "binary_function", - "binary_negate", "bitset", "byte", "char_traits", "codecvt_byname", "codecvt", "collate", - "collate_byname", "compare", "complex", "ctype_byname", "ctype", "default_delete", - "deque", "divides", "domain_error", "equal_to", "exception", "forward_list", "fpos", - "function", "greater_equal", "greater", "gslice_array", "gslice", "hash", "indirect_array", - "integer_sequence", "invalid_argument", "ios_base", "istream_iterator", "istreambuf_iterator", - "istrstream", "iterator_traits", "iterator", "length_error", "less_equal", "less", - "list", "locale", "localedef utility", "locale utility", "logic_error", "logical_and", - "logical_not", "logical_or", "map", "mask_array", "mem_fun", "mem_fun_ref", "messages", - "messages_byname", "minus", "modulus", "money_get", "money_put", "moneypunct", - "moneypunct_byname", "multimap", "multiplies", "multiset", "negate", "not_equal_to", - "num_get", "num_put", "numeric_limits", "numpunct", "numpunct_byname", - "ostream_iterator", "ostreambuf_iterator", "ostrstream", "out_of_range", - "overflow_error", "pair", "plus", "pointer_to_binary_function", - "pointer_to_unary_function", "priority_queue", "queue", "range_error", - "raw_storage_iterator", "reverse_iterator", "runtime_error", "set", "shared_ptr", - "slice_array", "slice", "stack", "string", "strstream", "strstreambuf", - "time_get_byname", "time_get", "time_put_byname", "time_put", "unary_function", - "unary_negate", "unique_ptr", "underflow_error", "unordered_map", "unordered_multimap", - "unordered_multiset", "unordered_set", "valarray", "vector", "weak_ptr", "wstring", - "__hash_not_enabled"}; - for (auto& name : stl_names) - gSTLNames.insert(name); - - // set opt level (default to 2 if not given; Cling itself defaults to 0) - int optLevel = 2; - if (std::getenv("CPPYY_OPT_LEVEL")) optLevel = atoi(std::getenv("CPPYY_OPT_LEVEL")); - if (optLevel != 0) { - std::ostringstream s; - s << "#pragma cling optimize " << optLevel; - gInterpreter->ProcessLine(s.str().c_str()); - } - - // load frequently used headers - const char* code = - "#include \n" - "#include \n" - "#include \n" // defines R__EXTERN - "#include \n" - "#include "; - gInterpreter->ProcessLine(code); - - // create helpers for comparing thingies - gInterpreter->Declare( - "namespace __cppyy_internal { template" - " bool is_equal(const C1& c1, const C2& c2) { return (bool)(c1 == c2); } }"); - gInterpreter->Declare( - "namespace __cppyy_internal { template" - " bool is_not_equal(const C1& c1, const C2& c2) { return (bool)(c1 != c2); } }"); - - // helper for multiple inheritance - gInterpreter->Declare("namespace __cppyy_internal { struct Sep; }"); - - // retrieve all initial (ROOT) C++ names in the global scope to allow filtering later - gROOT->GetListOfGlobals(true); // force initialize - gROOT->GetListOfGlobalFunctions(true); // id. - std::set initial; - Cppyy::GetAllCppNames(GLOBAL_HANDLE, initial); - gInitialNames = initial; - -#ifndef WIN32 - gRootSOs.insert("libCore.so "); - gRootSOs.insert("libRIO.so "); - gRootSOs.insert("libThread.so "); - gRootSOs.insert("libMathCore.so "); -#else - gRootSOs.insert("libCore.dll "); - gRootSOs.insert("libRIO.dll "); - gRootSOs.insert("libThread.dll "); - gRootSOs.insert("libMathCore.dll "); -#endif - - // start off with a reasonable size placeholder for wrappers - gWrapperHolder.reserve(1024); - - // create an exception handler to process signals - gExceptionHandler = new TExceptionHandlerImp{}; - } - - ~ApplicationStarter() { - for (auto wrap : gWrapperHolder) - delete wrap; - delete gExceptionHandler; gExceptionHandler = nullptr; - } -} _applicationStarter; - -} // unnamed namespace - - -// local helpers ------------------------------------------------------------- -static inline -TClassRef& type_from_handle(Cppyy::TCppScope_t scope) -{ - assert((ClassRefs_t::size_type)scope < g_classrefs.size()); - return g_classrefs[(ClassRefs_t::size_type)scope]; -} - -static inline -TFunction* m2f(Cppyy::TCppMethod_t method) { - CallWrapper* wrap = ((CallWrapper*)method); - if (!wrap->fTF) { - MethodInfo_t* mi = gInterpreter->MethodInfo_Factory(wrap->fDecl); - wrap->fTF = new TFunction(mi); - } - return wrap->fTF; -} - -/* -static inline -CallWrapper::DeclId_t m2d(Cppyy::TCppMethod_t method) { - CallWrapper* wrap = ((CallWrapper*)method); - if (!wrap->fTF || wrap->fTF->GetDeclId() != wrap->fDecl) { - MethodInfo_t* mi = gInterpreter->MethodInfo_Factory(wrap->fDecl); - wrap->fTF = new TFunction(mi); - } - return wrap->fDecl; -} -*/ - -static inline -char* cppstring_to_cstring(const std::string& cppstr) -{ - char* cstr = (char*)malloc(cppstr.size()+1); - memcpy(cstr, cppstr.c_str(), cppstr.size()+1); - return cstr; -} - -static inline -bool match_name(const std::string& tname, const std::string fname) -{ -// either match exactly, or match the name as template - if (fname.rfind(tname, 0) == 0) { - if ((tname.size() == fname.size()) || - (tname.size() < fname.size() && fname[tname.size()] == '<')) - return true; - } - return false; -} - -static inline -bool is_missclassified_stl(const std::string& name) -{ - std::string::size_type pos = name.find('<'); - if (pos != std::string::npos) - return gSTLNames.find(name.substr(0, pos)) != gSTLNames.end(); - return gSTLNames.find(name) != gSTLNames.end(); -} - - -// direct interpreter access ------------------------------------------------- -bool Cppyy::Compile(const std::string& code, bool /*silent*/) -{ - return gInterpreter->Declare(code.c_str()); -} - -std::string Cppyy::ToString(TCppType_t klass, TCppObject_t obj) -{ - if (klass && obj && !IsNamespace((TCppScope_t)klass)) - return gInterpreter->ToString(GetScopedFinalName(klass).c_str(), (void*)obj); - return ""; -} - - -// name to opaque C++ scope representation ----------------------------------- -std::string Cppyy::ResolveName(const std::string& cppitem_name) -{ -// Fully resolve the given name to the final type name. - -// try memoized type cache, in case seen before - std::string memoized = find_memoized_resolved_name(cppitem_name); - if (!memoized.empty()) return memoized; - -// remove global scope '::' if present - std::string tclean = cppitem_name.compare(0, 2, "::") == 0 ? - cppitem_name.substr(2, std::string::npos) : cppitem_name; - -// classes (most common) - tclean = TClassEdit::CleanType(tclean.c_str()); - if (tclean.empty() /* unknown, eg. an operator */) return cppitem_name; - -// reduce [N] to [] - if (tclean[tclean.size()-1] == ']') - tclean = tclean.substr(0, tclean.rfind('[')) + "[]"; - - if (tclean.rfind("byte", 0) == 0 || tclean.rfind("std::byte", 0) == 0) - return tclean; - -// remove __restrict and __restrict__ - auto pos = tclean.rfind("__restrict"); - if (pos != std::string::npos) - tclean = tclean.substr(0, pos); - - if (tclean.compare(0, 9, "std::byte") == 0) - return tclean; - -// check data types list (accept only builtins as typedefs will -// otherwise not be resolved) - if (IsBuiltin(tclean)) return tclean; - -// special case for enums - if (IsEnum(cppitem_name)) - return ResolveEnum(cppitem_name); - -// special case for clang's builtin __type_pack_element (which does not resolve) - pos = cppitem_name.size() > 20 ? \ - cppitem_name.rfind("__type_pack_element", 5) : std::string::npos; - if (pos != std::string::npos) { - // shape is "[std::]__type_pack_elementcpd": extract - // first the index, and from there the indexed type; finally, restore the - // qualifiers - const char* str = cppitem_name.c_str(); - char* endptr = nullptr; - unsigned long index = strtoul(str+20+pos, &endptr, 0); - - std::string tmplvars{endptr}; - auto start = tmplvars.find(',') + 1; - auto end = tmplvars.find(',', start); - while (index != 0) { - start = end+1; - end = tmplvars.find(',', start); - if (end == std::string::npos) end = tmplvars.rfind('>'); - --index; - } - - std::string resolved = tmplvars.substr(start, end-start); - auto cpd = tmplvars.rfind('>'); - if (cpd != std::string::npos && cpd+1 != tmplvars.size()) - return resolved + tmplvars.substr(cpd+1, std::string::npos); - return resolved; - } - -// typedefs etc. (and a couple of hacks around TClassEdit-isms, fixing of which -// in ResolveTypedef itself is a TODO ...) - tclean = TClassEdit::ResolveTypedef(tclean.c_str(), true); - pos = 0; - while ((pos = tclean.find("::::", pos)) != std::string::npos) { - tclean.replace(pos, 4, "::"); - pos += 2; - } - - if (tclean.compare(0, 6, "const ") != 0) - return TClassEdit::ShortType(tclean.c_str(), 2); - return "const " + TClassEdit::ShortType(tclean.c_str(), 2); -} - -#if 0 -//---------------------------------------------------------------------------- -static std::string extract_namespace(const std::string& name) -{ -// Find the namespace the named class lives in, take care of templates -// Note: this code also lives in CPyCppyy (TODO: refactor?) - if (name.empty()) - return name; - - int tpl_open = 0; - for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - ++tpl_open; - else if (c == '<') - --tpl_open; - - // collect name up to "::" - else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos-1); - } - } - -// no namespace; assume outer scope - return ""; -} -#endif - -std::string Cppyy::ResolveEnum(const std::string& enum_type) -{ -// The underlying type of a an enum may be any kind of integer. -// Resolve that type via a workaround (note: this function assumes -// that the enum_type name is a valid enum type name) - auto res = resolved_enum_types.find(enum_type); - if (res != resolved_enum_types.end()) - return res->second; - -// desugar the type before resolving - std::string et_short = TClassEdit::ShortType(enum_type.c_str(), 1); - if (et_short.find("(unnamed") == std::string::npos) { - std::ostringstream decl; - // TODO: now presumed fixed with https://sft.its.cern.ch/jira/browse/ROOT-6988 - for (auto& itype : {"unsigned int"}) { - // This is pure type introspection: silence any deprecation warning that - // would otherwise be emitted just because the enum being resolved (or its - // scope) happens to be marked deprecated. - decl << "_Pragma(\"clang diagnostic push\")" - "_Pragma(\"clang diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")" - << "std::is_same<" - << itype - << ", std::underlying_type<" - << et_short - << ">::type>::value;" - "_Pragma(\"clang diagnostic pop\")"; - if (gInterpreter->ProcessLine(decl.str().c_str())) { - // TODO: "re-sugaring" like this is brittle, but the top - // should be re-translated into AST-based code anyway - std::string resugared; - if (et_short.size() != enum_type.size()) { - auto pos = enum_type.find(et_short); - if (pos != std::string::npos) { - resugared = enum_type.substr(0, pos) + itype; - if (pos+et_short.size() < enum_type.size()) - resugared += enum_type.substr(pos+et_short.size(), std::string::npos); - } - } - if (resugared.empty()) resugared = itype; - resolved_enum_types[enum_type] = resugared; - return resugared; - } - } - } - -// failed or anonymous ... signal upstream to special case this - int ipos = (int)enum_type.size()-1; - for (; 0 <= ipos; --ipos) { - char c = enum_type[ipos]; - if (isspace(c)) continue; - if (isalnum(c) || c == '_' || c == '>' || c == ')') break; - } - bool isConst = enum_type.find("const ", 6) != std::string::npos; - std::string restype = isConst ? "const " : ""; - restype += "internal_enum_type_t"+enum_type.substr((std::string::size_type)ipos+1, std::string::npos); - resolved_enum_types[enum_type] = restype; - return restype; // should default to some int variant -} - -#if 0 -static Cppyy::TCppIndex_t ArgSimilarityScore(void *argqtp, void *reqqtp) -{ -// This scoring is not based on any particular rules - if (gInterpreter->IsSameType(argqtp, reqqtp)) - return 0; // Best match - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsSignedIntegerType(reqqtp)) || - (gInterpreter->IsUnsignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsFloatingType(reqqtp))) - return 1; - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp))) - return 2; - else if ((gInterpreter->IsIntegerType(argqtp) && gInterpreter->IsIntegerType(reqqtp))) - return 3; - else if ((gInterpreter->IsIntegralType(argqtp) && gInterpreter->IsIntegralType(reqqtp))) - return 4; - else if ((gInterpreter->IsVoidPointerType(argqtp) && gInterpreter->IsPointerType(reqqtp))) - return 5; - else - return 10; // Penalize heavily for no possible match -} -#endif - -Cppyy::TCppScope_t Cppyy::GetScope(const std::string& sname) -{ -// First, try cache - TCppType_t result = find_memoized_scope(sname); - if (result) return result; - -// Second, skip builtins before going through the more expensive steps of resolving -// typedefs and looking up TClass - if (g_builtins.find(sname) != g_builtins.end()) - return (TCppScope_t)0; - -// TODO: scope_name should always be final already? -// Resolve name fully before lookup to make sure all aliases point to the same scope - std::string scope_name = ResolveName(sname); - bool bHasAlias1 = sname != scope_name; - if (bHasAlias1) { - result = find_memoized_scope(scope_name); - if (result) { - g_name2classrefidx[sname] = result; - return result; - } - } - -// both failed, but may be STL name that's missing 'std::' now, but didn't before - bool b_scope_name_missclassified = is_missclassified_stl(scope_name); - if (b_scope_name_missclassified) { - result = find_memoized_scope("std::"+scope_name); - if (result) g_name2classrefidx["std::"+scope_name] = (ClassRefs_t::size_type)result; - } - bool b_sname_missclassified = bHasAlias1 ? is_missclassified_stl(sname) : false; - if (b_sname_missclassified) { - if (!result) result = find_memoized_scope("std::"+sname); - if (result) g_name2classrefidx["std::"+sname] = (ClassRefs_t::size_type)result; - } - - if (result) return result; - -// use TClass directly, to enable auto-loading; class may be stubbed (eg. for -// function returns) or forward declared, leading to a non-null TClass that is -// otherwise invalid/unusable - TClassRef cr(TClass::GetClass(scope_name.c_str(), true /* load */, true /* silent */)); - if (!cr.GetClass()) - return (TCppScope_t)0; - -// memoize found/created TClass - bool bHasAlias2 = cr->GetName() != scope_name; - if (bHasAlias2) { - result = find_memoized_scope(cr->GetName()); - if (result) { - g_name2classrefidx[scope_name] = result; - if (bHasAlias1) g_name2classrefidx[sname] = result; - return result; - } - } - - ClassRefs_t::size_type sz = g_classrefs.size(); - g_name2classrefidx[scope_name] = sz; - if (bHasAlias1) g_name2classrefidx[sname] = sz; - if (bHasAlias2) g_name2classrefidx[cr->GetName()] = sz; -// TODO: make ROOT/meta NOT remove std :/ - if (b_scope_name_missclassified) - g_name2classrefidx["std::"+scope_name] = sz; - if (b_sname_missclassified) - g_name2classrefidx["std::"+sname] = sz; - - g_classrefs.push_back(TClassRef(scope_name.c_str())); - - return (TCppScope_t)sz; -} - -bool Cppyy::IsTemplate(const std::string& template_name) -{ - return (bool)gInterpreter->CheckClassTemplate(template_name.c_str()); -} - -namespace { - class AutoCastRTTI { - public: - virtual ~AutoCastRTTI() {} - }; -} - -Cppyy::TCppType_t Cppyy::GetActualClass(TCppType_t klass, TCppObject_t obj) -{ - TClassRef& cr = type_from_handle(klass); - if (!cr.GetClass() || !obj) return klass; - - if (!(cr->ClassProperty() & kClassHasVirtual)) - return klass; // not polymorphic: no RTTI info available - -// TODO: ios class casting (ostream, streambuf, etc.) fails with a crash in GetActualClass() -// below on Mac ARM (it's likely that the found actual class was replaced, maybe because -// there are duplicates from pcm/pch?); filter them out for now as it's usually unnecessary -// anyway to autocast these - std::string clName = cr->GetName(); - if (clName.find("std::", 0, 5) == 0 && clName.find("stream") != std::string::npos) - return klass; - -#ifdef _WIN64 -// Cling does not provide a consistent ImageBase address for calculating relative addresses -// as used in Windows 64b RTTI. So, check for our own RTTI extension instead. If that fails, -// see whether the unmangled raw_name is available (e.g. if this is an MSVC compiled rather -// than JITed class) and pass on if it is. - volatile const char* raw = nullptr; // to prevent too aggressive reordering - try { - // this will filter those objects that do not have RTTI to begin with (throws) - AutoCastRTTI* pcst = (AutoCastRTTI*)obj; - raw = typeid(*pcst).raw_name(); - - // check the signature id (0 == absolute, 1 == relative, 2 == ours) - void* vfptr = *(void**)((intptr_t)obj); - void* meta = (void*)((intptr_t)*((void**)((intptr_t)vfptr-sizeof(void*)))); - if (*(intptr_t*)meta == 2) { - // access the extra data item which is an absolute pointer to the RTTI - void* ptdescr = (void*)((intptr_t)meta + 4*sizeof(unsigned long)+sizeof(void*)); - if (ptdescr && *(void**)ptdescr) { - auto rtti = *(std::type_info**)ptdescr; - raw = rtti->raw_name(); - if (raw && raw[0] != '\0') // likely unnecessary - return (TCppType_t)GetScope(rtti->name()); - } - - return klass; // do not fall through if no RTTI info available - } - - // if the raw name is the empty string (no guarantees that this is so as truly, the - // address is corrupt, but it is common to be empty), then there is no accessible RTTI - // and getting the unmangled name will crash ... - if (!raw) - return klass; - } catch (std::bad_typeid) { - return klass; // can't risk passing to ROOT/meta as it may do RTTI - } -#endif - - TClass* clActual = cr->GetActualClass((void*)obj); - // The additional check using TClass::GetClassInfo is to prevent returning classes of which the Interpreter has no info (see https://github.com/root-project/root/pull/16177) - if (clActual && clActual != cr.GetClass() && clActual->GetClassInfo()) { - auto itt = g_name2classrefidx.find(clActual->GetName()); - if (itt != g_name2classrefidx.end()) - return (TCppType_t)itt->second; - return (TCppType_t)GetScope(clActual->GetName()); - } - - return klass; -} - -size_t Cppyy::SizeOf(TCppType_t klass) -{ - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetClassInfo()) - return (size_t)gInterpreter->ClassInfo_Size(cr->GetClassInfo()); - return (size_t)0; -} - -size_t Cppyy::SizeOf(const std::string& type_name) -{ - TDataType* dt = gROOT->GetType(type_name.c_str()); - if (dt) return dt->Size(); - return SizeOf(GetScope(type_name)); -} - -bool Cppyy::IsBuiltin(const std::string& type_name) -{ - if (g_builtins.find(type_name) != g_builtins.end()) - return true; - - const std::string& tclean = TClassEdit::CleanType(type_name.c_str(), 1); - if (g_builtins.find(tclean) != g_builtins.end()) - return true; - - if (strstr(tclean.c_str(), "std::complex")) - return true; - - return false; -} - -bool Cppyy::IsComplete(const std::string& type_name) -{ -// verify whether the dictionary of this class is fully available - bool b = false; - - int oldEIL = gErrorIgnoreLevel; - gErrorIgnoreLevel = 3000; - TClass* klass = TClass::GetClass(type_name.c_str()); - if (klass && klass->GetClassInfo()) // works for normal case w/ dict - b = gInterpreter->ClassInfo_IsLoaded(klass->GetClassInfo()); - else { // special case for forward declared classes - ClassInfo_t* ci = gInterpreter->ClassInfo_Factory(type_name.c_str()); - if (ci) { - b = gInterpreter->ClassInfo_IsLoaded(ci); - gInterpreter->ClassInfo_Delete(ci); // we own the fresh class info - } - } - gErrorIgnoreLevel = oldEIL; - return b; -} - -// memory management --------------------------------------------------------- -Cppyy::TCppObject_t Cppyy::Allocate(TCppType_t type) -{ - TClassRef& cr = type_from_handle(type); - return (TCppObject_t)::operator new(gInterpreter->ClassInfo_Size(cr->GetClassInfo())); -} - -void Cppyy::Deallocate(TCppType_t /* type */, TCppObject_t instance) -{ - ::operator delete(instance); -} - -Cppyy::TCppObject_t Cppyy::Construct(TCppType_t type, void* arena) -{ - TClassRef& cr = type_from_handle(type); - if (arena) - return (TCppObject_t)cr->New(arena, TClass::kRealNew); - return (TCppObject_t)cr->New(TClass::kRealNew); -} - -static std::map sHasOperatorDelete; -void Cppyy::Destruct(TCppType_t type, TCppObject_t instance) -{ - TClassRef& cr = type_from_handle(type); - if (cr->ClassProperty() & (kClassHasExplicitDtor | kClassHasImplicitDtor)) - cr->Destructor((void*)instance); - else { - ROOT::DelFunc_t fdel = cr->GetDelete(); - if (fdel) fdel((void*)instance); - else { - auto ib = sHasOperatorDelete.find(type); - if (ib == sHasOperatorDelete.end()) { - TFunction *f = (TFunction *)cr->GetMethodAllAny("operator delete"); - sHasOperatorDelete[type] = (bool)(f && (f->Property() & kIsPublic)); - ib = sHasOperatorDelete.find(type); - } - ib->second ? cr->Destructor((void*)instance) : ::operator delete((void*)instance); - } - } -} - - -// method/function dispatching ----------------------------------------------- -static TInterpreter::CallFuncIFacePtr_t GetCallFunc(Cppyy::TCppMethod_t method) -{ -// TODO: method should be a callfunc, so that no mapping would be needed. - CallWrapper* wrap = (CallWrapper*)method; - - CallFunc_t* callf = gInterpreter->CallFunc_Factory(); - MethodInfo_t* meth = gInterpreter->MethodInfo_Factory(wrap->fDecl); - gInterpreter->CallFunc_SetFunc(callf, meth); - gInterpreter->MethodInfo_Delete(meth); - - if (!(callf && gInterpreter->CallFunc_IsValid(callf))) { - // TODO: propagate this error to caller w/o use of Python C-API - /* - PyErr_Format(PyExc_RuntimeError, "could not resolve %s::%s(%s)", - const_cast(klass).GetClassName(), - wrap.fName, callString.c_str()); */ - std::cerr << "TODO: report unresolved function error to Python\n"; - if (callf) gInterpreter->CallFunc_Delete(callf); - return TInterpreter::CallFuncIFacePtr_t{}; - } - -// generate the wrapper and JIT it; ignore wrapper generation errors (will simply -// result in a nullptr that is reported upstream if necessary; often, however, -// there is a different overload available that will do) - auto oldErrLvl = gErrorIgnoreLevel; - gErrorIgnoreLevel = kFatal; - wrap->fFaceptr = gInterpreter->CallFunc_IFacePtr(callf); - gErrorIgnoreLevel = oldErrLvl; - - gInterpreter->CallFunc_Delete(callf); // does not touch IFacePtr - return wrap->fFaceptr; -} - -static inline -bool copy_args(Parameter* args, size_t nargs, void** vargs) -{ - bool runRelease = false; - for (size_t i = 0; i < nargs; ++i) { - switch (args[i].fTypeCode) { - case 'X': /* (void*)type& with free */ - runRelease = true; - case 'V': /* (void*)type& */ - vargs[i] = args[i].fValue.fVoidp; - break; - case 'r': /* const type& */ - vargs[i] = args[i].fRef; - break; - default: /* all other types in union */ - vargs[i] = (void*)&args[i].fValue.fVoidp; - break; - } - } - return runRelease; -} - -static inline -void release_args(Parameter* args, size_t nargs) -{ - for (size_t i = 0; i < nargs; ++i) { - if (args[i].fTypeCode == 'X') - free(args[i].fValue.fVoidp); - } -} - -static inline bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* self, void* result) -{ - Parameter* args = (Parameter*)args_; - //bool is_direct = nargs & DIRECT_CALL; - nargs = CALL_NARGS(nargs); - - CallWrapper* wrap = (CallWrapper*)method; - const TInterpreter::CallFuncIFacePtr_t& faceptr = wrap->fFaceptr.fGeneric ? wrap->fFaceptr : GetCallFunc(method); - if (!faceptr.fGeneric) - return false; // happens with compilation error - - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kGeneric) { - bool runRelease = false; - if (nargs <= SMALL_ARGS_N) { - void* smallbuf[SMALL_ARGS_N]; - if (nargs) runRelease = copy_args(args, nargs, smallbuf); - faceptr.fGeneric(self, (int)nargs, smallbuf, result); - } else { - std::vector buf(nargs); - runRelease = copy_args(args, nargs, buf.data()); - faceptr.fGeneric(self, (int)nargs, buf.data(), result); - } - if (runRelease) release_args(args, nargs); - return true; - } - - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kCtor) { - bool runRelease = false; - if (nargs <= SMALL_ARGS_N) { - void* smallbuf[SMALL_ARGS_N]; - if (nargs) runRelease = copy_args(args, nargs, (void**)smallbuf); - faceptr.fCtor((void**)smallbuf, result, (unsigned long)nargs); - } else { - std::vector buf(nargs); - runRelease = copy_args(args, nargs, buf.data()); - faceptr.fCtor(buf.data(), result, (unsigned long)nargs); - } - if (runRelease) release_args(args, nargs); - return true; - } - - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kDtor) { - std::cerr << " DESTRUCTOR NOT IMPLEMENTED YET! " << std::endl; - return false; - } - - return false; -} - -template -static inline -T CallT(Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, size_t nargs, void* args) -{ - T t{}; - if (WrapperCall(method, nargs, args, (void*)self, &t)) - return t; - return (T)-1; -} - -#define CPPYY_IMP_CALL(typecode, rtype) \ -rtype Cppyy::Call##typecode(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args)\ -{ \ - return CallT(method, self, nargs, args); \ -} - -void Cppyy::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args) -{ - if (!WrapperCall(method, nargs, args, (void*)self, nullptr)) - return /* TODO ... report error */; -} - -CPPYY_IMP_CALL(B, unsigned char) -CPPYY_IMP_CALL(C, char ) -CPPYY_IMP_CALL(H, short ) -CPPYY_IMP_CALL(I, int ) -CPPYY_IMP_CALL(L, long ) -CPPYY_IMP_CALL(LL, Long64_t ) -CPPYY_IMP_CALL(F, float ) -CPPYY_IMP_CALL(D, double ) -CPPYY_IMP_CALL(LD, LongDouble_t ) - -void* Cppyy::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args) -{ - void* r = nullptr; - if (WrapperCall(method, nargs, args, (void*)self, &r)) - return r; - return nullptr; -} - -char* Cppyy::CallS( - TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length) -{ - char* cstr = nullptr; - TClassRef cr("std::string"); - std::string* cppresult = (std::string*)malloc(sizeof(std::string)); - if (WrapperCall(method, nargs, args, self, (void*)cppresult)) { - cstr = cppstring_to_cstring(*cppresult); - *length = cppresult->size(); - cppresult->std::string::~basic_string(); - } else - *length = 0; - free((void*)cppresult); - return cstr; -} - -Cppyy::TCppObject_t Cppyy::CallConstructor( - TCppMethod_t method, TCppType_t /* klass */, size_t nargs, void* args) -{ - void* obj = nullptr; - if (WrapperCall(method, nargs, args, nullptr, &obj)) - return (TCppObject_t)obj; - return (TCppObject_t)0; -} - -void Cppyy::CallDestructor(TCppType_t type, TCppObject_t self) -{ - TClassRef& cr = type_from_handle(type); - cr->Destructor((void*)self, true); -} - -Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, - TCppObject_t self, size_t nargs, void* args, TCppType_t result_type) -{ - TClassRef& cr = type_from_handle(result_type); - auto *classInfo = cr->GetClassInfo(); - // If the class info is missing, we better return null and let cppyy - // handle the error, before we step into undefined behavior - if(!classInfo) - return (TCppObject_t)0; - auto classSize = gInterpreter->ClassInfo_Size(classInfo); - // ClassInfo_Size returns -1 in case of invalid info, and 0 for - // forward-declared classes, which we can't use. - if (classSize <= 0) - return (TCppObject_t)0; - void* obj = ::operator new(classSize); - if (WrapperCall(method, nargs, args, self, obj)) - return (TCppObject_t)obj; - ::operator delete(obj); - return (TCppObject_t)0; -} - -Cppyy::TCppFuncAddr_t Cppyy::GetFunctionAddress(TCppMethod_t method, bool check_enabled) -{ - if (check_enabled && !gEnableFastPath) return (TCppFuncAddr_t)nullptr; - TFunction* f = m2f(method); - - TCppFuncAddr_t pf = (TCppFuncAddr_t)gInterpreter->FindSym(f->GetMangledName()); - if (pf) return pf; - - int ierr = 0; - const char* fn = TClassEdit::DemangleName(f->GetMangledName(), ierr); - if (ierr || !fn) - return pf; - - // TODO: the following attempts are all brittle and leak transactions, but - // each properly exposes the symbol so subsequent lookups will succeed - if (strstr(f->GetName(), "<")) { - // force explicit instantiation and try again - std::ostringstream sig; - sig << "template " << fn << ";"; - gInterpreter->ProcessLine(sig.str().c_str()); - } else { - std::ostringstream sig; - - std::string sfn = fn; - std::string::size_type pos = sfn.find('('); - if (pos != std::string::npos) sfn = sfn.substr(0, pos); - - // start cast - sig << '(' << f->GetReturnTypeName() << " ("; - - // add scope for methods - pos = sfn.rfind(':'); - if (pos != std::string::npos) { - std::string scope_name = sfn.substr(0, pos-1); - TCppScope_t scope = GetScope(scope_name); - if (scope && !IsNamespace(scope)) - sig << scope_name << "::"; - } - - // finalize cast - sig << "*)" << GetMethodSignature(method, false) - << ((f->Property() & kIsConstMethod) ? " const" : "") - << ')'; - - // load address - sig << '&' << sfn; - gInterpreter->Calc(sig.str().c_str()); - } - - return (TCppFuncAddr_t)gInterpreter->FindSym(f->GetMangledName()); -} - - -// handling of function argument buffer -------------------------------------- -void* Cppyy::AllocateFunctionArgs(size_t nargs) -{ - return new Parameter[nargs]; -} - -void Cppyy::DeallocateFunctionArgs(void* args) -{ - delete [] (Parameter*)args; -} - -size_t Cppyy::GetFunctionArgSizeof() -{ - return sizeof(Parameter); -} - -size_t Cppyy::GetFunctionArgTypeoffset() -{ - return offsetof(Parameter, fTypeCode); -} - - -// scope reflection information ---------------------------------------------- -bool Cppyy::IsNamespace(TCppScope_t scope) -{ -// Test if this scope represents a namespace. - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return cr->Property() & kIsNamespace; - return false; -} - -bool Cppyy::IsAbstract(TCppType_t klass) -{ -// Test if this type may not be instantiated. - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass()) - return cr->Property() & kIsAbstract; - return false; -} - -bool Cppyy::IsEnum(const std::string& type_name) -{ - if (type_name.empty()) return false; - - if (type_name.rfind("enum ", 0) == 0) - return true; // by definition (C-style) - - std::string tn_short = TClassEdit::ShortType(type_name.c_str(), 1); - if (tn_short.empty()) return false; - return gInterpreter->ClassInfo_IsEnum(tn_short.c_str()); -} - -bool Cppyy::IsAggregate(TCppType_t type) -{ -// Test if this type is a "plain old data" type - TClassRef& cr = type_from_handle(type); - if (cr.GetClass()) - return cr->ClassProperty() & kClassIsAggregate; - return false; -} - -bool Cppyy::IsIntegerType(const std::string &type_name) -{ - // Test if the named type is an integer type - TypeInfo_t *ti = gInterpreter->TypeInfo_Factory(type_name.c_str()); - if (!ti) - return false; - void *qtp = gInterpreter->TypeInfo_QualTypePtr(ti); - bool result = qtp ? gInterpreter->IsIntegerType(qtp) : false; - gInterpreter->TypeInfo_Delete(ti); - return result; -} - -bool Cppyy::IsDefaultConstructable(TCppType_t type) -{ -// Test if this type has a default constructor or is a "plain old data" type - TClassRef& cr = type_from_handle(type); - if (cr.GetClass()) - return cr->HasDefaultConstructor() || (cr->ClassProperty() & kClassIsAggregate); - return true; -} - -// helpers for stripping scope names -static -std::string outer_with_template(const std::string& name) -{ -// Cut down to the outer-most scope from , taking proper care of templates. - int tpl_open = 0; - for (std::string::size_type pos = 0; pos < name.size(); ++pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '<') - ++tpl_open; - else if (c == '>') - --tpl_open; - - // collect name up to "::" - else if (tpl_open == 0 && \ - c == ':' && pos+1 < name.size() && name[pos+1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos); - } - } - -// whole name is apparently a single scope - return name; -} - -static -std::string outer_no_template(const std::string& name) -{ -// Cut down to the outer-most scope from , drop templates - std::string::size_type first_scope = name.find(':'); - if (first_scope == std::string::npos) - return name.substr(0, name.find('<')); - std::string::size_type first_templ = name.find('<'); - if (first_templ == std::string::npos) - return name.substr(0, first_scope); - return name.substr(0, std::min(first_templ, first_scope)); -} - -#define FILL_COLL(type, filter) { \ - TIter itr{coll}; \ - type* obj = nullptr; \ - while ((obj = (type*)itr.Next())) { \ - const char* nm = obj->GetName(); \ - if (nm && nm[0] != '_' && !(obj->Property() & (filter))) { \ - if (gInitialNames.find(nm) == gInitialNames.end()) \ - cppnames.insert(nm); \ - }}} - -static inline -void cond_add(Cppyy::TCppScope_t scope, const std::string& ns_scope, - std::set& cppnames, const char* name, bool nofilter = false) -{ - if (!name || strstr(name, ".h") != 0) - return; - - if (scope == GLOBAL_HANDLE) { - std::string to_add = outer_no_template(name); - if ((nofilter || gInitialNames.find(to_add) == gInitialNames.end()) && !is_missclassified_stl(name)) - cppnames.insert(outer_no_template(name)); - } else if (scope == STD_HANDLE) { - if (strncmp(name, "std::", 5) == 0) { - name += 5; -#ifdef __APPLE__ - if (strncmp(name, "__1::", 5) == 0) name += 5; -#endif - } else if (!is_missclassified_stl(name)) - return; - cppnames.insert(outer_no_template(name)); - } else { - if (strncmp(name, ns_scope.c_str(), ns_scope.size()) == 0) - cppnames.insert(outer_with_template(name + ns_scope.size())); - } -} - -void Cppyy::GetAllCppNames(TCppScope_t scope, std::set& cppnames) -{ -// Collect all known names of C++ entities under scope. This is useful for IDEs -// employing tab-completion, for example. Note that functions names need not be -// unique as they can be overloaded. - TClassRef& cr = type_from_handle(scope); - if (scope != GLOBAL_HANDLE && !(cr.GetClass() && cr->Property())) - return; - - std::string ns_scope = GetFinalName(scope); - if (scope != GLOBAL_HANDLE) ns_scope += "::"; - -// add existing values from read rootmap files if within this scope - TCollection* coll = gInterpreter->GetMapfile()->GetTable(); - { - TIter itr{coll}; - TEnvRec* ev = nullptr; - while ((ev = (TEnvRec*)itr.Next())) { - // TEnv contains rootmap entries and user-side rootmap files may be already - // loaded on startup. Thus, filter on file name rather than load time. - if (gRootSOs.find(ev->GetValue()) == gRootSOs.end()) - cond_add(scope, ns_scope, cppnames, ev->GetName(), true); - } - } - -// do we care about the class table or are the rootmap and list of types enough? -/* - gClassTable->Init(); - const int N = gClassTable->Classes(); - for (int i = 0; i < N; ++i) - cond_add(scope, ns_scope, cppnames, gClassTable->Next()); -*/ - -#if 0 -// add interpreted classes (no load) - { - ClassInfo_t* ci = gInterpreter->ClassInfo_FactoryWithScope( - false /* all */, scope == GLOBAL_HANDLE ? nullptr : cr->GetName()); - while (gInterpreter->ClassInfo_Next(ci)) { - const char* className = gInterpreter->ClassInfo_FullName(ci); - if (strstr(className, "(anonymous)") || strstr(className, "(unnamed)")) - continue; - cond_add(scope, ns_scope, cppnames, className); - } - gInterpreter->ClassInfo_Delete(ci); - } -#endif - -// any other types (e.g. that may have come from parsing headers) - coll = gROOT->GetListOfTypes(); - { - TIter itr{coll}; - TDataType* dt = nullptr; - while ((dt = (TDataType*)itr.Next())) { - if (!(dt->Property() & kIsFundamental)) { - cond_add(scope, ns_scope, cppnames, dt->GetName()); - } - } - } - -// add functions - coll = (scope == GLOBAL_HANDLE) ? - gROOT->GetListOfGlobalFunctions(true) : cr->GetListOfMethods(true); - { - TIter itr{coll}; - TFunction* obj = nullptr; - while ((obj = (TFunction*)itr.Next())) { - const char* nm = obj->GetName(); - // skip templated functions, adding only the un-instantiated ones - if (nm && gInitialNames.find(nm) == gInitialNames.end()) - cppnames.insert(nm); - } - } - -// add uninstantiated templates - coll = (scope == GLOBAL_HANDLE) ? - gROOT->GetListOfFunctionTemplates() : cr->GetListOfFunctionTemplates(true); - FILL_COLL(TFunctionTemplate, kIsPrivate | kIsProtected) - -// add (global) data members - if (scope == GLOBAL_HANDLE) { - coll = gROOT->GetListOfGlobals(); - FILL_COLL(TGlobal, kIsEnum | kIsPrivate | kIsProtected) - } else { - coll = cr->GetListOfDataMembers(); - FILL_COLL(TDataMember, kIsEnum | kIsPrivate | kIsProtected) - coll = cr->GetListOfUsingDataMembers(); - FILL_COLL(TDataMember, kIsEnum | kIsPrivate | kIsProtected) - } - -// add enums values only for user classes/namespaces - if (scope != GLOBAL_HANDLE && scope != STD_HANDLE) { - coll = cr->GetListOfEnums(); - FILL_COLL(TEnum, kIsPrivate | kIsProtected) - } - -#ifdef __APPLE__ -// special case for Apple, add version namespace '__1' entries to std - if (scope == STD_HANDLE) - GetAllCppNames(GetScope("std::__1"), cppnames); -#endif -} - - -// class reflection information ---------------------------------------------- -std::vector Cppyy::GetUsingNamespaces(TCppScope_t scope) -{ - std::vector res; - if (!IsNamespace(scope)) - return res; - -#ifdef __APPLE__ - if (scope == STD_HANDLE) { - res.push_back(GetScope("__1")); - return res; - } -#endif - - TClassRef& cr = type_from_handle(scope); - if (!cr.GetClass() || !cr->GetClassInfo()) - return res; - - const std::vector& v = gInterpreter->GetUsingNamespaces(cr->GetClassInfo()); - res.reserve(v.size()); - for (const auto& uid : v) { - Cppyy::TCppScope_t uscope = GetScope(uid); - if (uscope) res.push_back(uscope); - } - - return res; -} - - -// class reflection information ---------------------------------------------- -std::string Cppyy::GetFinalName(TCppType_t klass) -{ - if (klass == GLOBAL_HANDLE) - return ""; - TClassRef& cr = type_from_handle(klass); - std::string clName = cr->GetName(); -// TODO: why is this template splitting needed? - std::string::size_type pos = clName.substr(0, clName.find('<')).rfind("::"); - if (pos != std::string::npos) - return clName.substr(pos+2, std::string::npos); - return clName; -} - -std::string Cppyy::GetScopedFinalName(TCppType_t klass) -{ - if (klass == GLOBAL_HANDLE) - return ""; - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass()) { - std::string name = cr->GetName(); - if (is_missclassified_stl(name)) - return std::string("std::")+cr->GetName(); - return cr->GetName(); - } - return ""; -} - -bool Cppyy::HasVirtualDestructor(TCppType_t klass) -{ - TClassRef& cr = type_from_handle(klass); - if (!cr.GetClass()) - return false; - - TFunction* f = cr->GetMethod(("~"+GetFinalName(klass)).c_str(), ""); - if (f && (f->Property() & kIsVirtual)) - return true; - - return false; -} - -bool Cppyy::HasComplexHierarchy(TCppType_t klass) -{ - int is_complex = 1; - size_t nbases = 0; - - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetListOfBases() != 0) - nbases = GetNumBases(klass); - - if (1 < nbases) - is_complex = 1; - else if (nbases == 0) - is_complex = 0; - else { // one base class only - TBaseClass* base = (TBaseClass*)cr->GetListOfBases()->At(0); - if (base->Property() & kIsVirtualBase) - is_complex = 1; // TODO: verify; can be complex, need not be. - else - is_complex = HasComplexHierarchy(GetScope(base->GetName())); - } - - return is_complex; -} - -Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppType_t klass) -{ -// Get the total number of base classes that this class has. - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetListOfBases() != 0) - return (TCppIndex_t)cr->GetListOfBases()->GetSize(); - return (TCppIndex_t)0; -} - -//////////////////////////////////////////////////////////////////////////////// -/// \fn Cppyy::TCppIndex_t GetLongestInheritancePath(TClass *klass) -/// \brief Retrieve number of base classes in the longest branch of the -/// inheritance tree of the input class. -/// \param[in] klass The class to start the retrieval process from. -/// -/// This is a helper function for Cppyy::GetNumBasesLongestBranch. -/// Given an inheritance tree, the function assigns weight 1 to each class that -/// has at least one base. Starting from the input class, the function is -/// called recursively on all the bases. For each base the return value is one -/// (the weight of the base itself) plus the maximum value retrieved for their -/// bases in turn. For example, given the following inheritance tree: -/// -/// ~~~{.cpp} -/// class A {}; class B: public A {}; -/// class X {}; class Y: public X {}; class Z: public Y {}; -/// class C: public B, Z {}; -/// ~~~ -/// -/// calling this function on an instance of `C` will return 3, the steps -/// required to go from C to X. -Cppyy::TCppIndex_t GetLongestInheritancePath(TClass *klass) -{ - - auto directbases = klass->GetListOfBases(); - if (!directbases) { - // This is a leaf with no bases - return 0; - } - auto ndirectbases = directbases->GetSize(); - if (ndirectbases == 0) { - // This is a leaf with no bases - return 0; - } else { - // If there is at least one direct base - std::vector nbases_branches; - nbases_branches.reserve(ndirectbases); - - // Traverse all direct bases of the current class and call the function - // recursively - for (auto baseclass : TRangeDynCast(directbases)) { - if (!baseclass) - continue; - if (auto baseclass_tclass = baseclass->GetClassPointer()) { - nbases_branches.emplace_back(GetLongestInheritancePath(baseclass_tclass)); - } - } - - // Get longest path among the direct bases of the current class - auto longestbranch = std::max_element(std::begin(nbases_branches), std::end(nbases_branches)); - - // Add 1 to include the current class in the count - return 1 + *longestbranch; - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// \fn Cppyy::TCppIndex_t Cppyy::GetNumBasesLongest(TCppType_t klass) -/// \brief Retrieve number of base classes in the longest branch of the -/// inheritance tree. -/// \param[in] klass The class to start the retrieval process from. -/// -/// The function converts the input class to a `TClass *` and calls -/// GetLongestInheritancePath. -Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppType_t klass) -{ - - const auto &cr = type_from_handle(klass); - - if (auto klass_tclass = cr.GetClass()) { - return GetLongestInheritancePath(klass_tclass); - } - - // In any other case, return zero - return 0; -} - -std::string Cppyy::GetBaseName(TCppType_t klass, TCppIndex_t ibase) -{ - TClassRef& cr = type_from_handle(klass); - return ((TBaseClass*)cr->GetListOfBases()->At((int)ibase))->GetName(); -} - -bool Cppyy::IsSubtype(TCppType_t derived, TCppType_t base) -{ - if (derived == base) - return true; - TClassRef& derived_type = type_from_handle(derived); - TClassRef& base_type = type_from_handle(base); - if (derived_type.GetClass() && base_type.GetClass()) - return derived_type->GetBaseClass(base_type) != 0; - return false; -} - -bool Cppyy::IsSmartPtr(TCppType_t klass) -{ - TClassRef& cr = type_from_handle(klass); - const std::string& tn = cr->GetName(); - if (gSmartPtrTypes.find(tn.substr(0, tn.find("<"))) != gSmartPtrTypes.end()) - return true; - return false; -} - -bool Cppyy::GetSmartPtrInfo( - const std::string& tname, TCppType_t* raw, TCppMethod_t* deref) -{ - const std::string& rn = ResolveName(tname); - if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) { - if (!raw && !deref) return true; - - TClassRef& cr = type_from_handle(GetScope(tname)); - if (cr.GetClass()) { - TFunction* func = cr->GetMethod("operator->", ""); - if (!func) - func = cr->GetMethod("operator->", ""); - if (func) { - if (deref) *deref = (TCppMethod_t)new_CallWrapper(func); - if (raw) *raw = GetScope(TClassEdit::ShortType( - func->GetReturnTypeNormalizedName().c_str(), 1)); - return (!deref || *deref) && (!raw || *raw); - } - } - } - - return false; -} - -void Cppyy::AddSmartPtrType(const std::string& type_name) -{ - gSmartPtrTypes.insert(ResolveName(type_name)); -} - -void Cppyy::AddTypeReducer(const std::string& /*reducable*/, const std::string& /*reduced*/) -{ - // This function is deliberately left empty, because it is not used in - // PyROOT, and synchronizing it with cppyy-backend upstream would require - // patches to ROOT meta. -} - - -// type offsets -------------------------------------------------------------- -ptrdiff_t Cppyy::GetBaseOffset(TCppType_t derived, TCppType_t base, - TCppObject_t address, int direction, bool rerror) -{ -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - if (derived == base || !(base && derived)) - return (ptrdiff_t)0; - - TClassRef& cd = type_from_handle(derived); - TClassRef& cb = type_from_handle(base); - - if (!cd.GetClass() || !cb.GetClass()) - return (ptrdiff_t)0; - - ptrdiff_t offset = -1; - if (!(cd->GetClassInfo() && cb->GetClassInfo())) { // gInterpreter requirement - // would like to warn, but can't quite determine error from intentional - // hiding by developers, so only cover the case where we really should have - // had a class info, but apparently don't: - if (cd->IsLoaded()) { - // warn to allow diagnostics - std::ostringstream msg; - msg << "failed offset calculation between " << cb->GetName() << " and " << cd->GetName(); - // TODO: propagate this warning to caller w/o use of Python C-API - // PyErr_WarnEx(PyExc_RuntimeWarning, const_cast(msg.str().c_str()), 1); - std::cerr << "Warning: " << msg.str() << '\n'; - } - - // return -1 to signal caller NOT to apply offset - return rerror ? (ptrdiff_t)offset : 0; - } - - offset = gInterpreter->ClassInfo_GetBaseOffset( - cd->GetClassInfo(), cb->GetClassInfo(), (void*)address, direction > 0); - if (offset == -1) // Cling error, treat silently - return rerror ? (ptrdiff_t)offset : 0; - - return (ptrdiff_t)(direction < 0 ? -offset : offset); -} - - -// method/function reflection information ------------------------------------ -Cppyy::TCppIndex_t Cppyy::GetNumMethods(TCppScope_t scope, bool accept_namespace) -{ - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) - return gROOT->GetListOfGlobalFunctions(true)->GetSize(); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass() && cr->GetListOfMethods(true)) { - Cppyy::TCppIndex_t nMethods = (TCppIndex_t)cr->GetListOfMethods(false)->GetSize(); - if (nMethods == (TCppIndex_t)0) { - std::string clName = GetScopedFinalName(scope); - if (clName.find('<') != std::string::npos) { - // chicken-and-egg problem: TClass does not know about methods until - // instantiation, so force it - std::ostringstream stmt; - stmt << "template class " << clName << ";"; - gInterpreter->Declare(stmt.str().c_str()/*, silent = true*/); - - // now reload the methods - return (TCppIndex_t)cr->GetListOfMethods(true)->GetSize(); - } - } - return nMethods; - } - - return (TCppIndex_t)0; // unknown class? -} - -std::vector Cppyy::GetMethodIndicesFromName( - TCppScope_t scope, const std::string& name) -{ - std::vector indices; - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - gInterpreter->UpdateListOfMethods(cr.GetClass()); - int imeth = 0; - TFunction* func = nullptr; - TIter next(cr->GetListOfMethods()); - while ((func = (TFunction*)next())) { - if (match_name(name, func->GetName())) { - // C++ functions should be public to allow access; C functions have no access - // specifier and should always be accepted - auto prop = func->Property(); - if ((prop & kIsPublic) || !(prop & (kIsPrivate | kIsProtected | kIsPublic))) - indices.push_back((TCppIndex_t)imeth); - } - ++imeth; - } - } else if (scope == GLOBAL_HANDLE) { - TCollection* funcs = gROOT->GetListOfGlobalFunctions(true); - - // tickle deserialization - if (!funcs->FindObject(name.c_str())) - return indices; - - TFunction* func = nullptr; - TIter ifunc(funcs); - while ((func = (TFunction*)ifunc.Next())) { - if (match_name(name, func->GetName())) - indices.push_back((TCppIndex_t)new_CallWrapper(func)); - } - } - - return indices; -} - -Cppyy::TCppMethod_t Cppyy::GetMethod(TCppScope_t scope, TCppIndex_t idx) -{ - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* f = (TFunction*)cr->GetListOfMethods(false)->At((int)idx); - if (f) return (Cppyy::TCppMethod_t)new_CallWrapper(f); - return (Cppyy::TCppMethod_t)nullptr; - } - - assert(klass == (Cppyy::TCppType_t)GLOBAL_HANDLE); - return (Cppyy::TCppMethod_t)idx; -} - -std::string Cppyy::GetMethodName(TCppMethod_t method) -{ - if (method) { - const std::string& name = ((CallWrapper*)method)->fName; - - if (name.compare(0, 8, "operator") != 0) - // strip template instantiation part, if any - return name.substr(0, name.find('<')); - return name; - } - return ""; -} - -std::string Cppyy::GetMethodFullName(TCppMethod_t method) -{ - if (method) { - std::string name = ((CallWrapper*)method)->fName; - name.erase(std::remove(name.begin(), name.end(), ' '), name.end()); - return name; - } - return ""; -} - -std::string Cppyy::GetMethodMangledName(TCppMethod_t method) -{ - if (method) - return m2f(method)->GetMangledName(); - return ""; -} - -std::string Cppyy::GetMethodResultType(TCppMethod_t method) -{ - if (method) { - TFunction* f = m2f(method); - if (f->ExtraProperty() & kIsConstructor) - return "constructor"; - std::string restype = f->GetReturnTypeName(); - // TODO: this is ugly; GetReturnTypeName() keeps typedefs, but may miss scopes - // for some reason; GetReturnTypeNormalizedName() has been modified to return - // the canonical type to guarantee correct namespaces. Sometimes typedefs look - // better, sometimes not, sometimes it's debatable (e.g. vector::size_type). - // So, for correctness sake, GetReturnTypeNormalizedName() is used, except for a - // special case of uint8_t/int8_t that must propagate as their typedefs. - if (restype.find("int8_t") != std::string::npos) - return restype; - restype = f->GetReturnTypeNormalizedName(); - if (restype == "(lambda)") { - std::ostringstream s; - // TODO: what if there are parameters to the lambda? - s << "__cling_internal::FT::F"; - TClass* cl = TClass::GetClass(s.str().c_str()); - if (cl) return cl->GetName(); - // TODO: signal some type of error (or should that be upstream? - } - return restype; - } - return ""; -} - -Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method) -{ - if (method) - return m2f(method)->GetNargs(); - return 0; -} - -Cppyy::TCppIndex_t Cppyy::GetMethodReqArgs(TCppMethod_t method) -{ - if (method) { - TFunction* f = m2f(method); - return (TCppIndex_t)(f->GetNargs() - f->GetNargsOpt()); - } - return (TCppIndex_t)0; -} - -std::string Cppyy::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) -{ - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - return arg->GetName(); - } - return ""; -} - -std::string Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) -{ - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - std::string ft = arg->GetFullTypeName(); - if (ft.rfind("enum ", 0) != std::string::npos) { // special case to preserve 'enum' tag - std::string arg_type = arg->GetTypeNormalizedName(); - return arg_type.insert(arg_type.rfind("const ", 0) == std::string::npos ? 0 : 6, "enum "); - } else if (g_builtins.find(ft) != g_builtins.end() || ft.find("int8_t") != std::string::npos) - return ft; // do not resolve int8_t and uint8_t typedefs - - return arg->GetTypeNormalizedName(); - } - return ""; -} - -Cppyy::TCppIndex_t Cppyy::CompareMethodArgType(TCppMethod_t method, TCppIndex_t iarg, const std::string &req_type) -{ - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg *)f->GetListOfMethodArgs()->At((int)iarg); - void *argqtp = gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); - - TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); - void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); - - // This scoring is not based on any particular rules - if (gInterpreter->IsSameType(argqtp, reqqtp)) - return 0; // Best match - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsSignedIntegerType(reqqtp)) || - (gInterpreter->IsUnsignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsFloatingType(reqqtp))) - return 1; - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp))) - return 2; - else if ((gInterpreter->IsIntegerType(argqtp) && gInterpreter->IsIntegerType(reqqtp))) - return 3; - else if ((gInterpreter->IsVoidPointerType(argqtp) && gInterpreter->IsPointerType(reqqtp))) - return 4; - else - return 10; // Penalize heavily for no possible match - } - return INT_MAX; // Method is not valid -} - -std::string Cppyy::GetMethodArgDefault(TCppMethod_t method, TCppIndex_t iarg) -{ - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - const char* def = arg->GetDefault(); - if (def) - return def; - } - - return ""; -} - -std::string Cppyy::GetMethodSignature(TCppMethod_t method, bool show_formalargs, TCppIndex_t maxargs) -{ - TFunction* f = m2f(method); - if (f) { - std::ostringstream sig; - sig << "("; - int nArgs = f->GetNargs(); - if (maxargs != (TCppIndex_t)-1) nArgs = std::min(nArgs, (int)maxargs); - for (int iarg = 0; iarg < nArgs; ++iarg) { - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At(iarg); - sig << arg->GetFullTypeName(); - if (show_formalargs) { - const char* argname = arg->GetName(); - if (argname && argname[0] != '\0') sig << " " << argname; - const char* defvalue = arg->GetDefault(); - if (defvalue && defvalue[0] != '\0') sig << " = " << defvalue; - } - if (iarg != nArgs-1) sig << (show_formalargs ? ", " : ","); - } - sig << ")"; - return sig.str(); - } - return ""; -} - -std::string Cppyy::GetMethodPrototype(TCppScope_t scope, TCppMethod_t method, bool show_formalargs) -{ - std::string scName = GetScopedFinalName(scope); - TFunction* f = m2f(method); - if (f) { - std::ostringstream sig; - sig << f->GetReturnTypeName() << " " - << scName << "::" << f->GetName(); - sig << GetMethodSignature(method, show_formalargs); - return sig.str(); - } - return ""; -} - -bool Cppyy::IsConstMethod(TCppMethod_t method) -{ - if (method) { - TFunction* f = m2f(method); - return f->Property() & kIsConstMethod; - } - return false; -} - -Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace) -{ - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) { - TCollection* coll = gROOT->GetListOfFunctionTemplates(); - if (coll) return (TCppIndex_t)coll->GetSize(); - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TCollection* coll = cr->GetListOfFunctionTemplates(true); - if (coll) return (TCppIndex_t)coll->GetSize(); - } - } - -// failure ... - return (TCppIndex_t)0; -} - -std::string Cppyy::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) -{ - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return ((THashList*)gROOT->GetListOfFunctionTemplates())->At((int)imeth)->GetName(); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return cr->GetListOfFunctionTemplates(false)->At((int)imeth)->GetName(); - } - -// failure ... - assert(!"should not be called unless GetNumTemplatedMethods() succeeded"); - return ""; -} - -bool Cppyy::IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth) -{ - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return false; - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunctionTemplate* f = (TFunctionTemplate*)cr->GetListOfFunctionTemplates(false)->At((int)imeth); - return f->ExtraProperty() & kIsConstructor; - } - - return false; -} - -bool Cppyy::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) -{ - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return (bool)gROOT->GetFunctionTemplate(name.c_str()); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return (bool)cr->GetFunctionTemplate(name.c_str()); - } - -// failure ... - return false; -} - -bool Cppyy::IsStaticTemplate(TCppScope_t scope, const std::string& name) -{ - TFunctionTemplate* tf = nullptr; - if (scope == (TCppScope_t)GLOBAL_HANDLE) - tf = gROOT->GetFunctionTemplate(name.c_str()); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - tf = cr->GetFunctionTemplate(name.c_str()); - } - - if (!tf) return false; - - return (bool)(tf->Property() & kIsStatic); -} - -bool Cppyy::IsMethodTemplate(TCppScope_t scope, TCppIndex_t idx) -{ - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* f = (TFunction*)cr->GetListOfMethods(false)->At((int)idx); - if (f && strstr(f->GetName(), "<")) return true; - return false; - } - - assert(scope == (Cppyy::TCppType_t)GLOBAL_HANDLE); - if (((CallWrapper*)idx)->fName.find('<') != std::string::npos) return true; - return false; -} - -// helpers for Cppyy::GetMethodTemplate() -static std::map gMethodTemplates; - -static inline -void remove_space(std::string& n) { - std::string::iterator pos = std::remove_if(n.begin(), n.end(), isspace); - n.erase(pos, n.end()); -} - -static inline -bool template_compare(std::string n1, std::string n2) { - if (n1.back() == '>') n1 = n1.substr(0, n1.size()-1); - remove_space(n1); - remove_space(n2); - return n2.compare(0, n1.size(), n1) == 0; -} - -Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto) -{ -// There is currently no clean way of extracting a templated method out of ROOT/meta -// for a variety of reasons, none of them fundamental. The game played below is to -// first get any pre-existing functions already managed by ROOT/meta, but if that fails, -// to do an explicit lookup that ignores the prototype (i.e. the full name should be -// enough), and finally to ignore the template arguments part of the name as this fails -// in cling if there are default parameters. - TFunction* func = nullptr; ClassInfo_t* cl = nullptr; - if (scope == (TCppScope_t)GLOBAL_HANDLE) { - func = gROOT->GetGlobalFunctionWithPrototype(name.c_str(), proto.c_str()); - if (func && name.back() == '>') { - // make sure that all template parameters match (more are okay, e.g. defaults or - // ones derived from the arguments or variadic templates) - if (!template_compare(name, func->GetName())) - func = nullptr; // happens if implicit conversion matches the overload - } - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - func = cr->GetMethodWithPrototype(name.c_str(), proto.c_str()); - if (!func) { - cl = cr->GetClassInfo(); - // try base classes to cover a common 'using' case (TODO: this is stupid and misses - // out on base classes; fix that with improved access to Cling) - TCppIndex_t nbases = GetNumBases(scope); - for (TCppIndex_t i = 0; i < nbases; ++i) { - TClassRef& base = type_from_handle(GetScope(GetBaseName(scope, i))); - if (base.GetClass()) { - func = base->GetMethodWithPrototype(name.c_str(), proto.c_str()); - if (func) break; - } - } - } - } - } - - if (!func && name.back() == '>' && (cl || scope == (TCppScope_t)GLOBAL_HANDLE)) { - // try again, ignoring proto in case full name is complete template - auto declid = gInterpreter->GetFunction(cl, name.c_str()); - if (declid) { - auto existing = gMethodTemplates.find(declid); - if (existing == gMethodTemplates.end()) { - auto cw = new_CallWrapper(declid, name); - existing = gMethodTemplates.insert(std::make_pair(declid, cw)).first; - } - return (TCppMethod_t)existing->second; - } - } - - if (func) { - // make sure we didn't match a non-templated overload - if (func->ExtraProperty() & kIsTemplateSpec) - return (TCppMethod_t)new_CallWrapper(func); - - // disregard this non-templated method as it will be considered when appropriate - return (TCppMethod_t)nullptr; - } - -// try again with template arguments removed from name, if applicable - if (name.back() == '>') { - auto pos = name.find('<'); - if (pos != std::string::npos) { - TCppMethod_t cppmeth = GetMethodTemplate(scope, name.substr(0, pos), proto); - if (cppmeth) { - // allow if requested template names match up to the result - const std::string& alt = GetMethodFullName(cppmeth); - if (name.size() < alt.size() && alt.find('<') == pos) { - if (template_compare(name, alt)) - return cppmeth; - } - } - } - } - -// failure ... - return (TCppMethod_t)nullptr; -} - -static inline -std::string type_remap(const std::string& n1, const std::string& n2) -{ -// Operator lookups of (C++ string, Python str) should succeed for the combos of -// string/str, wstring/str, string/unicode and wstring/unicode; since C++ does not have a -// operator+(std::string, std::wstring), we'll have to look up the same type and rely on -// the converters in CPyCppyy/_cppyy. - if (n1 == "str" || n1 == "unicode") { - if (n2 == "std::basic_string,std::allocator >") - return n2; // match like for like - return "std::string"; // probably best bet - } else if (n1 == "float") { - return "double"; // debatable, but probably intended - } else if (n1 == "complex") { - return "std::complex"; - } - return n1; -} - -Cppyy::TCppIndex_t Cppyy::GetGlobalOperator( - TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& opname) -{ -// Find a global operator function with a matching signature; prefer by-ref, but -// fall back on by-value if that fails. - std::string lcname1 = TClassEdit::CleanType(lc.c_str()); - const std::string& rcname = rc.empty() ? rc : type_remap(TClassEdit::CleanType(rc.c_str()), lcname1); - const std::string& lcname = type_remap(lcname1, rcname); - - std::string proto = lcname + "&" + (rc.empty() ? rc : (", " + rcname + "&")); - if (scope == (TCppScope_t)GLOBAL_HANDLE) { - TFunction* func = gROOT->GetGlobalFunctionWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)new_CallWrapper(func); - proto = lcname + (rc.empty() ? rc : (", " + rcname)); - func = gROOT->GetGlobalFunctionWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)new_CallWrapper(func); - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* func = cr->GetMethodWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)cr->GetListOfMethods()->IndexOf(func); - proto = lcname + (rc.empty() ? rc : (", " + rcname)); - func = cr->GetMethodWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)cr->GetListOfMethods()->IndexOf(func); - } - } - -// failure ... - return (TCppIndex_t)-1; -} - -// method properties --------------------------------------------------------- - -static inline bool testMethodProperty(Cppyy::TCppMethod_t method, EProperty prop) -{ - if (!method) - return false; - TFunction *f = m2f(method); - return f->Property() & prop; -} - -static inline bool testMethodExtraProperty(Cppyy::TCppMethod_t method, EFunctionProperty prop) -{ - if (!method) - return false; - TFunction *f = m2f(method); - return f->ExtraProperty() & prop; -} - -bool Cppyy::IsPublicMethod(TCppMethod_t method) -{ - return testMethodProperty(method, kIsPublic); -} - -bool Cppyy::IsProtectedMethod(TCppMethod_t method) -{ - return testMethodProperty(method, kIsProtected); -} - -bool Cppyy::IsConstructor(TCppMethod_t method) -{ - return testMethodExtraProperty(method, kIsConstructor); -} - -bool Cppyy::IsDestructor(TCppMethod_t method) -{ - return testMethodExtraProperty(method, kIsDestructor); -} - -bool Cppyy::IsStaticMethod(TCppMethod_t method) -{ - return testMethodProperty(method, kIsStatic); -} - -bool Cppyy::IsExplicit(TCppMethod_t method) -{ - return testMethodProperty(method, kIsExplicit); -} - -// data member reflection information ---------------------------------------- -Cppyy::TCppIndex_t Cppyy::GetNumDatamembers(TCppScope_t scope, bool accept_namespace) -{ - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) - return gROOT->GetListOfGlobals(true)->GetSize(); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass() && cr->GetListOfDataMembers()) - return cr->GetListOfDataMembers()->GetSize(); - - return (TCppIndex_t)0; // unknown class? -} - -std::string Cppyy::GetDatamemberName(TCppScope_t scope, TCppIndex_t idata) -{ - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->GetName(); - } - assert(scope == GLOBAL_HANDLE); - TGlobal* gbl = g_globalvars[idata]; - return gbl->GetName(); -} - -static inline -int count_scopes(const std::string& tpname) -{ - int count = 0; - std::string::size_type pos = tpname.find("::", 0); - while (pos != std::string::npos) { - count++; - pos = tpname.find("::", pos+1); - } - return count; -} - -std::string Cppyy::GetDatamemberType(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - std::string fullType = gbl->GetFullTypeName(); - - if ((int)gbl->GetArrayDim()) { - std::ostringstream s; - for (int i = 0; i < (int)gbl->GetArrayDim(); ++i) - s << '[' << gbl->GetMaxIndex(i) << ']'; - fullType.append(s.str()); - } - return fullType; - } - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - // TODO: fix this upstream ... Usually, we want m->GetFullTypeName(), because it - // does not resolve typedefs, but it looses scopes for inner classes/structs, it - // doesn't resolve constexpr (leaving unresolved names), leaves spurious "struct" - // or "union" in the name, and can not handle anonymous unions. In that case - // m->GetTrueTypeName() should be used. W/o clear criteria to determine all these - // cases, the general rules are to prefer the true name if the full type does not - // exist as a type for classes, and the most scoped name otherwise. - const char* ft = m->GetFullTypeName(); std::string fullType = ft ? ft : ""; - const char* tn = m->GetTrueTypeName(); std::string trueName = tn ? tn : ""; - if (!trueName.empty() && fullType != trueName && !IsBuiltin(trueName)) { - if ( (!TClass::GetClass(fullType.c_str()) && TClass::GetClass(trueName.c_str())) || \ - (count_scopes(trueName) > count_scopes(fullType)) ) { - bool is_enum_tag = fullType.rfind("enum ", 0) != std::string::npos; - fullType = trueName; - if (is_enum_tag) - fullType.insert(fullType.rfind("const ", 0) == std::string::npos ? 0 : 6, "enum "); - } - } - - if ((int)m->GetArrayDim()) { - std::ostringstream s; - for (int i = 0; i < (int)m->GetArrayDim(); ++i) - s << '[' << m->GetMaxIndex(i) << ']'; - fullType.append(s.str()); - } - -#if 0 - // this is the only place where anonymous structs are uniquely identified, so setup - // a class if needed, such that subsequent GetScope() and GetScopedFinalName() calls - // return the uniquely named class - auto declid = m->GetTagDeclId(); //GetDeclId(); - if (declid && (m->Property() & (kIsClass | kIsStruct | kIsUnion)) &&\ - (fullType.find("(anonymous)") != std::string::npos || fullType.find("(unnamed)") != std::string::npos)) { - - // use the (fixed) decl id address to guarantee a unique name, even when there - // are multiple anonymous structs in the parent scope - std::ostringstream fulls; - fulls << fullType << "@" << (void*)declid; - fullType = fulls.str(); - - if (g_name2classrefidx.find(fullType) == g_name2classrefidx.end()) { - ClassInfo_t* ci = gInterpreter->ClassInfo_Factory(declid); - TClass* cl = gInterpreter->GenerateTClass(ci, kTRUE /* silent */); - gInterpreter->ClassInfo_Delete(ci); - if (cl) cl->SetName(fullType.c_str()); - g_name2classrefidx[fullType] = g_classrefs.size(); - g_classrefs.emplace_back(cl); - } - } -#endif - return fullType; - } - - return ""; -} - -intptr_t Cppyy::GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - if (!gbl->GetAddress() || gbl->GetAddress() == (void*)-1) { - // CLING WORKAROUND: make sure variable is loaded - intptr_t addr = (intptr_t)gInterpreter->ProcessLine((std::string("&")+gbl->GetName()+";").c_str()); - if (gbl->GetAddress() && gbl->GetAddress() != (void*)-1) - return (intptr_t)gbl->GetAddress(); // now loaded! - return addr; // last resort ... - } - return (intptr_t)gbl->GetAddress(); - } - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - // CLING WORKAROUND: the following causes templates to be instantiated first within the proper - // scope, making the lookup succeed and preventing spurious duplicate instantiations later. Also, - // if the variable is not yet loaded, pull it in through gInterpreter. - intptr_t offset = (intptr_t)-1; - if (m->Property() & kIsStatic) { - if (strchr(cr->GetName(), '<')) - gInterpreter->ProcessLine(((std::string)cr->GetName()+"::"+m->GetName()+";").c_str()); - offset = (intptr_t)m->GetOffsetCint(); // yes, Cling (GetOffset() is both wrong - // and caches that wrong result! - if (offset == (intptr_t)-1) - return (intptr_t)gInterpreter->ProcessLine((std::string("&")+cr->GetName()+"::"+m->GetName()+";").c_str()); - } else - offset = (intptr_t)m->GetOffsetCint(); // yes, Cling, see above - return offset; - } - - return (intptr_t)-1; -} - -static inline -Cppyy::TCppIndex_t gb2idx(TGlobal* gb) -{ - if (!gb) return (Cppyy::TCppIndex_t)-1; - - auto pidx = g_globalidx.find(gb); - if (pidx == g_globalidx.end()) { - auto idx = g_globalvars.size(); - g_globalvars.push_back(gb); - g_globalidx[gb] = idx; - return (Cppyy::TCppIndex_t)idx; - } - return (Cppyy::TCppIndex_t)pidx->second; -} - -Cppyy::TCppIndex_t Cppyy::GetDatamemberIndex(TCppScope_t scope, const std::string& name) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gb = (TGlobal*)gROOT->GetListOfGlobals(false /* load */)->FindObject(name.c_str()); - if (!gb) gb = (TGlobal*)gROOT->GetListOfGlobals(true /* load */)->FindObject(name.c_str()); - if (!gb) { - // some enums are not loaded as they are not considered part of - // the global scope, but of the enum scope; get them w/o checking - TDictionary::DeclId_t did = gInterpreter->GetDataMember(nullptr, name.c_str()); - if (did) { - DataMemberInfo_t* t = gInterpreter->DataMemberInfo_Factory(did, nullptr); - ((TListOfDataMembers*)gROOT->GetListOfGlobals())->Get(t, true); - gb = (TGlobal*)gROOT->GetListOfGlobals(false /* load */)->FindObject(name.c_str()); - } - } - - if (gb && strcmp(gb->GetFullTypeName(), "(lambda)") == 0) { - // lambdas use a compiler internal closure type, so we wrap - // them, then return the wrapper's type - // TODO: this current leaks the std::function; also, if possible, - // should instantiate through TClass rather then ProcessLine - std::ostringstream s; - s << "auto __cppyy_internal_wrap_" << name << " = " - "new __cling_internal::FT::F" - "{" << name << "};"; - gInterpreter->ProcessLine(s.str().c_str()); - TGlobal* wrap = (TGlobal*)gROOT->GetListOfGlobals(true)->FindObject( - ("__cppyy_internal_wrap_"+name).c_str()); - if (wrap && wrap->GetAddress()) gb = wrap; - } - - return gb2idx(gb); - - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* dm = - (TDataMember*)cr->GetListOfDataMembers()->FindObject(name.c_str()); - // TODO: turning this into an index is silly ... - if (dm) return (TCppIndex_t)cr->GetListOfDataMembers()->IndexOf(dm); - } - } - - return (TCppIndex_t)-1; -} - -Cppyy::TCppIndex_t Cppyy::GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gb = (TGlobal*)((THashList*)gROOT->GetListOfGlobals(false /* load */))->At((int)idata); - return gb2idx(gb); - } - - return idata; -} - - -// data member properties ---------------------------------------------------- -bool Cppyy::IsPublicData(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsPublic; -} - -bool Cppyy::IsProtectedData(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsProtected; -} - -bool Cppyy::IsStaticData(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsStatic; -} - -bool Cppyy::IsConstData(TCppScope_t scope, TCppIndex_t idata) -{ - Long_t property = 0; - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - property = gbl->Property(); - } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - property = m->Property(); - } - -// if the data type is const, but the data member is a pointer/array, the data member -// itself is not const; alternatively it is a pointer that is constant - return ((property & kIsConstant) && !(property & (kIsPointer | kIsArray))) || (property & kIsConstPointer); -} - -bool Cppyy::IsEnumData(TCppScope_t scope, TCppIndex_t idata) -{ -// TODO: currently, ROOT/meta does not properly distinguish between variables of enum -// type, and values of enums. The latter are supposed to be const. This code relies on -// odd features (bugs?) to figure out the difference, but this should really be fixed -// upstream and/or deserves a new API. - - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - - // make use of an oddity: enum global variables do not have their kIsStatic bit - // set, whereas enum global values do - return (gbl->Property() & kIsEnum) && (gbl->Property() & kIsStatic); - } - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - std::string ti = m->GetTypeName(); - - // can't check anonymous enums by type name, so just accept them as enums - if (ti.rfind("(anonymous)") != std::string::npos || ti.rfind("(unnamed)") != std::string::npos) - return m->Property() & kIsEnum; - - // since there seems to be no distinction between data of enum type and enum values, - // check the list of constants for the type to see if there's a match - if (ti.rfind(cr->GetName(), 0) != std::string::npos) { - std::string::size_type s = strlen(cr->GetName())+2; - if (s < ti.size()) { - TEnum* ee = ((TListOfEnums*)cr->GetListOfEnums())->GetObject(ti.substr(s, std::string::npos).c_str()); - if (ee) return ee->GetConstant(m->GetName()); - } - } - } - -// this default return only means that the data will be writable, not that it will -// be unreadable or otherwise misrepresented - return false; -} - -int Cppyy::GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - return gbl->GetMaxIndex(dimension); - } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->GetMaxIndex(dimension); - } - return -1; -} - - -// enum properties ----------------------------------------------------------- -Cppyy::TCppEnum_t Cppyy::GetEnum(TCppScope_t scope, const std::string& enum_name) -{ - if (scope == GLOBAL_HANDLE) - return (TCppEnum_t)gROOT->GetListOfEnums(kTRUE)->FindObject(enum_name.c_str()); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return (TCppEnum_t)cr->GetListOfEnums(kTRUE)->FindObject(enum_name.c_str()); - - return (TCppEnum_t)0; -} - -Cppyy::TCppIndex_t Cppyy::GetNumEnumData(TCppEnum_t etype) -{ - return (TCppIndex_t)((TEnum*)etype)->GetConstants()->GetSize(); -} - -std::string Cppyy::GetEnumDataName(TCppEnum_t etype, TCppIndex_t idata) -{ - return ((TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata))->GetName(); -} - -long long Cppyy::GetEnumDataValue(TCppEnum_t etype, TCppIndex_t idata) -{ - TEnumConstant* ecst = (TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata); - return (long long)ecst->GetValue(); -} - - diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h deleted file mode 100644 index fbbbf21012931..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ /dev/null @@ -1,292 +0,0 @@ -#ifndef CPYCPPYY_CPPYY_H -#define CPYCPPYY_CPPYY_H - -// Standard -#include -#include -#include -#include -#include - -// some more types; assumes Cppyy.h follows Python.h -#ifndef PY_LONG_LONG -#ifdef _WIN32 -typedef __int64 PY_LONG_LONG; -#else -typedef long long PY_LONG_LONG; -#endif -#endif - -#ifndef PY_ULONG_LONG -#ifdef _WIN32 -typedef unsigned __int64 PY_ULONG_LONG; -#else -typedef unsigned long long PY_ULONG_LONG; -#endif -#endif - -#ifndef PY_LONG_DOUBLE -typedef long double PY_LONG_DOUBLE; -#endif - - -namespace Cppyy { - typedef size_t TCppScope_t; - typedef TCppScope_t TCppType_t; - typedef void* TCppEnum_t; - typedef void* TCppObject_t; - typedef intptr_t TCppMethod_t; - - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; - -// direct interpreter access ------------------------------------------------- - RPY_EXPORTED - bool Compile(const std::string& code, bool silent = false); - RPY_EXPORTED - std::string ToString(TCppType_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- - RPY_EXPORTED - std::string ResolveName(const std::string& cppitem_name); - RPY_EXPORTED - std::string ResolveEnum(const std::string& enum_type); - RPY_EXPORTED - TCppScope_t GetScope(const std::string& scope_name); - RPY_EXPORTED - TCppType_t GetActualClass(TCppType_t klass, TCppObject_t obj); - RPY_EXPORTED - size_t SizeOf(TCppType_t klass); - RPY_EXPORTED - size_t SizeOf(const std::string& type_name); - - RPY_EXPORTED - bool IsBuiltin(const std::string& type_name); - RPY_EXPORTED - bool IsComplete(const std::string& type_name); - - RPY_EXPORTED - TCppScope_t gGlobalScope; // for fast access - -// memory management --------------------------------------------------------- - RPY_EXPORTED - TCppObject_t Allocate(TCppType_t type); - RPY_EXPORTED - void Deallocate(TCppType_t type, TCppObject_t instance); - RPY_EXPORTED - TCppObject_t Construct(TCppType_t type, void* arena = nullptr); - RPY_EXPORTED - void Destruct(TCppType_t type, TCppObject_t instance); - -// method/function dispatching ----------------------------------------------- - RPY_EXPORTED - void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - - RPY_EXPORTED - void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); - RPY_EXPORTED - TCppObject_t CallConstructor(TCppMethod_t method, TCppType_t type, size_t nargs, void* args); - RPY_EXPORTED - void CallDestructor(TCppType_t type, TCppObject_t self); - RPY_EXPORTED - TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type); - - RPY_EXPORTED - TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true); - -// handling of function argument buffer -------------------------------------- - RPY_EXPORTED - void* AllocateFunctionArgs(size_t nargs); - RPY_EXPORTED - void DeallocateFunctionArgs(void* args); - RPY_EXPORTED - size_t GetFunctionArgSizeof(); - RPY_EXPORTED - size_t GetFunctionArgTypeoffset(); - -// scope reflection information ---------------------------------------------- - RPY_EXPORTED - bool IsNamespace(TCppScope_t scope); - RPY_EXPORTED - bool IsTemplate(const std::string& template_name); - RPY_EXPORTED - bool IsAbstract(TCppType_t type); - RPY_EXPORTED - bool IsEnum(const std::string& type_name); - RPY_EXPORTED - bool IsAggregate(TCppType_t type); - RPY_EXPORTED - bool IsIntegerType(const std::string &type_name); - RPY_EXPORTED - bool IsDefaultConstructable(TCppType_t type); - - RPY_EXPORTED - void GetAllCppNames(TCppScope_t scope, std::set& cppnames); - -// namespace reflection information ------------------------------------------ - RPY_EXPORTED - std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- - RPY_EXPORTED - std::string GetFinalName(TCppType_t type); - RPY_EXPORTED - std::string GetScopedFinalName(TCppType_t type); - RPY_EXPORTED - bool HasVirtualDestructor(TCppType_t type); - RPY_EXPORTED - bool HasComplexHierarchy(TCppType_t type); - RPY_EXPORTED - TCppIndex_t GetNumBases(TCppType_t type); - RPY_EXPORTED - TCppIndex_t GetNumBasesLongestBranch(TCppType_t type); - RPY_EXPORTED - std::string GetBaseName(TCppType_t type, TCppIndex_t ibase); - RPY_EXPORTED - bool IsSubtype(TCppType_t derived, TCppType_t base); - RPY_EXPORTED - bool IsSmartPtr(TCppType_t type); - RPY_EXPORTED - bool GetSmartPtrInfo(const std::string&, TCppType_t* raw, TCppMethod_t* deref); - RPY_EXPORTED - void AddSmartPtrType(const std::string&); - - RPY_EXPORTED - void AddTypeReducer(const std::string& reducable, const std::string& reduced); - -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - RPY_EXPORTED - ptrdiff_t GetBaseOffset( - TCppType_t derived, TCppType_t base, TCppObject_t address, int direction, bool rerror = false); - -// method/function reflection information ------------------------------------ - RPY_EXPORTED - TCppIndex_t GetNumMethods(TCppScope_t scope, bool accept_namespace = false); - RPY_EXPORTED - std::vector GetMethodIndicesFromName(TCppScope_t scope, const std::string& name); - - RPY_EXPORTED - TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth); - - RPY_EXPORTED - std::string GetMethodName(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodFullName(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodMangledName(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodResultType(TCppMethod_t); - RPY_EXPORTED - TCppIndex_t GetMethodNumArgs(TCppMethod_t); - RPY_EXPORTED - TCppIndex_t GetMethodReqArgs(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); - RPY_EXPORTED - std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - std::string GetMethodSignature(TCppMethod_t, bool show_formalargs, TCppIndex_t maxargs = (TCppIndex_t)-1); - RPY_EXPORTED - std::string GetMethodPrototype(TCppScope_t scope, TCppMethod_t, bool show_formalargs); - RPY_EXPORTED - bool IsConstMethod(TCppMethod_t); - - RPY_EXPORTED - TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); - RPY_EXPORTED - std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); - RPY_EXPORTED - bool IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth); - RPY_EXPORTED - bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); - RPY_EXPORTED - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); - RPY_EXPORTED - bool IsMethodTemplate(TCppScope_t scope, TCppIndex_t imeth); - RPY_EXPORTED - TCppMethod_t GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto); - - RPY_EXPORTED - TCppIndex_t GetGlobalOperator( - TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& op); - -// method properties --------------------------------------------------------- - RPY_EXPORTED - bool IsPublicMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsProtectedMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsConstructor(TCppMethod_t method); - RPY_EXPORTED - bool IsDestructor(TCppMethod_t method); - RPY_EXPORTED - bool IsStaticMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsExplicit(TCppMethod_t method); - -// data member reflection information ---------------------------------------- - RPY_EXPORTED - TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false); - RPY_EXPORTED - std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); - RPY_EXPORTED - TCppIndex_t GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata); - -// data member properties ---------------------------------------------------- - RPY_EXPORTED - bool IsPublicData(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - bool IsStaticData(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - bool IsConstData(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); - RPY_EXPORTED - int GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension); - -// enum properties ----------------------------------------------------------- - RPY_EXPORTED - TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name); - RPY_EXPORTED - TCppIndex_t GetNumEnumData(TCppEnum_t); - RPY_EXPORTED - std::string GetEnumDataName(TCppEnum_t, TCppIndex_t idata); - RPY_EXPORTED - long long GetEnumDataValue(TCppEnum_t, TCppIndex_t idata); - -} // namespace Cppyy - -#endif // !CPYCPPYY_CPPYY_H diff --git a/bindings/pyroot/cppyy/cppyy/.gitignore b/bindings/pyroot/cppyy/cppyy/.gitignore deleted file mode 100644 index 7cda4c971bdbe..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -# python compiled files -*.pyc -*.pyo - -# dictionary products -*.so -*.dll -*.lib -*.def -*.exp -*.pcm -*.rootmap -*_rflx.cpp - -.cache -.pytest_cache - -# distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# notebook products -.ipynb_checkpoints -doc/tutorial/gsl_selection.xml - -# saved log files of benches -bench/logs -bench/*.log -bench/swig_*.py -bench/*_wrap.cxx -bench/*_main diff --git a/bindings/pyroot/cppyy/cppyy/.readthedocs.yaml b/bindings/pyroot/cppyy/cppyy/.readthedocs.yaml deleted file mode 100644 index 49dafa0fa724d..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/.readthedocs.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.11" - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: doc/source/conf.py - -# We recommend specifying your dependencies to enable reproducible builds: -# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: doc/requirements.txt diff --git a/bindings/pyroot/cppyy/cppyy/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy/CMakeLists.txt deleted file mode 100644 index f1fb41ff94f26..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -# For the cppyy Python package -add_subdirectory(python) - -ROOT_ADD_TEST_SUBDIRECTORY(test) diff --git a/bindings/pyroot/cppyy/cppyy/LICENSE.txt b/bindings/pyroot/cppyy/cppyy/LICENSE.txt deleted file mode 100644 index 512f66c87d496..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/LICENSE.txt +++ /dev/null @@ -1,59 +0,0 @@ -Copyright (c) 2002-2021, The Regents of the University of California, -through Lawrence Berkeley National Laboratory (subject to receipt of -any required approvals from the U.S. Dept. of Energy). All rights -reserved. Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the following -conditions are met: - -(1) Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -(2) Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -(3) Neither the name of the University of California, Lawrence Berkeley -National Laboratory, U.S. Dept. of Energy nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - -You are under no obligation whatsoever to provide any bug fixes, -patches, or upgrades to the features, functionality or performance of -the source code ("Enhancements") to anyone; however, if you choose to -make your Enhancements available either publicly, or directly to -Lawrence Berkeley National Laboratory, without imposing a separate -written license agreement for such Enhancements, then you hereby grant -the following license: a non-exclusive, royalty-free perpetual license -to install, use, modify, prepare derivative works, incorporate into -other computer software, distribute, and sublicense such Enhancements -or derivative works thereof, in binary and source code form. - - -Additional copyright holders ----------------------------- - -In addition to LBNL/UC Berkeley, this package contains files copyrighted by -one or more of the following people and organizations, and licensed under -the same conditions (except for some compatible licenses as retained in the -source code): - - Lucio Asnaghi - Simone Bacchio - Aditi Dutta - Shaheed Haque - Aaron Jomy - Jonas Rembser - Toby StClere-Smithe - Stefan Wunsch - -Conda-forge recipes were provided by Julian Rueth and Isuru Fernando. diff --git a/bindings/pyroot/cppyy/cppyy/MANIFEST.in b/bindings/pyroot/cppyy/cppyy/MANIFEST.in deleted file mode 100644 index ae3ddffac029c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -# Include the license file -include LICENSE.txt - -# Add custom installer -recursive-include installer * - -# Do not add the test or doc directories -prune test -prune doc diff --git a/bindings/pyroot/cppyy/cppyy/README.rst b/bindings/pyroot/cppyy/cppyy/README.rst deleted file mode 100644 index 169abf334835a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/README.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. -*- mode: rst -*- - -cppyy: Python-C++ bindings interface based on Cling/LLVM -======================================================== - -cppyy provides fully automatic, dynamic Python-C++ bindings by leveraging -the Cling C++ interpreter and LLVM. - -These bindings work both ways, so you can call C++ code from Python and the other way around. - -You need no boilerplate code, and you can access Python APIs from Cpp, and the other way around. - -It supports both PyPy (natively), CPython, and C++ language standards -through C++20 (and parts of C++23). - -Details and performance are described in -`this paper `_, -originally presented at PyHPC'16, but since updated with improved performance -numbers. - -Full documentation: `cppyy.readthedocs.io `_. - -Notebook-based tutorial: `Cppyy Tutorial `_. - -For Anaconda/miniconda, install cppyy from `conda-forge `_. - ----- - -Change log: - https://cppyy.readthedocs.io/en/latest/changelog.html - -Bug reports/feedback: - https://github.com/wlav/cppyy/issues diff --git a/bindings/pyroot/cppyy/cppyy/bench/Makefile b/bindings/pyroot/cppyy/cppyy/bench/Makefile deleted file mode 100644 index 57d2f05bf3190..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -dicts := functioncallsDict.so runvectorDict.so -libs := libfunctioncalls.so librunvector.so -modules := -execs := functioncalls_main runvector_main -CHECK_PYBIND11 := $(shell python -m pybind11 1>/dev/null 2>/dev/null; echo $$?) -ifeq ($(CHECK_PYBIND11),0) - modules += py11_functioncalls.so -endif -CHECK_SWIG := $(shell swig -help 1>/dev/null 2>/dev/null; echo $$?) -ifeq ($(CHECK_SWIG),0) - modules += _swig_functioncalls.so swig_functioncalls.py - modules += _swig_runvector.so swig_runvector.py -endif -all : $(dicts) $(libs) $(execs) $(modules) - -clingconfig := cling-config - -cppflags:=-O3 -fPIC -cppyy_cppflags=$(shell $(clingconfig) --cflags) $(cppflags) - -PLATFORM := $(shell uname -s) -ifeq ($(PLATFORM),Darwin) - MACHINE := $(shell uname -m) - cppflags+=-dynamiclib -arch $(MACHINE) -undefined dynamic_lookup -endif - -py11_%.so: py11_%.cxx lib%.so - $(CXX) $(cppflags) -shared -std=c++11 -fPIC `python -m pybind11 --includes` py11_$*.cxx -o $@ -L. -l$* - -swig_%.py: %.h %.i - swig -python -c++ -builtin $*.i - -_swig_%.so: swig_%.py lib%.so - $(CXX) $(cppflags) -shared -I$(shell python -c 'import distutils.sysconfig as ds; print(ds.get_config_var("INCLUDEPY"))') -std=c++11 -fPIC $*_wrap.cxx -o $@ -L. -l$* - -%Dict.so: %_rflx.cpp lib%.so - $(CXX) $(cppyy_cppflags) -shared -o $@ $*_rflx.cpp -L. -l$* - -%_rflx.cpp: %.h %.xml - genreflex $< --selection=$*.xml --rootmap=$*Dict.rootmap --rootmap-lib=$*Dict.so - -lib%.so: %.cxx - $(CXX) $(cppflags) -shared -o lib$*.so $*.cxx - -# note -O2 for cxx to make sure code actually runs instead of being optimized out of existence -%_main: %_main.cxx lib%.so - $(CXX) -std=c++11 -O2 -fPIC -o $@ $*_main.cxx -L. -l$* - -.PHONY: bench clean - -bench: all - pytest -s bench_runvector.py --benchmark-sort=mean - -clean: - -rm -f $(dicts) $(libs) $(execs) $(modules) $(wildcard *.rootmap) $(wildcard *_rdict.pcm) $(wildcard *_wrap.cxx) diff --git a/bindings/pyroot/cppyy/cppyy/bench/basic_micro.py b/bindings/pyroot/cppyy/cppyy/bench/basic_micro.py deleted file mode 100644 index 712cac138c0cf..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/basic_micro.py +++ /dev/null @@ -1,38 +0,0 @@ -import cppyy, gc, math, os, psutil, time - -NLARGE = 20000000 - -process = psutil.Process(os.getpid()) - -def benchit(what, callf, N): - print("running:", what) - mpre = process.memory_info().rss/1024 - tpre = time.perf_counter() - for i in range(N): - callf() - tpost = time.perf_counter() - gc.collect() - mpost = process.memory_info().rss/1024 - if tpost - tpre < 1.: - print(" suggest increasing N by %dx" % math.ceil(1./(tpost-tpre))) - print(" time:", (tpost - tpre)/N) - if mpost == mpre: - print(" memcheck passed") - else: - print(" memcheck FAILED:", mpre, mpost) - -cppyy.cppdef(""" - void gfunc() {} - - class MyClass { - public: - void mfunc() {} - }; -""") - - -f = cppyy.gbl.gfunc -benchit("global function", f, NLARGE) - -inst = cppyy.gbl.MyClass() -benchit("member function", inst.mfunc, NLARGE) diff --git a/bindings/pyroot/cppyy/cppyy/bench/bench_functioncalls.py b/bindings/pyroot/cppyy/cppyy/bench/bench_functioncalls.py deleted file mode 100644 index 23c51d96cd61b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/bench_functioncalls.py +++ /dev/null @@ -1,168 +0,0 @@ -import py, pytest, os, sys, math, warnings -from support import setup_make - -setup_make("functioncallsDict.so") - - -import cppyy - -cppyy.load_library("functioncalls") -cppyy.load_library("functioncallsDict") - -import py_functioncalls - -all_configs = [('py', 'py_functioncalls'), ('cppyy', 'cppyy.gbl')] - -N = 10000 -preamble = "@pytest.mark.benchmark(group=group, warmup=True)" -looprange = range -if sys.hexversion < 0x3000000: - looprange = xrange - -try: - import __pypy__ -except ImportError: - try: - import py11_functioncalls - all_configs.append(('py11', 'py11_functioncalls')) - py11 = True - except ImportError: - warnings.warn('pybind11 tests disabled') - py11 = False - - try: - import swig_functioncalls - all_configs.append(('swig', 'swig_functioncalls')) - swig = True - except ImportError: - warnings.warn('swig tests disabled') - swig = False - -all_benches = [] - - -#- group: empty-free --------------------------------------------------------- -all_benches.append(('empty-free', ( -""" -def test_{0}_free_empty_call(benchmark): - benchmark({1}.empty_call) -""", -))) - -#- group: empty-inst --------------------------------------------------------- -def call_instance_empty(inst): - for i in looprange(N): - inst.empty_call() - -all_benches.append(('empty-inst', ( -""" -def test_{0}_inst_empty_call(benchmark): - inst = {1}.EmptyCall() - benchmark(call_instance_empty, inst) -""", -))) - - -#- group: builtin-args-free -------------------------------------------------- -all_benches.append(('builtin-args-free', ( -""" -def test_{0}_free_take_an_int(benchmark): - benchmark({1}.take_an_int, 1) -""", -""" -def test_{0}_free_take_a_double(benchmark): - benchmark({1}.take_a_double, 1.) -""", -""" -def test_{0}_free_take_a_struct(benchmark): - benchmark({1}.take_a_struct, {1}.Value()) -""", -))) - -#- group: builtin-args-inst -------------------------------------------------- -def call_instance_take_an_int(inst, val): - for i in looprange(N): - inst.take_an_int(1) - -def call_instance_take_a_double(inst, val): - for i in looprange(N): - inst.take_a_double(val) - -def call_instance_take_a_struct(inst, val): - for i in looprange(N): - inst.take_a_struct(val) - - -all_benches.append(('builtin-args-inst', ( -""" -def test_{0}_inst_take_an_int(benchmark): - inst = {1}.TakeAValue() - benchmark(call_instance_take_an_int, inst, 1) -""", -""" -def test_{0}_inst_take_a_double(benchmark): - inst = {1}.TakeAValue() - benchmark(call_instance_take_a_double, inst, 1.) -""", -""" -def test_{0}_inst_take_a_struct(benchmark): - inst = {1}.TakeAValue() - benchmark(call_instance_take_a_struct, inst, {1}.Value()) -""", -))) - -#- group: builtin-args-pass -------------------------------------------------- -def call_instance_pass_int(inst, val): - for i in looprange(N): - inst.pass_int(val) - -all_benches.append(('builtin-args-pass', ( -""" -def test_{0}_inst_pass_int(benchmark): - inst = {1}.TakeAValue() - benchmark(call_instance_pass_int, inst, 1) -""", -))) - - -#- group: do_work-free ------------------------------------------------------- -all_benches.append(('do_work-free', ( -""" -def test_{0}_free_do_work(benchmark): - benchmark({1}.do_work, 1.) -""", -))) - -#- group: do_work-inst ------------------------------------------------------- -def call_instance_do_work(inst): - for i in looprange(N): - inst.do_work(1.) - -all_benches.append(('do_work-inst', ( -""" -def test_{0}_inst_do_work(benchmark): - inst = {1}.DoWork() - benchmark(call_instance_do_work, inst) -""", -))) - - -#- group: overload-inst ------------------------------------------------------ -def call_instance_overload(inst): - for i in looprange(N): - inst.add_it(1.) - -all_benches.append(('overload-inst', ( -""" -def test_{0}_inst_overload(benchmark): - inst = {1}.OverloadedCall() - benchmark(call_instance_overload, inst) -""", -))) - - -#- actual creation of all benches -------------------------------------------- -for group, benches in all_benches: - for bench in benches: - for label, modname in all_configs: - exec(preamble+bench.format(label, modname)) diff --git a/bindings/pyroot/cppyy/cppyy/bench/bench_runvector.py b/bindings/pyroot/cppyy/cppyy/bench/bench_runvector.py deleted file mode 100644 index f6444922fe42f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/bench_runvector.py +++ /dev/null @@ -1,54 +0,0 @@ -import py, pytest, os, sys, math, warnings -from support import setup_make - -setup_make("runvectorDict.so") - - -import cppyy - -cppyy.load_library("runvector") -cppyy.load_library("runvectorDict") - -all_configs = [('cppyy', 'cppyy.gbl')] - -preamble = "@pytest.mark.benchmark(group=group, warmup=True)" - - -try: - import __pypy__ - import py_runvector - all_configs.append(('py', 'py_runvector')) # too slow to run on CPython -except ImportError: - try: - import py11_runvector - all_configs.append(('py11', 'py11_runvector')) - py11 = True - except ImportError: - warnings.warn('pybind11 tests disabled') - py11 = False - - try: - import swig_runvector - all_configs.append(('swig', 'swig_runvector.cvar')) - swig = True - except ImportError: - warnings.warn('swig tests disabled') - swig = False - -all_benches = [] - - -#- group: stl-vector --------------------------------------------------------- -all_benches.append(('stl-vector', ( -""" -def test_{0}_stl_vector(benchmark): - benchmark(sum, {1}.global_vector) -""", -))) - - -#- actual creation of all benches -------------------------------------------- -for group, benches in all_benches: - for bench in benches: - for label, modname in all_configs: - exec(preamble+bench.format(label, modname)) diff --git a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.cxx b/bindings/pyroot/cppyy/cppyy/bench/functioncalls.cxx deleted file mode 100644 index 38bfa4a4a906a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.cxx +++ /dev/null @@ -1,66 +0,0 @@ -#include "functioncalls.h" - -#include -#include - - -//- group: empty ------------------------------------------------------------- -void empty_call() { -/* empty, to measure pure call overhead */ -} - -void EmptyCall::empty_call() { -/* empty, to measure pure call overhead */ -} - - -//- group: builtin-args-free ------------------------------------------------- -void take_an_int(int /* unused */) { -/* empty, to measure pure call overhead */ -} - -void take_a_double(double /* unused */) { -/* empty, to measure pure call overhead */ -} - -void take_a_struct(Value /* unused */) { -/* empty, to measure pure call overhead */ -} - -//- group: builtin-args-inst ------------------------------------------------- -void TakeAValue::take_an_int(int /* unused */) { -/* empty, to measure pure call overhead */ -} - -void TakeAValue::take_a_double(double /* unused */) { -/* empty, to measure pure call overhead */ -} - -void TakeAValue::take_a_struct(Value /* unused */) { -/* empty, to measure pure call overhead */ -} - -//- group: builtin-args-pass ------------------------------------------------- -int TakeAValue::pass_int(int a) { - return a + 42; -} - - -//- group: do-work ----------------------------------------------------------- -double do_work(double arg) { - return atan(arg); -} - -double DoWork::do_work(double arg) { - return atan(arg); -} - - -//- group: overload-inst ----------------------------------------------------- -double OverloadedCall::add_it(int a, int b) { std::terminate(); } -double OverloadedCall::add_it(short a) { std::terminate(); } -double OverloadedCall::add_it(long a) { std::terminate(); } -double OverloadedCall::add_it(int a, int b, int c) { std::terminate(); } -double OverloadedCall::add_it(double a) { return 3.1415 + a; } -double OverloadedCall::add_it(float a) { return 3.1415 + a; } -double OverloadedCall::add_it(int a) { std::terminate(); } diff --git a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.h b/bindings/pyroot/cppyy/cppyy/bench/functioncalls.h deleted file mode 100644 index de9c9429a16c2..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CPPYY_FUNCTIONCALLS_H -#define CPPYY_FUNCTIONCALLS_H - -//- group: empty-free -------------------------------------------------------- -void empty_call(); - -//- group: empty-inst -------------------------------------------------------- -class EmptyCall { -public: - void empty_call(); -}; - - -//- group: builtin-args-free ------------------------------------------------- -struct Value { int m_int; }; - -void take_an_int(int); -void take_a_double(double); -void take_a_struct(Value); - -//- group: builtin-args-free ------------------------------------------------- -class TakeAValue { -public: - void take_an_int(int); - void take_a_double(double); - void take_a_struct(Value); - - int pass_int(int); -}; - - -//- group: do-work-free ------------------------------------------------------ -double do_work(double); - -//- group: do-work-inst ------------------------------------------------------ -class DoWork { -public: - double do_work(double); -}; - - -//- group: overload-inst ----------------------------------------------------- -class OverloadedCall { -public: - double add_it(int a, int b); - double add_it(short a); - double add_it(long a); - double add_it(int a, int b, int c); - double add_it(double a); - double add_it(float a); - double add_it(int a); -}; - -#endif // !CPPYY_FUNCTIONCALLS_H diff --git a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.i b/bindings/pyroot/cppyy/cppyy/bench/functioncalls.i deleted file mode 100644 index 5e38b8a0a9d68..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.i +++ /dev/null @@ -1,6 +0,0 @@ -%module swig_functioncalls -%{ -#include "functioncalls.h" -%} - -%include "functioncalls.h" diff --git a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.xml b/bindings/pyroot/cppyy/cppyy/bench/functioncalls.xml deleted file mode 100644 index dfa7809b6ae67..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/functioncalls.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/bench/functioncalls_main.cxx b/bindings/pyroot/cppyy/cppyy/bench/functioncalls_main.cxx deleted file mode 100644 index 2e76e204f19f0..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/functioncalls_main.cxx +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include - -#include "functioncalls.h" - - -static const int N = 1000000000; // 10^9, i.e. per call is in nanoseconds - -int cpp_loop_offset() { - int i = 0; - for ( ; i < N; ++i) - ; - return i; -} - -//- group: empty-free -------------------------------------------------------- -void cpp_free_empty_call() { - for (int i=0; i < N; ++i) - empty_call(); -} - -//- group: empty-inst -------------------------------------------------------- -void cpp_inst_empty_call() { - EmptyCall e; - for (int i=0; i < N; ++i) - e.empty_call(); -} - - -//- group: builtin-args-inst ------------------------------------------------- -void cpp_inst_pass_int() { - TakeAValue t; - for (int i=0; i < N; ++i) - t.pass_int(i); -} - - -//- group: do-work ----------------------------------------------------------- -void cpp_inst_do_work() { - DoWork d; - for (int i=0; i < N; ++i) - d.do_work(i); -} - - -//- group: do-overload ------------------------------------------------------- -void cpp_inst_overload() { - OverloadedCall o; - for (int i=0; i < N; ++i) - o.add_it((float)i); -} - - -//---------------------------------------------------------------------------- -void run_bench(void (*cpp_bench)(), const char* label) { - clock_t t1 = clock(); - cpp_loop_offset(); - clock_t t2 = clock(); - cpp_bench(); - clock_t t3 = clock(); - - std::cout << label << ": " << std::setprecision(8) - << ((t3-t2) - (t2-t1))/((double)CLOCKS_PER_SEC) << " nanoseconds" << std::endl; -} - -int main() { - run_bench(cpp_free_empty_call, "cpp_free_empty_call"); - run_bench(cpp_inst_empty_call, "cpp_inst_empty_call"); - run_bench(cpp_inst_pass_int, "cpp_inst_pass_int"); - run_bench(cpp_inst_do_work, "cpp_do_work"); - run_bench(cpp_inst_overload, "cpp_inst_overload"); - return 0; -} diff --git a/bindings/pyroot/cppyy/cppyy/bench/py11_functioncalls.cxx b/bindings/pyroot/cppyy/cppyy/bench/py11_functioncalls.cxx deleted file mode 100644 index 0412d9d6c5f3d..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/py11_functioncalls.cxx +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include "functioncalls.h" - - -namespace py = pybind11; - -PYBIND11_MODULE(py11_functioncalls, m) { -//- group: empty-free -------------------------------------------------------- - m.def("empty_call", &empty_call); - -//- group: empty-inst -------------------------------------------------------- - py::class_(m, "EmptyCall") - .def(py::init<>()) - .def("empty_call", &EmptyCall::empty_call); - - -//- group: builtin-args-free ------------------------------------------------- - py::class_(m, "Value") - .def(py::init<>()); - - m.def("take_an_int", &take_an_int); - m.def("take_a_double", &take_a_double); - m.def("take_a_struct", &take_a_struct); - -//- group: builtin-args-free ------------------------------------------------- - py::class_(m, "TakeAValue") - .def(py::init<>()) - .def("take_an_int", &TakeAValue::take_an_int) - .def("take_a_double", &TakeAValue::take_a_double) - .def("take_a_struct", &TakeAValue::take_a_struct) - -//- group: builtin-args-pass ------------------------------------------------- - .def("pass_int", &TakeAValue::pass_int); - - -//- group: do-work-free ------------------------------------------------------ - m.def("do_work", &do_work); - -//- group: do-work-inst ------------------------------------------------------ - py::class_(m, "DoWork") - .def(py::init<>()) - .def("do_work", &DoWork::do_work); - - -//- group: overload-inst ----------------------------------------------------- - py::class_(m, "OverloadedCall") - .def(py::init<>()) - .def("add_it", (double (OverloadedCall::*)(int, int)) &OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(short)) &OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(long)) &OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(int, int, int))&OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(double)) &OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(float)) &OverloadedCall::add_it) - .def("add_it", (double (OverloadedCall::*)(int)) &OverloadedCall::add_it); -} diff --git a/bindings/pyroot/cppyy/cppyy/bench/py_functioncalls.py b/bindings/pyroot/cppyy/cppyy/bench/py_functioncalls.py deleted file mode 100644 index c25a827dac49b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/py_functioncalls.py +++ /dev/null @@ -1,56 +0,0 @@ -import math - - -#- group: empty-free --------------------------------------------------------- -def empty_call(): - pass - -#- group: empty-inst --------------------------------------------------------- -class EmptyCall(object): - def empty_call(self): - pass - - -#- group: builtin-args-free -------------------------------------------------- -class Value(object): - def __init__(self): - self.m_int = 42 - -def take_an_int(val): - pass - -def take_a_double(val): - pass - -def take_a_struct(val): - pass - -#- group: builtin-args-inst -------------------------------------------------- -class TakeAValue(object): - def take_an_int(self, val): - pass - - def take_a_double(self, val): - pass - - def take_a_struct(self, val): - pass - - def pass_int(self, val): - return val + 42 - - -#- group: do_work-free ------------------------------------------------------- -def do_work(val): - return math.atan(val) - -#- group: do_work-inst ------------------------------------------------------- -class DoWork(object): - def do_work(self, val): - return math.atan(val) - - -#- group: overload-inst ------------------------------------------------------ -class OverloadedCall(object): - def add_it(self, *args): - return 3.1415 + sum(args) diff --git a/bindings/pyroot/cppyy/cppyy/bench/py_runvector.py b/bindings/pyroot/cppyy/cppyy/bench/py_runvector.py deleted file mode 100644 index e1f3649a95f22..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/py_runvector.py +++ /dev/null @@ -1,10 +0,0 @@ -import array, sys - -N = 100000000 # 10^8 - - -#- group: stl-vector --------------------------------------------------------- -looprange = range -if sys.hexversion < 0x3000000: - looprange = xrange -global_vector = array.array('i', looprange(N)) diff --git a/bindings/pyroot/cppyy/cppyy/bench/runvector.cxx b/bindings/pyroot/cppyy/cppyy/bench/runvector.cxx deleted file mode 100644 index fcabc9005d742..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/runvector.cxx +++ /dev/null @@ -1,15 +0,0 @@ -#include "runvector.h" - -std::vector global_vector; -double global_effect = 0.; - -static const int N = 100000000; // 10^8 - -namespace { - struct Initializer { - Initializer() { - global_vector.reserve(N); - for (int i=0; i < N; ++i) global_vector.push_back(i); - } - } _init_global_vector; -} diff --git a/bindings/pyroot/cppyy/cppyy/bench/runvector.h b/bindings/pyroot/cppyy/cppyy/bench/runvector.h deleted file mode 100644 index 52e0fc3b58981..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/runvector.h +++ /dev/null @@ -1,5 +0,0 @@ -#include - -extern std::vector global_vector; -extern double global_effect; - diff --git a/bindings/pyroot/cppyy/cppyy/bench/runvector.i b/bindings/pyroot/cppyy/cppyy/bench/runvector.i deleted file mode 100644 index 3626ca8443c66..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/runvector.i +++ /dev/null @@ -1,12 +0,0 @@ -%module swig_runvector -%{ -#include "runvector.h" -%} - -%include "runvector.h" -%include "std_vector.i" - -namespace std { - %template(vectori) vector; -} - diff --git a/bindings/pyroot/cppyy/cppyy/bench/runvector.xml b/bindings/pyroot/cppyy/cppyy/bench/runvector.xml deleted file mode 100644 index 1e7fbbea9681d..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/runvector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/bench/runvector_main.cxx b/bindings/pyroot/cppyy/cppyy/bench/runvector_main.cxx deleted file mode 100644 index e429814360747..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/runvector_main.cxx +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include -#include -#include - -#include "runvector.h" - - -static const int N = 100000000; // 10^8, i.e. per call is in 10 nanoseconds - -int cpp_loop_offset() { - int i = 0; - for ( ; i < N; ++i) - ; - return i; -} - - -//- group: stl-vector -------------------------------------------------------- -void cpp_runvector() { - for (auto i: global_vector) - global_effect += i; -} - - -//---------------------------------------------------------------------------- -void run_bench(void (*cpp_bench)(), const char* label) { - clock_t t1 = clock(); - cpp_loop_offset(); - clock_t t2 = clock(); - cpp_bench(); - clock_t t3 = clock(); - - std::cout << label << ": " << std::setprecision(8) - << 10.*((t3-t2) - (t2-t1))/((double)CLOCKS_PER_SEC) << " nanoseconds" << std::endl; -} - -int main() { - run_bench(cpp_runvector, "cpp_runvector"); - return 0; -} diff --git a/bindings/pyroot/cppyy/cppyy/bench/support.py b/bindings/pyroot/cppyy/cppyy/bench/support.py deleted file mode 100644 index a2eeeaf289773..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/bench/support.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import print_function -import py, sys, subprocess - -currpath = py.path.local(__file__).dirpath() - - -def setup_make(targetname): - if sys.platform == 'win32': - raise OSError("win32 not supported yet") - popen = subprocess.Popen(["make", targetname], cwd=str(currpath), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout, _ = popen.communicate() - if popen.returncode: - raise OSError("'make' failed:\n%s" % (stdout,)) diff --git a/bindings/pyroot/cppyy/cppyy/doc/.gitignore b/bindings/pyroot/cppyy/cppyy/doc/.gitignore deleted file mode 100644 index 378eac25d3117..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/bindings/pyroot/cppyy/cppyy/doc/Makefile b/bindings/pyroot/cppyy/cppyy/doc/Makefile deleted file mode 100644 index cbe159e5ce7fe..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/cppyy.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/cppyy.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/cppyy" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/cppyy" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/bindings/pyroot/cppyy/cppyy/doc/make.bat b/bindings/pyroot/cppyy/cppyy/doc/make.bat deleted file mode 100644 index a89c8bd30100c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\cppyy.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\cppyy.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/bindings/pyroot/cppyy/cppyy/doc/requirements.txt b/bindings/pyroot/cppyy/cppyy/doc/requirements.txt deleted file mode 100644 index 1678b6d6f330c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Defining the exact version will make sure things don't break -sphinx==6.2.1 -sphinx_rtd_theme==1.2.2 -readthedocs-sphinx-search==0.3.2 diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/_static/css/custom.css b/bindings/pyroot/cppyy/cppyy/doc/source/_static/css/custom.css deleted file mode 100644 index 5402ff8e7bd6d..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/_static/css/custom.css +++ /dev/null @@ -1,7 +0,0 @@ -.rst-content .toconly { - display: none; -} - -nav .toconly { - display: unset; -} diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/basic_types.rst b/bindings/pyroot/cppyy/cppyy/doc/source/basic_types.rst deleted file mode 100644 index 06ce060ed91ab..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/basic_types.rst +++ /dev/null @@ -1,194 +0,0 @@ -.. _basic_types: - - -Basic types -=========== - -C++ has a far richer set of builtin types than Python. -Most Python code can remain relatively agnostic to that, and ``cppyy`` -provides automatic conversions as appropriate. -On the other hand, Python builtin types such as lists and maps are far -richer than any builtin types in C++. -These are mapped to their Standard Template Library equivalents instead. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded before running any of the example code snippets. -Download it, save it under the name ``features.h``, and simply include it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - - -`Builtins` -"""""""""" - -The selection of builtin data types varies greatly between Python and C++. -Where possible, builtin data types map onto the expected equivalent Python -types, with the caveats that there may be size differences, different -precision or rounding, etc. -For example, a C++ ``float`` is returned as a Python ``float``, which is in -fact a C++ ``double``. -If sizes allow, conversions are automatic. -For example, a C++ ``unsigned int`` becomes a Python2 ``long`` or Python3 -``int``, but unsigned-ness is still honored: - - .. code-block:: python - - >>> cppyy.gbl.gUint - 0L - >>> type(cppyy.gbl.gUint) - - >>> cppyy.gbl.gUint = -1 - Traceback (most recent call last): - File "", line 1, in - ValueError: cannot convert negative integer to unsigned - >>> - -On some platforms, 8-bit integer types such as ``int8_t`` and ``uint8_t`` are -represented as `char` types. -For consistency, these are mapped onto Python `int`. - -Some types are builtin in Python, but (STL) classes in C++. -Examples are ``str`` vs. ``std::string`` (see also the -:doc:`Strings ` section) and ``complex`` vs. ``std::complex``. -These classes have been pythonized to behave the same wherever possible. -For example, string comparison work directly, and ``std::complex`` has -``real`` and ``imag`` properties: - - .. code-block:: python - - >>> c = cppyy.gbl.std.complex['double'](1, 2) - >>> c - (1+2j) - >>> c.real, c.imag - (1.0, 2.0) - >>> s = cppyy.gbl.std.string("aap") - >>> type(s) - - >>> s == "aap" - True - >>> - -To pass an argument through a C++ ``char`` (signed or unsigned) use a Python -string of size 1. -In many cases, the explicit C types from module ``ctypes`` can also be used, -but that module does not have a public API (for type conversion or otherwise), -so support is somewhat limited. - -There are automatic conversions between C++'s ``std::vector`` and Python's -``list`` and ``tuple``, where possible, as they are often used in a similar -manner. -These datatypes have completely different memory layouts, however, and the -``std::vector`` requires that all elements are of the same type and laid -out consecutively in memory. -Conversion thus requires type checks, memory allocation, and copies. -This can be rather expensive. -See the section on :ref:`STL `. - - -`Arrays` -"""""""" - -Builtin arrays are supported through arrays from module ``array`` (or any -other builtin-type array that implements the Python buffer interface, such -as numpy arrays) and a low-level view type from ``cppyy`` for returns and -variable access (that implements the buffer interface as well). -Out-of-bounds checking is limited to those cases where the size is known at -compile time. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> from array import array - >>> c = Concrete() - >>> c.array_method(array('d', [1., 2., 3., 4.]), 4) - 1 2 3 4 - >>> c.m_data[4] # static size is 4, so out of bounds - Traceback (most recent call last): - File "", line 1, in - IndexError: buffer index out of range - >>> - -Arrays of arrays are supported through the C++ low-level view objects. -This only works well if sizes are known at compile time or can be inferred. -If sizes are not known, the size is set to a large integer (depending on the -array element size) to allow access. -It is then up to the developer not to access the array out-of-bounds. -There is limited support for arrays of instances, but those should be avoided -in C++ anyway: - - .. code-block:: python - - >>> cppyy.cppdef('std::string str_array[3][2] = {{"aa", "bb"}, {"cc", "dd"}, {"ee", "ff"}};') - True - >>> type(cppyy.gbl.str_array[0][1]) - - >>> cppyy.gbl.str_array[0][1] - 'bb' - >>> cppyy.gbl.str_array[4][0] - Traceback (most recent call last): - File "", line 1, in - IndexError: tuple index out of range - >>> - - -`Pointers` -"""""""""" - -When the C++ code takes a pointer or reference type to a specific builtin -type (such as an ``unsigned int`` for example), then types need to match -exactly. -``cppyy`` supports the types provided by the standard modules ``ctypes`` and -``array`` for those cases. -Example of using a reference to builtin: - - .. code-block:: python - - >>> from ctypes import c_uint - >>> u = c_uint(0) - >>> c.uint_ref_assign(u, 42) - >>> u.value - 42 - >>> - -For objects, an object, a pointer to an object, and a smart pointer to an -object are represented the same way, with the necessary (de)referencing -applied automatically. -Pointer variables are also bound by reference, so that updates on either the -C++ or Python side are reflected on the other side as well. - - -`Enums` -""""""" - -Named, anonymous, and class enums are supported. -The Python-underlying type of an enum is implementation dependent and may even -be different for different enums on the same compiler. -Typically, however, the types are ``int`` or ``unsigned int``, which -translates to Python's ``int`` or ``long`` on Python2 or class ``int`` on -Python3. -Separate from the underlying, all enums have their own Python type to allow -them to be used in template instantiations: - - .. code-block:: python - - >>> from cppyy.gbl import kBanana # classic enum, globally available - >>> print(kBanana) - 29 - >>> cppyy.gbl.EFruit - - >>> print(cppyy.gbl.EFruit.kApple) - 78 - >>> cppyy.gbl.E1 # C++11 class enum, scoped - Traceback (most recent call last): - File "", line 1, in - AttributeError: has no attribute 'E1'. - >>> cppyy.gbl.NamedClassEnum.E1 - 42 - >>> - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/bugs.rst b/bindings/pyroot/cppyy/cppyy/doc/source/bugs.rst deleted file mode 100644 index b598a792c9f1a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/bugs.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. _bugs: - -Bugs and feedback -================= - -Please report bugs, ask questions, request improvements, and post general -comments on the `issue tracker`_ or on `stack overflow`_ (marked with the -"cppyy" tag). - -.. _`issue tracker`: https://github.com/wlav/cppyy/issues -.. _`stack overflow`: https://stackoverflow.com/questions/tagged/cppyy diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/changelog.rst b/bindings/pyroot/cppyy/cppyy/doc/source/changelog.rst deleted file mode 100644 index 808c34539d1ee..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/changelog.rst +++ /dev/null @@ -1,638 +0,0 @@ -.. _changelog: - -Changelog -========= - -For convenience, this changelog keeps tracks of changes with version numbers -of the main cppyy package, but many of the actual changes are in the lower -level packages, which have their own releases. -See :doc:`packages `, for details on the package structure. -PyPy support lags CPython support. - - -master ------- - -* Fix crash when looping over empty ``std::vector`` of pointer types. -* Fix offsets for protected data members for multiple cross-inheritance -* Remove string_view compatibility header for C++14 -* Fix for typedefs that are mistaken as const -* Fix versioning/installation issues on specific platforms -* Avoid ``nullptr`` dereferencing in ``CPyCppyy::BindCppObjectNoCast`` -* Scope values of class enums for template instantiations -* Fix true/false default arguments when using keywords -* Fix potential recursion on ``operator->()`` for unknown methods -* Fix type checking for static methods to allow derived instances - - -2024-12-16: 3.5.0 ------------------ - -* Fix buffering problems with std::string_view's on Python str objects -* Fix potential buffering problems in creation of initializer lists -* Improved overload selection for classes with deep hierarchies -* Fixed regression when calling static methods with default args on instances -* Fixed regression for pickling enums (in global scope only) -* Proper error handling on ``memoryview(array.array('B', []))`` -* Auto-cast elements of std::vector, with T a class type -* Add a ``Sequence_Check()`` method to the public API -* Fix offset calculation of ``std::vector`` datamember on Mac arm -* Extend API to define executor and converter aliases -* Use importlib.metadata instead of pkg_resources for py3.11 and later -* Added out-of-bounds handling for small char-based enums -* Fixed a leak check in the generic STL iterator protocol -* Represent arrays of ``signed char`` as low level views returning bytes -* Improve memory regulator's handling of smart pointers -* Version PCHs with the C++ standard version -* Fixes for py3.12 and py3.13 -* Upgrade backend to Clang16 - - -2023-11-15: 3.1.2 ------------------ - -* Deprecate 3.1.1 b/c of an installation problem outside of virtualenv -* Fix installation problem when purelib and platlib differ -* Alt fix for "failed to materialize symbols" on some Linux systems - - -2023-11-13: 3.1.0 ------------------ - -* Use xcrun to find header files on Mac as a last resort -* Fix for "symbols failed to materialize" with newer gcc on Linux -* Default to C++20 on all platforms -* Add C++20 standard headers to the PCH -* Fixes for new p11 and p12 type properties -* Fix std::span compatibility -* Look for ``__cast_cpp__`` for custom converters -* Add ``macro()`` helper for evaluation of preprocessor macros -* Extended support for int8_t/uint8_t array and pointer types -* Added ``cppyy.ll.as_memoryview()`` for byte-views of arrays of PODs -* Check for ``nullptr`` as ``false`` in ``operator bool()`` -* Automatically array-ify std::vector::data() results -* Use __name__ to stringify if an annotation object provides it -* Improve consistency of ``char[]`` arrays -* Extended Numba support -* Update to latest Cling release (6.30) - - -2023-03-19: 3.0.0 ------------------ - -* Upgrade backend to Cling on top of LLVM 13 -* Improve handling of `const char*` as template argument -* Fix regression in use of unnamed but typedef'ed enums -* Report C++ warnings from ``cppdef`` as ``SyntaxWarning`` -* Add pythonizations for ``std::unordered_map`` - - -2023-01-21: 2.4.2 ------------------ - -* Added a generic ``cppyy.default`` object -* Support explicitly created initializer lists as arguments -* Pass instances by-ref in Numba traces -* Support non-POD by-value returns in Numba traces -* Nullify derived class Python proxy when the C++ object is deleted -* Add ``__cpp_template__`` back reference for instantiated templated classes -* Improved buffer checking for ``std::initializer_list`` -* Add convenience functions ``argc()`` and ``argv()`` to ``cppyy.ll`` -* Added ``nullptr`` comparisons for for typed ``nullptr`` -* Support for ``using`` pointer types as template arguments -* Walk the full inheritance tree to find the overloads -* Allow ``__destruct__`` override in Python derived class -* Allow ``NULL`` function pointers to be returned as ``std::function`` objects -* Add Python traceback to C++ exception ``what()`` - - -2022-10-03: 2.4.1 ------------------ - -* Drop Numba extension entry point - - -2022-06-29: 2.4.0 ------------------ - -* Support for free (templated) functions in Numba -* Basic support for unboxing C++ public data members in Numba -* Basic support for calling methods of C++ structs in Numba -* Added conventional `__cpp_reflex__` method for inspection in Numba -* Support for globally overloaded ordering operators -* Special cases for `__repr__`/`__str__` returning C++ stringy types -* Fix lookup of templates of function with template args -* Correct typing of int8_t/uint8_t enums -* Basic support for hidden enums -* Support function pointer returns and optimize function point variables -* Fix reuse of CPPOverload proxies in vector calls from different threads -* Use `-march=native` instead of checking the cpu for avx -* Workaround for handling exceptions from JITed code on ARM -* Drop ``from cppyy.interactive import *`` from CPython 3.11 -* Fix regression in converting `std::vector` initialization from Python `str` (copies) -* Provide access to extern "C" declared functions in namespaces -* Support for (multiple and nested) anonymous structs -* Pull forward upstream patch for PPC -* Only apply system_dirs patch (for asan) on Linux -* Add not-yet loaded classes to namespaces in dir() -* Fix lookup of templates of function with template args -* Fix lookup of templates types with << in name -* Fix regression for accessing `char16_t` data member arrays -* Add custom `__reshape__` method to CPPInstance to allow array cast -* Prioritize callee exceptions over bindings exceptions -* Prevent infinite recursion when instantiating class with no constructors - - -2021-11-14: 2.2.0 ------------------ - -* Migrated repos to github/wlav -* Properly resolve enum type of class enums -* Get proper shape of ``void*`` and enum arrays -* Fix access to (const) ref data members -* Fix sometimes PCH uninstall issue -* Fix argument passing of fixed arrays of pointers -* Include all gcc system paths (for asan) -* Initial support for Apple M1 - - -2021-07-17: 2.1.0 ------------------ - -* Support for vector calls with CPython 3.8 and newer -* Support for typed C++ literals as defaults when mixing with keywords -* Enable reshaping of multi-dim LowLevelViews -* Refactored multi-dim arrays and support for multi-dim assignment -* Support tuple-based indexing for multi-dim arrays -* Direct support for C's _Complex (_Complex_double/_float on Windows) -* sizeof() forwards to ctypes.sizeof() for ctypes' types -* Upgrade cmake fragments for Clang9 -* Prevent clash with Julia's LLVM when loading cppyy into PyCall -* Upgrade to latest Cling patch release - - -2021-05-14: 2.0.0 ------------------ - -* Upgrade to latest Cling based on Clang/LLVM 9 -* Make C++17 the default standard on Windows - - -2021-04-28: 1.9.6 ------------------ - -* Reverse operators for ``std::complex`` targeting Python's ``complex`` -* Version the precompiled header with the ``cppyy-cling`` package version -* Cover more iterator protocol use cases -* Add missing cppyy/__pyinstaller pkg to sdist -* Single-inheritance support for cross-inherited templated constructors -* Disallow ``float`` -> ``const long long&`` conversion -* Capture python exception message string in PyException from callbacks -* Thread safety in enum lookups - - -2021-03-22: 1.9.5 ------------------ - -* Do not regulate direct smart pointers (many to one can lead to double deletion) -* Use pkg_resources of ``CPyCppyy``, if available, to find the API include path - - -2021-03-17: 1.9.4 ------------------ - -* Fix for installing into a directory that has a space in the name -* Fix empty collection printing through Cling on 64b Windows -* Fix accidental shadowing of derived class typedefs by same names in base -* Streamlined templated function lookups in namespaces -* Fix edge cases when decomposing std::function template arguments -* Enable multi-cross inheritance with non-C++ python bases -* Support Bound C++ functions as template argument -* Python functions as template arguments from ``__annotations__`` or ``__cpp_name__`` -* Removed functions/apis deprecated in py3.9 -* Improved support for older pip and different installation layouts - - -2021-02-15: 1.9.3 ------------------ - -* Wheels for Linux now follow manylinux2014 -* Enable direct calls of base class' methods in Python cross-overrides -* cppyy.bind_object can now re-cast types, incl. Python cross-derived ones -* Python cross-derived objects send to (and owned by) C++ retain Python state -* Ignore, for symbol lookups, libraries that can not be reloaded -* Use PathCanonicalize when resolving paths on Windows -* Add more ways of finding the backend library -* Improve error reporting when failed to find the backend library -* Workaround for mixing std::endl in JIT-ed and compiled code on Windows 32b -* Fixed a subtle crash that arises when an invalid ``using`` is the last method -* Filter -fno-plt (coming from anaconda builds; not understood by Cling) -* Fixed memory leak in generic base ``__str__`` - - -2021-01-05: 1.9.2 ------------------ - -* Added ``cppyy.types`` module for exposing cppyy builtin types -* Improve numpy integration with custom ``__array__`` methods -* Allow operator overload resolution mixing class and global methods -* Installation fixes for PyPy when using pip - - -2020-11-23: 1.9.1 ------------------ - -* Fix custom installer in pip sdist - - -2020-11-22: 1.9.0 ------------------ - -* In-tree build resolving build/install order for PyPy with pyproject.toml -* ``std::string`` not converterd to ``str`` on function returns -* Cover more use cases where C string memory can be managed -* Automatic memory management of converted python functions -* Added pyinstaller hooks (https://stackoverflow.com/questions/64406727) -* Support for enums in pseudo-constructors of aggregates -* Fixes for overloaded/split-access protected members in cross-inheritance -* Support for deep, mixed, hierarchies for multi-cross-inheritance -* Added tp_iter method to low level views - - -2020-11-06: 1.8.6 ------------------ - -* Fix preprocessor macro of CPyCppyy header for Windows/MSVC - - -2020-10-31: 1.8.5 ------------------ - -* Fix leaks when using vector iterators on Py3/Linux - - -2020-10-10: 1.8.4 ------------------ - -* ``std::string`` globals/data members no longer automatically converted to ``str`` -* New methods for std::string to allow ``str`` interchangability -* Added a ``decode`` method to ``std::string`` -* Add pythonized ``__contains__`` to ``std::set`` -* Fix constructor generation for aggregates with static data -* Fix performance bug when using implicit conversions -* Fix memory overwrite when parsing during sorting of methods -* PyPy pip install again falls back to setup.py install - - -2020-09-21: 1.8.3 ------------------ - -* Add initializer constructors for PODs and aggregates -* Use actual underlying type for enums, where possible -* Enum values remain instances of their type -* Expose enum underlying type name as ``__underlying`` and ``__ctype__`` -* Strictly follow C++ enum scoping rules -* Same enum in transparent scope refers to same type -* More detailed enum ``repr()`` printing, where possible -* Fix for (extern) explicit template instantiations in namespaces -* Throw objects from an std::tuple a life line -* Global pythonizors now always run on all classes -* Simplified iteraton over STL-like containers defining ``begin()``/``end()`` - - -2020-09-08: 1.8.2 ------------------ - -* Add ``cppyy.set_debug()`` to enable debug output for fixing template errors -* Cover more partial template instantiation use cases -* Force template instantiation if necessary for type deduction (i.e. ``auto``) - - -2020-09-01: 1.8.1 ------------------ - -* Setup build dependencies with pyproject.toml -* Simplified flow of pointer types for callbacks and cross-derivation -* Pointer-comparing objects performs auto-cast as needed -* Add main dimension for ptr-ptr to builtin returns -* Transparant handling of ptr-ptr to instance returns -* Stricter handling of bool type in overload with int types -* Fix uint64_t template instantiation regression -* Do not filter out enum data for ``__dir__`` -* Fix lookup of interpreter-only explicit instantiations -* Fix inconsistent naming of std types with char_traits -* Further hiding of upstream code/dependencies -* Extended documentation - - -2020-07-12: 1.8.0 ------------------ - -* Support mixing of Python and C++ types in global operators -* Capture Cling error messages from cppdef and include in the Python exception -* Add a cppexec method to evalutate statements in Cling's global scope -* Support initialization of ``std::array<>`` from sequences -* Support C++17 style initialization of common STL containers -* Allow base classes with no virtual destructor (with warning) -* Support const by-value returns in Python-side method overrides -* Support for cross-language multiple inheritance of C++ bases -* Allow for pass-by-value of ``std::unique_ptr`` through move -* Reduced dependencies on upstream code -* Put remaining upstream code in CppyyLegacy namespace - - -2020-06-06: 1.7.1 ------------------ - -* Expose protected members in Python derived classes -* Support for deep Python-side derived hierarchies -* Do not generate a copy ctor in the Python derived class if private -* include, c_include, and cppdef now raise exceptions on error -* Allow mixing of keywords and default values -* Fix by-ptr return of objects in Python derived classes -* Fix for passing numpy boolean array through ``bool*`` -* Fix assignment to ``const char*`` data members -* Support ``__restrict`` and ``__restrict__`` in interfaces -* Allow passing sequence of strings through ``const char*[]`` argument - - -2020-04-27: 1.7.0 ------------------ - -* Upgrade to cppyy-cling 6.20.4 -* Pre-empt upstream's propensity of making ``std`` classes etc. global -* Allow initialization of ``std::map`` from dict with the correct types -* Allow initialization of ``std::set`` from set with the correct types -* Add optional nonst/non-const selection to ``__overload__`` -* Automatic smartification of normal object passed as smartptr by value -* Fix crash when handing a by-value object to make_shared -* Fixed a few shared/unique_ptr corner cases -* Fixed conversion of ``std::function`` taking an STL class parameter -* No longer attempt auto-cast on classes without RTTI -* Fix for ``iter()`` iteration on generic STL container - - -2020-03-15: 1.6.2 ------------------ - -* Respect ``__len__`` when using bound C++ objects in boolean expressions -* Support UTF-8 encoded ``unicode`` through ``std::string`` -* Support for ``std::byte`` -* Enable assignment to function pointer variable -* Allow passing cppyy.nullptr where a function pointer is expected -* Disable copy construction into constructed object (use ``__assign__`` instead) -* Cover more cases when to set a lifeline -* Lower priority of implicit conversion to temporary with initializer_list ctor -* Add type reduction pythonization for trimming expression template type trees -* Allow mixing ``std::string`` and ``str`` as dictionary keys -* Support C-style pointer-to-struct as array -* Support C-style enum variable declarations -* Fixed const_iterator by-ref return type regression -* Resolve enums into the actual underlying type instead of int -* Remove '-isystem' from makepch flags -* Extended documentation - - -2020-01-04: 1.6.1 ------------------ - -* Mapped C++ exception reporting detailing -* Mapped C++ exception cleanup bug fix -* STL vector constructor passes the CPython sequence construction -* STL vector slicing passes the CPython sequence slicing tests -* Extended documentation - - -2019-12-23: 1.6.0 ------------------ - -* Classes derived from ``std::exception`` can be used as Python exceptions -* Template handling detailing (for Eigen) -* Support keyword arguments -* Added add_library_path at module level -* Extended documentation -* Fix regression bugs: #176, #179, #180, #182 - - -2019-11-07: 1.5.7 ------------------ - -* Allow implicit converions for move arguments -* Choose vector over initializer_list if part of the template argument list - - -2019-11-03: 1.5.6 ------------------ - -* Added public C++ API for some CPyCppyy core functions (CPython only) -* Support for char16_t/char16_t* and char32_t/char32_t* -* Respect ``std::hash`` in ``__hash__`` -* Fix iteration over vector of shared_ptr -* Length checking on global variables of type 'signed char[N]' -* Properly support overloaded templated with non-templated ``__setitem__`` -* Support for array of const char* as C-strings -* Enable type resolution of clang's builtin ``__type_pack_element`` -* Fix for inner class type naming when it directly declares a variable - - -2019-10-16: 1.5.5 ------------------ - -* Added signal -> exception support in cppyy.ll -* Support for lazily combining overloads of operator*/+- -* No longer call trivial destructors -* Support for free function unary operators -* Refactored and optimized operator==/!= usage -* Refactored converters/executors for lower memory usage -* Bug fixes in rootcling and _cppyy_generator.py - - -2019-09-25: 1.5.4 ------------------ - -* operator+/* now respect C++-side associativity -* Fix potential crash if modules are reloaded -* Fix some portability issues on Mac/Windows of cppyy-cling - - -2019-09-15: 1.5.3 ------------------ - -* Performance improvements -* Support for anonymous/unnamed/nested unions -* Extended documentation - - -2019-09-06: 1.5.2 ------------------ - -* Added a "low level" interface (cppyy.ll) for hard-casting and ll types -* Extended support for passing ctypes arguments through ptr, ref, ptr-ptr -* Fixed crash when creating an array of instances of a scoped inner struct -* Extended documentation - - -2019-08-26: 1.5.1 ------------------ - -* Upgrade cppyy-cling to 6.18.2 -* Various patches to upstream's pre-compiled header generation and use -* Instantiate templates with larger integer types if argument values require -* Improve cppyy.interactive and partially enable it on PyPy, IPython, etc. -* Let ``__overload__`` be more flexible in signature matching -* Make list filtering of dir(cppyy.gbl) on Windows same as Linux/Mac -* Extended documentation - - -2019-08-18: 1.5.0 ------------------ - -* Upgrade cppyy-cling to 6.18.0 -* Allow python-derived classes to be used in templates -* Stricter template resolution and better caching/performance -* Detailed memory management for make_shared and shared_ptr -* Two-way memory management for cross-inherited objects -* Reduced memory footprint of proxy objects in most common cases -* Allow implicit conversion from a tuple of arguments -* Data set on namespaces reflected on C++ even if data not yet bound -* Generalized resolution of binary operators in wrapper generation -* Proper naming of arguments in namespaces for ``std::function<>`` -* Cover more cases of STL-liker iterators -* Allow ``std::vector`` initialization with a list of constructor arguments -* Consistent naming of ``__cppname__`` to ``__cpp_name__`` -* Added ``__set_lifeline__`` attribute to overloads -* Fixes to the cmake fragments for Ubuntu -* Fixes linker errors on Windows in some configurations -* Support C++ naming of typedef of bool types -* Basic views of 2D arrays of builtin types -* Extended documentation - - -2019-07-01 : 1.4.12 -------------------- - -* Automatic conversion of python functions to ``std::function`` arguments -* Fix for templated operators that can map to different python names -* Fix on p3 crash when setting a detailed exception during exception handling -* Fix lookup of ``std::nullopt`` -* Fix bug that prevented certain templated constructors from being considered -* Support for enum values as data members on "enum class" enums -* Support for implicit conversion when passing by-value - - -2019-05-23 : 1.4.11 -------------------- - -* Workaround for JITed RTTI lookup failures on 64b MS Windows -* Improved overload resolution between f(void*) and f<>(T*) -* Minimal support for char16_t (Windows) and char32_t (Linux/Mac) -* Do not unnecessarily autocast smart pointers - - -2019-05-13 : 1.4.10 -------------------- - -* Imported several FindCppyy.cmake improvements from Camille's cppyy-bbhash -* Fixes to cppyy-generator for unresolved templates, void, etc. -* Fixes in typedef parsing for template arguments in unknown namespaces -* Fix in templated operator code generation -* Fixed ref-counting error for instantiated template methods - - -2019-04-25 : 1.4.9 ------------------- - -* Fix import error on pypy-c - - -2019-04-22 : 1.4.8 ------------------- - -* ``std::tuple`` is now iterable for return assignments w/o tie -* Support for opaque handles and typedefs of pointers to classes -* Keep unresolved enums desugared and provide generic converters -* Treat int8_t and uint8_t as integers (even when they are chars) -* Fix lookup of enum values in global namespace -* Backported name mangling (esp. for static/global data lookup) for 32b Windows -* Fixed more linker problems with malloc on 64b Windows -* Consistency in buffer length calculations and c_int/c_uint handling on Windows -* Properly resolve overloaded functions with using of templates from bases -* Get templated constructor info from decl instead of name comparison -* Fixed a performance regression for free functions. - - -2019-04-04 : 1.4.7 ------------------- - -* Enable initializer_list conversion on Windows as well -* Improved mapping of operator() for indexing (e.g. for matrices) -* Implicit conversion no longer uses global state to prevent recursion -* Improved overload reordering -* Fixes for templated constructors in namespaces - - -2019-04-02 : 1.4.6 ------------------- - -* More transparent use of smart pointers such as shared_ptr -* Expose versioned std namespace through using on Mac -* Improved error handling and interface checking in cross-inheritance -* Argument of (const/non-const) ref types support in callbacks/cross-inheritance -* Do template argument resolution in order: reference, pointer, value -* Fix for return type deduction of resolved but uninstantiated templates -* Fix wrapper generation for defaulted arguments of private types -* Several linker fixes on 64b Windows - - -2019-03-25 : 1.4.5 ------------------- - -* Allow templated free functions to be attached as methods to classes -* Allow cross-derivation from templated classes -* More support for 'using' declarations (methods and inner namespaces) -* Fix overload resolution for ``std::set::rbegin()``/``rend()`` ``operator==`` -* Fixes for bugs #61, #67 -* Several pointer truncation fixes for 64b Windows -* Linker and lookup fixes for Windows - - -2019-03-20 : 1.4.4 ------------------- - -* Support for 'using' of namespaces -* Improved support for alias templates -* Faster template lookup -* Have rootcling/genreflex respect compile-time flags (except for --std if - overridden by CLING_EXTRA_ARGS) -* Utility to build dictionarys on Windows (32/64) -* Name mangling fixes in Cling for JITed global/static variables on Windows -* Several pointer truncation fixes for 64b Windows - - -2019-03-10 : 1.4.3 ------------------- - -* Cross-inheritance from abstract C++ base classes -* Preserve 'const' when overriding virtual functions -* Support for by-ref (using ctypes) for function callbacks -* Identity of nested typedef'd classes matches actual -* Expose function pointer variables as ``std::function``'s -* More descriptive printout of global functions -* Ensure that standard pch is up-to-date and that it is removed on - uninstall -* Remove standard pch from wheels on all platforms -* Add -cxxflags option to rootcling -* Install clang resource directory on Windows diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/classes.rst b/bindings/pyroot/cppyy/cppyy/doc/source/classes.rst deleted file mode 100644 index efbf929a2db22..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/classes.rst +++ /dev/null @@ -1,417 +0,0 @@ -.. _classes: - - -Classes -======= - -Both Python and C++ support object-oriented code through classes and thus -it is logical to expose C++ classes as Python ones, including the full -inheritance hierarchy. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - - -`Basics` --------- - -All bound C++ code starts off from the global C++ namespace, represented in -Python by ``gbl``. -This namespace, as any other namespace, is treated as a module after it has -been loaded. -Thus, we can import C++ classes that live underneath it: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> Concrete - - >>> - -Placing classes in the same structure as imposed by C++ guarantees identity, -even if multiple Python modules bind the same class. -There is, however, no necessity to expose that structure to end-users: when -developing a Python package that exposes C++ classes through ``cppyy``, -consider ``cppyy.gbl`` an "internal" module, and expose the classes in any -structure you see fit. -The C++ names will continue to follow the C++ structure, however, as is needed -for e.g. pickling: - - .. code-block:: python - - >>> from cppyy.gbl import Namespace - >>> Concrete == Namespace.Concrete - False - >>> n = Namespace.Concrete.NestedClass() - >>> type(n) - - >>> type(n).__name__ - NestedClass - >>> type(n).__module__ - cppyy.gbl.Namespace.Concrete - >>> type(n).__cpp_name__ - Namespace::Concrete::NestedClass - >>> - - -`Constructors` --------------- - -Python and C++ both make a distinction between allocation (``__new__`` in -Python, ``operator new`` in C++) and initialization (``__init__`` in Python, -the constructor call in C++). -When binding, however, there comes a subtle semantic difference: the Python -``__new__`` allocates memory for the proxy object only, and ``__init__`` -initializes the proxy by creating or binding the C++ object. -Thus, no C++ memory is allocated until ``__init__``. -The advantages are simple: the proxy can now check whether it is initialized, -because the pointer to C++ memory will be NULL if not; it can be a reference -to another proxy holding the actual C++ memory; and it can now transparently -implement a C++ smart pointer. -If ``__init__`` is never called, eg. when a call to the base class -``__init__`` is missing in a derived class override, then accessing the proxy -will result in a Python ``ReferenceError`` exception. - - -`Destructors` -------------- - -There should no be reason to call a destructor directly in CPython, but e.g. -PyPy uses a garbage collector and that makes it sometimes useful to destruct -a C++ object exactly when you want it destroyed. -Destructors are by convention accessible through the ``__destruct__`` method -(since "~" can not be part of a Python method name). -If a Python-side derived class overrides ``__destruct__``, that method will -be called when the instance gets deleted in C++. -The Python destructor, ``__del__``, gets called when the Python proxy goes -away, which will only delete the C++ instance if owned by Python. -Note that ``__del__`` is not guaranteed to be called, it may e.g. be skipped -on program shutdown or because of an outstanding exception. -Accessing an object after it has been destroyed using ``__destruct__`` will -result in a Python ``ReferenceError`` exception. - - -`Inheritance` -------------- - -The output of help shows the inheritance hierarchy, constructors, public -methods, and public data. -For example, ``Concrete`` inherits from ``Abstract`` and it has -a constructor that takes an ``int`` argument, with a default value of 42. -Consider: - - .. code-block:: python - - >>> from cppyy.gbl import Abstract - >>> issubclass(Concrete, Abstract) - True - >>> a = Abstract() - Traceback (most recent call last): - File "", line 1, in - TypeError: cannot instantiate abstract class 'Abstract' - >>> c = Concrete() - >>> isinstance(c, Concrete) - True - >>> isinstance(c, Abstract) - True - >>> d = Concrete(13) - >>> - -Just like in C++, interface classes that define pure virtual methods, such -as ``Abstract`` does, can not be instantiated, but their concrete -implementations can. -As the output of ``help`` showed, the ``Concrete`` constructor takes -an integer argument, that by default is 42. - - -`Cross-inheritance` -------------------- - -Python classes that derive from C++ classes can override virtual methods as -long as those methods are declared on class instantiation (adding methods to -the Python class after the fact will not provide overrides on the C++ side, -only on the Python side). -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Abstract, call_abstract_method - >>> class PyConcrete(Abstract): - ... def abstract_method(self): - ... return "Hello, Python World!\n" - ... def concrete_method(self): - ... pass - ... - >>> pc = PyConcrete() - >>> call_abstract_method(pc) - Hello, Python World! - >>> - -Note that it is not necessary to provide a constructor (``__init__``), but -if you do, you *must* call the base class constructor through the ``super`` -mechanism. - - -`Multiple cross-inheritance` ----------------------------- - -Python requires that any multiple inheritance (also in pure Python) has an -unambiguous method resolution order (mro), including for classes and thus -also for meta-classes. -In Python2, it was possible to resolve any mro conflicts automatically, but -meta-classes in Python3, although syntactically richer, have functionally -become far more limited. -In particular, the mro is checked in the builtin class builder, instead of -in the meta-class of the meta-class (which in Python3 is the builtin ``type`` -rather than the meta-class itself as in Python2, another limitation, and -which actually checks the mro a second time for no reason). -The upshot is that a helper is required (``cppyy.multi``) to resolve the mro -to support Python3. -The helper is written to also work in Python2. -Example: - - .. code-block:: python - - >>> class PyConcrete(cppyy.multi(cppyy.gbl.Abstract1, cppyy.gbl.Abstract2)): - ... def abstract_method1(self): - ... return "first message" - ... def abstract_method2(self): - ... return "second message" - ... - >>> pc = PyConcrete() - >>> cppyy.gbl.call_abstract_method1(pc) - first message - >>> cppyy.gbl/call_abstract_method2(pc) - second message - >>> - -Contrary to multiple inheritance in Python, in C++ there are no two separate -instances representing the base classes. -Thus, a single ``__init__`` call needs to construct and initialize all bases, -rather than calling ``__init__`` on each base independently. -To support this syntax, the arguments to each base class should be grouped -together in a tuple. -If there are no arguments, provide an empty tuple (or omit them altogether, -if these arguments apply to the right-most base(s)). - - - .. _sec-methods-label: - -`Methods` ---------- - -C++ methods are represented as Python ones: these are first-class objects and -can be bound to an instance. -If a method is virtual in C++, the proper concrete method is called, whether -or not the concrete class is bound. -Similarly, if all classes are bound, the normal Python rules apply: - - .. code-block:: python - - >>> c.abstract_method() - called Concrete::abstract_method - >>> c.concrete_method() - called Concrete::concrete_method - >>> m = c.abstract_method - >>> m() - called Concrete::abstract_method - >>> - - -`Data members` --------------- - -Data members are implemented as properties, using descriptors. -For example, The ``Concrete`` instances have a public data member ``m_int``: - - .. code-block:: python - - >>> c.m_int, d.m_int - (42, 13) - >>> - -Note however, that the data members are typed: setting them results in a -memory write on the C++ side. -This is different in Python, where references are replaced, and thus any -type will do: - - .. code-block:: python - - >>> c.m_int = 3.14 # a float does not fit in an int - Traceback (most recent call last): - File "", line 1, in - TypeError: int/long conversion expects an integer object - >>> c.m_int = int(3.14) - >>> c.m_int, d.m_int - (3, 13) - >>> - -Private and protected data members are not accessible, contrary to Python -data members, and C++ const-ness is respected: - - .. code-block:: python - - >>> c.m_const_int = 71 # declared 'const int' in class definition - Traceback (most recent call last): - File "", line 1, in - TypeError: assignment to const data not allowed - >>> - -Static C++ data members act like Python class-level data members. -They are also represented by property objects and both read and write access -behave as expected: - - .. code-block:: python - - >>> Concrete.s_int # access through class - 321 - >>> c.s_int = 123 # access through instance - >>> Concrete.s_int - 123 - - - .. _sec-operators-label: - - -`Structs/Unions` ----------------- - -Structs and unions are both supported, named or anonymous. -If the latter, the field are accessible through the parent scope by their -declared name. -For example: - - .. code-block:: python - - >>> cppyy.cppdef("""\ - ... struct PointXYZ { - ... PointXYZI() : intensity(5.) {} - ... double x, y, z; - ... union { - ... int offset1; - ... struct { - ... int offset2; - ... float intensity; - ... }; - ... float data_c[4]; - ... }; - ... };""") - True - >>> p = cppyy.gbl.PointXYZI() - >>> type(p.x) - - >>> p.intensity - 5.0 - >>> type(p.data_c[1]) - - >>> p.data_c[1] = 3.0 - >>> p.intensity - 3.0 - >>> - - -`Operators` ------------ - -Many C++ operators can be mapped to their Python equivalent. -When the operators are part of the C++ class definition, this is done -directly. -If they are defined globally, the lookup is done lazily (ie. can resolve -after the class definition by loading the global definition or by defining -them interactively). -Some operators have no Python equivalent and are instead made available by -mapping them onto the following conventional functions: - -=================== =================== -C++ Python -=================== =================== -``operator=`` ``__assign__`` -``operator++(int)`` ``__postinc__`` -``operator++()`` ``__preinc__`` -``operator--(int)`` ``__postdec__`` -``operator--()`` ``__predec__`` -``unary operator*`` ``__deref__`` -``operator->`` ``__follow__`` -``operator&&`` ``__dand__`` -``operator||`` ``__dor__`` -``operator,`` ``__comma__`` -=================== =================== - -Here is an example of operator usage, using STL iterators directly (note that -this is not necessary in practice as STL and STL-like containers work -transparently in Python for-loops): - - .. code-block:: python - - >>> v = cppyy.gbl.std.vector[int](range(3)) - >>> i = v.begin() - >>> while (i != v.end()): - ... print(i.__deref__()) - ... _ = i.__preinc__() - ... - 0 - 1 - 2 - >>> - -Overridden ``operator new`` and ``operator delete``, as well as their array -equivalents, are not accessible but will be called as appropriate. - - -`Templates` ------------ - -Templated classes are instantiated using square brackets. -(For backwards compatibility reasons, parentheses work as well.) -The instantiation of a templated class yields a class, which can then -be used to create instances. - -Templated classes need not pre-exist in the bound code, just their -declaration needs to be available. -This is true for e.g. all of STL: - - .. code-block:: python - - >>> cppyy.gbl.std.vector # template metatype - - >>> cppyy.gbl.std.vector(int) # instantiates template -> class - at 0x1532190> - cppyy.gbl.std.vector[int]() # instantiates class -> object - object at 0x2341ec0> - >>> - -The template arguments may be actual types or their names as a string, -whichever is more convenient. -Thus, the following are equivalent: - - .. code-block:: python - - >>> from cppyy.gbl.std import vector - >>> type1 = vector[Concrete] - >>> type2 = vector['Concrete'] - >>> type1 == type2 - True - >>> - - -`Typedefs` ----------- - -Typedefs are simple python references to the actual classes to which -they refer. - - .. code-block:: python - - >>> from cppyy.gbl import Concrete_t - >>> Concrete is Concrete_t - True - >>> - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/cmake_interface.rst b/bindings/pyroot/cppyy/cppyy/doc/source/cmake_interface.rst deleted file mode 100644 index 1a2efeb642d84..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/cmake_interface.rst +++ /dev/null @@ -1,248 +0,0 @@ -.. _cmake_interface: - -CMake interface -=============== - -CMake fragments are provided for an Automated generation of an end-user -bindings package from a CMake-based project build. -The bindings generated by rootcling, are 'raw' in the sense that: - -* The .cpp file be compiled. The required compilation steps are - platform-dependent. -* The bindings are not packaged for distribution. Typically, users expect - to have a pip-compatible package. -* The binding are in the 'cppyy.gbl' namespace. This is an inconvenience at - best for users who might expect C++ code from KF5::Config to appear in - Python via "import KF5.Config". -* The bindings are loaded lazily, which limits the discoverability of the - content of the bindings. -* ``cppyy`` supports customization of the bindings via 'Pythonization' but - there is no automated way to load them. - -These issues are addressed by the CMake support. This is a blend of Python -packaging and CMake where CMake provides: - -* Platform-independent scripting of the creation of a Python 'wheel' package - for the bindings. -* An facility for CMake-based projects to automate the entire bindings - generation process, including basic automated tests. - -.. note:: - - The JIT needs to resolve linker symbols in order to call them through - generated wrappers. - Thus, any classes, functions, and data that will be used in Python need - to be exported. - This is the default behavior on Mac and Linux, but not on Windows. - On that platform, use ``__declspec(dllexport)`` to explicitly export the - classes and function you expect to call. - CMake has simple `support for exporting all`_ C++ symbols. - - -Python packaging ----------------- - -Modern Python packaging usage is based on the 'wheel'. This is places the onus -on the creation of binary artifacts in the package on the distributor. In this -case, this includes the platform-dependent steps necessary to compile the .cpp -file. - -The generated package also takes advantage of the __init__.py load-time -mechanism to enhance the bindings: - -* The bindings are rehosted in a "native" namespace so that C++ code from - KF5::Config appears in Python via "import KF5.Config". -* (TBD) Load Pythonizations. - -Both of these need/can use the output of the -:ref:`cppyy-generator ` (included in the package) as well as -other runtime support included in ``cppyy``. - - -CMake usage ------------ - -The CMake usage is via two modules: - -* FindLibClang.cmake provides some bootstrap support needed to locate clang. - This is provided mostly as a temporary measure; hopefully upstream support - will allow this to be eliminated in due course. -* FindCppyy.cmake provides the interface described further here. - -Details of the usage of these modules is within the modules themselves, but -here is a summary of the usage. ``FindLibClang.cmake`` sets the following -variables: - -:: - - LibClang_FOUND - True if libclang is found. - LibClang_LIBRARY - Clang library to link against. - LibClang_VERSION - Version number as a string (e.g. "3.9"). - LibClang_PYTHON_EXECUTABLE - Compatible python version. - - -``FindCppyy.cmake`` sets the following variables: - -:: - - Cppyy_FOUND - set to true if Cppyy is found - Cppyy_DIR - the directory where Cppyy is installed - Cppyy_EXECUTABLE - the path to the Cppyy executable - Cppyy_INCLUDE_DIRS - Where to find the Cppyy header files. - Cppyy_VERSION - the version number of the Cppyy backend. - -and also defines the following functions:: - - cppyy_add_bindings - Generate a set of bindings from a set of header files. - cppyy_find_pips - Return a list of available pip programs. - -cppyy_add_bindings -^^^^^^^^^^^^^^^^^^ - -Generate a set of bindings from a set of header files. Somewhat like CMake's -add_library(), the output is a compiler target. In addition ancillary files -are also generated to allow a complete set of bindings to be compiled, -packaged and installed:: - - cppyy_add_bindings( - pkg - pkg_version - author - author_email - [URL url] - [LICENSE license] - [LANGUAGE_STANDARD std] - [LINKDEFS linkdef...] - [IMPORTS pcm...] - [GENERATE_OPTIONS option...] - [COMPILE_OPTIONS option...] - [INCLUDE_DIRS dir...] - [LINK_LIBRARIES library...] - [H_DIRS H_DIRSectory] - H_FILES h_file...) - -The bindings are based on https://cppyy.readthedocs.io/en/latest/, and can be -used as per the documentation provided via the cppyy.gbl namespace. First add -the directory of the .rootmap file to the LD_LIBRARY_PATH environment -variable, then "import cppyy; from cppyy.gbl import ". - -Alternatively, use "import ". This convenience wrapper supports -"discovery" of the available C++ entities using, for example Python 3's command -line completion support. - -The bindings are complete with a setup.py, supporting Wheel-based -packaging, and a test.py supporting pytest/nosetest sanity test of the bindings. - -The bindings are generated/built/packaged using 3 environments: - -- One compatible with the header files being bound. This is used to - generate the generic C++ binding code (and some ancillary files) using - a modified C++ compiler. The needed options must be compatible with the - normal build environment of the header files. -- One to compile the generated, generic C++ binding code using a standard - C++ compiler. The resulting library code is "universal" in that it is - compatible with both Python2 and Python3. -- One to package the library and ancillary files into standard Python2/3 - wheel format. The packaging is done using native Python tooling. - -+----------------------+---------------------------------------------------------------------------------------------+ -|Arguments and options | Description | -+======================+=============================================================================================+ -|pkg | The name of the package to generate. This can be either | -| | of the form "simplename" (e.g. "Akonadi"), or of the | -| | form "namespace.simplename" (e.g. "KF5.Akonadi"). | -+----------------------+---------------------------------------------------------------------------------------------+ -|pkg_version | The version of the package. | -+----------------------+---------------------------------------------------------------------------------------------+ -|author | The name of the library author. | -+----------------------+---------------------------------------------------------------------------------------------+ -|author_email | The email address of the library author. | -+----------------------+---------------------------------------------------------------------------------------------+ -|URL url | The home page for the library. Default is | -| | "https://pypi.python.org/pypi/". | -+----------------------+---------------------------------------------------------------------------------------------+ -|LICENSE license | The license, default is "LGPL 2.0". | -+----------------------+---------------------------------------------------------------------------------------------+ -|LANGUAGE_STANDARD std | The version of C++ in use, "14" by default. | -+----------------------+---------------------------------------------------------------------------------------------+ -|IMPORTS pcm | Files which contain previously-generated bindings | -| | which pkg depends on. | -+----------------------+---------------------------------------------------------------------------------------------+ -|GENERATE_OPTIONS optio| Options which are to be passed into the rootcling | -| | command. For example, bindings which depend on Qt | -| | may need "-D__PIC__;-Wno-macro-redefined". | -+----------------------+---------------------------------------------------------------------------------------------+ -|LINKDEFS def | Files or lines which contain extra #pragma content | -| | for the linkdef.h file used by rootcling. See | -| | https://root.cern.ch/root/html/guides/users-guide/AddingaClass.html#the-linkdef.h-file. | -| | | -| | In lines, literal semi-colons must be escaped: "\;". | -+----------------------+---------------------------------------------------------------------------------------------+ -|EXTRA_CODES code | Files which contain extra code needed by the bindings. | -| | Customization is by routines named "c13n_"; | -| | each such routine is passed the module for : | -| | | -| | :: code-block python | -| | | -| | def c13n_doit(pkg_module): | -| | print(pkg_module.__dict__) | -| | | -| | The files and individual routines within files are | -| | processed in alphabetical order. | -+----------------------+---------------------------------------------------------------------------------------------+ -|EXTRA_HEADERS hdr | Files which contain extra headers needed by the bindings. | -+----------------------+---------------------------------------------------------------------------------------------+ -|EXTRA_PYTHONS py | Files which contain extra Python code needed by the bindings. | -+----------------------+---------------------------------------------------------------------------------------------+ -|COMPILE_OPTIONS option| Options which are to be passed into the compile/link | -| | command. | -+----------------------+---------------------------------------------------------------------------------------------+ -|INCLUDE_DIRS dir | Include directories. | -+----------------------+---------------------------------------------------------------------------------------------+ -|LINK_LIBRARIES library| Libraries to link against. | -+----------------------+---------------------------------------------------------------------------------------------+ -|H_DIRS directory | Base directories for H_FILES. | -+----------------------+---------------------------------------------------------------------------------------------+ -|H_FILES h_file | Header files for which to generate bindings in pkg. | -| | Absolute filenames, or filenames relative to H_DIRS. All | -| | definitions found directly in these files will contribute | -| | to the bindings. (NOTE: This means that if "forwarding | -| | headers" are present, the real "legacy" headers must be | -| | specified as H_FILES). | -| | All header files which contribute to a given C++ namespace | -| | should be grouped into a single pkg to ensure a 1-to-1 | -| | mapping with the implementing Python class. | -+----------------------+---------------------------------------------------------------------------------------------+ - -Returns via PARENT_SCOPE variables:: - - target The CMake target used to build. - setup_py The setup.py script used to build or install pkg. - -Examples:: - - find_package(Qt5Core NO_MODULE) - find_package(KF5KDcraw NO_MODULE) - get_target_property(_H_DIRS KF5::KDcraw INTERFACE_INCLUDE_DIRECTORIES) - get_target_property(_LINK_LIBRARIES KF5::KDcraw INTERFACE_LINK_LIBRARIES) - set(_LINK_LIBRARIES KF5::KDcraw ${_LINK_LIBRARIES}) - include(${KF5KDcraw_DIR}/KF5KDcrawConfigVersion.cmake) - - cppyy_add_bindings( - "KDCRAW" "${PACKAGE_VERSION}" "Shaheed" "srhaque@theiet.org" - LANGUAGE_STANDARD "14" - LINKDEFS "../linkdef_overrides.h" - GENERATE_OPTIONS "-D__PIC__;-Wno-macro-redefined" - INCLUDE_DIRS ${Qt5Core_INCLUDE_DIRS} - LINK_LIBRARIES ${_LINK_LIBRARIES} - H_DIRS ${_H_DIRS} - H_FILES "dcrawinfocontainer.h;kdcraw.h;rawdecodingsettings.h;rawfiles.h") - - -cppyy_find_pips -^^^^^^^^^^^^^^^ - -Return a list of available pip programs. - - -.. _`support for exporting all`: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/conf.py b/bindings/pyroot/cppyy/cppyy/doc/source/conf.py deleted file mode 100644 index b845404034d8a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/conf.py +++ /dev/null @@ -1,361 +0,0 @@ -# -*- coding: utf-8 -*- -# -# cppyy documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 12 14:35:45 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os -import shlex - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx_rtd_theme', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'cppyy' -copyright = u'2018-23, Wim Lavrijsen' -author = u'Wim Lavrijsen' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '3.5' -# The full version, including alpha/beta/rc tags. -release = '3.5.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -#pygments_style = 'sphinx' -pygments_style = 'default' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -import sphinx_rtd_theme -html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'cppyydoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'cppyy.tex', u'cppyy Documentation', - u'Wim Lavrijsen', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'cppyy', u'cppyy Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'cppyy', u'cppyy Documentation', - author, 'cppyy', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The basename for the epub file. It defaults to the project name. -#epub_basename = project - -# The HTML theme for the epub output. Since the default themes are not optimized -# for small screen space, using the same theme for HTML and epub output is -# usually not wise. This defaults to 'epub', a theme designed to save visual -# space. -#epub_theme = 'epub' - -# The language of the text. It defaults to the language option -# or 'en' if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - -# Choose between 'default' and 'includehidden'. -epub_tocscope = 'default' - -# Fix unsupported image types using the Pillow. -#epub_fix_images = False - -# Scale large images. -#epub_max_image_width = 0 - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#epub_show_urls = 'inline' - -# If false, no index is generated. -#epub_use_index = True - -def setup(app): - app.add_css_file('css/custom.css') diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/cppyy_features_header.rst b/bindings/pyroot/cppyy/cppyy/doc/source/cppyy_features_header.rst deleted file mode 100644 index 513a629c0ada3..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/cppyy_features_header.rst +++ /dev/null @@ -1,162 +0,0 @@ -File features.h -=============== - -.. code-block:: c++ - :linenos: - - #include - #include - #include - - - //----- - unsigned int gUint = 0; - - //----- - class Abstract { - public: - virtual ~Abstract() {} - virtual std::string abstract_method() = 0; - virtual void concrete_method() = 0; - }; - - void Abstract::concrete_method() { - std::cout << "called Abstract::concrete_method" << std::endl; - } - - //----- - class Concrete : Abstract { - public: - Concrete(int n=42) : m_int(n), m_const_int(17) {} - ~Concrete() {} - - virtual std::string abstract_method() { - return "called Concrete::abstract_method"; - } - - virtual void concrete_method() { - std::cout << "called Concrete::concrete_method" << std::endl; - } - - void array_method(int* ad, int size) { - for (int i=0; i < size; ++i) - std::cout << ad[i] << ' '; - std::cout << '\n'; - } - - void array_method(double* ad, int size) { - for (int i=0; i < size; ++i) - std::cout << ad[i] << ' '; - std::cout << '\n'; - } - - void uint_ref_assign(unsigned int& target, unsigned int value) { - target = value; - } - - Abstract* show_autocast() { - return this; - } - - operator const char*() { - return "Hello operator const char*!"; - } - - public: - double m_data[4]; - int m_int; - const int m_const_int; - - static int s_int; - }; - - typedef Concrete Concrete_t; - - int Concrete::s_int = 321; - - void call_abstract_method(Abstract* a) { - a->abstract_method(); - } - - //----- - class Abstract1 { - public: - virtual ~Abstract1() {} - virtual std::string abstract_method1() = 0; - }; - - class Abstract2 { - public: - virtual ~Abstract2() {} - virtual std::string abstract_method2() = 0; - }; - - std::string call_abstract_method1(Abstract1* a) { - return a->abstract_method1(); - } - - std::string call_abstract_method2(Abstract2* a) { - return a->abstract_method2(); - } - - //----- - int global_function(int) { - return 42; - } - - double global_function(double) { - return std::exp(1); - } - - int call_int_int(int (*f)(int, int), int i1, int i2) { - return f(i1, i2); - } - - template - C multiply(A a, B b) { - return C{a*b}; - } - - //----- - namespace Namespace { - - class Concrete { - public: - class NestedClass { - public: - std::vector m_v; - }; - - }; - - int global_function(int i) { - return 2*::global_function(i); - } - - double global_function(double d) { - return 2*::global_function(d); - } - - } // namespace Namespace - - //----- - enum EFruit {kApple=78, kBanana=29, kCitrus=34}; - enum class NamedClassEnum { E1 = 42 }; - - //----- - void throw_an_error(int i); - - class SomeError : public std::exception { - public: - explicit SomeError(const std::string& msg) : fMsg(msg) {} - const char* what() const throw() override { return fMsg.c_str(); } - - private: - std::string fMsg; - }; - - class SomeOtherError : public SomeError { - public: - explicit SomeOtherError(const std::string& msg) : SomeError(msg) {} - SomeOtherError(const SomeOtherError& s) : SomeError(s) {} - }; diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/cuda.rst b/bindings/pyroot/cppyy/cppyy/doc/source/cuda.rst deleted file mode 100644 index 03e11f2d905e1..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/cuda.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. _cuda: - - -CUDA support -============ - -.. warning:: - - This is an **experimental** feature, available starting with release - 2.3.0. - It is still incomplete and has only been tested on Linux on x86_64. - -CUDA is supported by passing all JITed code through two pipelines: one for the -CPU and one for the GPU. -Use of the ``__CUDA__`` pre-processor macro enables more fine-grained control -over which pipeline sees what, which is used e.g. in the pre-compiled header: -the GPU pipeline has the CUDA headers included, the CPU pipeline does not. -Building the pre-compiled header will also pick up common CUDA libraries such -as cuBLAS, if installed. - -Each version of CUDA requires specific versions of Clang and the system -compiler (e.g. gcc) for proper functioning; it's therefore best to build the -backend (``cppyy-cling``) from source for the specific combination of -interest. -The 3.x series of cppyy uses Clang13, the 2.x series Clang9, and this may -limit the CUDA versions supported (especially since CUDA has changed the APIs -for launching kernels in v11). - -There are three environment variables to control Cling's handling of CUDA: - -* ``CLING_ENABLE_CUDA`` (required): set to ``1`` to enable the CUDA - backend. - -* ``CLING_CUDA_PATH`` (optional): set to the local CUDA installation if not - in a standard location. - -* ``CLING_CUDA_ARCH`` (optional): set the architecture to target; default is - ``sm_35`` (Clang9 is limited to ``sm_75``). - -After enabling CUDA with ``CLING_ENABLE_CUDA=1`` CUDA code can be used and -kernels can be launched from JITed code by in ``cppyy.cppdef()``. -There is currently no syntax or helpers yet to launch kernels from Python. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/debugging.rst b/bindings/pyroot/cppyy/cppyy/doc/source/debugging.rst deleted file mode 100644 index d2ac36b7ad82b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/debugging.rst +++ /dev/null @@ -1,113 +0,0 @@ -.. _debugging: - -Debugging -========= - -By default, the ``clang`` JIT as used by cppyy does not generate debugging -information. -This is first of all because it has proven to be not reliable in all cases, -but also because in a production setting this information, being internal to -the wrapper generation, goes unused. -However, that does mean that a debugger that starts from python will not be -able to step through JITed code into the C++ function that needs debugging, -even when such information is available for that C++ function. - -To enable debugging information in JITed code, set the ``EXTRA_CLING_ARGS`` -envar to ``-g`` (and any further compiler options you need, e.g. add ``-O2`` -to debug optimized code). - -On a crash in C++, the backend will attempt to provide a stack trace. -This works quite well on Linux (through ``gdb``) and decently on MacOS -(through ``unwind``), but is currently unreliable on MS Windows. -To prevent printing of this trace, which can be slow to produce, set the -envar ``CPPYY_CRASH_QUIET`` to '1'. - -It is even more useful to obtain a traceback through the Python code that led -up to the problem in C++. -Many modern debuggers allow mixed-mode C++/Python debugging (for example -`gdb`_ and `MSVC`_), but cppyy can also turn abortive C++ signals (such as a -segmentation violation) into Python exceptions, yielding a normal traceback. -This is particularly useful when working with cross-inheritance and other -cross-language callbacks. - -To enable the signals to exceptions conversion, import the lowlevel module -``cppyy.ll`` and use: - - .. code-block:: python - - import cppyy.ll - cppyy.ll.set_signals_as_exception(True) - -Call ``set_signals_as_exception(False)`` to disable the conversion again. -It is recommended to only have the conversion enabled around the problematic -code, as it comes with a performance penalty. -If the problem can be localized to a specific function, you can use its -``__sig2exc__`` flag to only have the conversion active in that function. -Finally, for convenient scoping, you can also use: - - .. code-block:: python - - with cppyy.ll.signals_as_exception(): - # crashing code goes here - -The translation of signals to exceptions is as follows (all of the exceptions -are subclasses of ``cppyy.ll.FatalError``): - -======================================== ======================================== -C++ signal Python exception -======================================== ======================================== -``SIGSEGV`` ``cppyy.ll.SegmentationViolation`` -``SIGBUS`` ``cppyy.ll.BusError`` -``SIGABRT`` ``cppyy.ll.AbortSignal`` -``SIGILL`` ``cppyy.ll.IllegalInstruction`` -======================================== ======================================== - -As an example, consider the following cross-inheritance code that crashes -with a segmentation violation in C++, because a ``nullptr`` is dereferenced: - - .. code-block:: python - - import cppyy - import cppyy.ll - - cppyy.cppdef(""" - class Base { - public: - virtual ~Base() {} - virtual int runit() = 0; - }; - - int callback(Base* b) { - return b->runit(); - } - - void segfault(int* i) { *i = 42; } - """) - - class Derived(cppyy.gbl.Base): - def runit(self): - print("Hi, from Python!") - cppyy.gbl.segfault(cppyy.nullptr) - -If now used with ``signals_as_exception``, e.g. like so: - - .. code-block:: python - - d = Derived() - with cppyy.ll.signals_as_exception(): - cppyy.gbl.callback(d) - -it produces the following, very informative, Python-side trace:: - - Traceback (most recent call last): - File "crashit.py", line 25, in - cppyy.gbl.callback(d) - cppyy.ll.SegmentationViolation: int ::callback(Base* b) => - SegmentationViolation: void ::segfault(int* i) => - SegmentationViolation: segfault in C++; program state was reset - -whereas without, there would be no Python-side information at all. - - -.. _`gdb`: https://wiki.python.org/moin/DebuggingWithGdb -.. _`MSVC`: https://docs.microsoft.com/en-us/visualstudio/python/debugging-mixed-mode-c-cpp-python-in-visual-studio diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/examples.rst b/bindings/pyroot/cppyy/cppyy/doc/source/examples.rst deleted file mode 100644 index 99d11edf579ae..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/examples.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _examples: - -Example repos -============= - -The detailed feature lists have examples that work using a header file, and -there is the `tutorial`_ that shows mixing of C++ and Python interactively. -The `cookie cutter`_ repo provides a good cmake based example. -More complete examples that show packaging include these repos: - - * `flatsurf`_ - * `iheartia`_ - * `ns-3`_ - * `popsicle`_ - * `ogdf-python`_ - * `python-vspline`_ - * `mupdf`_ - * `NWChemEx`_ - * `EPICpy`_ - * `bgfx-python`_ - * `cppyy-bbhash`_ - * `dnpy`_ - * `PyEtaler`_ - * `gco-cppyy`_ - * `gmpxxyy`_ - * `cppyy-knearestneighbors`_ - * `lyncs`_ - * `libsemigroups_cppyy`_ - * `SopraClient`_ - -.. _tutorial: https://github.com/wlav/cppyy/blob/master/doc/tutorial/CppyyTutorial.ipynb -.. _cookie cutter: https://github.com/camillescott/cookiecutter-cppyy-cmake -.. _flatsurf: https://github.com/flatsurf -.. _iheartia: https://github.com/iheartla/iheartla -.. _ns-3: https://www.nsnam.org -.. _popsicle: https://github.com/kunitoki/popsicle -.. _ogdf-python: https://github.com/N-Coder/ogdf-python -.. _python-vspline: https://bitbucket.org/kfj/python-vspline -.. _mupdf: https://mupdf.com/ -.. _NWChemEx: https://github.com/NWChemEx-Project -.. _EPICpy: https://github.com/travisseymour/EPICpy -.. _bgfx-python: https://github.com/fbertola/bgfx-python -.. _cppyy-bbhash: https://github.com/camillescott/cppyy-bbhash -.. _dnpy: https://github.com/txjmb/dnpy -.. _PyEtaler: https://github.com/etaler/PyEtaler -.. _gco-cppyy: https://github.com/agoose77/gco-cppyy -.. _gmpxxyy: https://github.com/flatsurf/gmpxxyy -.. _cppyy-knearestneighbors: https://github.com/jclay/cppyy-knearestneighbors-example -.. _lyncs: https://github.com/Lyncs-API -.. _libsemigroups_cppyy: https://github.com/libsemigroups/libsemigroups_cppyy -.. _SopraClient: https://github.com/SoPra-Team-17/Client diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/exceptions.rst b/bindings/pyroot/cppyy/cppyy/doc/source/exceptions.rst deleted file mode 100644 index f4d403203e96f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/exceptions.rst +++ /dev/null @@ -1,101 +0,0 @@ -.. _exceptions: - - -Exceptions -========== - -All C++ exceptions are converted to Python exceptions and all Python -exceptions are converted to C++ exceptions, to allow exception propagation -through multiple levels of callbacks, while retaining the option to handle -the outstanding exception as needed in either language. -To preserve an exception across the language boundaries, it must derive from -``std::exception``. -If preserving the exception (or its type) is not possible, generic exceptions -are used to propagate the exception: ``Exception`` in Python or -``CPyCppyy::PyException`` in C++. - -In the most common case of an instance of a C++ exception class derived from -``std::exception`` that is thrown from a compiled library and which is -copyable, the exception can be caught and handled like any other bound C++ -object (or with ``Exception`` on the Python and ``std::exception`` on the -C++ side). -If the exception is not copyable, but derived from ``std::exception``, the -result of its ``what()`` reported with an instance of Python's ``Exception``. -In all other cases, including exceptions thrown from interpreted code (due to -limitations of the Clang JIT), the exception will turn into an instance of -``Exception`` with a generic message. - -The standard C++ exceptions are explicitly not mapped onto standard Python -exceptions, since other than a few simple cases, the mapping is too crude to -be useful as the typical usage in each standard library is too different. -Thus, for example, a thrown ``std::runtime_error`` instance will become a -``cppyy.gbl.std.runtime_error`` instance on the Python side (with Python's -``Exception`` as its base class), not a ``RuntimeError`` instance. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - -In addition, the examples require the ``throw`` to be in compiled code. -Save the following and build it into a shared library ``libfeatures.so`` (or -``libfeatures.dll`` on MS Windows): - - .. code-block:: C++ - - #include "features.h" - - void throw_an_error(int i) { - if (i) throw SomeError{"this is an error"}; - throw SomeOtherError{"this is another error"}; - } - -And load the resulting library: - - .. code-block:: python - - >>> cppyy.load_library('libfeatures') - >>> - -Then try it out: - - .. code-block:: python - - >>> cppyy.gbl.throw_an_error(1) - Traceback (most recent call last): - File "", line 1, in - cppyy.gbl.SomeError: void ::throw_an_error(int i) => - SomeError: this is an error - >>> - -Note how the full type is preserved and how the result of ``what()`` is used -for printing the exception. -By preserving the full C++ type, it is possible to call any other member -functions the exception may provide beyond ``what`` or access any additional -data it carries. - -To catch the exception, you can either use the full type, or any of its base -classes, including ``Exception`` and ``cppyy.gbl.std.exception``: - - .. code-block:: python - - >>> try: - ... cppyy.gbl.throw_an_error(0) - ... except cppyy.gbl.SomeOtherError as e: # catch by exact type - ... print("received:", e) - ... - received: - >>> try: - ... cppyy.gbl.throw_an_error(0) - ... except Exception as e: # catch through base class - ... print("received:", e) - ... - received: - >>> - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/functions.rst b/bindings/pyroot/cppyy/cppyy/doc/source/functions.rst deleted file mode 100644 index 3645e14564d45..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/functions.rst +++ /dev/null @@ -1,468 +0,0 @@ -.. _functions: - - -Functions -========= - -C++ functions are first-class objects in Python and can be used wherever -Python functions can be used, including for dynamically constructing -classes. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - -Function argument type conversions follow the expected rules, with implicit -conversions allowed, including between Python builtin types and STL types, -but it is rather more efficient to make conversions explicit. - - -`Free functions` ----------------- - -All bound C++ code starts off from the global C++ namespace, represented in -Python by ``gbl``. -This namespace, as any other namespace, is treated as a module after it has -been loaded. -Thus, we can directly import C++ functions from it and other namespaces that -themselves may contain more functions. -All lookups on namespaces are done lazily, thus if loading more headers bring -in more functions (incl. new overloads), these become available dynamically. - - .. code-block:: python - - >>> from cppyy.gbl import global_function, Namespace - >>> global_function == Namespace.global_function - False - >>> from cppyy.gbl.Namespace import global_function - >>> global_function == Namespace.global_function - True - >>> from cppyy.gbl import global_function - >>> - -Free functions can be bound to a class, following the same rules as apply to -Python functions: unless marked as static, they will turn into member -functions when bound to an instance, but act as static functions when called -through the class. -Consider this example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete, call_abstract_method - >>> c = Concrete() - >>> Concrete.callit = call_abstract_method - >>> Concrete.callit(c) - called Concrete::abstract_method - >>> c.callit() - called Concrete::abstract_method - >>> Concrete.callit = staticmethod(call_abstract_method) - >>> c.callit() - Traceback (most recent call last): - File "", line 1, in - TypeError: void ::call_abstract_method(Abstract* a) => - TypeError: takes at least 1 arguments (0 given) - >>> c.callit(c) - called Concrete::abstract_method - >>> - - -`Static methods` ----------------- - -Class static functions are treated the same way as free functions, except -that they are accessible either through the class or through an instance, -just like Python's ``staticmethod``. - - -`Instance methods` ------------------- - -For class methods, see the :ref:`methods section ` under -the :doc:`classes heading`. - - -`Lambda's` ----------- - -C++ lambda functions are supported by first binding to a ``std::function``, -then providing a proxy to that on the Python side. -Example:: - - >>> cppyy.cppdef("""\ - ... auto create_lambda(int a) { - ... return [a](int b) { return a+b; }; - ... }""") - True - >>> l = cppyy.gbl.create_lambda(4) - >>> type(l) - at 0x11505b830> - >>> l(2) - 6 - >>> - - -`Operators` ------------ - -Globally defined operators are found lazily (ie. can resolve after the class -definition by loading the global definition or by defining them interactively) -and are mapped onto a Python equivalent when possible. -See the :ref:`operators section ` under the -:doc:`classes heading` for more details. - - -`Templates` ------------ - -Templated functions (and class methods) can either be called using square -brackets (``[]``) to provide the template arguments explicitly, or called -directly, through automatic lookup. -The template arguments may either be a string of type names (this results -in faster code, as it needs no further lookup/verification) or a list of -the actual types to use (which tends to be more convenient). - -**Note**: the Python type ``float`` maps to the C++ type ``float``, even -as Python uses a C ``double`` as its internal representation. -The motivation is that doing so makes the Python code more readable (and -Python may anyway change its internal representation in the future). -The same has been true for Python ``int``, which used to be a C ``long`` -internally. - -Examples, using multiply from :doc:`features.h `: - - .. code-block:: python - - >>> mul = cppyy.gbl.multiply - >>> mul(1, 2) - 2 - >>> mul(1., 5) - 5.0 - >>> mul[int](1, 1) - 1 - >>> mul[int, int](1, 1) - 1 - >>> mul[int, int, float](1, 1) - 1.0 - >>> mul[int, int](1, 'a') - TypeError: Template method resolution failed: - none of the 6 overloaded methods succeeded. Full details: - int ::multiply(int a, int b) => - TypeError: could not convert argument 2 (int/long conversion expects an integer object) - ... - Failed to instantiate "multiply(int,std::string)" - >>> mul['double, double, double'](1., 5) - 5.0 - >>> - - -`Overloading` -------------- - -C++ supports overloading, whereas Python supports "duck typing", thus C++ -overloads have to be selected dynamically in response to the available -"ducks." -This may lead to additional lookups or template instantiations. -However, pre-existing methods (incl. auto-instantiated methods) are always -preferred over new template instantiations: - - .. code-block:: python - - >>> global_function(1.) # selects 'double' overload - 2.718281828459045 - >>> global_function(1) # selects 'int' overload - 42 - >>> - -C++ does a static dispatch at compile time based on the argument types. -The dispatch is a selection among overloads (incl. templates) visible at the -current parse location in the *translation unit*. -Bound C++ in Python does a dynamic dispatch: it considers all overloads -visible *globally* at the time of execution. -These two approaches, even if completely in line with the expectations of the -respective languages, are fundamentally different and there can thus be -discrepancies in overload selection. -For example, if overloads live in different header files and are only an -implicit conversion apart; or if types that have no direct equivalent in -Python, such as e.g. ``unsigned short``, are used. - -It is implicitly assumed that the Python code is correct as-written and there -are no warnings or errors for overloads that C++ would consider ambiguous, -but only if every possible overload fails. -For example, the following overload would be ambiguous in C++ (the value -provided is an integer, but can not be passed through a 4-byte ``int`` type), -but instead ``cppyy`` silently accepts promotion to ``double``: - - .. code-block:: python - - >>> cppyy.cppdef(r"""\ - ... void process_data(double) { std::cerr << "processing double\n"; } - ... void process_data(int32_t) { std::cerr << "processing int\n"; }""") - True - >>> cppyy.gbl.process_data(2**32) # too large for int32_t type - processing double - >>> - -There are two rounds to run-time overload resolution. -The first round considers all overloads in sorted order, with promotion but -no implicit conversion allowed. -The sorting is based on priority scores of each overload. -Higher priority is given to overloads with argument types that can be -promoted or align better with Python types. -E.g. ``int`` is preferred over ``double`` and ``double`` is preferred over -``float``. -If argument conversion fails for all overloads during this round *and* at -least one argument converter has indicated that it can do implicit -conversion, a second round is tried where implicit conversion, including -instantiation of temporaries, is allowed. -The implicit creation of temporaries, although convenient, can be costly in -terms of run-time performance. - -During some template calls, implicit conversion is not allowed, giving -preference to new instantiations (as is the case in C++). -If, however, a previously instantiated overload is available and would match -with promotion, it is preferred over a (costly) new instantiation, unless a -template overload is explicitly selected using template arguments. -For example: - - .. code-block:: python - - >>> cppyy.cppdef(r"""\ - ... template - ... T process_T(T t) { return t; }""") - True - >>> type(cppyy.gbl.process_T(1.0)) - - >>> type(cppyy.gbl.process_T(1)) # selects available "double" overload - - >>> type(cppyy.gbl.process_T[int](1)) # explicit selection of "int" overload - - >>> - -The template parameters used for instantiation can depend on the argument -values. -For example, if the type of an argument is Python ``int``, but its value is -too large for a 4-byte C++ ``int``, the template may be instantiated with, -for example, an ``int64_t`` instead (if available on the platform). -Since Python does not have unsigned types, the instantiation mechanism -strongly prefers signed types. -However, if an argument value is too large to fit in a signed integer type, -but would fit in an unsigned type, then that will be used. - -If it is important that a specific overload is selected, then use the -``__overload__`` method to match a specific function signature. -An optional boolean second parameter can be used to restrict the selected -method to be const (if ``True``) or non-const (if ``False``). -The return value of which is a first-class callable object, that can be -stored to by-pass the overload resolution: - - .. code-block:: python - - >>> gf_double = global_function.__overload__('double') - >>> gf_double(1) # int implicitly promoted - 2.718281828459045 - >>> - -The ``__overload__`` method only does a lookup; it performs no (implicit) -conversions and the types in the signature to match should be the fully -resolved ones (no typedefs). -To see all overloads available for selection, use ``help()`` on the function -or look at its ``__doc__`` string: - - .. code-block:: python - - >>> print(global_function.__doc__) - int ::global_function(int) - double ::global_function(double) - >>> - -For convenience, the ``:any:`` signature allows matching any overload, for -example to reduce a method to its ``const`` overload only, use: - - .. code-block:: python - - MyClass.some_method = MyClass.some_method.__overload__(':any:', True) - - -`Overloads and exceptions` --------------------------- - -Python error reporting is done using exceptions. -Failed argument conversion during overload resolution can lead to different -types of exceptions coming from respective attempted overloads. -The final error report issued if all overloads fail, is a summary of the -individual errors, but by Python language requirements it has to have a -single exception type. -If all the exception types match, that type is used, but if there is an -amalgam of types, the exception type chosen will be ``TypeError``. -For example, attempting to pass a too large value through ``uint8_t`` will -uniquely raise a ``ValueError`` - - .. code-block:: python - - >>> cppyy.cppdef("void somefunc(uint8_t) {}") - True - >>> cppyy.gbl.somefunc(2**16) - Traceback (most recent call last): - File "", line 1, in - ValueError: void ::somefunc(uint8_t) => - ValueError: could not convert argument 1 (integer to character: value 65536 not in range [0,255]) - >>> - -But if other overloads are present that fail in a different way, the error -report will be a ``TypeError``: - - .. code-block:: python - - >>> cppyy.cppdef(r""" - ... void somefunc(uint8_t) {} - ... void somefunc(std::string) {}""") - True - >>> cppyy.gbl.somefunc(2**16) - Traceback (most recent call last): - File "", line 1, in - TypeError: none of the 2 overloaded methods succeeded. Full details: - void ::somefunc(std::string) => - TypeError: could not convert argument 1 - void ::somefunc(uint8_t) => - ValueError: could not convert argument 1 (integer to character: value 65536 not in range [0,255]) - >>> - -Since C++ exceptions are converted to Python ones, there is an interplay -possible between the two as part of overload resolution and ``cppyy`` -allows C++ exceptions as such, enabling detailed type disambiguation and -input validation. -(The original use case was for filling database fields, requiring an exact -field label and data type match.) - -If, however, all methods fail and there is only one C++ exception (the other -exceptions originating from argument conversion, never succeeding to call -into C++), this C++ exception will be preferentially reported and will have -the original C++ type. - - -`Return values` ---------------- - -Most return types are readily amenable to automatic memory management: builtin -returns, by-value returns, (const-)reference returns to internal data, smart -pointers, etc. -The important exception is pointer returns. - -A function that returns a pointer to an object over which Python should claim -ownership, should have its ``__creates__`` flag set through its -:doc:`pythonization `. -Well-written APIs will have clear clues in their naming convention about the -ownership rules. -For example, functions called ``New...``, ``Clone...``, etc. can be expected -to return freshly allocated objects. -A basic name-matching in the pythonization then makes it simple to mark all -these functions as creators. - -The return values are :ref:`auto-casted `. - - -`\*args and \*\*kwds` ---------------------- - -C++ default arguments work as expected. -Keywords, however, are a Python language feature that does not exist in C++. -Many C++ function declarations do have formal arguments, but these are not -part of the C++ interface (the argument names are repeated in the definition, -making the names in the declaration irrelevant: they do not even need to be -provided). -Thus, although ``cppyy`` will map keyword argument names to formal argument -names from the C++ declaration, use of this feature is not recommended unless -you have a guarantee that the names in C++ the interface are maintained. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> c = Concrete() # uses default argument - >>> c.m_int - 42 - >>> c = Concrete(13) # uses provided argument - >>> c.m_int - 13 - >>> args = (27,) - >>> c = Concrete(*args) # argument pack - >>> c.m_int - 27 - >>> c = Concrete(n=17) - >>> c.m_int - 17 - >>> kwds = {'n' : 18} - >>> c = Concrete(**kwds) - >>> c.m_int - 18 - >>> - - -`Callbacks` ------------ - -Python callables (functions/lambdas/instances) can be passed to C++ through -function pointers and/or ``std::function``. -This involves creation of a temporary wrapper, which has the same life time as -the Python callable it wraps, so the callable needs to be kept alive on the -Python side if the C++ side stores the callback. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import call_int_int - >>> print(call_int_int.__doc__) - int ::call_int_int(int(*)(int,int) f, int i1, int i2) - >>> def add(a, b): - ... return a+b - ... - >>> call_int_int(add, 3, 7) - 7 - >>> call_int_int(lambda x, y: x*y, 3, 7) - 21 - >>> - -Python functions can be used to instantiate C++ templates, assuming the -type information of the arguments and return types can be inferred. -If this can not be done directly from the template arguments, then it can -be provided through Python annotations, by explicitly adding the -``__annotations__`` special data member (e.g. for older versions of Python -that do not support annotations), or by the function having been bound by -``cppyy`` in the first place. -For example: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.cppdef("""\ - ... template - ... R callT(R(*f)(U...), A&&... a) { - ... return f(a...); - ... }""") - True - >>> def f(a: 'int') -> 'double': - ... return 3.1415*a - ... - >>> cppyy.gbl.callT(f, 2) - 6.283 - >>> def f(a: 'int', b: 'int') -> 'int': - ... return 3*a*b - ... - >>> cppyy.gbl.callT(f, 6, 7) - 126 - >>> - - -`extern "C"` ------------- - -Functions with C linkage are supported and are simply represented as -overloads of a single function. -Such functions are allowed both globally as well as in namespaces. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/history.rst b/bindings/pyroot/cppyy/cppyy/doc/source/history.rst deleted file mode 100644 index ed166a4be33b5..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/history.rst +++ /dev/null @@ -1,52 +0,0 @@ -.. _history: - -History -======= - -.. toctree:: - :hidden: - -What is now called `cppyy` started life as `RootPython` from `CERN`_, but -cppyy is not associated with CERN (it is still used there, however, -underpinning `PyROOT`_). - -Back in late 2002, Pere Mato of CERN, had the idea of using the `CINT`_ C++ -interpreter, which formed the interactive interface to `ROOT`_, to call from -Python into C++: this became RootPython. -This binder interfaced with Python through `boost.python`_ (v1), transpiling -Python code into C++ and interpreting the result with CINT. -In early 2003, I ported this code to boost.python v2, then recently released. -In practice, however, re-interpreting the transpiled code was unusably slow, -thus I modified the code to make direct use of CINT's internal reflection -system, gaining about 25x in performance. -I presented this work as `PyROOT` at the ROOT Users' Workshop in early 2004, -and, after removing the boost.python dependency by using the C-API directly -(gaining another factor 7 in speedup!), it was included in ROOT. -PyROOT was presented at the SciPy'06 conference, but was otherwise not -advocated outside of High Energy Physics (HEP). - -In 2010, the PyPy core developers and I held a `sprint at CERN`_ to use -`Reflex`, a standalone alternative to CINT's reflection of C++, to add -automatic C++ bindings, PyROOT-style, to `PyPy`_. -This is where the name "cppyy" originated. -Coined by Carl Friedrich Bolz, if you want to understand the meaning, just -pronounce it slowly: cpp-y-y. - -After the ROOT team replaced CINT with `Cling`_, PyROOT soon followed. -As part of Google's Summer of Code '16, Aditi Dutta moved PyPy/cppyy to Cling -as well, and packaged the code for use through `PyPI`_. -I continued this integration with the Python eco-system by forking PyROOT, -reducing its dependencies, and repackaging it as CPython/cppyy. -The combined result is the current cppyy project. -Mid 2018, version 1.0 was released. - - -.. _`CERN`: https://cern.ch/ -.. _`PyROOT`: https://root.cern.ch/root/htmldoc/guides/users-guide/ROOTUsersGuide.html#python-interface -.. _`CINT`: https://en.wikipedia.org/wiki/CINT -.. _`ROOT`: https://root.cern.ch -.. _`boost.python`: https://wiki.python.org/moin/boost.python/GettingStarted -.. _`sprint at CERN`: https://morepypy.blogspot.com/2010/07/cern-sprint-report-wrapping-c-libraries.html -.. _`PyPy`: https://www.pypy.org/ -.. _`Cling`: https://github.com/vgvassilev/cling -.. _`PyPI`: https://pypi.org/ diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/index.rst b/bindings/pyroot/cppyy/cppyy/doc/source/index.rst deleted file mode 100644 index 106de46bd6d5f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/index.rst +++ /dev/null @@ -1,197 +0,0 @@ -.. cppyy documentation master file, created by - sphinx-quickstart on Wed Jul 12 14:35:45 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -.. meta:: - :description: cppyy: Automatic Python-C++ bindings - :keywords: Python, C++, llvm, cling, binding, bindings, automatic bindings, bindings generator, cross-language inheritance, calling C++ from Python, calling Python from C++, high performance, data science - -cppyy: Automatic Python-C++ bindings -==================================== - -cppyy is an automatic, run-time, Python-C++ bindings generator, for calling -C++ from Python and Python from C++. -Run-time generation enables detailed specialization for higher performance, -lazy loading for reduced memory use in large scale projects, Python-side -cross-inheritance and callbacks for working with C++ frameworks, run-time -template instantiation, automatic object downcasting, exception mapping, and -interactive exploration of C++ libraries. -cppyy delivers this without any language extensions, intermediate languages, -or the need for boiler-plate hand-written code. -For design and performance, see this `PyHPC'16 paper`_, albeit that the -CPython/cppyy performance has been vastly improved since, as well as this -`CAAS presentation`_. -For a quick teaser, see `Jason Turner's`_ introduction video. - -cppyy is based on `Cling`_, the C++ interpreter, to match Python's dynamism, -interactivity, and run-time behavior. -Consider this session, showing dynamic, interactive, mixing of C++ and Python -features (there are more examples throughout the documentation and in the -`tutorial`_): - -.. code-block:: python - - >>> import cppyy - >>> cppyy.cppdef(""" - ... class MyClass { - ... public: - ... MyClass(int i) : m_data(i) {} - ... virtual ~MyClass() {} - ... virtual int add_int(int i) { return m_data + i; } - ... int m_data; - ... };""") - True - >>> from cppyy.gbl import MyClass - >>> m = MyClass(42) - >>> cppyy.cppdef(""" - ... void say_hello(MyClass* m) { - ... std::cout << "Hello, the number is: " << m->m_data << std::endl; - ... }""") - True - >>> MyClass.say_hello = cppyy.gbl.say_hello - >>> m.say_hello() - Hello, the number is: 42 - >>> m.m_data = 13 - >>> m.say_hello() - Hello, the number is: 13 - >>> class PyMyClass(MyClass): - ... def add_int(self, i): # python side override (CPython only) - ... return self.m_data + 2*i - ... - >>> cppyy.cppdef("int callback(MyClass* m, int i) { return m->add_int(i); }") - True - >>> cppyy.gbl.callback(m, 2) # calls C++ add_int - 15 - >>> cppyy.gbl.callback(PyMyClass(1), 2) # calls Python-side override - 5 - >>> - -With a modern C++ compiler having its back, cppyy is future-proof. -Consider the following session using ``boost::any``, a capsule-type that -allows for heterogeneous containers in C++. -The `Boost`_ library is well known for its no holds barred use of modern C++ -and heavy use of templates: - -.. code-block:: python - - >>> import cppyy - >>> cppyy.include('boost/any.hpp') # assumes you have boost installed - >>> from cppyy.gbl import std, boost - >>> val = boost.any() # the capsule - >>> val.__assign__(std.vector[int]()) # assign it a std::vector - - >>> val.type() == cppyy.typeid(std.vector[int]) # verify type - True - >>> extract = boost.any_cast[int](std.move(val)) # wrong cast - Traceback (most recent call last): - File "", line 1, in - cppyy.gbl.boost.bad_any_cast: Could not instantiate any_cast: - int boost::any_cast(boost::any&& operand) => - wrapexcept: boost::bad_any_cast: failed conversion using boost::any_cast - >>> extract = boost.any_cast[std.vector[int]](val) # correct cast - >>> type(extract) is std.vector[int] - True - >>> extract += xrange(100) - >>> len(extract) - 100 - >>> val.__assign__(std.move(extract)) # move forced - - >>> len(extract) # now empty (or invalid) - 0 - >>> extract = boost.any_cast[std.vector[int]](val) - >>> list(extract) - [0, 1, 2, 3, 4, 5, 6, ..., 97, 98, 99] - >>> - -Of course, there is no reason to use Boost from Python (in fact, this example -calls out for :doc:`pythonizations `), but it shows that -cppyy seamlessly supports many advanced C++ features. - -cppyy is available for both `CPython`_ (v2 and v3) and `PyPy`_, reaching -C++-like performance with the latter. -It makes judicious use of precompiled headers, dynamic loading, and lazy -instantiation, to support C++ programs consisting of millions of lines of -code and many thousands of classes. -cppyy minimizes dependencies to allow its use in distributed, heterogeneous, -development environments. - -.. _Cling: https://github.com/vgvassilev/cling -.. _tutorial: https://github.com/wlav/cppyy/blob/master/doc/tutorial/CppyyTutorial.ipynb -.. _`PyHPC'16 paper`: http://wlav.web.cern.ch/wlav/Cppyy_LavrijsenDutta_PyHPC16.pdf -.. _`CAAS presentation`: https://www.youtube.com/watch?v=stMD7VDWlVU -.. _`Jason Turner's`: https://www.youtube.com/watch?v=TL83P77vZ1k -.. _`Boost`: http://www.boost.org/ -.. _`CPython`: http://python.org -.. _`PyPy`: http://pypy.org - - -.. only: not latex - - Contents: - -.. toctree:: - :maxdepth: 1 - - changelog - license - -.. toctree:: - :caption: Getting Started - :maxdepth: 1 - - installation - starting - examples - bugs - -.. toctree:: - :caption: Features - :maxdepth: 1 - - toplevel - basic_types - strings - classes - functions - type_conversions - stl - exceptions - python - numba - cuda - lowlevel - misc - debugging - -.. toctree:: - :caption: Redistribution - :maxdepth: 1 - - pythonizations - utilities - cmake_interface - -.. toctree:: - :caption: Developers - :maxdepth: 1 - - packages - repositories - testing - -.. toctree:: - :caption: Background - :maxdepth: 1 - - history - philosophy - - -Bugs and feedback ------------------ - -Please report bugs or requests for improvement on the `issue tracker`_. - - -.. _`issue tracker`: https://github.com/wlav/cppyy/issues diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/installation.rst b/bindings/pyroot/cppyy/cppyy/doc/source/installation.rst deleted file mode 100644 index 6b0356df81ec2..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/installation.rst +++ /dev/null @@ -1,236 +0,0 @@ -.. _installation: - -Installation -============ - -cppyy requires a (modern) C++ compiler. -When installing through `conda-forge`_, ``conda`` will install the compiler -for you, to match the other conda-forge packages. -When using ``pip`` and the wheels from `PyPI`_, you minimally need gcc5, -clang5, or MSVC'17. - -.. note:: - - On Windows, a command prompt from which to run Python (or Python run - directly) needs to be opened from within an environment with MSVC setup, - otherwise the compiler will not be accessible. - -When installing from source, the only requirement is a compiler that supports -C++14, this in order to build LLVM. - -With CPython on Linux or MacOS, probably by far the easiest way to install -cppyy, is through conda-forge on `Anaconda`_ (or `miniconda`_). -A Windows recipe for ``conda`` is not available yet, but is forthcoming, so -use ``pip`` for that platform for now (see below). -PyPI always has the authoritative releases (conda-forge pulls the sources -from there), so conda-forge may sometimes lag PyPI. -If you absolutely need the latest release, use PyPI or consider -:ref:`building from source `. - -To install using ``conda``, create and/or activate your (new) work environment -and install from the conda-forge channel:: - - $ conda create -n WORK - $ conda activate WORK - (WORK) $ conda install -c conda-forge cppyy - (WORK) [current compiler] $ - - -To install with ``pip`` through `PyPI`_, use `venv`. -The use of virtual environment (`venv`) prevents pollution of any system directories and allows -you to wipe out the full installation simply by removing the virtual environment (`venv`) -created directory ("WORK" in this example):: - - $ python -m venv WORK - $ WORK\Scripts\activate - (WORK) $ python -m pip install cppyy - (WORK) $ - -.. note:: - If you are using python version less than 3.3, you should use `virtualenv` instead of `venv`. - First install virtualenv package that allows you to create virtual environment. - - $ python -m pip install virtualenv - - $ virtualenv WORK - - $ source WORK/bin/activate - - (WORK) $ python -m pip install cppyy - - (WORK) $ - -If you use the ``--user`` option to ``pip`` and use ``pip`` directly on the -command line, instead of through ``python``, make sure that the ``PATH`` -envar points to the bin directory that will contain the installed entry -points during the installation, as the build process needs them. -You may also need to install ``wheel`` first if you have an older version of -``pip`` and/or do not use virtualenv (which installs wheel by default). -Example:: - - $ python -m pip install wheel --user - $ PATH=$HOME/.local/bin:$PATH python -m pip install cppyy --user - - -Wheels on PyPI --------------- - -Wheels for the backend (``cppyy-cling``) are available on PyPI for GNU/Linux, -MacOS-X, and MS Windows (both 32b and 64b). -The Linux wheels are built for manylinux2014, but with the dual ABI enabled. -The wheels for MS Windows were build with MSVC Community Edition 2017. - -There are no wheels for the ``CPyCppyy`` and ``cppyy`` packages, to allow -the C++ standard chosen to match the local compiler. - - -pip with conda --------------- - -Although installing ``cppyy`` through `conda-forge`_ is recommended, it is -possible to build/install with ``pip`` under Anaconda/miniconda. - -Typical Python extensions only expose a C interface for use through the -Python C-API, requiring only calling conventions (and the Python C-API -version, of course) to match to be binary compatible. -Here, cppyy differs because it exposes C++ APIs: it thus requires a C++ -run-time that is ABI compatible with the C++ compiler that was used during -build-time. - -A set of modern compilers is available through conda-forge, but are only -intended for use with ``conda-build``. -In particular, the corresponding run-time is installed (for use through rpath -when building), but not set up. -That is, the conda compilers are added to ``PATH`` but not their libraries -to ``LD_LIBRARY_PATH`` (Mac, Linux; ``PATH`` for both on MS Windows). -Thus, you get the conda compilers and your system libraries mixed in the same -build environment, unless you set ``LD_LIBRARY_PATH`` (``PATH`` on Windows) -explicitly, e.g. by adding ``$CONDA_PREFIX/lib``. -Note that the conda documentation recommends against this. -Furthermore, the compilers from conda-forge are not vanilla distributions: -header files have been modified, which can can lead to parsing problems if -your system C library does not support C11, for example. - -Nevertheless, with the above caveats, if your system C/C++ run-times are new -enough, the following can be made to work:: - - $ conda create -n WORK - $ conda activate WORK - (WORK) $ conda install python - (WORK) $ conda install -c conda-forge compilers - (WORK) [current compiler] $ python -m pip install cppyy - - -C++ standard with pip ---------------------- - -The C++20 standard is the default on all systems as of release 3.0.1 (both -PyPI and conda-forge); it is C++17 for older releases. -When installing from PyPI using ``pip``, you can control the standard -selection by setting the ``STDCXX`` envar to '20', '17', or '14' (for Linux, -the backend does not need to be recompiled) for the 3.x releases; '17', '14', -or '11' for the 2.x releases. -Note that the build will automatically lower your choice if the compiler used -does not support a newer standard. - - -Install from source -------------------- -.. _installation_from_source: - -To build an existing release from source, tell ``pip`` to not download any -binary wheels. -Build-time only dependencies are ``cmake`` (for general build), ``python`` -(obviously, but also for LLVM), and a modern C++ compiler (one that supports -at least C++14). -Use the envar ``STDCXX`` to control the C++ standard version; ``MAKE`` to -change the ``make`` command, ``MAKE_NPROCS`` to control the maximum number of -parallel jobs allowed, and ``VERBOSE=1`` to see full build/compile commands. -Example (using ``--verbose`` to see ``pip`` progress):: - - $ STDCXX=17 MAKE_NPROCS=32 pip install --verbose cppyy --no-binary=cppyy-cling - -Compilation of the backend, which contains a customized version of -Clang/LLVM, can take a long time, so by default the setup script will use all -cores (x2 if hyperthreading is enabled). -Once built, however, the wheel of ``cppyy-cling`` is reused by pip for all -versions of CPython and for PyPy, thus the long compilation is needed only -once for all different versions of Python on the same machine. - -See the :ref:`section on repos ` for more -details/options. - - -PyPy ----- - -PyPy 5.7 and 5.8 have a built-in module ``cppyy``. -You can still install the cppyy package, but the built-in module takes -precedence. -To use cppyy, first import a compatibility module:: - - $ pypy - [PyPy 5.8.0 with GCC 5.4.0] on linux2 - >>>> import cppyy_compat, cppyy - >>>> - -You may have to set ``LD_LIBRARY_PATH`` appropriately if you get an -``EnvironmentError`` (it will indicate the needed directory). - -Note that your python interpreter (whether CPython or ``pypy-c``) may not have -been linked by the C++ compiler. -This can lead to problems during loading of C++ libraries and program shutdown. -In that case, re-linking is highly recommended. - -Very old versions of PyPy (5.6.0 and earlier) have a built-in ``cppyy`` based -on `Reflex`_, which is less feature-rich and no longer supported. -However, both the :doc:`distribution utilities ` and user-facing -Python codes are very backwards compatible, making migration straightforward. - - -Precompiled header ------------------- - -For performance reasons (reduced memory and CPU usage), a precompiled header -(PCH) of the system and compiler header files will be installed or, failing -that, generated on startup. -Obviously, this PCH is not portable and should not be part of any wheel. - -Some compiler features, such as AVX, OpenMP, fast math, etc. need to be -active during compilation of the PCH, as they depend both on compiler flags -and system headers (for intrinsics, or API calls). -You can control compiler flags through the ``EXTRA_CLING_ARGS`` envar and thus -what is active in the PCH. -In principle, you can also change the C++ language standard by setting the -appropriate flag on ``EXTRA_CLING_ARGS`` and rebuilding the PCH. -However, if done at this stage, that disables some automatic conversion for -C++ types that were introduced after C++11 (such as ``string_view`` and -``optional``). - -If you want multiple PCHs living side-by-side, you can generate them -yourself (note that the given path must be absolute):: - - >>> import cppyy_backend.loader as l - >>> l.set_cling_compile_options(True) # adds defaults to EXTRA_CLING_ARGS - >>> install_path = '/full/path/to/target/location/for/PCH' - >>> l.ensure_precompiled_header(install_path) - -You can then select the appropriate PCH with the ``CLING_STANDARD_PCH`` envar:: - - $ export CLING_STANDARD_PCH=/full/path/to/target/location/for/PCH/allDict.cxx.pch - -Or disable it completely by setting that envar to "none". - -.. note:: - - Without the PCH, the default C++ standard will be the one with which - ``cppyy-cling`` was built. - - -.. _`conda-forge`: https://anaconda.org/conda-forge/cppyy -.. _`Anaconda`: https://www.anaconda.com/distribution/ -.. _`miniconda`: https://docs.conda.io/en/latest/miniconda.html -.. _`PyPI`: https://pypi.python.org/pypi/cppyy/ -.. _`virtualenv`: https://pypi.python.org/pypi/virtualenv -.. _`venv`: https://docs.python.org/3/library/venv.html -.. _`Reflex`: https://root.cern.ch/how/how-use-reflex diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/license.rst b/bindings/pyroot/cppyy/cppyy/doc/source/license.rst deleted file mode 100644 index 21e880acffc49..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/license.rst +++ /dev/null @@ -1,85 +0,0 @@ -License and copyright -===================== - -Copyright (c) 2017-2021, The Regents of the University of California, -through Lawrence Berkeley National Laboratory (subject to receipt of -any required approvals from the U.S. Dept. of Energy). All rights -reserved. Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the following -conditions are met: - -(1) Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -(2) Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -(3) Neither the name of the University of California, Lawrence Berkeley -National Laboratory, U.S. Dept. of Energy nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - -You are under no obligation whatsoever to provide any bug fixes, -patches, or upgrades to the features, functionality or performance of -the source code ("Enhancements") to anyone; however, if you choose to -make your Enhancements available either publicly, or directly to -Lawrence Berkeley National Laboratory, without imposing a separate -written license agreement for such Enhancements, then you hereby grant -the following license: a non-exclusive, royalty-free perpetual license -to install, use, modify, prepare derivative works, incorporate into -other computer software, distribute, and sublicense such Enhancements -or derivative works thereof, in binary and source code form. - - -Additional copyright holders ----------------------------- - -In addition to LBNL/UC Berkeley, this package contains files copyrighted by -one or more of the following people and organizations, and licensed under -the same conditions (except for some compatible licenses as retained in the -source code): - - * CERN - * Lucio Asnaghi - * Simone Bacchio - * Robert Bradshaw - * Ellis Breen - * Antonio Cuni - * Aditi Dutta - * Shaheed Haque - * Jonsomi - * Max Kolin - * Alvaro Moran - * Tarmo Pikaro - * Matti Picus - * Camille Scott - * Toby StClere-Smithe - * Stefan Wunsch - -Conda-forge recipes were provided by Julian Rueth and Isuru Fernando. - - -External code -------------- - -The create_src_directory.py script will pull in ROOT and LLVM sources, which -are licensed differently: - - LLVM: distributed under University of Illinois/NCSA Open Source License - https://opensource.org/licenses/UoI-NCSA.php - ROOT: distributed under LGPL 2.1 - https://root.cern.ch/license - -The ROOT and LLVM/Clang codes are modified/patched, as part of the build -process. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/lowlevel.rst b/bindings/pyroot/cppyy/cppyy/doc/source/lowlevel.rst deleted file mode 100644 index c01396f0ff853..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/lowlevel.rst +++ /dev/null @@ -1,327 +0,0 @@ -Low-level code -============== - -.. toctree:: - :hidden: - -C code and older C++ code sometimes makes use of low-level features such as -pointers to builtin types, some of which do not have any Python equivalent -(e.g. ``unsigned short*``). -Furthermore, such codes tend to be ambiguous: the information from header -file is not sufficient to determine the full purpose. -For example, an ``int*`` type may refer to the address of a single ``int`` -(an out-parameter, say) or it may refer to an array of ``int``, the ownership -of which is not clear either. -cppyy provides a few low-level helpers and integration with the Python -`ctypes module`_ to cover these cases. - -Use of these low-level helpers will obviously lead to very "C-like" code and -it is recommended to :doc:`pythonize ` the code, perhaps -using the Cling JIT and embedded C++. - -Note: the low-level module is not loaded by default (since its use is, or -should be, uncommon). -It needs to be imported explicitly: - - .. code-block:: python - - >>> import cppyy.ll - >>> - - -`LowLevelView` --------------- - -Python has an elaborate array interface (buffer) specification, but no -standard library array type that completely implements it; instead, the -canonical Python array type is the NumPy one. -cppyy introduces the basic ``LowLevelView`` array class to avoid having a -direct dependency on NumPy and to guarantee zero copy. -The ``LowLevelView`` type gives access to array details such as the size, -type, etc. and allows reading/writing of array elements, both for interactive -use and through the buffer interface to allow NumPy to interface with them. -For more complex operations, it's recommended to copy from the -``LowLevelView`` inta a NumPy array, or to create a NumPy view (see below, -under :ref:`NumPy Casts `). - - -`C/C++ casts` -------------- - -C++ instances are auto-casted to the most derived available type, so do not -require explicit casts even when a function returns a pointer to a base -class or interface. -However, when given only a ``void*`` or ``intptr_t`` type on return, a cast -is required to turn it into something usable. - -* **bind_object**: This is the preferred method to proxy a C++ address, - and lives in ``cppyy``, not ``cppyy.ll``, as it is not a low-level C++ - cast, but a ``cppyy`` API that is also used internally. - It thus plays well with object identity, references, etc. - Example: - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... struct MyStruct { int fInt; }; - ... void* create_mystruct() { return new MyStruct{42}; } - ... """) - ... - >>> s = cppyy.gbl.create_mystruct() - >>> print(s) - - >>> sobj = cppyy.bind_object(s, 'MyStruct') - >>> print(sobj) - - >>> print(sobj.fInt) - 42 - >>> - - Instead of the type name as a string, ``bind_object`` can also take the - actual class (here: ``cppyy.gbl.MyStruct``). - -* **Typed nullptr**: A Python side proxy can pass through a pointer to - pointer function argument, but if the C++ side allocates memory and - stores it in the pointer, the result is a memory leak. - In that case, use ``bind_object`` to bind ``cppyy.nullptr`` instead, to - get a typed nullptr to pass to the function. - Example (continuing from the example above): - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... void create_mystruct(MyStruct** ptr) { *ptr = new MyStruct{42}; } - ... """) - ... - >>> s = cppyy.bind_object(cppyy.nullptr, 'MyStruct') - >>> print(s) - - >>> cppyy.gbl.create_mystruct(s) - >>> print(s) - - >>> print(s.fInt) - 42 - >>> - -* **C-style cast**: This is the simplest option for builtin types. - The syntax is "template-style", example: - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... void* get_data(int sz) { - ... int* iptr = (int*)malloc(sizeof(int)*sz); - ... for (int i=0; i>> NDATA = 4 - >>> d = cppyy.gbl.get_data(NDATA) - >>> print(d) - - >>> d = cppyy.ll.cast['int*'](d) - >>> d.reshape((NDATA,)) - >>> print(list(d)) - [0, 1, 2, 3] - >>> - -* **C++-style casts**: Similar to the C-style cast, there are - ``ll.static_cast`` and ``ll.reinterpret_cast``. - There should never be a reason for a ``dynamic_cast``, since that only - applies to objects, for which auto-casting will work. - The syntax is "template-style", just like for the C-style cast above. - - -.. _npcasts: - -`NumPy casts` -------------- - -The ``cppyy.LowLevelView`` type returned for pointers to basic types, -including for ``void*``, is a simple and light-weight view on memory, given a -pointer, type, and number of elements (or unchecked, if unknown). -It only supports basic operations such as indexing and iterations, but also -the buffer protocol for integration with full-fledged functional arrays such -as NumPy`s ``ndarray``. - -In addition, specifically when dealing with ``void*`` returns, you can use -NumPy's low-level ``frombuffer`` interface to perform the cast. -Example: - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... void* create_float_array(int sz) { - ... float* pf = (float*)malloc(sizeof(float)*sz); - ... for (int i = 0; i < sz; ++i) pf[i] = 2*i; - ... return pf; - ... }""") - ... - >>> import numpy as np - >>> NDATA = 8 - >>> arr = cppyy.gbl.create_float_array(NDATA) - >>> print(arr) - - >>> arr.reshape((NDATA,)) # adjust the llv's size - >>> v = np.frombuffer(arr, dtype=np.float32, count=NDATA) # cast to float - >>> print(len(v)) - 8 - >>> print(v) - array([ 0., 2., 4., 6., 8., 10., 12., 14.], dtype=float32) - >>> - -Note that NumPy will internally check the total buffer size, so if the size -you are casting *to* is larger than the size you are casting *from*, then -the number of elements set in the ``reshape`` call needs to be adjusted -accordingly. - - -`Capsules` ----------- - -It is not possible to pass proxies from cppyy through function arguments of -another binder (and vice versa, with the exception of ``ctypes``, see below), -because each will use a different internal representation, including for type -checking and extracting the C++ object address. -However, all Python binders are able to rebind (just like ``bind_object`` -above for cppyy) the result of at least one of the following: - -* **ll.addressof**: Takes a cppyy bound C++ object and returns its address as - an integer value. - Takes an optional ``byref`` parameter and if set to true, returns a pointer - to the address instead. - -* **ll.as_capsule**: Takes a cppyy bound C++ object and returns its address as - a PyCapsule object. - Takes an optional ``byref`` parameter and if set to true, returns a pointer - to the address instead. - -* **ll.as_cobject**: Takes a cppyy bound C++ object and returns its address as - a PyCObject object for Python2 and a PyCapsule object for Python3. - Takes an optional ``byref`` parameter and if set to true, returns a pointer - to the address instead. - -* **ll.as_ctypes**: Takes a cppyy bound C++ object and returns its address as - a ``ctypes.c_void_p`` object. - Takes an optional ``byref`` parameter and if set to true, returns a pointer - to the address instead. - - -`ctypes` --------- - -The `ctypes module`_ has been part of Python since version 2.5 and provides a -Python-side foreign function interface. -It is clunky to use and has very bad performance, but it is guaranteed to be -available. -It does not have a public C interface, only the Python one, but its internals -have been stable since its introduction, making it safe to use for tight and -efficient integration at the C level (with a few Python helpers to assure -lazy lookup). - -Objects from ``ctypes`` can be passed through arguments of functions that -take a pointer to a single C++ builtin, and ``ctypes`` pointers can be passed -when a pointer-to-pointer is expected, e.g. for array out-parameters. -This leads to the following set of possible mappings: - -======================================== ======================================== -C++ ctypes -======================================== ======================================== -by value (ex.: ``int``) ``.value`` (ex.: ``c_int(0).value``) -by const reference (ex.: ``const int&``) ``.value`` (ex.: ``c_int(0).value``) -by reference (ex.: ``int&``) direct (ex.: ``c_int(0)``) -by pointer (ex.: ``int*``) direct (ex.: ``c_int(0)``) -by ptr-ref (ex.: ``int*&``) ``pointer`` (ex.: ``pointer(c_int(0))``) -by ptr-ptr **in** (ex.: ``int**``) ``pointer`` (ex.: ``pointer(c_int(0))``) -by ptr-ptr **out** (ex.: ``int**``) ``POINTER`` (ex.: ``POINTER(c_int)()``) -======================================== ======================================== - -The ``ctypes`` pointer objects (from ``POINTER``, ``pointer``, or ``byref``) -can also be used for pass by reference or pointer, instead of the direct -object, and ``ctypes.c_void_p`` can pass through all pointer types. -The addresses will be adjusted internally by cppyy. - -Note that ``ctypes.c_char_p`` is expected to be a NULL-terminated C string, -not a character array (see the `ctypes module`_ documentation), and that -``ctypes.c_bool`` is a C ``_Bool`` type, not C++ ``bool``. - - -`Memory` --------- - -C++ has three ways of allocating heap memory (``malloc``, ``new``, and -``new[]``) and three corresponding ways of deallocation (``free``, -``delete``, and ``delete[]``). -Direct use of ``malloc`` and ``new`` should be avoided for C++ classes, as -these may override ``operator new`` to control their own allocation. -However these low-level allocators can be necessary for builtin types on -occasion if the C++ side takes ownership (otherwise, prefer either -``array`` from the builtin module ``array`` or ``ndarray`` from Numpy). - -The low-level module adds the following functions: - -* **ll.malloc**: an interface on top of C's malloc. - Use it as a template with the number of elements (not the number types) to - be allocated. - The result is a ``cppyy.LowLevelView`` with the proper type and size: - - .. code-block:: python - - >>> arr = cppyy.ll.malloc[int](4) # allocates memory for 4 C ints - >>> print(len(arr)) - 4 - >>> print(type(arr[0])) - - >>> - - The actual C malloc can also be used directly, through ``cppyy.gbl.malloc``, - taking the number of *bytes* to be allocated and returning a ``void*``. - -* **ll.free**: an interface to C's free, to deallocate memory allocated by - C's malloc. - To continue to example above: - - .. code-block:: python - - >>> cppyy.ll.free(arr) - >>> - - The actual C free can also be used directly, through ``cppyy.gbl.free``. - -* **ll.array_new**: an interface on top of C++'s ``new[]``. - Use it as a template; the result is a ``cppyy.LowLevelView`` with the - proper type and size: - - .. code-block:: python - - >>> arr = cppyy.ll.array_new[int](4) # allocates memory for 4 C ints - >>> print(len(arr)) - 4 - >>> print(type(arr[0])) - - >>> - -* **ll.array_delete**: an interface on top of C++'s ``delete[]``. - To continue to example above: - - .. code-block:: python - - >>> cppyy.ll.array_delete(arr) - >>> - - -`argc/argv` ------------ - -C/C++'s ``main`` function can take the number of command line arguments -(``argc``) and their values (``argv``) as function arguments. -A common idiom has these values subsequently passed on to the entry point of -e.g. a framework or library. -Since the type of ``argv`` in particular (``char*[]``) is clunky to work with -in Python, the low level module contains two convenient helper functions, -``ll.argc()`` and ``ll.argv()``, that convert the command line arguments as -provided by Python's ``sys`` module, into typed values that are can be passed -to by C/C++. - - -.. _`ctypes module`: https://docs.python.org/3/library/ctypes.html diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/misc.rst b/bindings/pyroot/cppyy/cppyy/doc/source/misc.rst deleted file mode 100644 index 9eacb6e480e27..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/misc.rst +++ /dev/null @@ -1,186 +0,0 @@ -.. _features: - -Miscellaneous -============= - -.. toctree:: - :hidden: - - cppyy_features_header - -This is a collection of a few more features listed that do not have a proper -place yet in the rest of the documentation. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - - -`Special variables` -------------------- - -There are several conventional "special variables" that control behavior of -functions or provide (internal) information. -Often, these can be set/used in pythonizations to handle memory management or -Global Interpreter Lock (GIL) release. - -* ``__python_owns__``: a flag that every bound instance carries and determines - whether Python or C++ owns the C++ instance (and associated memory). - If Python owns the instance, it will be destructed when the last Python - reference to the proxy disappears. - You can check/change the ownership with the __python_owns__ flag that every - bound instance carries. - Example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> c = Concrete() - >>> c.__python_owns__ # True: object created in Python - True - >>> - -* ``__creates__``: a flag that every C++ overload carries and determines - whether the return value is owned by C++ or Python: if ``True``, Python owns - the return value, otherwise C++. - -* ``__set_lifeline__``: a flag that every C++ overload carries and determines - whether the return value should place a back-reference on ``self``, to - prevent the latter from going out of scope before the return value does. - The default is ``False``, but will be automatically set at run-time if a - return value's address is a C++ object pointing into the memory of ``this``, - or if ``self`` is a by-value return. - -* ``__release_gil__``: a flag that every C++ overload carries and determines - whether the Global Interpreter Lock (GIL) should be released during the C++ - call to allow multi-threading. - The default is ``False``. - -* ``__useffi__``: a flag that every C++ overload carries and determines - whether generated wrappers or direct foreign functions should be used. - This is for PyPy only; the flag has no effect on CPython. - -* ``__sig2exc__``: a flag that every C++ overload carries and determines - whether C++ signals (such as SIGABRT) should be converted into Python - exceptions. - -* ``__cpp_name__``: a string that every C++ bound class carries and contains - the actual C++ name (as opposed to ``__name__`` which has the Python name). - This can be useful for template instantiations, documentation, etc. - -* ``__cpp_template__``: a back-reference to the template used to instantiate - a templated class. - This variable only exists if the class was dynamically instantiated from - Python at least once. - - -`STL algorithms` ----------------- - -It is usually easier to use a Python equivalent or code up the effect of an -STL algorithm directly, but when operating on a large container, calling an -STL algorithm may offer better performance. -It is important to note that all STL algorithms are templates and need the -correct types to be properly instantiated. -STL containers offer typedefs to obtain those exact types and these should -be used rather than relying on the usual implicit conversions of Python types -to C++ ones. -For example, as there is no ``char`` type in Python, the ``std::remove`` call -below can not be instantiated using a Python string, but the -``std::string::value_type`` must be used instead: - - .. code-block:: python - - >>> cppstr = cppyy.gbl.std.string - >>> n = cppstr('this is a C++ string') - >>> print(n) - this is a C++ string - >>> n.erase(cppyy.gbl.std.remove(n.begin(), n.end(), cppstr.value_type(' '))) - object at 0x7fba35d1af50> - >>> print(n) - thisisaC++stringing - >>> - - -`Reduced typing` ----------------- - -Note: ``from cppyy.interactive import *`` is no longer supported for CPython -3.11 and later because the ``dict`` object features it relies on have been -removed. - -Typing ``cppyy.gbl`` all the time gets old rather quickly, but the dynamic -nature of ``cppyy`` makes something like ``from cppyy.gbl import *`` -impossible. -For example, classes can be defined dynamically after that statement and then -they would be missed by the import. -In scripts, it is easy enough to rebind names to achieve a good amount of -reduction in typing (and a modest performance improvement to boot, because of -fewer dictionary lookups), e.g.: - - .. code-block:: python - - import cppyy - std = cppyy.gbl.std - v = std.vector[int](range(10)) - -But even such rebinding becomes annoying for (brief) interactive sessions. - -For CPython only (and not with tools such as IPython or in IDEs that replace -the interactive prompt), there is a fix, using -``from cppyy.interactive import *``. -This makes lookups in the global dictionary of the current frame also -consider everything under ``cppyy.gbl``. -This feature comes with a performance `penalty` and is not meant for -production code. -Example usage: - - .. code-block:: python - - >>> from cppyy.interactive import * - >>> v = std.vector[int](range(10)) - >>> print(list(v)) - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - >>> cppdef("struct SomeStruct {};") - True - >>> s = SomeStruct() # <- dynamically made available - >>> s - - >>> - -For PyPy, IPython, etc. ``cppyy.gbl`` is simply rebound as ``g`` and -``cppyy.gbl.std`` is made available as ``std``. -Not as convenient as full lookup, and missing any other namespaces that may be -available, but still saves some typing in may cases. - - -`Odds and ends` ---------------- - -* **namespaces**: Are represented as python classes. - Namespaces are more open-ended than classes, so sometimes initial access may - result in updates as data and functions are looked up and constructed - lazily. - Thus the result of ``dir()`` on a namespace shows the classes and functions - available for binding, even if these may not have been created yet. - Once created, namespaces are registered as modules, to allow importing from - them. - The global namespace is ``cppyy.gbl``. - -* **NULL**: Is represented as ``cppyy.nullptr``. - Starting C++11, the keyword ``nullptr`` is used to represent ``NULL``. - For clarity of intent, it is recommended to use this instead of ``None`` - (or the integer ``0``, which can serve in some cases), as ``None`` is better - understood as ``void`` in C++. - -* **default value**: Represented with The untyped ``cppyy.default``. - The generic value ``cppyy.default`` will convert to the type specific default - value (per C++ rules) when used as a function argument or in assignment. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/numba.rst b/bindings/pyroot/cppyy/cppyy/doc/source/numba.rst deleted file mode 100644 index 4cf83e99747aa..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/numba.rst +++ /dev/null @@ -1,363 +0,0 @@ -.. _numba: - - -Numba support -============= - -.. caution:: - - This is an **experimental** feature, available starting with release - 2.4.0. - It is still incomplete (see listing below) and has only been tested on - Linux on x86_64. - -Numba `is a JIT compiler`_ for Python functions that can be statically typed -based on their input arguments. -Since C++ objects are always statically typed and already implemented at the -machine level, they can be dynamically integrated into the Numba type tracing -and lowering by exposing type details through C++ reflection at runtime. - -JIT-compiling traces of mixed Python/bound C++ code reduces, and in some -cases removes, the overhead of boxing/unboxing native data into their Python -proxies and vice versa. -It can also reduce or remove temporaries, especially for template -expressions. -Thus, there can be significant speedups for mixed code, beyond the Numba -compilation of Python code itself. -The current implementation integrates compiled C++ through function pointers, -object pointers, and pointer offsets, into the intermediate representation -(IR) as generated by Numba. -A future version may integrate Cling-generated IR directly into Numba IR (or -vice versa), e.g. if the C++ code is exposed from (precompiled) headers. -This would allow inlining of C++ code into Numba traces, for further -expected speedups. - - -Why Numba? ----------- - -The advertised premise of Numba is that it "makes Python code fast." -However, there is a much more compelling reason: Numba allows developers to -stay in their chosen ecosystem, be it Python or C++, in mixed environments, -without paying for their choice in lost performance. -For example, a Python developer using Numba does not need to rewrite a kernel -into C++ just to run performantly in a C++ framework. -Similarly, a C++ developer can use Numba to compile and create function -pointers to Python code for easy, performant, access. -This becomes even more compelling if the deployment target is a GPU, which -would otherwise certainly require a rewrite of the Python code. -Add that Numba, as a JIT-compiler, is fully run-time just like ``cppyy``, -and the use case for integration is clear. -(Numba does not currently provide support for C++.) - - -Usage -------- - -``cppyy`` does not use Numba extension hooks to minimize accidental -dependencies. -Instead, it requires that the extensions are loaded explicitly by any code -that uses it:: - - import cppyy.numba_ext - -After that, Numba is able to trace ``cppyy`` bound code when applying the -usual ``numba.njit`` decorator. - -Numba type declarations are done lazily, with the ``numba_ext`` module only -initially registering hooks on proxy base classes, to keep overheads in -Numba's type-resolution to a minimum. -On use in a JITed trace, each C++ type or function call is refined to the -actual, concrete types and type-specific overloads, with templates -instantiated as-needed. -Where possible, lowering is kept generic to reduce the number of callbacks -in Numba's compilation chain. - - -Examples --------- - -The following, non-exhaustive, set of examples gives an idea of the -current level of support. -More examples can be found in the `test suite`_. - -C++ free (global) functions can be called and overloads will be selected, or -a template will be instantiated, based on the provided types. -Exact type matches are fully supported, there is some support for typedefs -add implicit conversions for builtin types, there is no support for -conversions of custom types or default arguments. - -- **Basic usage**: To use ``cppyy`` in Numba JITed code, simply import - ``cppyy.numba_ext``, after which further use is transparent and the same - as when otherwise using ``cppyy`` in Python. - Example: - -.. code-block:: python - - >>> import numba - >>> import cppyy - >>> import cppyy.numba_ext # enables numba to work with cppyy - >>> import math - >>> @numba.jit(nopython=True) - ... def cpp_sqrt(x): - ... return cppyy.gbl.sqrt(x) # direct use, no extra setup required - >>> print("Sqrt of 4: ", cpp_sqrt(4.0)) - Sqrt of 4: 2.0 - >>> print("Sqrt of Pi: ", cpp_sqrt(math.pi)) - Sqrt of Pi: 1.7724538509055159 - - -- **Overload selection**: C++ overloads provide different implementations - for different argument types (not to be confused with Numba overloads, - which provide different implementations for the same argument types). - Unfortunately, mapping of Python types to C++ types is often not exact, - so a "best match" is chosen, similarly to what ``cppyy`` normally does. - However, the latter, being dynamic, is more flexible. - For example, best-match C++ integer type can be value dependent, whereas - in the Numba trace, it is by definition fixed at JIT time. - Example: - -.. code-block:: python - - >>> cppyy.cppdef(""" - ... int mul(int x) { return x * 2; } - ... float mul(float x) { return x * 3; } - ... """) - >>> @numba.jit(nopython=True) - ... def oversel(a): - ... total = type(a[0])(0) - ... for i in range(len(a)): - ... total += cppyy.gbl.mul(a[i]) - ... return total - - >>> a = np.array(range(10), dtype=np.float32) - >>> print("Array: ", a) - Array: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.] - >>> print("Overload selection output: ", oversel(a)) - Overload selection output: 135.0 - >>> a = np.array(range(10), dtype=np.int32) - >>> print("Array: ", a) - Array: [0 1 2 3 4 5 6 7 8 9] - >>> print("Overload selection output: ", oversel(a)) - Overload selection output: 90 - -- **Template instantiation**: templates are instantiated as needed as part - of the overload selection. - The best match is done for the arguments provided at the point of first - use. - If those arguments vary based on program input, it may make sense to - provide Numba with type hints and pre-compile such functions. - Example: - -.. code-block:: python - - >>> import cppyy - >>> import cppyy.numba_ext - >>> import numba - >>> import numpy as np - >>> cppyy.cppdef(""" - ... template - ... T square(T t) { return t*t; } - ... """) - >>> @numba.jit(nopython=True) - ... def tsa(a): - ... total = type(a[0])(0) - ... for i in range(len(a)): - ... total += cppyy.gbl.square(a[i]) - ... return total - >>> a = np.array(range(10), dtype=np.float32) - >>> print("Float array: ", a) - Float array: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.] - >>> print("Sum of squares: ", tsa(a)) - Sum of squares: 285.0 - >>> print() - >>> a = np.array(range(10), dtype=np.int32) - >>> print("Integer array: ", a) - Integer array: [0 1 2 3 4 5 6 7 8 9] - >>> print("Sum of squares: ", tsa(a)) - Sum of squares: 285 - - -Instances of C++ classes can be passed into Numba traces. -They can be returned from functions called *within* the trace, but cannot yet -be returned *from* the trace. -Their public data is accessible (read-only) if of built-in type and their -public methods can be called, for which overload selection works. -Example: - -.. code-block:: python - - >>> import cppyy - >>> import numba - >>> import numpy as np - >>> - >>> cppyy.cppdef("""\ - ... class MyData { - ... public: - ... MyData(int i, int j) : fField1(i), fField2(j) {} - ... - ... public: - ... int get_field1() { return fField1; } - ... int get_field2() { return fField2; } - ... - ... MyData copy() { return *this; } - ... - ... public: - ... int fField1; - ... int fField2; - ... };""") - True - >>> @numba.jit(nopython=True) - >>> def tsdf(a, d): - ... total = type(a[0])(0) - ... for i in range(len(a)): - ... total += a[i] + d.fField1 + d.fField2 - ... return total - ... - >>> d = cppyy.gbl.MyData(5, 6) - >>> a = np.array(range(10), dtype=np.int32) - >>> print(tsdf(a, d)) - 155 - >>> # example of method calls - >>> @numba.jit(nopython=True) - >>> def tsdm(a, d): - ... total = type(a[0])(0) - ... for i in range(len(a)): - ... total += a[i] + d.get_field1() + d.get_field2() - ... return total - ... - >>> print(tsdm(a, d)) - 155 - >>> # example of object return by-value - >>> @numba.jit(nopython=True) - >>> def tsdcm(a, d): - ... total = type(a[0])(0) - ... for i in range(len(a)): - ... total += a[i] + d.copy().fField1 + d.get_field2() - ... return total - ... - >>> print(tsdcm(a, d)) - 155 - >>> - - -Demo: Numba physics example ---------------------------- - -Motivating example taken from: -`numba_scalar_impl.py `_ - -.. code-block:: python - - >>> import numba - >>> import cppyy - >>> import cppyy.numba_ext - ... - >>> cppyy.cppdef(""" - ... #include - ... struct Atom { - ... float x; - ... float y; - ... float z; - ... }; - ... - ... std::vector atoms = {{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}, {5, 6, 7}}; - ... """) - ... - >>> @numba.njit - >>> def lj_numba_scalar(r): - ... sr6 = (1./r)**6 - ... pot = 4.*(sr6*sr6 - sr6) - ... return pot - - >>> @numba.njit - >>> def distance_numba_scalar(atom1, atom2): - ... dx = atom2.x - atom1.x - ... dy = atom2.y - atom1.y - ... dz = atom2.z - atom1.z - ... - ... r = (dx * dx + dy * dy + dz * dz) ** 0.5 - ... - ... return r - ... - >>> def potential_numba_scalar(cluster): - ... energy = 0.0 - ... for i in range(cluster.size() - 1): - ... for j in range(i + 1, cluster.size()): - ... r = distance_numba_scalar(cluster[i], cluster[j]) - ... e = lj_numba_scalar(r) - ... energy += e - ... - ... return energy - ... - >>> print("Total lennard jones potential =", potential_numba_scalar(cppyy.gbl.atoms)) - Total lennard jones potential = -0.5780277345740283 - - -Overhead --------- - -The main overhead of JITing Numba traces is in the type annotation in Numba -itself, optimization of the IR and assembly by the backend less so. -(There is also a non-negligible cost to Numba initialization, which is why -``cppyy`` does not provide automatic extension hooks.) -The use of ``cppyy`` bound C++, which relies on the same Numba machinery, -does not change that, since the reflection-based lookups are in C++ and -comparatively very fast. -For example, there is no appreciable difference in wall clock time to JIT a -trace using Numba's included math functions (from module ``math`` or -``numpy``) or one that uses C++ bound ones whether from the standard library -or a templated versions from e.g. Eigen. -Use of very complex template expressions may change this balance, but in -principle, wherever it makes sense in the first place to use Numba JITing, it -is also fine, performance-wise, to use ``cppyy`` bound C++ inside the trace. - -A second important overhead is in unboxing Python proxies of C++ objects, -in particular when passed as an argument to a Numba-JITed function. -The main costs are in the lookup (types are matched at every invocation) and -to a lesser extent the subsequent copying of the instance data. -Thus, functions that take a C++ object as an argument will require more time -spent in the function body for JITing to be worth it than functions that do -not. - -The current implementation invokes C++ callables through function pointers -and accesses data through offsets calculations from the object's base -address. -A future implementation may be able to inline C++ into the Numba trace if -code is available in headers files or was JITed. - - -Further Information -------------------- - -- Numba documentation: - `numba.readthedocs.io `_. - -- "Using C++ From Numba, Fast and Automatic", presented at `PyHEP 2022 `_ - - - `PyHEP 2022 video `_ - - `PyHEP 2022 slides `_ - - `PyHEP 2022 notebook `_ - -- Presentation at CERN's ROOT Parallelism, Performance and Programming Model - (`PPP `_) Meeting - - - `PPP slides `_ - - `PPP notebook `_ - - -Acknowledgements ----------------- - -This work is supported in part by the `Compiler Research Organization`_ -(Princeton University), with contributions from -`Vassil Vassilev `_, -`Baidyanath Kundu `_, and -`Aaron Jomy `_. - - -.. _is a JIT compiler: https://numba.pydata.org/ - -.. _test suite: https://github.com/wlav/cppyy/blob/master/test/test_numba.py - -.. _Compiler Research Organization: https://compiler-research.org/ diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/packages.rst b/bindings/pyroot/cppyy/cppyy/doc/source/packages.rst deleted file mode 100644 index aa8aa3259d222..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/packages.rst +++ /dev/null @@ -1,73 +0,0 @@ -.. _packages: - -PyPI Packages -============= - -Cppyy ------ - -The ``cppyy`` module is a frontend (see :ref:`Package Structure -`), and most of the code is elsewhere. However, it does -contain the docs for all of the modules, which are built using -Sphinx: http://www.sphinx-doc.org/en/stable/ and published to -http://cppyy.readthedocs.io/en/latest/index.html using a webhook. To create -the docs:: - - $ pip install sphinx_rtd_theme - Collecting sphinx_rtd_theme - ... - Successfully installed sphinx-rtd-theme-0.2.4 - $ cd docs - $ make html - -The Python code in this module supports: - -* Interfacing to the correct backend for CPython or PyPy. -* Pythonizations (TBD) - -Cppyy-backend -------------- - -The ``cppyy-backend`` module contains two areas: - -* A patched copy of cling -* Wrapper code - - -Package structure ------------------ -.. _package-structure: - -There are four PyPA packages involved in a full installation, with the -following structure:: - - (A) _cppyy (PyPy) - / \ - (1) cppyy (3) cppyy-backend -- (4) cppyy-cling - \ / - (2) CPyCppyy (CPython) - -The user-facing package is always ``cppyy`` (1). -It is used to select the other (versioned) required packages, based on the -python interpreter for which it is being installed. - -Below (1) follows a bifurcation based on interpreter. -This is needed for functionality and performance: for CPython, there is the -CPyCppyy package (2). -It is written in C++, makes use of the Python C-API, and installs as a Python -extension module. -For PyPy, there is the builtin module ``_cppyy`` (A). -This is not a PyPA package. -It is written in RPython as it needs access to low-level pointers, JIT hints, -and the ``_cffi_backend`` backend module (itself builtin). - -Shared again across interpreters is the backend, which is split in a small -wrapper (3) and a large package that contains Cling/LLVM (4). -The former is still under development and expected to be updated frequently. -It is small enough to download and build very quickly. -The latter, however, takes a long time to build, but since it is very stable, -splitting it off allows the creation of binary wheels that need updating -only infrequently (expected about twice a year). - -All code is publicly available; see the -:doc:`section on repositories `. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/philosophy.rst b/bindings/pyroot/cppyy/cppyy/doc/source/philosophy.rst deleted file mode 100644 index 4ff9a2b7dd44b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/philosophy.rst +++ /dev/null @@ -1,236 +0,0 @@ -.. _philosophy: - -Philosophy -========== - -.. toctree:: - :hidden: - -As a Python-C++ language binder, cppyy has several unique features: it fills -gaps and covers use cases not available through other binders. -This document explains some of the design choices made and the thinking -behind the implementations of those features. -It's categorized as "philosophy" because a lot of it is open to -interpretation. -Its main purpose is simply to help you decide whether cppyy covers your use -cases and binding requirements, before committing any time to -:ref:`trying it out `. - - -Run-time v.s. compile-time --------------------------- - -What performs better, run-time or compile-time? -The obvious answer is compile-time: see the performance differences between -C++ and Python, for example. -Obvious, but completely wrong, however. -In fact, when it comes to Python, it is even the `wrong question.` - -Everything in Python is run-time: modules, classes, functions, etc. are all -run-time constructs. -A Python module that defines a class is a set of instructions to the Python -interpreter that lead to the construction of the desired class object. -A C/C++ extension module that defines a class does the same thing by calling -a succession of Python interpreter Application Programming Interfaces (APIs; -the exact same that Python uses itself internally). -If you use a compile-time binder such as `SWIG`_ or `pybind11`_ to bind a C++ -class, then what gets compiled is the series of API calls necessary to -construct a Python-side equivalent at `run-time` (when the module gets -loaded), not the Python class object. -In short, whether a binding is created at "compile-time" or at run-time has -no measurable bearing on performance. - -What does affect performance is the overhead to cross the language barrier. -This consists of unboxing Python objects to extract or convert the underlying -objects or data to something that matches what C++ expects; overload -resolution based on the unboxed arguments; offset calculations; and finally -the actual dispatch. -As a practical matter, overload resolution is the most costly part, followed -by the unboxing and conversion. -Best performance is achieved by specialization of the paths through the -run-time: recognize early the case at hand and select an optimized path. -For that reason, `PyPy`_ is so fast: JIT-ed traces operate on unboxed objects -and resolved overloads are baked into the trace, incurring no further cost. -Similarly, this is why pybind11 is so slow: its code generation is the C++ -compiler's template engine, so complex path selection and specialization is -very hard to do in a performance-portable way. - -In cppyy, a great deal of attention has gone into built-in specialization -paths, which drives its performance. -For example, basic inheritance sequentially lines up classes, whereas -multiple (virtual) inheritance usually requires thunks. -Thus, when calling base class methods on a derived instance, the latter -requires offset calculations that depend on that instance, whereas the former -has fixed offsets fully determined by the class definitions themselves. -By labeling classes appropriately, single inheritance classes (by far the -most common case) do not incur the overhead in PyPy's JIT-ed traces that is -otherwise unavoidable for multiple virtual inheritance. -As another example, consider that the C++ standard does not allow modifying -a ``std::vector`` while looping over it, whereas Python has no such -restriction, complicating loops. -Thus, cppyy has specialized ``std::vector`` iteration for both PyPy and -CPython, easily outperforming looping over an equivalent numpy array. - -In CPython, the performance of `non-overloaded` function calls depends -greatly on the Python interpreter's internal specializations; and Python3 -has many specializations specific to basic extension modules (C function -pointer calls), gaining a performance boost of more than 30% over Python2. -Only since Python3.8 is there also better support for closure objects (vector -calls) as cppyy uses, to short-cut through the interpreter's own overhead. - -As a practical consideration, whether a binder performs well on code that you -care about, depends `entirely` on whether it has the relevant specializations -for your most performance-sensitive use cases. -The only way to know for sure is to write a test application and measure, but -a binder that provides more specializations, or makes it easy to add your -own, is more likely to deliver. - - -`Manual v.s. automatic` ------------------------ - -Python is, today, one of the most popular programming languages and has a -rich and mature eco-system around it. -But when the project that became cppyy started in the field of High Energy -Physics (HEP), Python usage was non-existent there. -As a Python user to work in this predominantly C++ environment, you had to -bring your own bindings, thus automatic was the only way to go. -Binders such as SWIG, SIP (or even boost.python with Pyste) all had the fatal -assumption that you were providing Python bindings to your `own` C++ code, -and that you were thus able to modify those (many) areas of the C++ codes -that their parsers could not handle. -The `CINT`_ interpreter was already well established in HEP, however, and -although it, too, had many limitations, C++ developers took care not to write -code that it could not parse. -In particular, since CINT drove automatic I/O, all data classes as needed for -analysis were parsable by CINT and consequently, by using CINT for the -bindings, at the very least one could run any analysis in Python. -This was key. - -Besides not being able to parse some code (a problem that's history for cppyy -since moving to Cling), all automatic parsers suffer from the problem that -the bindings produced have a strong "C++ look-and-feel" and that choices need -to be made in cases that can be bound in different, equally valid, ways. -As an example of the latter, consider the return of an ``std::vector``: -should this be automatically converted to a Python ``list``? -Doing so is more "pythonic", but incurs a significant overhead, and no -automatic choice will satisfy all cases: user input is needed. - -The typical way to solve these issues, is to provide an intermediate language -where corner cases can be brushed up, code can be made more Python friendly, -and design choices can be resolved. -Unfortunately, learning an intermediate language is quite an investment in -time and effort. -With cppyy, however, no such extra language is needed: using Cling, C++ code -can be embedded and JIT-ed for the same purpose. -In particular, cppyy can handle `boxed` Python objects and the full Python -C-API is available through Cling, allowing complete manual control where -necessary, and all within a single code base. -Similarly, a more pythonistic look-and-feel can be achieved in Python itself. -As a rule, Python is always the best place, far more so than any intermediate -language, to do Python-thingies. -Since all bound proxies are normal Python classes, functions, etc., Python's -introspection (and regular expressions engine) can be used to provide rule -based improvements in a way similar to the use of directives in an -intermediate language. - -On a practical note, it's often said that an automatic binder can provide -bindings to 95% of your code out-of-the-box, with only the remaining part -needing manual intervention. -This is broadly true, but realize that that 5% contains the most difficult -cases and is where 20-30% of the effort would have gone in case the bindings -were done fully manually. -It is therefore important to consider what manual tools an automatic binder -offers and to make sure they fit your work style and needs, because you are -going to spend a significant amount of time with them. - - -`LLVM dependency` ------------------ - -cppyy depends on `LLVM`_, through Cling. -LLVM is properly internalized, so that it doesn't conflict with other uses; -and in particular it is fine to mix `Numba`_ and cppyy code. -It does mean a download cost of about 20MB for the binary wheel (exact size -differs per platform) on installation, and additional `primarily initial` -memory overheads at run-time. -Whether this is onerous depends strongly not only on the application, but -also on the rest of the software stack. - -The initial cost of loading cppyy, and thus starting the Cling interpreter, -is about 45MB (platform dependent). -Initial uses of standard (e.g. STL) C++ results in deserialization of the -precompiled header at another eventual total cost of about 25MB (again, -platform dependent). -The actual bindings of course also carry overheads. -As a rule of thumb, you should budget for ~100MB all-in for the overhead -caused by the bindings. - -Other binders do not have this initial memory overhead, but do of course -occur an overhead per module, class, function, etc. -At scale, however, cppyy has some advantages: all binding is lazy (including -the option of automatic loading), standard classes are never duplicated, and -there is no additional "per-module" overhead. -Thus, eventually (depending on the number of classes bound, across how many -modules, what use fraction, etc.), this initial cost is recouped when -compared to other binders. -As a rule of thumb, if about 10% of classes are used, it takes several -hundreds of bound classes before the cppyy-approach is beneficial. -In High Energy Physics, from which it originated, cppyy is regularly used in -software stacks of many thousands of classes, where this advantage is very -important. - - -`Distributing headers` ----------------------- - -cppyy requires C/C++ headers to be available at run-time, which was never a -problem in the developer-centric world from which it originated: software -always had supported C++ APIs already, made available through header files, -and Python simply piggy-backed onto those. -JIT-ing code in those headers, which potentially picked up system headers -that were configured differently, was thus also never a problem. -Or rather, the same problem exists for C++, and configuration for C++ to -resolve potential issues translates transparently to Python. - -There are only two alternatives: precompile headers into LLVM bitcode and -distribute those or provide a restricted set of headers. -Precompiled headers (and modules) were never designed to be portable and -relocatable, however, thus that may not be the panacea it seems. -A restricted set of headers is some work, but cppyy can operate on abstract -interface classes just fine (including Python-side cross-inheritance). - - -`Large deployment` ------------------- - -The single biggest headache in maintaining an installation of Python -extension modules is that Python patch releases can break them. -The two typical solutions are to either restrict the choice of Python -interpreter and version that are supported (common in HPC) or to provide -binaries (wheels) for a large range of different interpreters and versions -(as e.g. done for conda). - -In the case of cppyy, only CPython/CPyCppyy and PyPy/_cppyy (an internal -module) depend on the Python interpreter (see: -:ref:`Package Structure `). -The user-facing ``cppyy`` module is pure Python and the backend (Cling) is -Python-independent. -Most importantly, since all bindings are generated at run-time, there are no -extension modules to regenerate and/or recompile. - -Thus, the end-user only needs to rebuild/reinstall CPyCppyy for each relevant -version of Python (and nothing extra is needed for PyPy) to switch Python -versions and/or interpreter. -The rest of the software stack remains completely unchanged. -Only if Cling in cppyy's backend is updated, which happens infrequently, and -non-standard precompiled headers or modules are used, do these need to be -rebuild in full. - - -.. _`SWIG`: http://swig.org/ -.. _`pybind11`: https://pybind11.readthedocs.io/en/stable/ -.. _`PyPy`: https://www.pypy.org/ -.. _`CINT`: https://en.wikipedia.org/wiki/CINT -.. _`LLVM`: https://llvm.org/ -.. _`Numba`: http://numba.pydata.org/ diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/python.rst b/bindings/pyroot/cppyy/cppyy/doc/source/python.rst deleted file mode 100644 index 760d2786bd1e4..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/python.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. _python: - - -Python -====== - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - - -`PyObject` ----------- - -Arguments and return types of ``PyObject*`` can be used, and passed on to -CPython API calls (or through ``cpyext`` in PyPy). - - -`Doc strings` -------------- - -The documentation string of a method or function contains the C++ -arguments and return types of all overloads of that name, as applicable. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> print Concrete.array_method.__doc__ - void Concrete::array_method(int* ad, int size) - void Concrete::array_method(double* ad, int size) - >>> - - -`Help` ------- - -Bound C++ class is first-class Python and can thus be inspected like any -Python objects can. -For example, we can ask for ``help()``: - - .. code-block:: python - - >>> help(Concrete) - Help on class Concrete in module gbl: - - class Concrete(Abstract) - | Method resolution order: - | Concrete - | Abstract - | CPPInstance - | __builtin__.object - | - | Methods defined here: - | - | __assign__(self, const Concrete&) - | Concrete& Concrete::operator=(const Concrete&) - | - | __init__(self, *args) - | Concrete::Concrete(int n = 42) - | Concrete::Concrete(const Concrete&) - | - etc. .... - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/pythonizations.rst b/bindings/pyroot/cppyy/cppyy/doc/source/pythonizations.rst deleted file mode 100644 index 71049e505e96c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/pythonizations.rst +++ /dev/null @@ -1,172 +0,0 @@ -.. _pythonizations: - -Pythonizations -============== - -Automatic bindings generation mostly gets the job done, but unless a C++ -library was designed with expressiveness and interactivity in mind, using it -will feel stilted. -Thus, if you are not the end-user of a set of bindings, it is beneficial to -implement *pythonizations*. -Some of these are already provided by default, e.g. for STL containers. -Consider the following code, iterating over an STL map, using naked bindings -(i.e. "the C++ way"): - -.. code-block:: python - - >>> from cppyy.gbl import std - >>> m = std.map[int, int]() - >>> for i in range(10): - ... m[i] = i*2 - ... - >>> b = m.begin() - >>> while b != m.end(): - ... print(b.__deref__().second, end=' ') - ... b.__preinc__() - ... - 0 2 4 6 8 10 12 14 16 18 - >>> - -Yes, that is perfectly functional, but it is also very clunky. -Contrast this to the (automatic) pythonization: - -.. code-block:: python - - >>> for key, value in m: - ... print(value, end=' ') - ... - 0 2 4 6 8 10 12 14 16 18 - >>> - -Such a pythonization can be written completely in Python using the bound C++ -methods, with no intermediate language necessary. -Since it is written on abstract features, there is also only one such -pythonization that works for all STL map instantiations. - - -Python callbacks ----------------- - -Since bound C++ entities are fully functional Python ones, pythonization can -be done explicitly in an end-user facing Python module. -However, that would prevent lazy installation of pythonizations, so instead a -callback mechanism is provided. - -A callback is a function or callable object taking two arguments: the Python -proxy class to be pythonized and its C++ name. -The latter is provided to allow easy filtering. -This callback is then installed through ``cppyy.py.add_pythonization`` and -ideally only for the relevant namespace (installing callbacks for classes in -the global namespace is supported, but beware of name clashes). - -Pythonization is most effective of well-structured C++ libraries that have -idiomatic behaviors. -It is then straightforward to use Python reflection to write rules. -For example, consider this callback that looks for the conventional C++ -function ``GetLength`` and replaces it with Python's ``__len__``: - -.. code-block:: python - - >>> import cppyy - >>> - >>> def replace_getlength(klass, name): - ... try: - ... klass.__len__ = klass.__dict__['GetLength'] - ... del klass.GetLength - ... except KeyError: - ... pass - ... - >>> cppyy.py.add_pythonization(replace_getlength, 'MyNamespace') - >>> - >>> cppyy.cppdef(""" - ... namespace MyNamespace { - ... class MyClass { - ... public: - ... MyClass(int i) : fInt(i) {} - ... int GetLength() { return fInt; } - ... - ... private: - ... int fInt; - ... }; - ... }""") - True - >>> m = cppyy.gbl.MyNamespace.MyClass(42) - >>> len(m) - 42 - >>> m.GetLength() - Traceback (most recent call last): - File "", line 1, in - AttributeError: 'MyClass' object has no attribute 'GetLength' - >>> - -The deletion of ``GetLength`` method with ``del`` can be omitted -if both ``MyClass.GetLength`` and ``MyClass.__len__`` should be valid. - -C++ callbacks -------------- - -If you are familiar with the Python C-API, it may sometimes be beneficial to -add unique optimizations to your C++ classes to be picked up by the -pythonization layer. -There are two conventional function that cppyy will look for (no registration -of callbacks needed): - -.. code-block:: C++ - - static void __cppyy_explicit_pythonize__(PyObject* klass, const std::string&); - -which is called *only* for the class that declares it. -And: - -.. code-block:: C++ - - static void __cppyy_pythonize__(PyObject* klass, const std::string&); - -which is also called for all derived classes. - -Just as with the Python callbacks, the first argument will be the Python -class proxy, the second the C++ name, for easy filtering. -When called, cppyy will be completely finished with the class proxy, so any -and all changes are fair game, including the low-level ones such as the replacement of -iteration or buffer protocols. - -An example pythonization replacing ``MyClass.GetLength`` method with Python's ``__len__`` -done with the C++ callbacks: - -.. code-block:: python - - >>> import cppyy - >>> - >>> cppyy.cppdef(""" - ... #include - ... - ... namespace MyNamespace { - ... class MyClass { - ... public: - ... MyClass(int i) : fInt(i) {} - ... int GetLength() { return fInt; } - ... - ... private: - ... int fInt; - ... - ... // pythonizations - ... public: - ... static void __cppyy_pythonize__(PyObject* klass, const std::string&){ - ... auto cppName = "GetLength"; - ... auto pythonizationName = "__len__"; - ... auto* methodObject = PyObject_GetAttrString(klass, cppName); - ... PyObject_SetAttrString(klass, pythonizationName, methodObject); - ... Py_DECREF(methodObject); - ... PyObject_DelAttrString(klass, cppName); - ... } - ... }; - ... }""") - True - >>> m = cppyy.gbl.MyNamespace.MyClass(42) - >>> len(m) - 42 - >>> m.GetLength() - Traceback (most recent call last): - File "", line 1, in - AttributeError: 'MyClass' object has no attribute 'GetLength' - >>> diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/repositories.rst b/bindings/pyroot/cppyy/cppyy/doc/source/repositories.rst deleted file mode 100644 index 37951760ea9e8..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/repositories.rst +++ /dev/null @@ -1,161 +0,0 @@ -.. _repositories: - -Repositories -============ - -The ``cppyy`` module is a frontend that requires an intermediate (Python -interpreter dependent) layer, and a backend (see -:ref:`Package Structure `). -Because of this layering and because it leverages several existing packages -through reuse, the relevant codes are contained across a number of -repositories. - -* Frontend, cppyy: https://github.com/wlav/cppyy -* CPython (v2/v3) intermediate: https://github.com/wlav/CPyCppyy -* PyPy intermediate (module _cppyy): https://foss.heptapod.net/pypy -* Backend, cppyy: https://github.com/wlav/cppyy-backend - -The backend repo contains both the cppyy-cling (under "cling") and -cppyy-backend (under "clingwrapper") packages. - - -.. _building_from_source: - -Building from source --------------------- - -Except for cppyy-cling, the structure in the repositories follows a normal -PyPA package and they are thus ready to build with `setuptools`_: simply -clone the package and either run ``python setup.py``, or use ``pip``. - -It is highly recommended to follow the dependency chain when manually -upgrading packages individually (i.e. ``cppyy-cling``, ``cppyy-backend``, -``CPyCppyy`` if on CPython, and then finally ``cppyy``), because upstream -packages expose headers that are used by the ones downstream. -Of course, if only building for a patch/point release, there is no need to -re-install the full chain (or follow the order). -Always run the local updates from the package directories (i.e. where the -``setup.py`` file is located), as some tools rely on the package structure. - -The ``STDCXX`` envar can be used to control the C++ standard version; use -``MAKE`` to change the ``make`` command; and ``MAKE_NPROCS`` to control the -maximum number of parallel jobs. -Compilation of the backend, which contains a customized version of -Clang/LLVM, can take a long time, so by default the setup script will use all -cores (x2 if hyperthreading is enabled). - -On MS Windows, some temporary path names may be too long, causing the build to -fail. -To resolve this issue, point the ``TMP`` and ``TEMP`` envars to an existing -directory with a short name before the build: -For example:: - - > set TMP=C:\TMP - > set TEMP=C:\TMP - -The first package to build is ``cppyy-cling``. -It may take a long time, especially on a laptop (Mac ARM being a notable -exception), since Cling comes with a builtin version of LLVM/Clang. -Consider therefore for a moment your reasons for building from source: there -being no pre-built wheel for the platform that you're interested in or simply -needing the latest version from the repository; or perhaps you are planning -to develop/modify the sources. - -If the former, clone the repository, check out a specific tagged release as -needed, then run the following steps to add Cling and build a wheel. -Once built, install the wheel as appropriate:: - - $ git clone https://github.com/wlav/cppyy-backend.git - $ cd cppyy-backend/cling - $ python setup.py egg_info - $ python create_src_directory.py - $ python setup.py bdist_wheel - $ python -m pip install dist/cppyy_cling-* --upgrade - -.. note:: - ``cppyy-cling`` wheels do not depend on the Python interpreter and can - thus be re-used for any version of Python or PyPy. - -The ``egg_info`` setup command is needed for ``create_src_directory.py`` to -find the right version. -That script in turn downloads the proper release from `upstream`_, trims and -patches it, -and installs the result in the "src" directory. -When done, the structure of ``cppyy-cling`` looks again like a PyPA package -and can be used/installed as expected, here done with ``pip``. - -By default, the setup script will use all cores (x2 if hyperthreading is -enabled). -You can change this behavior by setting the ``MAKE_NPROCS`` envar to the -desired number of allowable sub jobs. - -If on the other hand you are building from source to develop/modify -``cppyy-cling``, consider using the ``cmake`` interface. -The first installation will still be just as slow, but subsequent builds can -be incremental and thus much faster. -For this use, first install the latest version from a pre-built wheel, which -will setup the proper directory structure, then use cmake to build and -install the latest or modified version of ``cppyy-cling`` into that:: - - $ python -m pip install cppyy-cling - $ git clone https://github.com/wlav/cppyy-backend.git - $ cd cppyy-backend/cling - $ python setup.py egg_info - $ python create_src_directory.py - $ mkdir dev - $ cd dev - $ cmake ../src -Wno-dev -DCMAKE_CXX_STANDARD=17 -DLLVM_ENABLE_EH=0 -DLLVM_ENABLE_RTTI=0 -DLLVM_ENABLE_TERMINFO=0 -DLLVM_ENABLE_ASSERTIONS=0 -Dminimal=ON -Druntime_cxxmodules=OFF -Dbuiltin_zlib=ON -Dbuiltin_cling=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX= - $ make -j install - -where the ``cmake`` command needs to be given the full path to -`site-packages/cppyy_backend` in the virtual environment or other -installation location. -Adjust other options (esp. ``CMAKE_CXX_STANDARD``) as needed. -For the build command, adjust the ``cmake`` command as appropriate for your -favorite, or platform-specific, build system and/or use ``cmake --build`` -instead of ``make`` directly. -See the `cmake documentation`_ for details. - -Next up is ``cppyy-backend`` (cppyy-backend, subdirectory "clingwrapper"; omit -the first step if you already cloned the repo for ``cppyy-cling``):: - - $ git clone https://github.com/wlav/cppyy-backend.git - $ cd cppyy-backend/clingwrapper - $ python -m pip install . --upgrade --no-use-pep517 --no-deps - -Note the use of ``--no-use-pep517``, which prevents ``pip`` from needlessly -going out to pypi.org and creating a local "clean" build environment from the -cached or remote wheels. -Instead, by skipping PEP 517, the local installation will be used. -This is imperative if there was a change in public headers or if the version -of ``cppyy-cling`` was locally updated and is thus not available on PyPI. - -Upgrading ``CPyCppyy`` (if on CPython; it's not needed for PyPy) and ``cppyy`` -is very similar:: - - $ git clone https://github.com/wlav/CPyCppyy.git - $ cd CPyCppyy - $ python -m pip install . --upgrade --no-use-pep517 --no-deps - -Just like ``cppyy-cling``, ``CPyCppyy`` has ``cmake`` scripts which are the -recommended way for development, as incremental builds are faster:: - - $ mkdir build - $ cmake ../CPyCppyy - $ make -j - -then simply point the ``PYTHONPATH`` envar to the `build` directory above to -pick up the local `cppyy.so` module. - -Finally, the top-level package ``cppyy``:: - - $ git clone https://github.com/wlav/cppyy.git - $ cd cppyy - $ python -m pip install . --upgrade --no-deps - -Please see the `pip documentation`_ for more options, such as developer mode. - -.. _`setuptools`: https://setuptools.readthedocs.io/ -.. _`upstream`: https://root.cern.ch/download/ -.. _`cmake documentation`: https://cmake.org/ -.. _`pip documentation`: https://pip.pypa.io/ diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/starting.rst b/bindings/pyroot/cppyy/cppyy/doc/source/starting.rst deleted file mode 100644 index 826d388dd374f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/starting.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _starting: - -Trying it out -============= - -This is a basic guide to try cppyy and see whether it works for you. -Large code bases will benefit from more advanced features such as -:doc:`pythonizations ` for a cleaner interface to clients; -precompiled modules for faster parsing and reduced memory usage; -":ref:`dictionaries `" to package locations and manage -dependencies; and mapping files for automatic, lazy, loading. -You can, however, get very far with just the basics and it may even be -completely sufficient for small packages with fewer classes. - -cppyy works by parsing C++ definitions through ``cling``, generating tiny -wrapper codes to honor compile-time features and create standardized -interfaces, then compiling/linking those wrappers with the ``clang`` JIT. -It thus requires only those two ingredients: *C++ definitions* and -*linker symbols*. -All cppyy uses, the basic and the more advanced, are variations on the -theme of bringing these two together at the point of use. - -Definitions typically live in header files and symbols in libraries. -Headers can be loaded with ``cppyy.include`` and libraries with the -``cppyy.load_library`` call. -Loading the header is sufficient to start exploring, with ``cppyy.gbl`` the -starting point of all things C++, while the linker symbols are only needed at -the point of first use. - -Here is an example using the `zlib`_ library, which is likely available on -your system: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('zlib.h') # bring in C++ definitions - >>> cppyy.load_library('libz') # load linker symbols - >>> cppyy.gbl.zlibVersion() # use a zlib API - '1.2.11' - >>> - -Since header files can include other header files, it is easy to aggregate -all relevant ones into a single header to include. -If there are project-specific include paths, you can add those paths through -``cppyy.add_include_path``. -If a header is C-only and not set for use with C++, use ``cppyy.c_include``, -which adds ``extern "C"`` around the header. - -Library files can be aggregated by linking all relevant ones to a single -library to load. -Using the linker for this purpose allows regular system features such as -``rpath`` and envars such as ``LD_LIBRARY_PATH`` to be applied as usual. -Note that any mechanism that exposes the library symbols will work. -For example, you could also use the standard module ``ctypes`` through -``ctypes.CDLL`` with the ``ctypes.RTLD_GLOBAL`` option. - -To explore, start from ``cppyy.gbl`` to access your namespaces, classes, -functions, etc., etc. directly; or use python's ``dir`` (or tab-completion) -to see what is available. -Use python's ``help`` to see list the methods and data members of classes and -see the interfaces of functions. - -Now try this out for some of your own headers, libraries, and APIs! - -.. _`zlib`: https://en.wikipedia.org/wiki/Zlib diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/stl.rst b/bindings/pyroot/cppyy/cppyy/doc/source/stl.rst deleted file mode 100644 index 8db0710441289..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/stl.rst +++ /dev/null @@ -1,282 +0,0 @@ -.. _stl: - - -STL -=== - -Parts of the Standard Template Library (STL), in particular its container -types, are the de facto equivalent of Python's builtin types. -STL is written in C++ and Python bindings of it are fully functional as-is, -but are much more useful when pluggable into idiomatic expressions where -Python builtin containers are expected (e.g. in list contractions). - -There are two extremes to achieve such drop-in behavior: copy into Python -builtins, so that the Python-side always deals with true Python objects; or -adjust the C++ interfaces to be the same as their Python equivalents. -Neither is very satisfactory: the former is not because of the existence of -global/static variables and return-by-reference. -If only a copy is available, then expected modifications do not propagate. -Copying is also either slow (when copying every time) or memory intensive (if -the results are cached). -Filling out the interfaces may look more appealing, but all operations then -involve C++ function calls, which can be slower than the Python equivalents, -and C++-style error handling. - -Given that neither choice will satisfy all cases, ``cppyy`` aims to maximize -functionality and minimum surprises based on common use. -Thus, for example, ``std::vector`` grows a pythonistic ``__len__`` method, -but does not lose its C++ ``size`` method. -Passing a Python container through a const reference to a ``std::vector`` -will trigger automatic conversion, but such an attempt through a non-const -reference will fail since a non-temporary C++ object is required [#f1]_ to -return any updates/changes. - -``std::string`` is almost always converted to Python's ``str`` on function -returns (the exception is return-by-reference when assigning), but not when -its direct use is more likely such as in the case of (global) variables or -when iterating over a ``std::vector``. - -The rest of this section shows examples of how STL containers can be used in -a natural, pythonistic, way. - - -`std::vector` -------------- - -A ``std::vector`` is the most commonly used C++ container type because it is -more efficient and performant than specialized types such as ``list`` and -``map``, unless the number of elements gets very large. -Python has several similar types, from the builtin ``tuple`` and ``list``, -the ``array`` from builtin module ``array``, to "as-good-as-builtin" -``numpy.ndarray``. -A vector is more like the latter two in that it can contain only one type, -but more like the former two in that it can contain objects. -In practice, it can interplay well with all these containers, but e.g. -efficiency and performance can differ significantly. - -A vector can be instantiated from any sequence, including generators, and -vectors of objects can be recursively constructed. -If the template type is to be inferred from the argument to the constructor, -the first element needs to be accessible, which precludes generators. - - .. code-block:: python - - >>> from cppyy.gbl.std import vector, pair - >>> v = vector[int](range(10)) # from generator - >>> len(v) - 10 - >>> v = vector([x for x in range(10)]) # type inferred - >>> type(v) - at 0x12d226f00> - >>> len(v) - 10 - >>> vp = vector[pair[int, int]](((1, 2), (3, 4))) - >>> len(vp) - 2 - >>> vp[1][0] - 3 - >>> - -To extend a vector in-place with another sequence object, use ``+=``, just as -for Python's ``list``: - - .. code-block:: python - - >>> v += range(10, 20) - >>> len(v) - 20 - >>> - -Indexing and slicing of a vector follows the normal Python slicing rules; -printing a vector prints all its elements: - - .. code-block:: python - - >>> v[1] - 1 - >>> v[-1] - 19 - >>> v[-4:] - object at 0x7f9051057650> - >>> print(v[-4:]) - { 6, 7, 8, 9 } - >>> - -The usual iteration operations work on vector, but the C++ rules still apply, -so a vector that is being iterated over can *not* be modified in the loop -body. -(On the plus side, this makes it much faster to iterate over a vector than, -say, a numpy ndarray.) - - .. code-block:: python - - >>> for i in v[2:5]: - ... print(i) - ... - 2 - 3 - 4 - >>> 2 in v - True - >>> sum(v) - 190 - >>> - -When a function takes a non-l-value (const-ref, move, or by-value) vector as -a parameter, another sequence can be used and cppyy will automatically -generate a temporary. -Typically, this will be faster than coding up such a temporary on the Python -side, but if the same sequence is used multiple times, creating a temporary -once and re-using it will be the most efficient approach.o - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... int sumit1(const std::vector& data) { - ... return std::accumulate(data.begin(), data.end(), 0); - ... } - ... int sumit2(std::vector data) { - ... return std::accumulate(data.begin(), data.end(), 0); - ... } - ... int sumit3(const std::vector&& data) { - ... return std::accumulate(data.begin(), data.end(), 0); - ... }""") - ... - True - >>> cppyy.gbl.sumit1(range(5)) - 10 - >>> cppyy.gbl.sumit2(range(6)) - 16 - >>> cppyy.gbl.sumit3(range(7)) - 21 - >>> - -The temporary vector is created using the vector constructor taking an -``std::initializer_list``, which is more flexible than constructing a -temporary vector and filling it: it allows the data in the container to be -implicitly converted (e.g. from ``int`` to ``double`` type, or from -pointer to derived to pointer to base class). -As a consequence, however, with STL containers being allowed where Python -containers are, this in turn means that you can pass e.g. an -``std::vector`` (or ``std::list``) where a ``std::vector`` -is expected and a temporary is allowed: - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... double sumit4(const std::vector& data) { - ... return std::accumulate(data.begin(), data.end(), 0); - ... }""") - ... - True - >>> cppyy.gbl.sumit4(vector[int](range(7))) - 21.0 - >>> - -Normal overload resolution rules continue to apply, however, thus if an -overload were available that takes an ``const std::vector&``, it would -be preferred. - -When templates are involved, overload resolution is stricter, to ensure that -a better matching instantiation is preferred over an implicit conversion. -However, that does mean that as-is, C++ is actually more flexible: it has the -curly braces initializer syntax to explicitly infer an -``std::initializer_list``, with no such equivalent in Python. - -Although in general this approach guarantees the intended result, it does put -some strictures on the Python side, requiring careful use of types. -However, an easily fixable error is preferable over an implicitly wrong -result. -Note the type of the init argument in the call resulting in an (attempted) -implicit instantiation in the following example: - - .. code-block:: python - - >>> cppyy.cppdef(""" - ... template - ... T sumit_T(const std::vector& data, T init) { - ... return std::accumulate(data.begin(), data.end(), init); - ... }""") - ... - True - >>> cppyy.gbl.sumit_T(vector['double'](range(7)), 0) - Traceback (most recent call last): - File "", line 1, in - TypeError: Template method resolution failed: - Failed to instantiate "sumit_T(std::vector&,int)" - Failed to instantiate "sumit_T(std::vector*,int)" - Failed to instantiate "sumit_T(std::vector,int)" - >>> cppyy.gbl.sumit_T(vector['double'](range(7)), 0.) - 21.0 - >>> - -To be sure, the code is `too` strict in the simplistic example above, and -with a future version of Cling it should be possible to lift some of these -restrictions without causing incorrect results. - - -`std::map` ----------- - -C++'s ``map`` is an associative container similar to Python's ``dict``, -albeit one that has stronger type constraints. -A ``map`` can be instantiated from a ``dict`` (and types can be inferred) or -from a collection of ``pair`` mappings. - - .. code-block:: python - - >>> from cppyy.gbl.std import map - >>> m = map[str, int](*("one", 1), ("two", 2))) # type explicit, from pairs - >>> print(m) - { "one" => 1, "two" => 2 } - >>> m = map({1: "one", 2: "two"}) # type implicit, from dict - >>> type(m) - at 0x12d068d60> - >>> print(m) - { 1 => "one", 2 => "two" } - >>> - - -`std::string` -------------- - -Python's `str` is a unicode type since Python3, whereas ``std::string`` is -single-byte char-based. -Having the two correctly interact therefore deserves it's own -:doc:`chapter `. - - -`std::tuple` ------------- - -C++ ``tuple`` is supported but it should be noted that its use, and in -particular instantiating (heavily overloaded) ``get<>`` functions for member -access is inefficient. -They are really only meant for use when you have to pass a ``tuple`` to C++ -code; and if returned from a C++ function, it is easier to simply unpack them. -In all other cases, prefer Python's builtin ``tuple``. -Example usage: - - .. code-block:: python - - >>> from cppyy.gbl.std import make_tuple, get - >>> t = make_tuple(1, '2', 5.) - >>> print(t) - object at 0x12033ee70> - >>> len(t) - 3 - >>> get[0](t) # access with templated std::get<> - 1 - >>> get[1](t) - b'2' - >>> get[2](t) - 5.0 - >>> a, b, c = t # unpack through iteration - >>> print(a, b, c) - 1 2 5.0 - >>> - - -.. rubric:: Footnotes - -.. [#f1] The meaning of "temporary" differs between Python and C++: in a statement such as ``func(std.vector[int]((1, 2, 3)))``, there is no temporary as far as Python is concerned, even as there clearly is in the case of a similar statement in C++. Thus that call will succeed even if ``func`` takes a non-const reference. diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/strings.rst b/bindings/pyroot/cppyy/cppyy/doc/source/strings.rst deleted file mode 100644 index a986a8e465d99..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/strings.rst +++ /dev/null @@ -1,115 +0,0 @@ -.. _strings: - - -Strings/Unicode -=============== - -Both Python and C++ have core types to represent text and these are expected -to be freely interchangeable. -``cppyy`` makes it easy to do just that for the most common cases, while -allowing customization where necessary to cover the full range of diverse use -cases (such as different codecs). -In addition to these core types, there is a range of other character types, -from ``const char*`` and ``std::wstring`` to ``bytes``, that see much less -use, but are also fully supported. - - -`std::string` -""""""""""""" - -The C++ core type ``std::string`` is considered the equivalent of Python's -``str``, even as purely implementation-wise, it is more akin to ``bytes``: -as a practical matter, a C++ programmer would use ``std::string`` where a -Python developer would use ``str`` (and vice versa), not ``bytes``. - -A Python ``str`` is unicode, however, whereas an ``std::string`` is character -based, thus conversions require encoding or decoding. -To allow for different encodings, ``cppyy`` defers implicit conversions -between the two types until forced, at which point it will default to seeing -``std::string`` as ASCII based and ``str`` to use the UTF-8 codec. -To support this, the bound ``std::string`` has been pythonized to allow it to -be a drop-in for a range of uses as appropriate within the local context. - -In particular, it is sometimes necessary (e.g. for function arguments that -take a non-const reference or a pointer to non-const ``std::string`` -variables), to use an actual ``std::string`` instance to allow in-place -modifications. -The pythonizations then allow their use where ``str`` is expected. -For example: - - .. code-block:: python - - >>> cppyy.cppexec("std::string gs;") - True - >>> cppyy.gbl.gs = "hello" - >>> type(cppyy.gbl.gs) # C++ std::string type - - >>> d = {"hello": 42} # dict filled with str - >>> d[cppyy.gbl.gs] # drop-in use of std::string -> str - 42 - >>> - -To handle codecs other than UTF-8, the ``std::string`` pythonization adds a -``decode`` method, with the same signature as the equivalent method of -``bytes``. -If it is known that a specific C++ function always returns an ``std::string`` -representing unicode with a codec other than UTF-8, it can in turn be -explicitly pythonized to do the conversion with that codec. - - -`std::string_view` -"""""""""""""""""" - -It is possible to construct a (char-based) ``std::string_view`` from a Python -``str``, but it requires the unicode object to be encoded and by default, -UTF-8 is chosen. -This will give the expected result if all characters in the ``str`` are from -the ASCII set, but otherwise it is recommend to encode on the Python side and -pass the resulting ``bytes`` object instead. - - -`std::wstring` -"""""""""""""" - -C++'s "wide" string, ``std::wstring``, is based on ``wchar_t``, a character -type that is not particularly portable as it can be 2 or 4 bytes in size, -depending on the platform. -cppyy supports ``std::wstring`` directly, using the ``wchar_t`` array -conversions provided by Python's C-API. - - -`const char*` -""""""""""""" - -The C representation of text, ``const char*``, is problematic for two -reasons: it does not express ownership; and its length is implicit, namely up -to the first occurrence of ``'\0'``. -The first can, up to an extent, be ameliorated: there are a range of cases -where ownership can be inferred. -In particular, if the C string is set from a Python ``str``, it is the latter -that owns the memory and the bound proxy of the former that in turn owns the -(unconverted) ``str`` instance. -However, if the ``const char*``'s memory is allocated in C/C++, memory -management is by necessity fully manual. -Length, on the other hand, can only be known in the case of a fixed array. -However even then, the more common case is to use the fixed array as a -buffer, with the actual string still only extending up to the ``'\0'`` char, -so that is assumed. -(C++'s ``std::string`` suffers from none of these issues and should always be -preferred when you have a choice.) - - -`char*` -""""""" - -The C representation of a character array, ``char*``, has all the problems of -``const char*``, but in addition is often used as "data array of 8-bit int". - - -`character types` -""""""""""""""""" - -cppyy directly supports the following character types, both as single -variables and in array form: ``char``, ``signed char``, ``unsigned char``, -``wchar_t``, ``char16_t``, and ``char32_t``. - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/testing.rst b/bindings/pyroot/cppyy/cppyy/doc/source/testing.rst deleted file mode 100644 index d485bba103348..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/testing.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. _testing: - - -Test suite -========== - -The cppyy tests live in the top-level cppyy package, can be run for -both CPython and PyPy, and exercises the full setup, including the backend. -Most tests are standalone and can be run independently, with a few exceptions -in the template tests (see file ``test_templates.py``). - -To run the tests, first install cppyy by any usual means, then clone the -cppyy repo, and enter the ``test`` directory:: - - $ git clone https://github.com/wlav/cppyy.git - $ cd cppyy/test - -Next, build the dictionaries, the manner of which depends on your platform. -On Linux or MacOS-X, run ``make``:: - - $ make all - -On Windows, run the dictionary building script:: - - $ python make_dict_win32.py all - -Next, make sure you have `pytest`_ installed, for example with ``pip``:: - - $ python -m pip install pytest - -and finally run the tests:: - - $ python -m pytest -sv - -On Linux and MacOS-X, all tests should succeed. -On MS Windows 32bit there are 4 failing tests, on 64bit there are 5 still -failing. - - -.. _`pytest`: https://docs.pytest.org/en/latest/ diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/toplevel.rst b/bindings/pyroot/cppyy/cppyy/doc/source/toplevel.rst deleted file mode 100644 index 740d419a1d9bf..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/toplevel.rst +++ /dev/null @@ -1,144 +0,0 @@ -.. _toplevel: - - -Top Level -========= - -cppyy provides a couple of helper functions at the module level that provide -(direct) access to the Cling interpreter (any C++ code is always accessed -through the global namespace ``cppyy.gbl``). -The documentation makes use of these helpers throughout, so they are listed -here first, but their documentation is more conveniently accessible through -the Python interpreter itself, using the ``help()`` function:: - - $ python - >>> import cppyy - >>> help(cppyy) - - -`Loading C++` -------------- - -C++ code can be loaded as text to be JITed, or be compiled ahead of time and -supplied in the form of a shared library. -In the latter case, C++ headers need to be loaded as well to declare -classes, functions, and variables to Cling. -Instead of headers, pre-compiled code can be used; in particular all of the -standard C++ headers and several system headers are pre-compiled at startup. -cppyy provides the following helpers to load C++ code: - -* ``cppdef``: direct access to the interpreter. - This function accepts C++ declarations as a string and JITs them (bindings - are not created until actual use). - The code is loaded into the global scope, thus any previously loaded code - is available from one ``cppdef`` call to the next, as are all standard - C++ headers that have been loaded through pre-compiled headers. - Example:: - - >>> cppyy.cppdef(r"""\ - ... void hello() { - ... std::cout << "Hello, World!" << std::endl; - ... }""") - True - >>> cppyy.gbl.hello() - Hello, World! - >>> - -* ``cppexec``: direct access to the interpreter. - This function accepts C++ statements as a string, JITs and executes them. - Just like ``cppdef``, execution is in the global scope and all previously - loaded code is available. - If the statements are declarations, the effect is the same as ``cppdef``, - but ``cppexec`` also accepts executable lines. - Example:: - - >>> cppyy.cppexec(r"""std::string hello = "Hello, World!";""") - True - >>> cppyy.cppexec("std::cout << hello << std::endl;") - Hello, World! - True - >>> - -* ``include``: load declarations into the interpreter. - This function accepts C++ declarations from a file, typically a header. - Files are located through include paths given to the Cling. - Example:: - - >>> cppyy.include("vector") # equivalent to "#include " - True - >>> - -* ``c_include``: load declarations into the interpreter. - This function accepts C++ declarations from a file, typically a header. - Name mangling is an important difference between C and C++ code. - The use of ``c_include`` instead of ``include`` prevents mangling. - -* ``load_library``: load compiled C++ into the interpreter. - This function takes the name of a shared library and loads it into current - process, exposing all external symbols to Cling. - Libraries are located through load paths given to Cling, either through the - "-L" compiler flag or the dynamic search path environment variable (system - dependent). - Any method that brings symbols into the process (including normal linking, - e.g. when embedding Python in a C++ application) is suitable to expose - symbols. - An alternative for ``load_library`` is for example ``ctypes.CDLL``, but - that function does not respect dynamic load paths on all platforms. - -If a compilation error occurs during JITing of C++ code in any of the above -helpers, a Python ``SyntaxError`` exception is raised. -If a compilation warning occurs, a Python warning is issued. - - -`Configuring Cling` -------------------- - -It is often convenient to add additional search paths for Cling to find -headers and libraries when loading a module (Python does not have standard -locations to place headers and libraries, but their locations can usually -be inferred from the location of the module, i.e. it's ``__file__`` -attribute). -cppyy provides the following two helpers: - -* ``add_include_path``: add additional paths for Cling to look for headers. - -* ``add_library_path``: add additional paths for Cling to look for libraries. - -Both functions accept either a string (a single path) or a list (for adding -multiple paths). -Paths are allowed to be relative, but absolute paths are recommended. - - -`C++ language` --------------- - -Some C++ compilation-time features have no Python equivalent. -Instead, convenience functions are provided: - -* ``sizeof``: takes a proxied C++ type or its name as a string and returns - the storage size (in units of ``char``). - -* ``typeid``: takes a proxied C++ type or its name as a string and returns - the the C++ runtime type information (RTTI). - -* ``nullptr``: C++ ``NULL``. - - -`Preprocessor` --------------- - -Preprocessor macro's (``#define``) are not available on the Python side, -because there is no type information available for them. -They are, however, often used for constant data (e.g. flags or numbers; note -that modern C++ recommends the use of ``const`` and ``constexpr`` instead). -Within limits, macro's representing constant data are accessible through the -``macro`` helper function. -Example:: - - >>> import cppyy - >>> cppyy.cppdef('#define HELLO "Hello, World!"') - True - >>> cppyy.macro("HELLO") - 'Hello, World!' - >>> - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/type_conversions.rst b/bindings/pyroot/cppyy/cppyy/doc/source/type_conversions.rst deleted file mode 100644 index 18d04b1feb58e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/type_conversions.rst +++ /dev/null @@ -1,89 +0,0 @@ -.. _type_conversions: - - -Type conversions -================ - -Most type conversions are done automatically, e.g. between Python ``str`` -and C++ ``std::string`` and ``const char*``, but low-level APIs exist to -perform explicit conversions. - -The C++ code used for the examples below can be found -:doc:`here `, and it is assumed that that code is -loaded at the start of any session. -Download it, save it under the name ``features.h``, and load it: - - .. code-block:: python - - >>> import cppyy - >>> cppyy.include('features.h') - >>> - - - .. _sec-auto-casting-label: - -`Auto-casting` --------------- - -Object pointer returns from functions provide the most derived class known -(i.e. exposed in header files) in the hierarchy of the object being returned. -This is important to preserve object identity as well as to make casting, -a pure C++ feature after all, superfluous. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Abstract, Concrete - >>> c = Concrete() - >>> Concrete.show_autocast.__doc__ - 'Abstract* Concrete::show_autocast()' - >>> d = c.show_autocast() - >>> type(d) - - >>> - -As a consequence, if your C++ classes should only be used through their -interfaces, then no bindings should be provided to the concrete classes -(e.g. by excluding them using a :ref:`selection file `). -Otherwise, more functionality will be available in Python than in C++. - -Sometimes, however, full control over a cast is needed. -For example, if the instance is bound by another tool or even a 3rd party, -hand-written, extension library. -Assuming the object supports the ``PyCapsule`` or ``CObject`` abstraction, -then a C++-style reinterpret_cast (i.e. without implicitly taking offsets -into account), can be done by taking and rebinding the address of an -object: - - .. code-block:: python - - >>> from cppyy import addressof, bind_object - >>> e = bind_object(addressof(d), Abstract) - >>> type(e) - - >>> - - -`Operators` ------------ - -If conversion operators are defined in the C++ class and a Python equivalent -exists (i.e. all builtin integer and floating point types, as well as -``bool``), then these will map onto those Python conversions. -Note that ``char*`` is mapped onto ``__str__``. -Example: - - .. code-block:: python - - >>> from cppyy.gbl import Concrete - >>> print(Concrete()) - Hello operator const char*! - >>> - -C++ code can overload conversion operators by providing methods in a class or -global functions. -Special care needs to be taken for the latter: first, make sure that they are -actually available in some header file. -Second, make sure that headers are loaded in the desired order. -I.e. that these global overloads are available before use. - diff --git a/bindings/pyroot/cppyy/cppyy/doc/source/utilities.rst b/bindings/pyroot/cppyy/cppyy/doc/source/utilities.rst deleted file mode 100644 index 77135000b3519..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/source/utilities.rst +++ /dev/null @@ -1,283 +0,0 @@ -.. _utilities: - -Utilities -========= - -The ``cppyy-backend`` package brings in the following utilities to help -with repackaging and redistribution: - - * cling-config: for compile time flags - * rootcling and genreflex: for dictionary generation - * cppyy-generator: part of the :doc:`CMake interface ` - - -Compiler/linker flags ---------------------- - -``cling-config`` is a small utility to provide access to the as-installed -configuration, such as compiler/linker flags and installation directories, of -other components. -Usage examples:: - - $ cling-config --help - Usage: cling-config [--cflags] [--cppflags] [--cmake] - $ cling-config --cmake - /usr/local/lib/python2.7/dist-packages/cppyy_backend/cmake - - -.. _dictionaries: - -Dictionaries ------------- - -Loading header files or code directly into ``cling`` is fine for interactive -work and smaller packages, but large scale applications benefit from -pre-compiling code, using the automatic class loader, and packaging -dependencies in so-called "dictionaries." - -A `dictionary` is a generated C++ source file containing references to the -header locations used when building (and any additional locations provided), -a set of forward declarations to reduce the need of loading header files, and -a few I/O helper functions. -The name "dictionary" is historic: before ``cling`` was used, it contained -the complete generated C++ reflection information, whereas now that is -derived at run-time from the header files. -It is still possible to fully embed header files rather than only storing -their names and search locations, to make the dictionary more self-contained. - -After generating the dictionary, it should be compiled into a shared library. -This provides additional dependency control: by linking it directly with any -further libraries needed, you can use standard mechanisms such as ``rpath`` -to locate those library dependencies. -Alternatively, you can add the additional libraries to load to the mapping -files of the class loader (see below). - -.. note:: - - The JIT needs to resolve linker symbols in order to call them through - generated wrappers. - Thus, any classes, functions, and data that will be used in Python need - to be exported. - This is the default behavior on Mac and Linux, but not on Windows. - On that platform, use ``__declspec(dllexport)`` to explicitly export the - classes and function you expect to call. - CMake has simple `support for exporting all`_ C++ symbols. - -In tandem with any dictionary, a pre-compiled module (.pcm) file will be -generated. -C++ modules are still on track for inclusion in the C++20 standard and most -modern C++ compilers, ``clang`` among them, already have implementations. -The benefits for cppyy include faster bindings generation, lower memory -footprint, and isolation from preprocessor macros and compiler flags. -The use of modules is transparent, other than the requirement that they -need to be co-located with the compiled dictionary shared library. - -Optionally, the dictionary generation process also produces a mapping file, -which lists the libraries needed to load C++ classes on request (for details, -see the section on the class loader below). - -Structurally, you could have a single dictionary for a project as a whole, -but more likely a large project will have a pre-existing functional -decomposition that can be followed, with a dictionary per functional unit. - - -Generation -^^^^^^^^^^ - -There are two interfaces onto the same underlying dictionary generator: -``rootcling`` and ``genreflex``. -The reason for having two is historic and they are not complete duplicates, -so one or the other may suit your preference better. -It is foreseen that both will be replaced once C++ modules become more -mainstream, as that will allow simplification and improved robustness. - -rootcling -""""""""" - -The first interface is called ``rootcling``:: - - $ rootcling - Usage: rootcling [-v][-v0-4] [-f] [out.cxx] [opts] file1.h[+][-][!] file2.h[+][-][!] ...[Linkdef.h] - For more extensive help type: /usr/local/lib/python2.7/dist-packages/cppyy_backend/bin/rootcling -h - -Rather than providing command line options, the main steering of -``rootcling`` behavior is done through -`#pragmas in a Linkdef.h `_ -file, with most pragmas dedicated to selecting/excluding (parts of) classes -and functions. -Additionally, the Linkdef.h file may contain preprocessor macros. - -The output consists of a dictionary file (to be compiled into a shared -library), a C++ module, and an optional mapping file, as described above. - -genreflex -""""""""" - -The second interface is called ``genreflex``:: - - $ genreflex - Generates dictionary sources and related ROOT pcm starting from an header. - Usage: genreflex headerfile.h [opts] [preproc. opts] - ... - -``genreflex`` has a richer command line interface than ``rootcling`` as can -be seen from the full help message. - -.. _selection-files: - -Selection/exclusion is driven through a `selection file`_ using an XML format -that allows both exact and pattern matching to namespace, class, enum, -function, and variable names. - -.. _`selection file`: https://linux.die.net/man/1/genreflex - - -Example -""""""" - -Consider the following basic example code, living in a header "MyClass.h": - - .. code-block:: C++ - - class MyClass { - public: - MyClass(int i) : fInt(i) {} - int get_int() { return fInt; } - - private: - int fInt; - }; - -and a corresponding "Linkdef.h" file, selecting only ``MyClass``:: - - #ifdef __ROOTCLING__ - #pragma link off all classes; - #pragma link off all functions; - #pragma link off all globals; - #pragma link off all typedef; - - #pragma link C++ class MyClass; - - #endif - -For more pragmas, see the `rootcling manual`_. -E.g., a commonly useful pragma is one that selects all C++ entities that are -declared in a specific header file:: - - #pragma link C++ defined_in "MyClass.h"; - -Next, use ``rootcling`` to generate the dictionary (here: -``MyClass_rflx.cxx``) and module files:: - - $ rootcling -f MyClass_rflx.cxx MyClass.h Linkdef.h - -Alternatively, define a "myclass_selection.xml" file:: - - - - - -serving the same purpose as the Linkdef.h file above (in fact, ``rootcling`` -accepts a "selection.xml" file in lieu of a "Linkdef.h"). -For more tags, see the `selection file`_ documentation. -Commonly used are ``namespace``, ``function``, ``enum``, or ``variable`` -instead of the ``class`` tag, and ``pattern`` instead of ``name`` with -wildcarding in the value string. - -Next, use ``genreflex`` to generate the dictionary (here: -``MyClass_rflx.cxx``) and module files:: - - $ genreflex MyClass.h --selection=myclass_selection.xml -o MyClass_rflx.cxx - -From here, compile and link the generated dictionary file with the project -and/or system specific options and libraries into a shared library, using -``cling-config`` for the relevant cppyy compiler/linker flags. -(For work on MS Windows, this `helper script`_ may be useful.) -To continue the example, assuming Linux:: - - $ g++ `cling-config --cppflags` -fPIC -O2 -shared MyClass_rflx.cxx -o MyClassDict.so - -Instead of loading the header text into ``cling``, you can now load the -dictionary: - -.. code-block:: python - - >>> import cppyy - >>> cppyy.load_reflection_info('MyClassDict') - >>> cppyy.gbl.MyClass(42) - - >>> print(_.get_int()) - 42 - >>> - -and use the selected C++ entities as if the header was loaded. - -The dictionary shared library can be relocated, as long as it can be found -by the dynamic loader (e.g. through ``LD_LIBRARY_PATH``) and the header file -is fully embedded or still accessible (e.g. through a path added to -``cppyy.add_include_path`` at run-time, or with ``-I`` to -``rootcling``/``genreflex`` during build time). -When relocating the shared library, move the .pcm with it. -Once support for C++ modules is fully fleshed out, access to the header file -will no longer be needed. - -.. _`rootcling manual`: https://root.cern.ch/root/html/guides/users-guide/AddingaClass.html#the-linkdef.h-file -.. _`helper script`: https://github.com/wlav/cppyy/blob/master/test/make_dict_win32.py - - -Class loader -^^^^^^^^^^^^ - -Explicitly loading dictionaries is fine if this is hidden under the hood of -a Python package and thus transparently done on ``import``. -Otherwise, the automatic class loader is more convenient, as it allows direct -use without having to manually find and load dictionaries (assuming these are -locatable by the dynamic loader). - -The class loader utilizes so-called rootmap files, which by convention should -live alongside the dictionary shared library (and C++ module file). -These are simple text files, which map C++ entities (such as classes) to the -dictionaries and other libraries that need to be loaded for their use. - -With ``genreflex``, the mapping file can be automatically created with -``--rootmap-lib=MyClassDict``, where "MyClassDict" is the name of the shared -library (without the extension) build from the dictionary file. -With ``rootcling``, create the same mapping file with -``-rmf MyClassDict.rootmap -rml MyClassDict``. -It is necessary to provide the final library name explicitly, since it is -only in the separate linking step where these names are fixed and those names -may not match the default choice. - -With the mapping file in place, the above example can be rerun without -explicit loading of the dictionary: - -.. code-block:: python - - >>> import cppyy - >>> from cppyy.gbl import MyClass - >>> MyClass(42).get_int() - 42 - >>> - - -.. _cppyy-generator: - -Bindings collection -------------------- - -``cppyy-generator`` is a clang-based utility program which takes a set of C++ -header files and generates a JSON output file describing the objects found in -them. -This output is intended to support more convenient access to a set of -cppyy-supported bindings:: - - $ cppyy-generator --help - usage: cppyy-generator [-h] [-v] [--flags FLAGS] [--libclang LIBCLANG] - output sources [sources ...] - ... - -This utility is mainly used as part of the -:doc:`CMake interface `. - - -.. _`support for exporting all`: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html diff --git a/bindings/pyroot/cppyy/cppyy/doc/tutorial/CppyyTutorial.ipynb b/bindings/pyroot/cppyy/cppyy/doc/tutorial/CppyyTutorial.ipynb deleted file mode 100644 index 1fe0fdba2246a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/tutorial/CppyyTutorial.ipynb +++ /dev/null @@ -1,496 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import print_function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cppyy Tutorial\n", - "\n", - "_(Modified from Enrico Guiraud's cppyy tutorial.)_\n", - "\n", - "This tutorial introduces the basic concepts for using cppyy, the automatic Python-C++ generator. To install cppyy on your system, simply run (this may take a while as it will pull in and compile a custom version of LLVM):\n", - "\n", - "```\n", - "$ pip install cppyy\n", - "```\n", - "\n", - "For further details on the installation, as well as the location of binary wheels, see:\n", - " http://cppyy.readthedocs.io/en/latest/installation.html\n", - "\n", - "To start, import module cppyy. All functionality, including using bound classes, always starts at this top-level." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import cppyy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are three layers to cppyy: at the top there are the module 'gbl' (the _global_ namespace), a range of helper functions, and a set of sub-modules (such as `py`) that serve specific purposes. Let's start with defining a little helper class in C++ using the helper function `cppdef`, to make the example more interesting:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "cppyy.cppdef(\"\"\"\n", - "class Integer1 {\n", - "public:\n", - " Integer1(int i) : m_data(i) {}\n", - " int m_data;\n", - "};\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now have a class 'Integer1'. Note that this class exists on the C++ side and has to follow C++ rules. For example, whereas in Python we can simply redefine a class, we can't do that in C++. Therefore, we will number the `Integer` classes as we go along, to be able to extend the example as we see fit.\n", - "\n", - "Python classes are constructed dynamically. It doesn't matter where or how they are defined, whether in a Python script, \"compiled\" into a C extension module, or otherwise. Cppyy takes advantage of this fact to generate bindings on-the-fly. This leads to performance advantages for large libraries with thousands of C++ classes; general distribution advantages since, other than the module cppyy itself, no code depends on any specific version of Python; and it enablers, through the Cling backend, interactive access to C++.\n", - "\n", - "To access our first class, find it in gbl, the global namespace:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "print(cppyy.gbl.Integer1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Namespaces have similarities to modules, so we could have imported the class as well.\n", - "\n", - "Bound C++ classes are first-class Python object. We can instantiate them, use normal Python introspection tools, call `help()`, they raise Python exceptions on failure, manage memory through Python's ref-counting and garbage collection, etc., etc. Furthermore, we can use them in conjunction with other C++ classes." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Variable has an 'm_data' data member? Yes!\n", - "Variable is an instance of int? No!\n", - "Variable is an instance of Integer1? Yes!\n" - ] - } - ], - "source": [ - "# for convenience, bring Integer1 into __main__\n", - "from cppyy.gbl import Integer1\n", - "\n", - "# create a C++ Integer1 object\n", - "i = Integer1(42)\n", - "\n", - "# use Python inspection\n", - "print(\"Variable has an 'm_data' data member?\", hasattr(i, 'm_data') and 'Yes!' or 'No!')\n", - "print(\"Variable is an instance of int?\", isinstance(i, int) and 'Yes!' or 'No!')\n", - "print(\"Variable is an instance of Integer1?\", isinstance(i, Integer1) and 'Yes!' or 'No!')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " object at 0x336ddb0>\n" - ] - } - ], - "source": [ - "# pull in the STL vector class\n", - "from cppyy.gbl.std import vector\n", - "\n", - "# create a vector of Integer1 objects; note how [] instantiates the template and () instantiates the class\n", - "v = vector[Integer1]()\n", - "\n", - "# populate it\n", - "v += [Integer1(j) for j in range(10)]\n", - "\n", - "# display our vector\n", - "print(v)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Hum, that doesn't look very pretty. However, since Integer1 is now a Python class we can decorate it, with a custom `__repr__` function (we'll punt on the `vector` and instead convert it to a Python `list` for printing)." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" - ] - } - ], - "source": [ - "# add a custom conversion for printing\n", - "Integer1.__repr__ = lambda self: repr(self.m_data)\n", - "\n", - "# now try again (note the conversion of the vector to a Python list)\n", - "print(list(v))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Pythonizations\n", - "\n", - "As we have seen so far, automatic bindings are simple and easy to use. However, even though they are first-class Python objects, they do have some rough C++ edges left. There is some _pythonization_ going on in the background: the vector, for example, played nice with `+=` and the list conversion. But for presenting your own classes to end-users, specific pythonizations are desirable. To have this work correctly with lazy binding, a callback-based API exists.\n", - "\n", - "Now, it's too late for Integer1, so let's create Integer2, which lives in a namespace and in addition has a conversion feature." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# create an Integer2 class, living in namespace Math\n", - "cppyy.cppdef(\"\"\"\n", - "namespace Math {\n", - " class Integer2 : public Integer1 {\n", - " public:\n", - " using Integer1::Integer1;\n", - " operator int() { return m_data; }\n", - " };\n", - "}\"\"\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# prepare a pythonizor\n", - "def pythonizor(klass, name):\n", - " # A pythonizor receives the freshly prepared bound C++ class, and a name stripped down to\n", - " # the namespace the pythonizor is applied. Also accessible are klass.__name__ (for the\n", - " # Python name) and klass.__cpp_name__ (for the C++ name)\n", - " if name == 'Integer2':\n", - " klass.__repr__ = lambda self: repr(self.m_data)\n", - "\n", - "# install the pythonizor as a callback on namespace 'Math' (default is the global namespace)\n", - "cppyy.py.add_pythonization(pythonizor, 'Math')" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", - "Converted Integer2 variable: 13\n" - ] - } - ], - "source": [ - "# when we next get the Integer2 class, it will have been decorated\n", - "Integer2 = cppyy.gbl.Math.Integer2 # first time a new namespace is used, it can not be imported from\n", - "v2 = vector[Integer2]()\n", - "v2 += [Integer2(j) for j in range(10)]\n", - "\n", - "# now test the effect of the pythonizor:\n", - "print(list(v2))\n", - "\n", - "# in addition, Integer2 has a conversion function, which is automatically recognized and pythonized\n", - "i2 = Integer2(13)\n", - "print(\"Converted Integer2 variable:\", int(i2))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "# continue the decoration on the C++ side, by adding an operator+ overload\n", - "cppyy.cppdef(\"\"\"\n", - "namespace Math {\n", - " Integer2 operator+(const Integer2& left, const Integer1& right) {\n", - " return left.m_data + right.m_data;\n", - " }\n", - "}\"\"\")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "55 55\n" - ] - } - ], - "source": [ - "# now use that fresh decoration (it will be located and bound on use):\n", - "k = i2 + i\n", - "print(k, i2.m_data + i.m_data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Class Hierarchies\n", - "\n", - "Both Python and C++ support multiple programming paradigms, making it relatively straightforward to map language features (e.g. class inheritance, free functions, etc.); many other features can be cleanly hidden, merely because the syntax is very similar or otherwise natural (e.g. overloading, abstract classes, static data members, etc.); and yet others map gracefully because their semantic intent is expressed clearly in the syntax (e.g. smart pointers, STL, etc.).\n", - "\n", - "The following presents a range of C++ features that map naturally, and exercises them in Python." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "# create some animals to play with\n", - "cppyy.cppdef(\"\"\"\n", - "namespace Zoo {\n", - "\n", - " enum EAnimal { eLion, eMouse };\n", - " \n", - " class Animal {\n", - " public:\n", - " virtual ~Animal() {}\n", - " virtual std::string make_sound() = 0;\n", - " };\n", - " \n", - " class Lion : public Animal {\n", - " public:\n", - " virtual std::string make_sound() { return s_lion_sound; }\n", - " static std::string s_lion_sound;\n", - " };\n", - " std::string Lion::s_lion_sound = \"growl!\";\n", - "\n", - " class Mouse : public Animal {\n", - " public:\n", - " virtual std::string make_sound() { return \"peep!\"; }\n", - " };\n", - "\n", - " Animal* release_animal(EAnimal animal) {\n", - " if (animal == eLion) return new Lion{};\n", - " if (animal == eMouse) return new Mouse{};\n", - " return nullptr;\n", - " }\n", - "\n", - " std::string identify_animal(Lion*) {\n", - " return \"the animal is a lion\";\n", - " }\n", - "\n", - " std::string identify_animal(Mouse*) {\n", - " return \"the animal is a mouse\";\n", - " }\n", - "\n", - "}\n", - "\"\"\")\n", - "\n", - "# pull in the Zoo (after which we can import from it)\n", - "Zoo = cppyy.gbl.Zoo\n", - "\n", - "# pythonize the animal release function to take ownership on return\n", - "Zoo.release_animal.__creates__ = True" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Failed: none of the 2 overloaded methods succeeded. Full details:\n", - " cannot instantiate abstract class 'Zoo::Animal'\n", - " cannot instantiate abstract class 'Zoo::Animal' \n", - "\n", - "A Lion is an Animal? Yes! \n", - "\n", - "Type of mouse: \n", - "Type of lion: \n", - "\n", - "Does Python own the 'lion'? Yes!\n", - "Does Python own the 'mouse'? Yes! \n", - "\n", - "The mouse says: peep!\n", - "The lion says: growl! \n", - "\n", - "The lion says: mooh! \n", - "\n", - "Identification of 'mouse': the animal is a mouse\n", - "Identification of 'lion': the animal is a lion\n" - ] - } - ], - "source": [ - "# abstract base classes can not be instantiated:\n", - "try:\n", - " animal = Zoo.Animal()\n", - "except TypeError as e:\n", - " print('Failed:', e, '\\n')\n", - "\n", - "# derived classes can be inspected in the same class hierarchy on the Python side\n", - "print('A Lion is an Animal?', issubclass(Zoo.Lion, Zoo.Animal) and 'Yes!' or 'No!', '\\n')\n", - "\n", - "# returned pointer types are auto-casted to the lowest known derived type:\n", - "mouse = Zoo.release_animal(Zoo.eMouse)\n", - "print('Type of mouse:', type(mouse))\n", - "lion = Zoo.release_animal(Zoo.eLion)\n", - "print('Type of lion:', type(lion), '\\n')\n", - "\n", - "# as pythonized, the ownership of the return value from release_animal is Python's\n", - "print(\"Does Python own the 'lion'?\", lion.__python_owns__ and 'Yes!' or 'No!')\n", - "print(\"Does Python own the 'mouse'?\", mouse.__python_owns__ and 'Yes!' or 'No!', '\\n')\n", - "\n", - "# virtual functions work as expected:\n", - "print('The mouse says:', mouse.make_sound())\n", - "print('The lion says:', lion.make_sound(), '\\n')\n", - "\n", - "# now change what the lion says through its static (class) variable\n", - "Zoo.Lion.s_lion_sound = \"mooh!\"\n", - "print('The lion says:', lion.make_sound(), '\\n')\n", - "\n", - "# overloads are combined into a single function on the Python side and resolved dynamically\n", - "print(\"Identification of \\'mouse\\':\", Zoo.identify_animal(mouse))\n", - "print(\"Identification of \\'lion\\':\", Zoo.identify_animal(lion))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Modern C++\n", - "\n", - "As C++ matures, more and more semantic intent (such as object ownership) is expressed in the syntax. This not for the benefit of bindings generators, but for the poor programmer having to read the code. Still, a bindings generator benefits greatly from this increased expression." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "cppyy.cppdef(\"\"\"\n", - "namespace Zoo {\n", - " std::shared_ptr free_lion{new Lion{}};\n", - "\n", - " std::string identify_animal_smart(std::shared_ptr& smart) {\n", - " return \"the animal is a lion\";\n", - " }\n", - "}\n", - "\"\"\")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Type of the 'free_lion' global: Lion\n", - "Type of the 'free_lion' smart ptr: shared_ptr\n", - "Dumb passing: the animal is a lion\n", - "Smart passing: the animal is a lion\n" - ] - } - ], - "source": [ - "# shared pointers are presented transparently as the wrapped type\n", - "print(\"Type of the 'free_lion' global:\", type(Zoo.free_lion).__name__)\n", - "\n", - "# if need be, the smart pointer is accessible with a helper\n", - "smart_lion = Zoo.free_lion.__smartptr__()\n", - "print(\"Type of the 'free_lion' smart ptr:\", type(smart_lion).__name__)\n", - "\n", - "# pass through functions that expect a naked pointer or smart pointer\n", - "print(\"Dumb passing: \", Zoo.identify_animal(Zoo.free_lion))\n", - "print(\"Smart passing:\", Zoo.identify_animal_smart(Zoo.free_lion))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.16" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/bindings/pyroot/cppyy/cppyy/doc/tutorial/GSLPythonizationTutorial.ipynb b/bindings/pyroot/cppyy/cppyy/doc/tutorial/GSLPythonizationTutorial.ipynb deleted file mode 100644 index 58cf576cb186e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/doc/tutorial/GSLPythonizationTutorial.ipynb +++ /dev/null @@ -1,202 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import print_function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## GSL Pythonization Tutorial\n", - "\n", - "_(Hat tip to Neil Dhir for the idea.)_\n", - "\n", - "This tutorial introduces pythonizations and how they can be used to solve low-level problems.\n", - "\n", - "The setup: imagine you want to use numpy, but are given a C or C++ library that is based on the GNU Scientific Library (GSL). How do make the two of them play nice together?\n", - "\n", - "GSL is written in C, but is very structured: it has consistent naming conventions, provides allocators/deallocators for its main structs, and has clear ownership rules. Because of this structure, it is possible to write pythonizations based on reflection information that end up being very simple and therefore easy to maintain." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import cppyy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For simplicity, we will use `gsl_blas_dgemm` as a stand-in for the \"C/C++ library based on GSL.\" To make our life easier, we will wrap up the bindings to GSL and GSLBLAS into a single reflection dictionary. This is overkill for simple projects, but if we want access to all of GSL (as opposed to GSL and GSLBLAS separately, say), this approach is fine." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# first, pull in all headers from the GSL installation directory (/usr/include on my system).\n", - "import glob, os\n", - "GSL_HOME = '/usr/include'\n", - "gsl_headers = [os.path.relpath(x, GSL_HOME) for x in glob.glob(GSL_HOME+'/gsl/*.h')]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we write a selection file that picks up all interesting bits from GSL. This file can be kept simple because of the proper naming conventions (it could have been simpler still if GSL were a C++ library, living in a single namespace). We then run the genreflex command to generate the dictionary file and compile it, linking in GSL and GSLBLAS. Finally, we're ready to load the dictionary reflection file into cppyy." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting gsl_selection.xml\n" - ] - } - ], - "source": [ - "%%file gsl_selection.xml\n", - "\n", - " \n", - " \n", - " \n", - " \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "genreflex done\n" - ] - } - ], - "source": [ - "# conventional name for generated output\n", - "rfldct = 'GSLDict'\n", - "\n", - "if not os.path.exists('%s_rflx.cpp' % rfldct):\n", - " import subprocess, sys\n", - "\n", - " # generate the reflection dictionary\n", - " try:\n", - " subprocess.check_output(\n", - " ['genreflex', # utility installed by pip when installing cppyy\n", - " '-s', 'gsl_selection.xml', # selection file (see above)\n", - " '-o', '%s_rflx.cpp'%rfldct, # intermediate output file\n", - " '-I'+GSL_HOME]+ # include search path for GSL headers\n", - " gsl_headers) # headers themselves\n", - " except subprocess.CalledProcessError as e:\n", - " print(\"genreflex failed (%d):\" % e.returncode, e.output)\n", - " else:\n", - " print(\"genreflex done\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cling-config done\n", - "compilation done\n" - ] - } - ], - "source": [ - "if not os.path.exists('%s.so' % rfldct):\n", - " # get command line arguments for compiler from cling\n", - " try:\n", - " clingflags = subprocess.check_output(\n", - " ['cling-config', # utility installed by pip when installing cppyy\n", - " '--cppflags'])\n", - " except subprocess.CalledProcessError as e:\n", - " print('cling-config failed (%d):' % e.returncode, e.output)\n", - " raise\n", - " else:\n", - " print('cling-config done')\n", - "\n", - " # compile generated file\n", - " try:\n", - " subprocess.check_output(\n", - " ['g++', # C++ compiler\n", - " '-fPIC', # require position independent code\n", - " '-shared', # generate shared library\n", - " '-o', '%s.so'%rfldct, # output file\n", - " '-I'+GSL_HOME, # include search path for GSL headers\n", - " '%s_rflx.cpp'%rfldct]+ # intermediate file to compile\n", - " clingflags.split()+ # extra flags provided by cling\n", - " ['-lgsl', '-lgslcblas']) # link in GSL and GSLBLAS\n", - " except subprocess.CalledProcessError as e:\n", - " print('compilation failed (%d):' % e.returncode, e.output)\n", - " else:\n", - " print('compilation done')" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# load the generated dictionary\n", - "cppyy.load_reflection_info(rfldct)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.14" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/bindings/pyroot/cppyy/cppyy/etc/valgrind-cppyy-cling.supp b/bindings/pyroot/cppyy/cppyy/etc/valgrind-cppyy-cling.supp deleted file mode 100644 index 851bdec634c10..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/etc/valgrind-cppyy-cling.supp +++ /dev/null @@ -1,758 +0,0 @@ -# -# Cling/LLVM code generation: since cppyy is a python module, there is no -# proper control over offload ands since functions may be used during atexit() -# calls, we'll just leak all JITed code rather than risk spurious crashes in -# hard-to-reproduce cases. -# -{ - lazily JITed callfuncs - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEm - fun:_ZN5clang7CodeGen13CodeGenModule23GetOrCreateLLVMFunctionEN4llvm9StringRefEPNS2_4TypeENS_10GlobalDeclEbbbNS2_13AttributeListENS0_15ForDefinition_tE -} - -{ - lazily JITed template instantiations - match-leak-kinds: possible - fun:_Znwm - ... - fun:__ZN5clang24TemplateDeclInstantiator18VisitCXXMethodDeclEPNS_13CXXMethodDeclEPNS_21TemplateParameterListEb -} - -{ - lazily JITed template instantiations - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang4Sema38InstantiateClassTemplateSpecializationENS_14SourceLocationEPNS_31ClassTemplateSpecializationDeclENS_26TemplateSpecializationKindEb -} - -{ - lazily JITed global operators - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEmj - fun:_ZN4llvm14BinaryOperator6CreateENS_11Instruction9BinaryOpsEPNS_5ValueES4_RKNS_5TwineEPS1_ -} - -{ - lazily JITed casts - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEmj - fun:_ZN4llvm8CastInst6CreateENS_11Instruction7CastOpsEPNS_5ValueEPNS_4TypeERKNS_5TwineEPS1_ -} - -{ - lazily JITed casts - Memcheck:Leak - match-leak-kinds: definite - fun:_Znam - ... - fun:_ZN20TClingDataMemberInfo6OffsetEv -} - -{ - cling initialization - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5cling11InterpreterC1EiPKPKcS2_bb -} - -{ - clang module symbol table - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:allocate - ... - fun:_ZN5clang7CodeGen13CodeGenModule7ReleaseEv - fun:_ZN5cling17IncrementalParser18codeGenTransactionEPNS_11TransactionE - fun:_ZN5cling17IncrementalParser17commitTransactionEN4llvm14PointerIntPairIPNS_11TransactionELj2ENS0_12EParseResultENS1_21PointerLikeTypeTraitsIS4_EEEE -} - -{ - CodeGen - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZL21EmitMemberInitializerRN5clang7CodeGen15CodeGenFunctionEPKNS_13CXXRecordDeclEPNS_18CXXCtorInitializerEPKNS_18CXXConstructorDeclERNS0_15FunctionArgListE -} - -{ - CodeGen cleanup - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZL11EmitCleanupRN5clang7CodeGen15CodeGenFunctionEPNS0_12EHScopeStack7CleanupENS4_5FlagsEPN4llvm5ValueE -} - -{ - CodeGen globals - Memcheck:Leak - match-leak-kinds: possible - fun:_Zn* - ... - fun:_ZN5clang7CodeGen13CodeGenModule20EmitGlobalDefinitionENS_10GlobalDeclEPN4llvm11GlobalValueE -} - -{ - CodeGen cleanup - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEmj - ... - fun:_ZN5clang7CodeGen15CodeGenFunction15PopCleanupBlockEb -} - -{ - cling Module use - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN4llvm6ModuleC1ENS_9StringRefERNS_11LLVMContextE -} - -{ - cling Module use - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang17CodeGeneratorImpl11StartModuleERKSsRN4llvm11LLVMContextERKNS_14CodeGenOptionsE -} - -{ - cling macro - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang19PreprocessorOptions11addMacroDefEN4llvm9StringRefE -} - -{ - llvm target - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN4llvm21RegisterTargetMachineINS_16X86TargetMachineEE9AllocatorERKNS_6TargetENS_9StringRefES6_S6_RKNS_13TargetOptionsENS_5Reloc5ModelENS_9CodeModel5ModelENS_10CodeGenOpt5LevelE -} - -{ - CodeGent SetFunctionAttributes - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang7CodeGen13CodeGenModule21SetFunctionAttributesENS_10GlobalDeclEPN4llvm8FunctionEbb -} - -{ - clang using statement - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZNK5clang11DeclContext16using_directivesEv -} - -{ - clang 'make decl visible' - Memcheck:Leak - match-leak-kinds: possible - ... - fun:_ZN5clang11DeclContext28makeDeclVisibleInContextImplEPNS_9NamedDeclEb -} - -{ - clang visibility - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZL24getExplicitVisibilityAuxPKN5clang9NamedDeclENS0_22ExplicitVisibilityKindEb -} - -{ - clang result of lookup declaration name - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang11DeclContext6lookupENS_15DeclarationNameE -} - -{ - clang result of lookup declaration name - Memcheck:Leak - match-leak-kinds: possible - fun:malloc - ... - fun:_ZN5clang11DeclContext6lookupENS_15DeclarationNameE -} - -{ - clang ASTReader - Memcheck:Leak - match-leak-kinds: possible - fun:malloc - ... - fun:_ZN5clang9ASTReader* -} - -{ - clang ASTReader - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang9ASTReader* -} - -{ - clang ASTReader - Memcheck:Leak - match-leak-kinds: possible - fun:realloc - ... - fun:_ZN5clang9ASTReader* -} - -{ - Sema ActOnEndOfTranslation - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang4Sema25ActOnEndOfTranslationUnitEv -} - -{ - Function CodeGen - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang7CodeGen15CodeGenFunction12GenerateCodeENS_10GlobalDeclEPN4llvm8FunctionERKNS0_14CGFunctionInfoE -} - -{ - clang CodeGenFunction Emit* - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang7CodeGen15CodeGenFunction*Emit* -} - -{ - clang CodeGenFunction global state - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEmj - ... - fun:_ZL21emitPointerArithmeticRN5clang7CodeGen15CodeGenFunctionERKN12_GLOBAL__N_19BinOpInfoEb -} - -{ - clang CodeGenModule Emit* - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang7CodeGen13CodeGenModule*Emit* -} - -{ - clang CodeGenModule Emit* - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang7CodeGen13CodeGenModule*emit* -} - -{ - CodeGen VTables - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN4llvm4UsernwEmj - ... - fun:_ZN5clang7CodeGen14CodeGenVTables17* -} - -{ - clang AddIncludePath - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang19HeaderSearchOptions7AddPathEN4llvm9StringRefENS_8frontend15IncludeDirGroupEbb -} - -{ - clang FileManager - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang12HeaderSearch10LookupFileEN4llvm9StringRefENS_14SourceLocationEbPKNS_15DirectoryLookupERS6_NS1_8ArrayRefISt4pairIPKNS_9FileEntryEPKNS_14DirectoryEntryEEEEPNS1_15SmallVectorImplIcEESK_PNS_9ModuleMap11KnownHeaderEbbb -} - -{ - clang getFile - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang11FileManager7getFileEN4llvm9StringRefEbb -} - -{ - clang ScalarExprEmitter - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN12_GLOBAL__N_117ScalarExprEmitter20EmitScalarConversionEPN4llvm5ValueEN5clang8QualTypeES5_ -} - -{ - llvm pass manager - Memcheck:Leak - match-leak-kinds: possible - ... - fun:_ZN4llvm13FPPassManager13runOnFunctionERNS_8FunctionE -} - - -{ - llvm PassManager initialization - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN4llvm13FPPassManager16doInitializationERNS_6ModuleE -} - -{ - llvm SubTarget initialization - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN4llvm15MCSubtargetInfo19InitMCSubtargetInfoENS_9StringRefES1_S1_NS_8ArrayRefINS_18SubtargetFeatureKVEEES4_PKNS_15SubtargetInfoKVEPKNS_19MCWriteProcResEntryEPKNS_19MCWriteLatencyEntryEPKNS_18MCReadAdvanceEntryEPKNS_10InstrStageEPKjSL_ -} - -{ - cling dynamic library manager - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5cling21DynamicLibraryManager11loadLibraryERKSsb -} - -{ - llvm compile layer - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN4llvm18ObjectMemoryBufferC1EONS_15SmallVectorImplIcEE - fun:_ZNK4llvm14SimpleCompilerclERNS_6ModuleE - fun:_ZNSt17_Function_handlerIFN4llvm6object12OwningBinaryINS1_10ObjectFileEEERNS0_6ModuleEENS0_14SimpleCompilerEE9_M_invokeERKSt9_Any_dataS6_ - fun:_ZNKSt8functionIFN4llvm6object12OwningBinaryINS1_10ObjectFileEEERNS0_6ModuleEEEclES6_ - fun:_ZN4llvm14IRCompileLayerINS_18ObjectLinkingLayerIN5cling14IncrementalJIT19NotifyObjectLoadedTEEEE12addModuleSetISt6vectorIPNS_6ModuleESaISA_EEEESt14_List_iteratorINS_22ObjectLinkingLayerBase15LinkedObjectSetEET_St10unique_ptrINS_19RTDyldMemoryManagerESt14default_deleteISJ_EE -} - -{ - InputValidator string - Memcheck:Leak - match-leak-kinds: possible - ... - fun:_ZNSs15_M_replace_safeEmmPKcm - fun:_ZN5cling14InputValidator5resetEv -} - -{ - cling pointer checker on uninitialized memory (ROOT-8144) - Memcheck:Param - write(buf) - fun:__write_nocancel - ... - fun:cling_runtime_internal_throwIfInvalidPointer -} - - -# -# ROOT/meta wrappers to JITed Cling code, same as above: kept around so as to -# not have to deal with complicated shutdown sequences. -# -{ - typedef string - Memcheck:Leak - match-leak-kinds: definite - fun:_Znam - ... - fun:_ZN10TClassEdit14ResolveTypedefB5cxx11EPKcb -} - -{ - TEnums leak - Memcheck:Leak - fun:_Znwm - fun:_ZN8TStorage11ObjectAllocEm - fun:_ZN7TObjectnwEm - fun:_ZN5TROOT14GetListOfEnumsEv -} - -{ - list of enums - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - fun:_ZN8TStorage11ObjectAllocEm - fun:_ZN7TObjectnwEm - fun:_ZN12TListOfEnumsC1EP6TClass -} - -{ - list of enums - Memcheck:Leak - match-leak-kinds: definite - fun:_Znwm - fun:_ZN8TStorage11ObjectAllocEm - fun:operator new - fun:_ZN6TClass14GetListOfEnumsEb -} - -{ - TObject::TObject() uses uninitialized value when allocated on the stack - Memcheck:Cond - fun:_ZN7TObjectC1Ev -} - -{ - TObject::TObject() uses uninitialized value when allocated on the stack - Memcheck:Cond - fun:TObject -} - -{ - dictgen fwd decls - Memcheck:Leak - match-leak-kinds: possible - fun:_Znwm - ... - fun:_ZN5clang6Parser30ParseCXXClassMemberDeclarationENS_15AccessSpecifierEPNS_13AttributeListERKNS0_18ParsedTemplateInfoEPNS_21ParsingDeclRAIIObjectE -} - -{ - exception translation - Memcheck:Leak - match-leak-kinds: possible - fun:_Znam - ... - fun:_ZL26HandleInterpreterExceptionPN5cling13MetaProcessorEPKcRNS_11Interpreter17CompilationResultEPNS_5ValueE -} - -{ - Ignore deficient in dlopen and/or pthread and/or valgrind. See for example https://bugs.kde.org/show_bug.cgi?id=358980. - Memcheck:Leak - match-leak-kinds: definite - ... - fun:_dl_signal_error -} - - -# -# Python Arena magic checkers -# -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Value4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) - Memcheck:Value8 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 8 - Memcheck:Value8 - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:PyObject_Free -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:PyObject_Realloc -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 8 - Memcheck:Value8 - fun:PyObject_Realloc -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:PyObject_Realloc -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - ... - fun:_PyObject_GC_Resize -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:realloc - ... - fun:_PyObject_GC_Resize -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - ... - fun:_PyObject_GC_NewVar -} - -# -# Leaks (including possible leaks) -# Hmmm, I wonder if this masks some real leaks. I think it does. -# Will need to fix that. -# - -{ - Suppress leaking the GIL. Happens once per process, see comment in ceval.c. - Memcheck:Leak - fun:malloc - fun:PyThread_allocate_lock - fun:PyEval_InitThreads -} - -{ - Suppress leaking the GIL after a fork. - Memcheck:Leak - fun:malloc - fun:PyThread_allocate_lock - fun:PyEval_ReInitThreads -} - -{ - Suppress leaking the autoTLSkey. This looks like it shouldn't leak though. - Memcheck:Leak - fun:malloc - fun:PyThread_create_key - fun:_PyGILState_Init - fun:Py_InitializeEx - fun:Py_Main -} - -{ - Hmmm, is this a real leak or like the GIL? - Memcheck:Leak - fun:malloc - fun:PyThread_ReInitTLS -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:realloc - fun:_PyObject_GC_Resize - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_New - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_NewVar - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -# -# Non-python specific leaks -# - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:calloc - fun:allocate_dtv - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:memalign - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -### -### All the suppressions below are for errors that occur within libraries -### that Python uses. The problems to not appear to be related to Python's -### use of the libraries. -### - -{ - Generic ubuntu ld problems - Memcheck:Addr8 - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so -} - -{ - Generic gentoo ld problems - Memcheck:Cond - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so -} - -{ - DBM problems, see test_dbm - Memcheck:Param - write(buf) - fun:write - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_close -} - -{ - DBM problems, see test_dbm - Memcheck:Value8 - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - GDBM problems, see test_gdbm - Memcheck:Param - write(buf) - fun:write - fun:gdbm_open - -} - -{ - ZLIB problems, see test_gzip - Memcheck:Cond - obj:/lib/libz.so.1.2.3 - obj:/lib/libz.so.1.2.3 - fun:deflate -} - -{ - Avoid problems w/readline doing a putenv and leaking on exit - Memcheck:Leak - fun:malloc - fun:xmalloc - fun:sh_set_lines_and_columns - fun:_rl_get_screen_size - fun:_rl_init_terminal_io - obj:/lib/libreadline.so.4.3 - fun:rl_initialize -} diff --git a/bindings/pyroot/cppyy/cppyy/installer/cppyy_monkey_patch.py b/bindings/pyroot/cppyy/cppyy/installer/cppyy_monkey_patch.py deleted file mode 100644 index 697eff678b7de..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/installer/cppyy_monkey_patch.py +++ /dev/null @@ -1,39 +0,0 @@ -# monkey patch to be able to select a specific backend based on PyPy's version, -# which is not possible in the pyproject.toml file as there is currently no -# marker for it (this may change, after which this file can be removed) - -try: - # _BACKEND is the primary, __legacy__ the backwards compatible backend - from setuptools.build_meta import _BACKEND - main = _BACKEND -except (NameError, ImportError): - # fallback as the name __legacy__ is actually documented (and part of __all__) - main = __legacy__ - -# the following ensures proper build/installation order, after which the normal -# install through setup.py picks up their wheels from the cache (TODO: note the -# duplication here with setup.py; find a better way) -_get_requires_for_build_wheel = main.get_requires_for_build_wheel -def get_requires_for_build_wheel(*args, **kwds): - try: - import __pypy__, sys - version = sys.pypy_version_info - requirements = ['cppyy-cling==6.32.8'] - if version[0] == 5: - if version[1] <= 9: - requirements = ['cppyy-cling<6.12'] - elif version[1] <= 10: - requirements = ['cppyy-cling<=6.15'] - elif version[0] == 6: - if version[1] <= 0: - requirements = ['cppyy-cling<=6.15'] - elif version[0] == 7: - if version[1] <= 3 and version[2] <= 3: - requirements = ['cppyy-cling<=6.18.2.3'] - except ImportError: - # CPython - requirements = ['cppyy-backend==1.15.3', 'cppyy-cling==6.32.8'] - - return requirements + _get_requires_for_build_wheel(*args, **kwds) - -main.get_requires_for_build_wheel = get_requires_for_build_wheel diff --git a/bindings/pyroot/cppyy/cppyy/pyproject.toml b/bindings/pyroot/cppyy/cppyy/pyproject.toml deleted file mode 100644 index 91c1695fbc051..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/pyproject.toml +++ /dev/null @@ -1,4 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -backend-path = ["installer"] -build-backend = "cppyy_monkey_patch:main" diff --git a/bindings/pyroot/cppyy/cppyy/python/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy/python/CMakeLists.txt deleted file mode 100644 index cba6aeaed1298..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -ROOT_PYTHON_PACKAGE(cppyy - SOURCES - cppyy/_stdcpp_fix.py - cppyy/__init__.py - cppyy/_cpython_cppyy.py - cppyy/_pypy_cppyy.py - cppyy/_pythonization.py - cppyy/_typemap.py - cppyy/_version.py - cppyy/interactive.py - cppyy/ll.py - cppyy/numba_ext.py - cppyy/reflex.py - cppyy/types.py -) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py deleted file mode 100644 index 88154b57e5ab4..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ /dev/null @@ -1,449 +0,0 @@ -"""Dynamic C++ bindings generator. - -This module provides dynamic bindings to C++ through Cling, the LLVM-based C++ -interpreter, allowing interactive mixing of Python and C++. Example: - - >>> import cppyy - >>> cppyy.cppdef(\"\"\" - ... class MyClass { - ... public: - ... MyClass(int i) : m_data(i) {} - ... int m_data; - ... };\"\"\") - True - >>> from cppyy.gbl import MyClass - >>> m = MyClass(42) - >>> cppyy.cppdef(\"\"\" - ... void say_hello(MyClass* m) { - ... std::cout << "Hello, the number is: " << m->m_data << std::endl; - ... }\"\"\") - True - >>> MyClass.say_hello = cppyy.gbl.say_hello - >>> m.say_hello() - Hello, the number is: 42 - >>> m.m_data = 13 - >>> m.say_hello() - Hello, the number is: 13 - >>> - -For full documentation, see: - https://cppyy.readthedocs.io/ - -""" - -__author__ = 'Wim Lavrijsen ' - -__all__ = [ - 'cppdef', # declare C++ source to Cling - 'cppexec', # execute a C++ statement - 'macro', # attempt to evaluate a cpp macro - 'include', # load and jit a header file - 'c_include', # load and jit a C header file - 'load_library', # load a shared library - 'nullptr', # unique pointer representing NULL - 'sizeof', # size of a C++ type - 'typeid', # typeid of a C++ type - 'multi', # helper for multiple inheritance - 'add_include_path', # add a path to search for headers - 'add_library_path', # add a path to search for headers - 'add_autoload_map', # explicitly include an autoload map - 'set_debug', # enable/disable debug output - ] - -import ctypes -import os -import sys -import sysconfig -import warnings - -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - -from . import _typemap -from ._version import __version__ - -# import separately instead of in the above try/except block for easier to -# understand tracebacks -if ispypy: - from ._pypy_cppyy import * -else: - from ._cpython_cppyy import * - - -#- allow importing from gbl -------------------------------------------------- -sys.modules['cppyy.gbl'] = gbl -sys.modules['cppyy.gbl.std'] = gbl.std - - -#- external typemap ---------------------------------------------------------- -_typemap.initialize(_backend) # also creates (u)int8_t mapper - -try: - gbl.std.int8_t = gbl.int8_t # ensures same _integer_ type - gbl.std.uint8_t = gbl.uint8_t -except (AttributeError, TypeError): - pass - - -#- pythonization factories --------------------------------------------------- -from . import _pythonization as py -py._set_backend(_backend) - -def _standard_pythonizations(pyclass, name): - # pythonization of tuple; TODO: placed here for convenience, but a custom case - # for tuples on each platform can be made much more performant ... - if name.find('tuple<', 0, 6) == 0: - import cppyy - pyclass._tuple_len = cppyy.gbl.std.tuple_size(pyclass).value - def tuple_len(self): - return self.__class__._tuple_len - pyclass.__len__ = tuple_len - def tuple_getitem(self, idx, get=cppyy.gbl.std.get): - if idx < self.__class__._tuple_len: - res = get[idx](self) - try: - res.__life_line = self - except Exception: - pass - return res - raise IndexError(idx) - pyclass.__getitem__ = tuple_getitem - - # pythonization of std::string; placed here because it's simpler to write the - # custom "npos" object (to allow easy result checking of find/rfind) in Python - elif pyclass.__cpp_name__ == "std::string": - class NPOS(0x3000000 <= sys.hexversion and int or long): - def __eq__(self, other): - return other == -1 or int(self) == other - def __ne__(self, other): - return other != -1 and int(self) != other - del pyclass.__class__.npos # drop b/c is const data - pyclass.npos = NPOS(pyclass.npos) - - return True - -if not ispypy: - py.add_pythonization(_standard_pythonizations, "std") -# TODO: PyPy still has the old-style pythonizations, which require the full -# class name (not possible for std::tuple ...) - -# std::make_shared/unique create needless templates: rely on Python's introspection -# instead. This also allows Python derived classes to be handled correctly. -class py_make_smartptr(object): - __slots__ = ['cls', 'ptrcls'] - def __init__(self, cls, ptrcls): - self.cls = cls - self.ptrcls = ptrcls - def __call__(self, *args): - if len(args) == 1 and type(args[0]) == self.cls: - obj = args[0] - else: - obj = self.cls(*args) - return self.ptrcls[self.cls](obj) # C++ takes ownership - -# The introspection trick above only works for class types the smart pointer can -# adopt from a Python-owned object. An arithmetic element type (int, double, ...) -# has no such object, so it needs a heap-allocated scalar. We test for *arithmetic* -# rather than *fundamental* types because std::is_fundamental also matches 'void' -# and 'std::nullptr_t', neither of which can back a 'new T{}' scalar. -# -# _py_arithmetic maps the Python numeric builtins onto a C++ spelling by convention -# (Python's int is arbitrary-precision, bool subclasses int); they carry no -# __cpp_name__ to query, so the mapping has to be explicit. -_py_arithmetic = {int: 'int', float: 'float', bool: 'bool'} - -def _as_arithmetic(cls): - """Return the C++ type name if 'cls' denotes an arithmetic type, else None. - - 'cls' may be a Python builtin type (int/float/bool), a string naming a C++ - type, or a cppyy type proxy (e.g. cppyy.gbl.int).""" - name = _py_arithmetic.get(cls) - if name is not None: - return name - name = cls if isinstance(cls, str) else getattr(cls, '__cpp_name__', None) - if name is None: - return None - # Let C++ be the source of truth: std::is_arithmetic resolves canonical - # spellings and typedefs (size_t, Int_t, ...) alike, with no hand-kept list to - # drift out of sync. cppyy caches the trait instantiation itself. - # - # A name that is no resolvable type (e.g. a typo) makes the trait lookup raise; - # treat that as "not arithmetic" so it falls through to the class path, which - # reports the bad name itself instead of leaking std::is_arithmetic<...>. - try: - is_arith = bool(gbl.std.is_arithmetic[name].value) - except Exception: - is_arith = False - return name if is_arith else None - -# Helpers that build a smart pointer to a heap-allocated scalar. They are -# deliberately *non-variadic*: instantiating std::make_unique/make_shared for an -# arithmetic type makes the interpreter JIT a call wrapper whose variadic Args... -# pack is mis-desugared on some platforms (macOS with C++ modules) into invalid -# code like 'make_unique'; these single-argument templates avoid it. -# See https://github.com/root-project/root/issues/19122. -# -# Each body static_asserts T is arithmetic, mirroring the Python-side -# _as_arithmetic() gate (class types take the adopt path instead). The assert is -# in the body, not the signature: an extra template parameter is exactly what -# triggers the mis-desugaring above. -_scalar_makers = {} -def _scalar_maker(name): - maker = _scalar_makers.get(name) - if maker is None: - if not _scalar_makers: - cppdef("""namespace __cppyy_internal { - template std::unique_ptr make_unique_scalar() { static_assert(std::is_arithmetic_v, "scalar maker requires an arithmetic type"); return std::unique_ptr(new T{}); } - template std::unique_ptr make_unique_scalar(T v) { static_assert(std::is_arithmetic_v, "scalar maker requires an arithmetic type"); return std::unique_ptr(new T(v)); } - template std::shared_ptr make_shared_scalar() { static_assert(std::is_arithmetic_v, "scalar maker requires an arithmetic type"); return std::shared_ptr(new T{}); } - template std::shared_ptr make_shared_scalar(T v) { static_assert(std::is_arithmetic_v, "scalar maker requires an arithmetic type"); return std::shared_ptr(new T(v)); } - }""") - maker = getattr(gbl.__cppyy_internal, name) - _scalar_makers[name] = maker - return maker - -class make_smartptr(object): - __slots__ = ['ptrcls', 'maker', 'scalarname'] - def __init__(self, ptrcls, maker, scalarname): - self.ptrcls = ptrcls - self.maker = maker - self.scalarname = scalarname - def __call__(self, ptr): - return py_make_smartptr(type(ptr), self.ptrcls)(ptr) - def __getitem__(self, cls): - # Arithmetic element types: build from a heap scalar (see _scalar_maker). - name = _as_arithmetic(cls) - if name is not None: - return _scalar_maker(self.scalarname)[name] - # Class types (cppyy proxies or Python classes, incl. Python-derived): - # construct in Python and let the smart pointer adopt the object. - try: - if not cls.__module__ == int.__module__: - return py_make_smartptr(cls, self.ptrcls) - except AttributeError: - pass - if isinstance(cls, str): - return py_make_smartptr(getattr(gbl, cls), self.ptrcls) - return self.maker[cls] - -gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared, 'make_shared_scalar') -gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique, 'make_unique_scalar') -del make_smartptr - - -#--- interface to Cling ------------------------------------------------------ -class _stderr_capture(object): - def __init__(self): - self._capture = not gbl.gDebug and True or False - self.err = "" - - def __enter__(self): - if self._capture: - _begin_capture_stderr() - return self - - def __exit__(self, tp, val, trace): - if self._capture: - self.err = _end_capture_stderr() - -def _cling_report(msg, errcode, msg_is_error=False): - # errcode should be authorative, but at least on MacOS, Cling does not report an - # error when it should, so also check for the typical compilation signature that - # Cling puts out as an indicator than an error occurred - if 'input_line' in msg: - if 'warning' in msg and not 'error' in msg: - warnings.warn(msg, SyntaxWarning) - msg_is_error=False - - if 'error' in msg: - errcode = 1 - - if errcode or (msg and msg_is_error): - raise SyntaxError('Failed to parse the given C++ code%s' % msg) - -def cppdef(src): - """Declare C++ source to Cling.""" - with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare(src) - _cling_report(err.err, int(not errcode), msg_is_error=True) - return True - -def cppexec(stmt): - """Execute C++ statement in Cling's global scope.""" - if stmt and stmt[-1] != ';': - stmt += ';' - - # capture stderr, but note that ProcessLine could legitimately be writing to - # std::cerr, in which case the captured output needs to be printed as normal - with _stderr_capture() as err: - errcode = ctypes.c_int(0) - try: - gbl.gInterpreter.ProcessLine(stmt, ctypes.pointer(errcode)) - except Exception as e: - sys.stderr.write("%s\n\n" % str(e)) - if not errcode.value: - errcode.value = 1 - - _cling_report(err.err, errcode.value) - if err.err and err.err[1:] != '\n': - sys.stderr.write(err.err[1:]) - - return True - -def macro(cppm): - """Attempt to evalute a C/C++ pre-processor macro as a constant""" - - try: - macro_val = getattr(getattr(gbl, '__cppyy_macros', None), cppm+'_', None) - if macro_val is None: - cppdef("namespace __cppyy_macros { auto %s_ = %s; }" % (cppm, cppm)) - return getattr(getattr(gbl, '__cppyy_macros'), cppm+'_') - except Exception: - pass - - raise ValueError('Failed to evaluate macro %s', cppm) - - -def load_library(name): - """Explicitly load a shared library.""" - with _stderr_capture() as err: - gSystem = gbl.gSystem - if name[:3] != 'lib': - if not gSystem.FindDynamicLibrary(gbl.TString(name), True) and\ - gSystem.FindDynamicLibrary(gbl.TString('lib'+name), True): - name = 'lib'+name - sc = gSystem.Load(name) - if sc == -1: - # special case for Windows as of python3.8: use winmode=0, otherwise the default - # will not consider regular search paths (such as $PATH) - if 0x3080000 <= sys.hexversion and 'win32' in sys.platform and os.path.isabs(name): - return ctypes.CDLL(name, ctypes.RTLD_GLOBAL, winmode=0) # raises on error - raise RuntimeError('Unable to load library "%s"%s' % (name, err.err)) - return True - -def include(header): - """Load (and JIT) header file
into Cling.""" - with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare('#include "%s"' % header) - if not errcode: - raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) - return True - -def c_include(header): - """Load (and JIT) header file
into Cling.""" - with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare("""extern "C" { -#include "%s" -}""" % header) - if not errcode: - raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) - return True - -def add_include_path(path): - """Add a path to the include paths available to Cling.""" - if not os.path.isdir(path): - raise OSError('No such directory: %s' % path) - gbl.gInterpreter.AddIncludePath(path) - -def add_library_path(path): - """Add a path to the library search paths available to Cling.""" - if not os.path.isdir(path): - raise OSError('No such directory: %s' % path) - gbl.gSystem.AddDynamicPath(path) - -# add access to Python C-API headers -apipath = sysconfig.get_path('include', 'posix_prefix' if os.name == 'posix' else os.name) -if os.path.exists(apipath): - add_include_path(apipath) -elif ispypy: - # possibly structured without 'pythonx.y' in path - apipath = os.path.dirname(apipath) - if os.path.exists(apipath) and os.path.exists(os.path.join(apipath, 'Python.h')): - add_include_path(apipath) - -if os.getenv('CONDA_PREFIX'): - # MacOS, Linux - include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'include') - if os.path.exists(include_path): - add_include_path(include_path) - - # Windows - include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'include') - if os.path.exists(include_path): - add_include_path(include_path) - -# assuming that we are in PREFIX/lib/python/site-packages/cppyy, -# add PREFIX/include to the search path -include_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), *(4*[os.path.pardir]+['include']))) -if os.path.exists(include_path): - add_include_path(include_path) - -del include_path, apipath, ispypy - -def add_autoload_map(fname): - """Add the entries from a autoload (.rootmap) file to Cling.""" - if not os.path.isfile(fname): - raise OSError("no such file: %s" % fname) - gbl.gInterpreter.LoadLibraryMap(fname) - -def set_debug(enable=True): - """Enable/disable debug output.""" - if enable: - gbl.gDebug = 10 - else: - gbl.gDebug = 0 - -def _get_name(tt): - if isinstance(tt, str): - return tt - try: - ttname = tt.__cpp_name__ - except AttributeError: - ttname = tt.__name__ - return ttname - -_sizes = {} -def sizeof(tt): - """Returns the storage size (in chars) of C++ type .""" - if not isinstance(tt, type) and not isinstance(tt, str): - tt = type(tt) - try: - return _sizes[tt] - except KeyError: - try: - sz = ctypes.sizeof(tt) - except TypeError: - sz = gbl.gInterpreter.ProcessLine("sizeof(%s);" % (_get_name(tt),)) - _sizes[tt] = sz - return sz - -_typeids = {} -def typeid(tt): - """Returns the C++ runtime type information for type .""" - if not isinstance(tt, type): - tt = type(tt) - try: - return _typeids[tt] - except KeyError: - tidname = 'typeid_'+str(len(_typeids)) - gbl.gInterpreter.ProcessLine( - "namespace _cppyy_internal { auto* %s = &typeid(%s); }" %\ - (tidname, _get_name(tt),)) - tid = getattr(gbl._cppyy_internal, tidname) - _typeids[tt] = tid - return tid - -def multi(*bases): # after six, see also _typemap.py - """Resolve metaclasses for multiple inheritance.""" - # contruct a "no conflict" meta class; the '_meta' is needed by convention - nc_meta = type.__new__( - type, 'cppyy_nc_meta', tuple(type(b) for b in bases if type(b) is not type), {}) - class faux_meta(type): - def __new__(mcs, name, this_bases, d): - return nc_meta(name, bases, d) - return type.__new__(faux_meta, 'faux_meta', (), {}) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/__init__.py deleted file mode 100644 index 84cb94ecc0979..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -__all__ = ['get_hook_dirs'] - -def get_hook_dirs(): - return [os.path.dirname(__file__)] diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/hook-cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/hook-cppyy.py deleted file mode 100644 index 4819b6ab00b01..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/hook-cppyy.py +++ /dev/null @@ -1,34 +0,0 @@ -# PyInstaller hooks to declare the "data files" (libraries, headers, etc.) of -# cppyy-backend. Placed here rather then in cppyy-backend to guarantee that any -# packaging of top-level "cppyy" picks up the backend as well. -# -# See also setup.cfg. - -__all__ = ['datas'] - - -def _backend_files(): - import cppyy_backend, glob, os - - all_files = glob.glob(os.path.join( - os.path.dirname(cppyy_backend.__file__), '*')) - - def datafile(path): - return path, os.path.join('cppyy_backend', os.path.basename(path)) - - return [datafile(filename) for filename in all_files if os.path.isdir(filename)] - -def _api_files(): - import cppyy, os - - paths = str(cppyy.gbl.gInterpreter.GetIncludePath()).split('-I') - for p in paths: - if not p: continue - - apipath = os.path.join(p.strip()[1:-1], 'CPyCppyy') - if os.path.exists(apipath): - return [(apipath, os.path.join('include', 'CPyCppyy'))] - - return [] - -datas = _backend_files()+_api_files() diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py deleted file mode 100644 index f98a34a697c1b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py +++ /dev/null @@ -1,215 +0,0 @@ -""" CPython-specific touch-ups -""" - -import ctypes -import platform -import sys - -from . import _stdcpp_fix - -__all__ = [ - 'gbl', - 'load_reflection_info', - 'addressof', - 'bind_object', - 'nullptr', - 'default', - '_backend', - '_begin_capture_stderr', - '_end_capture_stderr' - ] - -if platform.system() == "Windows": - # On Windows, the library has to be searched without prefix - import libcppyy as _backend -else: - import cppyy.libcppyy as _backend - -# explicitly expose APIs from libcppyy -_w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) - - -# some beautification for inspect (only on p2) -if sys.hexversion < 0x3000000: - # TODO: this reliese on CPPOverload cooking up a func_code object, which atm - # is simply not implemented for p3 :/ - - # convince inspect that cppyy method proxies are possible drop-ins for python - # methods and classes for pydoc - import inspect - - inspect._old_isfunction = inspect.isfunction - def isfunction(object): - if isinstance(object, _backend.CPPOverload) and not object.im_class: - return True - return inspect._old_isfunction(object) - inspect.isfunction = isfunction - - inspect._old_ismethod = inspect.ismethod - def ismethod(object): - if isinstance(object, _backend.CPPOverload): - return True - return inspect._old_ismethod(object) - inspect.ismethod = ismethod - del isfunction, ismethod - - -### template support --------------------------------------------------------- -class Template(object): # expected/used by ProxyWrappers.cxx in CPyCppyy - stl_sequence_types = ['std::vector', 'std::list', 'std::set', 'std::deque'] - stl_unrolled_types = ['std::pair'] - stl_fixed_size_types = ['std::array'] - stl_mapping_types = ['std::map', 'std::unordered_map'] - - def __init__(self, name): - self.__name__ = name - self.__cpp_name__ = name - self._instantiations = dict() - - def __repr__(self): - return "" % (self.__name__, hex(id(self))) - - def __getitem__(self, *args): - # multi-argument to [] becomes a single tuple argument - if args and isinstance(args[0], tuple): - args = args[0] - - # if already instantiated, return the existing class - try: - return self._instantiations[args] - except KeyError: - pass - - # construct the type name from the types or their string representation - newargs = [self.__name__] - for arg in args: - if isinstance(arg, str): - arg = ','.join(map(lambda x: x.strip(), arg.split(','))) - newargs.append(arg) - pyclass = _backend.MakeCppTemplateClass(*newargs) - - # memoize the class to prevent spurious lookups/re-pythonizations - self._instantiations[args] = pyclass - - # special case pythonization (builtin_map is not available from the C-API) - if 'push_back' in pyclass.__dict__ and not '__iadd__' in pyclass.__dict__: - if 'reserve' in pyclass.__dict__: - def iadd(self, ll): - self.reserve(len(ll)) - for x in ll: - self.push_back(x) - return self - else: - def iadd(self, ll): - for x in ll: - self.push_back(x) - return self - pyclass.__iadd__ = iadd - - # back-pointer for reflection - pyclass.__cpp_template__ = self - - return pyclass - - def __call__(self, *args): - # for C++17, we're required to derive the type when using initializer syntax - # (i.e. a tuple or list); not sure how to do that in general, but below the - # most common cases are covered - if args: - args0 = args[0] - if args0 and isinstance(args0, (tuple, list)): - t = type(args0[0]) - if t is float: t = 'double' - - if self.__name__ in self.stl_sequence_types: - return self[t](*args) - if self.__name__ in self.stl_fixed_size_types: - return self[t, len(args0)](*args) - if self.__name__ in self.stl_unrolled_types: - return self[tuple(type(a) for a in args0)](*args0) - - if args0 and isinstance(args0, dict): - if self.__name__ in self.stl_mapping_types: - try: - pair = args0.items().__iter__().__next__() - except AttributeError: - pair = args0.items()[0] - t1 = type(pair[0]) - if t1 is float: t1 = 'double' - t2 = type(pair[1]) - if t2 is float: t2 = 'double' - return self[t1, t2](*args) - - return self.__getitem__(*(type(a) for a in args0))(*args) - - # old 'metaclass-style' template instantiations - return self.__getitem__(*args) - -_backend.Template = Template - - -#- :: and std:: namespaces --------------------------------------------------- -gbl = _backend.CreateScopeProxy('') -gbl.__class__.__repr__ = lambda cls : '' % id(cls) -gbl.std = _backend.CreateScopeProxy('std') -# for move, we want our "pythonized" one, not the C++ template -gbl.std.move = _backend.move - - -#- add to the dynamic path as needed ----------------------------------------- -import os -def add_default_paths(): - gSystem = gbl.gSystem - if os.getenv('CONDA_PREFIX'): - # MacOS, Linux - lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'lib') - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) - - # Windows - lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'lib') - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) - - # assuming that we are in PREFIX/lib/python/site-packages/cppyy, add PREFIX/lib to the search path - lib_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir)) - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) - - try: - with open('/etc/ld.so.conf') as ldconf: - for line in ldconf: - f = line.strip() - if (os.path.exists(f)): - gSystem.AddDynamicPath(f) - except IOError: - pass -add_default_paths() -del add_default_paths - - -#- exports ------------------------------------------------------------------- -addressof = _backend.addressof -bind_object = _backend.bind_object -nullptr = _backend.nullptr -default = _backend.default - -def load_reflection_info(name): - sc = gbl.gSystem.Load(name) - if sc == -1: - raise RuntimeError("Unable to load reflection library "+name) - -def _begin_capture_stderr(): - _backend._begin_capture_stderr() - -def _end_capture_stderr(): - err = _backend._end_capture_stderr() - if err: - try: - return "\n%s" % err - except UnicodeDecodeError as e: - original_error = e - try: - return "\n%s" % err.decode('gbk') # not guaranteed, but common - except UnicodeDecodeError: - pass - return "C++ issued an error message that could not be decoded (%s)" % str(original_error) - return "" diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py deleted file mode 100644 index 1e57791a67680..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py +++ /dev/null @@ -1,74 +0,0 @@ -""" PyPy-specific touch-ups -""" - -from . import _stdcpp_fix - -import os -import sys -from cppyy_backend import loader - -__all__ = [ - 'gbl', - 'addressof', - 'bind_object', - 'nullptr', - ] - -# first load the dependency libraries of the backend, then -# pull in the built-in low-level cppyy -try: - c = loader.load_cpp_backend() -except RuntimeError: - import sysconfig - os.environ['CPPYY_BACKEND_LIBRARY'] = "libcppyy_backend"+sysconfig.get_config_var("SO") - c = loader.load_cpp_backend() -os.environ['CPPYY_BACKEND_LIBRARY'] = c._name - -# some older versions can be fixed up through a compatibility -# module on the python side; load it, if available -try: - import cppyy_compat -except ImportError: - pass - -import _cppyy as _backend # built-in module -try: - _backend._post_import_startup() -except AttributeError: - pass -_backend._cpp_backend = c - -def fixup_legacy(): - version = sys.pypy_version_info - if version[0] < 7 or (version[0] == 7 and version[1] <= 3 and version[2] <= 3): - _backend.gbl.CppyyLegacy = _backend.gbl -fixup_legacy() -del fixup_legacy - - -#- exports ------------------------------------------------------------------- -_thismodule = sys.modules[__name__] -for name in __all__: - try: - setattr(_thismodule, name, getattr(_backend, name)) - except AttributeError: - pass -del name -nullptr = _backend.nullptr - -def load_reflection_info(name): - sc = _backend.gbl.gSystem.Load(name) - if sc == -1: - raise RuntimeError("Unable to load reflection library "+name) - -def _begin_capture_stderr(): - pass - -def _end_capture_stderr(): - return "" - -# add other exports to all -__all__.append('load_reflection_info') -__all__.append('_backend') -__all__.append('_begin_capture_stderr') -__all__.append('_end_capture_stderr') diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py deleted file mode 100644 index 01bd03cec6483..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '3.5.0' diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py deleted file mode 100644 index 0b4bdd7ce471f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - - -#- fake namespace for interactive lazy lookups ------------------------------- -class InteractiveLazy(object): - def __init__(self, hook_okay): - self._hook_okay = hook_okay - - def __getattr__(self, attr): - import cppyy - - if attr == '__all__': - # copy all exported items from cppyy itself - for v in cppyy.__all__: - self.__dict__[v] = getattr(cppyy, v) - - # add the lookup hook into cppyy.gbl if legal, or put it under 'g' - # if not (PyPy and IPython for now) - if self._hook_okay: - caller = sys.modules[sys._getframe(1).f_globals['__name__']] - cppyy._backend._set_cpp_lazy_lookup(caller.__dict__) - return cppyy.__all__ - self.__dict__['g'] = cppyy.gbl - self.__dict__['std'] = cppyy.gbl.std - return ['g', 'std']+cppyy.__all__ - return getattr(cppyy, attr) - -sys.modules['cppyy.interactive'] = InteractiveLazy(\ - not ispypy and not (hasattr(__builtins__, '__IPYTHON__') or 'IPython' in sys.modules)) -del InteractiveLazy, ispypy diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py deleted file mode 100644 index 9fb6034ce0843..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py +++ /dev/null @@ -1,162 +0,0 @@ -""" Low-level utilities, to be used for "emergencies only". -""" - -import cppyy -import ctypes -import sys -import warnings - -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - -__all__ = [ - 'argv', - 'argc', - 'cast', - 'static_cast', - 'reinterpret_cast', - 'dynamic_cast', - 'malloc', - 'free', - 'array_new', - 'array_delete', - 'signals_as_exception', - 'set_signals_as_exception', - 'FatalError', - 'BusError', - 'SegmentationViolation', - 'IllegalInstruction', - 'AbortSignal', - ] - - -# convenience functions to create C-style argv/argc -def argv(): - """Return C's argv for use with cppyy/ctypes.""" - cargsv = (ctypes.c_char_p * len(sys.argv))(*(x.encode() for x in sys.argv)) - return ctypes.POINTER(ctypes.c_char_p)(cargsv) - -def argc(): - """Return C's argc for use with cppyy/ctypes.""" - return len(sys.argv) - -# import low-level python converters -for _name in ['addressof', 'as_cobject', 'as_capsule', 'as_ctypes', 'as_memoryview']: - try: - exec('%s = cppyy._backend.%s' % (_name, _name)) - __all__.append(_name) - except AttributeError: - pass -del _name - - -# create low-level helpers -cppyy.cppdef("""namespace __cppyy_internal { -// type casting - template - T cppyy_cast(U val) { return (T)val; } - - template - T cppyy_static_cast(U val) { return static_cast(val); } - - template - T cppyy_reinterpret_cast(U val) { return reinterpret_cast(val); } - - template - T* cppyy_dynamic_cast(S* obj) { return dynamic_cast(obj); } - -// memory allocation/free-ing - template - T* cppyy_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } - - template - T* cppyy_array_new(size_t count) { return new T[count]; } - - template - void cppyy_array_delete(T* ptr) { delete[] ptr; } -}""") - - -# helper for sizing arrays -class ArraySizer(object): - def __init__(self, func): - self.func = func - def __getitem__(self, t): - self.array_type = t - return self - def __call__(self, size, managed=False): - res = self.func[self.array_type](size) - try: - res.reshape((size,)+res.shape[1:]) - if managed: - res.__python_owns__ = True - except AttributeError: - res.__reshape__((size,)) - if managed: - warnings.warn("managed low-level arrays of instances not supported") - return res - -class CArraySizer(ArraySizer): - def __call__(self, size, managed=False): - res = ArraySizer.__call__(self, size, managed) - res.__cpp_array__ = False - return res - - -# import casting helpers -cast = cppyy.gbl.__cppyy_internal.cppyy_cast -static_cast = cppyy.gbl.__cppyy_internal.cppyy_static_cast -reinterpret_cast = cppyy.gbl.__cppyy_internal.cppyy_reinterpret_cast -dynamic_cast = cppyy.gbl.__cppyy_internal.cppyy_dynamic_cast - -# import memory allocation/free-ing helpers -malloc = CArraySizer(cppyy.gbl.__cppyy_internal.cppyy_malloc) -free = cppyy.gbl.free # for symmetry -array_new = ArraySizer(cppyy.gbl.__cppyy_internal.cppyy_array_new) -array_delete = cppyy.gbl.__cppyy_internal.cppyy_array_delete - -# signals as exceptions -if not ispypy: - FatalError = cppyy._backend.FatalError - BusError = cppyy._backend.BusError - SegmentationViolation = cppyy._backend.SegmentationViolation - IllegalInstruction = cppyy._backend.IllegalInstruction - AbortSignal = cppyy._backend.AbortSignal - - class signals_as_exception: - def __enter__(self): - cppyy._backend.SetGlobalSignalPolicy(1) - - def __exit__(self, type, value, traceback): - cppyy._backend.SetGlobalSignalPolicy(0) - - set_signals_as_exception = cppyy._backend.SetGlobalSignalPolicy - -else: - class FatalError(Exception): - pass - class BusError(FatalError): - pass - class SegmentationViolation(FatalError): - pass - class IllegalInstruction(FatalError): - pass - class AbortSignal(FatalError): - pass - - class signals_as_exception: - def __enter__(self): - pass # not yet implemented - - def __exit__(self, type, value, traceback): - pass # not yet implemented - - def set_signals_as_exception(seton): - return False - -del ispypy - diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/reflex.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/reflex.py deleted file mode 100644 index 35730cbb47c38..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/reflex.py +++ /dev/null @@ -1,28 +0,0 @@ -""" cppyy reflection -""" - -import cppyy - -try: - import __pypy__ - __all__ = [ - - ] - -except ImportError: - __all__ = [ - 'RETURN_TYPE', - ] - - cppyy.include("CPyCppyy/Reflex.h") - - IS_NAMESPACE = cppyy.gbl.Cppyy.Reflex.IS_NAMESPACE - IS_AGGREGATE = cppyy.gbl.Cppyy.Reflex.IS_AGGREGATE - - OFFSET = cppyy.gbl.Cppyy.Reflex.OFFSET - RETURN_TYPE = cppyy.gbl.Cppyy.Reflex.RETURN_TYPE - TYPE = cppyy.gbl.Cppyy.Reflex.TYPE - - OPTIMAL = cppyy.gbl.Cppyy.Reflex.OPTIMAL - AS_TYPE = cppyy.gbl.Cppyy.Reflex.AS_TYPE - AS_STRING = cppyy.gbl.Cppyy.Reflex.AS_STRING diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py deleted file mode 100644 index 4a979ac59c285..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py +++ /dev/null @@ -1,35 +0,0 @@ -""" C++ proxy types. -""" - -import cppyy - -bck = cppyy._backend -Instance = bck.CPPInstance - -try: - import __pypy__ - __all__ = [ - 'Instance' - ] - -except ImportError: - __all__ = [ - 'DataMember', - 'Instance', - 'Function', - 'Method', - 'Scope', - 'InstanceArray', - 'LowLevelView', - 'Template' - ] - - DataMember = bck.CPPDataMember - Function = bck.CPPOverload - Method = bck.CPPOverload - Scope = bck.CPPScope - InstanceArray = bck.InstanceArray - LowLevelView = bck.LowLevelView - Template = bck.TemplateProxy - -del bck diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy_compat/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy_compat/__init__.py deleted file mode 100644 index d9d360553699d..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy_compat/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Compatibility layer for PyPy 5.7-9 -""" - -def pypy58_57_compat(): - import imp, os - - # first load and move the builtin cppyy module - if not 'cppyy' in sys.modules: - try: - olddir = os.getcwd() - from cppyy_backend import loader - c = loader.load_cpp_backend() - # move to the location of the backend, just in case '.' is - # in the dynloader's path - os.chdir(os.path.dirname(c._name)) - imp.init_builtin('cppyy') - except ImportError: - raise EnvironmentError('"%s" missing in LD_LIBRARY_PATH' %\ - os.path.dirname(c._name)) - finally: - os.chdir(olddir) - - sys.modules['_cppyy'] = sys.modules['cppyy'] - del sys.modules['cppyy'] - - # now locate and load the pip cppyy module - decdir = os.path.join(os.path.dirname(__file__), os.path.pardir) - for path in sys.path: # walk over sys.path skips builtins - try: - fp, pathname, description = imp.find_module('cppyy', [path]) - sys.modules['cppyy'] = imp.load_module('cppyy_', fp, pathname, description) - break - except ImportError: - pass - - # copy over the _cppyy functions into cppyy - old = sys.modules['_cppyy'] - new = sys.modules['cppyy'] - for name in dir(old): - if not hasattr(new, name): - setattr(new, name, getattr(old, name)) - -# for pypy5.9 we may need to move to the location of the backend, if '.' happens -# to be in LD_LIBRARY_PATH, but not the full directory -def py59_compat(): - import os, cppyy_backend - olddir = os.getcwd() - c = cppyy_backend.loader.load_cpp_backend() - os.chdir(os.path.dirname(c._name)) - try: - global __name__ - actual_name = __name__; __name__ = '' - import _cppyy as _backend - except ImportError: - raise EnvironmentError('"%s" missing in LD_LIBRARY_PATH' % os.path.dirname(c._name)) - finally: - __name__ = actual_name - os.chdir(olddir) - _backend.nullptr = _backend.gbl.nullptr - - -import sys -version = sys.pypy_version_info -if version[0] == 5: - if 6 < version[1] <= 8: - pypy58_57_compat() - elif version[1] == 9: - py59_compat() -del version, sys diff --git a/bindings/pyroot/cppyy/cppyy/setup.cfg b/bindings/pyroot/cppyy/cppyy/setup.cfg deleted file mode 100644 index cc470d8cba241..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/setup.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[bdist_wheel] -universal=0 - -[metadata] -license_files = LICENSE.txt - -[options.entry_points] -pyinstaller40 = - hook-dirs = cppyy.__pyinstaller:get_hook_dirs diff --git a/bindings/pyroot/cppyy/cppyy/setup.py b/bindings/pyroot/cppyy/cppyy/setup.py deleted file mode 100755 index 0bb4ed7449b46..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/setup.py +++ /dev/null @@ -1,160 +0,0 @@ -import codecs, glob, os, sys, re -from setuptools import setup, find_packages, Extension -from distutils import log - -from setuptools.command.install import install as _install - -add_pkg = ['cppyy', 'cppyy.__pyinstaller'] -try: - import __pypy__, sys - version = sys.pypy_version_info - requirements = ['cppyy-backend==1.15.3', 'cppyy-cling==6.32.8'] - if version[0] == 5: - if version[1] <= 9: - requirements = ['cppyy-backend<0.3', 'cppyy-cling<6.12'] - add_pkg += ['cppyy_compat'] - elif version[1] <= 10: - requirements = ['cppyy-backend<0.4', 'cppyy-cling<=6.15'] - elif version[0] == 6: - if version[1] <= 0: - requirements = ['cppyy-backend<1.1', 'cppyy-cling<=6.15'] - elif version[0] == 7: - if version[1] <= 3 and version[2] <= 3: - requirements = ['cppyy-backend<=1.10', 'cppyy-cling<=6.18.2.3'] -except ImportError: - # CPython - requirements = ['CPyCppyy==1.13.0', 'cppyy-backend==1.15.3', 'cppyy-cling==6.32.8'] - -setup_requirements = ['wheel'] -if 'build' in sys.argv or 'install' in sys.argv: - setup_requirements += requirements - -here = os.path.abspath(os.path.dirname(__file__)) -with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: - long_description = f.read() - -# https://packaging.python.org/guides/single-sourcing-package-version/ -def read(*parts): - with codecs.open(os.path.join(here, *parts), 'r') as fp: - return fp.read() - -def find_version(*file_paths): - version_file = read(*file_paths) - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") - - -# -# customized commands -# -class my_install(_install): - def __init__(self, *args, **kwds): - if 0x3000000 <= sys.hexversion: - super(_install, self).__init__(*args, **kwds) - else: - # b/c _install is a classobj, not type - _install.__init__(self, *args, **kwds) - - try: - import cppyy_backend as cpb - self._pchname = 'allDict.cxx.pch.' + str(cpb.__version__) - except (ImportError, AttributeError): - self._pchname = None - - def run(self): - # base install - _install.run(self) - - # force build of the .pch underneath the cppyy package if not available yet - install_path = os.path.join(os.getcwd(), self.install_libbase, 'cppyy') - - if self._pchname: - try: - import cppyy_backend as cpb - if not os.path.exists(os.path.join(cpb.__file__, 'etc', self._pchname)): - log.info("installing pre-compiled header in %s", install_path) - cpb.loader.set_cling_compile_options(True) - cpb.loader.ensure_precompiled_header(install_path, self._pchname) - except (ImportError, AttributeError): - # ImportError may occur with wrong pip requirements resolution (unlikely) - # AttributeError will occur with (older) PyPy as it relies on older backends - self._pchname = None - - def get_outputs(self): - outputs = _install.get_outputs(self) - if self._pchname: - # pre-emptively add allDict.cxx.pch, which may or may not be created; need full - # path to make sure the final relative path is correct - outputs.append(os.path.join(os.getcwd(), self.install_libbase, 'cppyy', self._pchname)) - return outputs - - -cmdclass = { - 'install': my_install } - - -setup( - name='cppyy', - version=find_version('python', 'cppyy', '_version.py'), - description='Cling-based Python-C++ bindings', - long_description=long_description, - - url='http://cppyy.readthedocs.org', - - # Author details - author='Wim Lavrijsen', - author_email='WLavrijsen@lbl.gov', - - license='LBNL BSD', - - classifiers=[ - 'Development Status :: 5 - Production/Stable', - - 'Intended Audience :: Developers', - - 'Topic :: Software Development', - 'Topic :: Software Development :: Interpreters', - - 'License :: OSI Approved :: BSD License', - - 'Programming Language :: Python :: Implementation :: PyPy', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: C', - 'Programming Language :: C++', - - 'Natural Language :: English' - ], - - setup_requires=setup_requirements, - install_requires=requirements, - - keywords='C++ bindings data science calling language integration', - - include_package_data=True, - package_data={'': ['installer/cppyy_monkey_patch.py']}, - - package_dir={'': 'python'}, - packages=find_packages('python', include=add_pkg), - - # TODO: numba_extensions will load all extensions even if the package - # itself is not otherwise imported, just installed; in the case of cppyy, - # that is currently too heavy (and breaks on conda) - - #entry_points={ - # 'numba_extensions': [ - # 'init = cppyy.numba_ext:_init_extension', - # ], - #}, - - cmdclass=cmdclass, - - zip_safe=False, -) diff --git a/bindings/pyroot/cppyy/cppyy/test/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy/test/CMakeLists.txt deleted file mode 100644 index dd9f50e17a931..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/CMakeLists.txt +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -set(common_args GENERIC PYTHON_DEPS pytest) - -if(NOT MSVC) - list(APPEND common_args ENVIRONMENT ${ld_library_path}=${CMAKE_CURRENT_BINARY_DIR}) -endif() - -set(root_cmd $ -b -q) - -function(ADD_PYUNITTEST_ACLIC TESTNAME) - set(options) - set(oneValueArgs) - set(multiValueArgs DICTNAME) - cmake_parse_arguments(ARG - "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} - ) - - ROOT_ADD_PYUNITTEST(${TESTNAME} ${TESTNAME}.py ${common_args} - COPY_TO_BUILDDIR ${ARG_DICTNAME}.cxx ${ARG_DICTNAME}.h - PRECMD ${root_cmd} -e ".L ${ARG_DICTNAME}.cxx++" - ) -endfunction() - -ADD_PYUNITTEST_ACLIC(test_conversions DICTNAME conversions) -ADD_PYUNITTEST_ACLIC(test_cpp11features DICTNAME cpp11features) -ADD_PYUNITTEST_ACLIC(test_crossinheritance DICTNAME crossinheritance) -ADD_PYUNITTEST_ACLIC(test_doc_features DICTNAME doc_helper) -ADD_PYUNITTEST_ACLIC(test_fragile DICTNAME fragile) -ADD_PYUNITTEST_ACLIC(test_operators DICTNAME operators) -ADD_PYUNITTEST_ACLIC(test_overloads DICTNAME overloads) -ADD_PYUNITTEST_ACLIC(test_pythonization DICTNAME pythonizables) -ADD_PYUNITTEST_ACLIC(test_stltypes DICTNAME stltypes) -ADD_PYUNITTEST_ACLIC(test_streams DICTNAME std_streams) -ADD_PYUNITTEST_ACLIC(test_templates DICTNAME templates) - -ROOT_ADD_PYUNITTEST(test_pythonify test_pythonify.py ${common_args} - COPY_TO_BUILDDIR example01.cxx example01.h - PRECMD ${root_cmd} -e ".L example01.cxx++" - FIXTURES_SETUP example01_dict_fixture -) - -ROOT_ADD_PYUNITTEST(test_advancedcpp test_advancedcpp.py ${common_args} - COPY_TO_BUILDDIR advancedcpp.cxx advancedcpp.h advancedcpp2.cxx advancedcpp2.h - PRECMD ${root_cmd} -e ".L advancedcpp.cxx++" -e ".L advancedcpp2.cxx++" -) - -ROOT_ADD_PYUNITTEST(test_lowlevel test_lowlevel.py ${common_args} - COPY_TO_BUILDDIR datatypes.cxx datatypes.h - PRECMD ${root_cmd} -e ".L datatypes.cxx++" - FIXTURES_SETUP datatypes_dict_fixture -) - -ROOT_ADD_PYUNITTEST(test_aclassloader test_aclassloader.py ${common_args} FIXTURES_REQUIRED example01_dict_fixture) -ROOT_ADD_PYUNITTEST(test_api test_api.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_boost test_boost.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_concurrent test_concurrent.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_datatypes test_datatypes.py ${common_args} FIXTURES_REQUIRED datatypes_dict_fixture) -ROOT_ADD_PYUNITTEST(test_eigen test_eigen.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_leakcheck test_leakcheck.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_numba test_numba.py ${common_args}) -ROOT_ADD_PYUNITTEST(test_regression test_regression.py ${common_args}) diff --git a/bindings/pyroot/cppyy/cppyy/test/__init__.py b/bindings/pyroot/cppyy/cppyy/test/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.h b/bindings/pyroot/cppyy/cppyy/test/advancedcpp.h deleted file mode 100644 index ca37831ace816..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.h +++ /dev/null @@ -1,533 +0,0 @@ -#include -#include -#include -#include -#include - -//=========================================================================== -#define DECLARE_DEFAULTERS(type, tname) \ -class tname##_defaulter { \ -public: \ - tname##_defaulter(type a = 11, type b = 22, type c = 33); \ - \ -public: \ - type m_a, m_b, m_c; \ -}; \ -type tname##_defaulter_func(int idx = 0, type a = 11, type b = 22, type c = 33); -DECLARE_DEFAULTERS(short, short) // for testing of default arguments -DECLARE_DEFAULTERS(unsigned short, ushort) -DECLARE_DEFAULTERS(int, int) -DECLARE_DEFAULTERS(unsigned, uint) -DECLARE_DEFAULTERS(long, long) -DECLARE_DEFAULTERS(unsigned long, ulong) -DECLARE_DEFAULTERS(long long, llong) -DECLARE_DEFAULTERS(unsigned long long, ullong) -DECLARE_DEFAULTERS(float, float) -DECLARE_DEFAULTERS(double, double) - -std::string string_defaulter_func(int idx, const std::string& name1 = "aap", std::string name2 = "noot"); - - -//=========================================================================== -class base_class { // for simple inheritance testing -public: - base_class() { m_b = 1; m_db = 1.1; } - virtual ~base_class() {} - virtual int get_value() { return m_b; } - double get_base_value() { return m_db; } - - virtual base_class* cycle(base_class* b) { return b; } - virtual base_class* clone() { return new base_class; } - - virtual void* mask(void* p) { return p; } - -public: - int m_b; - double m_db; -}; - -class derived_class : public base_class { -public: - derived_class() { m_d = 2; m_dd = 2.2;} - virtual int get_value() { return m_d; } - double get_derived_value() { return m_dd; } - virtual base_class* clone() { return new derived_class; } - -public: - int m_d; - double m_dd; -}; - - -//=========================================================================== -class a_class { // for esoteric inheritance testing -public: - a_class() { m_a = 1; m_da = 1.1; } - virtual ~a_class() {} - virtual int get_value() = 0; - -public: - int m_a; - double m_da; -}; - -class b_class : public virtual a_class { -public: - b_class() { m_b = 2; m_db = 2.2;} - virtual int get_value() { return m_b; } - -public: - int m_b; - double m_db; -}; - -class c_class_1 : public virtual a_class, public virtual b_class { -public: - c_class_1() { m_c = 3; } - virtual int get_value() { return m_c; } - -public: - int m_c; -}; - -class c_class_2 : public virtual b_class, public virtual a_class { -public: - c_class_2() { m_c = 3; } - virtual int get_value() { return m_c; } - -public: - int m_c; -}; - -typedef c_class_2 c_class; - -class d_class : public virtual c_class, public virtual a_class { -public: - d_class() { m_d = 4; } - virtual int get_value() { return m_d; } - -public: - int m_d; -}; - -a_class* create_c1(); -a_class* create_c2(); - -int get_a(a_class& a); -int get_b(b_class& b); -int get_c(c_class& c); -int get_d(d_class& d); - - -//=========================================================================== -namespace a_ns { // for namespace testing - extern int g_a; - int get_g_a(); - - struct b_class { - b_class() { m_b = -2; } - int m_b; - static int s_b; - - struct c_class { - c_class() { m_c = -3; } - int m_c; - static int s_c; - }; - }; - - namespace d_ns { - extern int g_d; - int get_g_d(); - - struct e_class { - e_class() { m_e = -5; } - int m_e; - static int s_e; - - struct f_class { - f_class() { m_f = -6; } - int m_f; - static int s_f; - }; - }; - - } // namespace d_ns - -} // namespace a_ns - - -//=========================================================================== -template // for template testing -class T1 { -public: - T1(T t = T(1)) : m_t1(t) {} - T get_value() { return m_t1; } - -public: - T m_t1; -}; - -template -class T2 { -public: - T2(T t = T(2)) : m_t2(t) {} - T get_value() { return m_t2; } - -public: - T m_t2; -}; - -template -class T3 { -public: - T3(T t = T(3), U u = U(33)) : m_t3(t), m_u3(u) {} - T get_value_t() { return m_t3; } - U get_value_u() { return m_u3; } - -public: - T m_t3; - U m_u3; -}; - -namespace a_ns { - - template - class T4 { - public: - T4(T t = T(4)) : m_t4(t) {} - T get_value() { return m_t4; } - - public: - T m_t4; - }; - -} // namespace a_ns - -extern template class T1; -extern template class T2 >; -extern template class T3; -extern template class T3, T2 > >; -extern template class a_ns::T4; -extern template class a_ns::T4 > >; - - -//=========================================================================== -// for checking pass-by-reference of builtin types -void set_int_through_ref(int& i, int val); -int pass_int_through_const_ref(const int& i); -void set_long_through_ref(long& l, long val); -long pass_long_through_const_ref(const long& l); -void set_double_through_ref(double& d, double val); -double pass_double_through_const_ref(const double& d); - - -//=========================================================================== -class some_abstract_class { // to test abstract class handling -public: - virtual ~some_abstract_class() {} - virtual void a_virtual_method() = 0; -}; - -class some_concrete_class : public some_abstract_class { -public: - virtual void a_virtual_method() {} -}; - - -//=========================================================================== -class ref_tester { // for assignment by-ref testing -public: - ref_tester() : m_i(-99) {} - ref_tester(int i) : m_i(i) {} - ref_tester(const ref_tester& s) : m_i(s.m_i) {} - ref_tester& operator=(const ref_tester& s) { - if (&s != this) m_i = s.m_i; - return *this; - } - ~ref_tester() {} - -public: - int m_i; -}; - - -//=========================================================================== -class some_convertible { // for math conversions testing -public: - some_convertible() : m_i(-99), m_d(-99.) {} - - operator int() { return m_i; } - operator long() { return m_i; } - operator double() { return m_d; } - -public: - int m_i; - double m_d; -}; - - -class some_comparable { -}; - -bool operator==(const some_comparable& c1, const some_comparable& c2); -bool operator!=(const some_comparable& c1, const some_comparable& c2); - - -//=========================================================================== -extern double my_global_double; // a couple of globals for access testing -extern double my_global_array[500]; -extern double* my_global_ptr; -static const char my_global_string1[] = "aap " " noot " " mies"; -extern const char my_global_string2[]; -extern const char* my_global_string3[3]; - -class some_int_holder { -public: - some_int_holder(int val) : m_val(val) {} - -public: - int m_val; - char gap[7]; -}; -extern some_int_holder my_global_int_holders[5]; - -extern some_abstract_class* g_abstract_ptr; - - -//=========================================================================== -class some_class_with_data { // for life-line and identity testing -public: - class some_data { - public: - some_data() { ++s_num_data; } - some_data(const some_data&) { ++s_num_data; } - ~some_data() { --s_num_data; } - - static int s_num_data; - }; - - some_class_with_data gime_copy() { - return *this; - } - - const some_data& gime_data() { /* TODO: methptrgetter const support */ - return m_data; - } - - int m_padding; - some_data m_data; -}; - -class refers_to_self { // for data member reuse testing -public: - refers_to_self* m_other = nullptr; -}; - - -//=========================================================================== -class pointer_pass { // for testing passing of void*'s -public: - intptr_t gime_address_ptr(void* obj) { - return (intptr_t)obj; - } - - intptr_t gime_address_ptr_ptr(void** obj) { - return (intptr_t)*((long**)obj); - } - - intptr_t gime_address_ptr_ref(void*& obj) { - return (intptr_t)obj; - } - - static intptr_t set_address_ptr_ptr(void** obj) { - (*(intptr_t**)obj) = (intptr_t*)0x4321; - return 42; - } - - static intptr_t set_address_ptr_ref(void*& obj) { - obj = (void*)0x1234; - return 21; - } -}; - - -//=========================================================================== -class multi1 { // for testing multiple inheritance -public: - multi1(int val) : m_int(val) {} - virtual ~multi1(); - int get_multi1_int() { return m_int; } - -private: - int m_int; -}; - -class multi2 { -public: - multi2(int val) : m_int(val) {} - virtual ~multi2(); - int get_multi2_int() { return m_int; } - -private: - int m_int; -}; - -class multi : public multi1, public multi2 { -public: - multi(int val1, int val2, int val3) : - multi1(val1), multi2(val2), m_int(val3) {} - virtual ~multi(); - int get_my_own_int() { return m_int; } - -private: - int m_int; -}; - - -//=========================================================================== -class new_overloader { // for testing calls to overloaded new -public: - static int s_instances; - -public: - void* operator new(std::size_t size); - void* operator new(std::size_t, void* p) throw(); - void operator delete(void* p, std::size_t size); -}; - - -//=========================================================================== -template // more template testing -class my_templated_class { -public: - T m_b; -}; - -template -T my_templated_function(T t) { return t; } - -template class my_templated_class >; -template char my_templated_function(char); -template double my_templated_function(double); - - -//=========================================================================== -class overload_one_way { // overload order testing -public: - int gime() const; - std::string gime(); -}; - -class overload_the_other_way { -public: - std::string gime(); - int gime() const; -}; - - -//=========================================================================== -class Thrower { // exception handling testing -public: - void throw_anything(); - void throw_exception(); -}; - - -//=========================================================================== -class UsingBase1 { // using declaration testing -public: - UsingBase1(int n = 13) : m_int(n) {} - virtual ~UsingBase1() {} - virtual char vcheck() { return 'A'; } - int m_int; -}; - -class UsingDerived1 : public UsingBase1 { -public: - using UsingBase1::UsingBase1; - virtual char vcheck() { return 'B'; } - int m_int2 = 42; -}; - -class UsingBase2 { -public: - virtual ~UsingBase2() {} - virtual char vcheck() { return 'A'; } -}; - -class UsingDerived2 : public UsingBase2 { -public: - using UsingBase2::vcheck; - virtual char vcheck(int) { return 'B'; } -}; - - -//=========================================================================== -class TypedefToPrivateClass { // typedef resolution testing -private: - class PC { - public: - PC(int i) : m_val(i) {} - int m_val; - }; - -public: - typedef PC PP; - PP f() { return PC(42); } -}; - - -//=========================================================================== -namespace Cpp2PyPrinting { // operator to __str__ mapping - -class Printable1 { -public: - std::ostream& operator<<(std::ostream& os); -}; - -class Printable2 { /* empty */ }; -class Printable3 { /* empty */ }; - -std::ostream& operator<<(std::ostream& os, const Printable2&); - -} // namespace Cpp2PyPrinting - -class Printable4 { /* empty */ }; - -std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&); -std::ostream& operator<<(std::ostream& os, const Printable4&); - -template -class PrintableBase1 {}; -class Printable5 : public PrintableBase1 {}; - -template -std::ostream& operator<<(std::ostream& os, const PrintableBase1& y) { return os << "Ok."; } - -class Printable6 { -public: - Printable6& operator<<(int); -}; - -std::ostream& operator<<(std::ostream& os, const Printable6& y); - - -//=========================================================================== -namespace UsedSpace1 { // for using directives testing - int foo1(); - - namespace inner { - int foo2(); - } -} - -namespace UsedSpace2 { - int bar(); -} - -namespace UserDirs { - using namespace UsedSpace1; - using namespace UsedSpace2; - using namespace UsedSpace1::inner; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.xml b/bindings/pyroot/cppyy/cppyy/test/advancedcpp.xml deleted file mode 100644 index e414fe9400123..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.cxx b/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.cxx deleted file mode 100644 index 3eb76b26eba81..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.cxx +++ /dev/null @@ -1,13 +0,0 @@ -#include "advancedcpp2.h" - - -// for namespace testing -int a_ns::g_g = 77; -int a_ns::g_class::s_g = 88; -int a_ns::g_class::h_class::s_h = 99; -int a_ns::d_ns::g_i = 111; -int a_ns::d_ns::i_class::s_i = 222; -int a_ns::d_ns::i_class::j_class::s_j = 333; - -int a_ns::get_g_g() { return g_g; } -int a_ns::d_ns::get_g_i() { return g_i; } diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.h b/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.h deleted file mode 100644 index 86167cce99aec..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.h +++ /dev/null @@ -1,36 +0,0 @@ -//=========================================================================== -namespace a_ns { // for namespace testing - extern int g_g; - int get_g_g(); - - struct g_class { - g_class() { m_g = -7; } - int m_g; - static int s_g; - - struct h_class { - h_class() { m_h = -8; } - int m_h; - static int s_h; - }; - }; - - namespace d_ns { - extern int g_i; - int get_g_i(); - - struct i_class { - i_class() { m_i = -9; } - int m_i; - static int s_i; - - struct j_class { - j_class() { m_j = -10; } - int m_j; - static int s_j; - }; - }; - - } // namespace d_ns - -} // namespace a_ns diff --git a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.xml b/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.xml deleted file mode 100644 index 43925d1039ef3..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/assert_interactive.py b/bindings/pyroot/cppyy/cppyy/test/assert_interactive.py deleted file mode 100644 index c3bd515720c61..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/assert_interactive.py +++ /dev/null @@ -1,17 +0,0 @@ -from cppyy.interactive import * - -# namespace at the global level -assert std - -# cppyy functions -assert cppdef -assert include - -try: - import __pypy__ - # 'cppyy.gbl' bound to 'g' - assert g - assert g.std -except ImportError: - # full lazy lookup available - assert gInterpreter diff --git a/bindings/pyroot/cppyy/cppyy/test/bindexplib.py b/bindings/pyroot/cppyy/cppyy/test/bindexplib.py deleted file mode 100755 index 838dca4d50df0..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/bindexplib.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function - -import os, sys, subprocess - -target = sys.argv[1] -output = sys.argv[2] - -def isokay(name): - # filter standard symbols - return name[0] != '_' and not name in {'memcpy', 'memmove', 'memset'} - -popen = subprocess.Popen(['dumpbin', '/SYMBOLS', target+'.obj'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - -stdout, _ = popen.communicate() -stdout = stdout.decode('utf-8').strip() - -outf = open(output+'.def', 'w') -outf.write('LIBRARY %s.dll\nEXPORTS\n' % output) -for line in stdout.split('\r\n'): - parts = line.split() - if len(parts) < 8: - continue - if parts[7][0:4] in ['??_G', '??_E']: # do not export deleting destructors - continue - if parts[4] == 'External': - if isokay(parts[6]): - outf.write('\t%s\tDATA\n' % parts[6]) - elif parts[4] == '()' and parts[5] == 'External': - if isokay(parts[7]): - outf.write('\t%s\n' % parts[7]) - diff --git a/bindings/pyroot/cppyy/cppyy/test/conftest.py b/bindings/pyroot/cppyy/cppyy/test/conftest.py deleted file mode 100644 index c2931cdc64be7..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/conftest.py +++ /dev/null @@ -1 +0,0 @@ -disabled = None diff --git a/bindings/pyroot/cppyy/cppyy/test/conversions.cxx b/bindings/pyroot/cppyy/cppyy/test/conversions.cxx deleted file mode 100644 index 2340b42f62d20..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/conversions.cxx +++ /dev/null @@ -1,51 +0,0 @@ -#include "conversions.h" - -#include - - -//=========================================================================== -double CNS::sumit(const std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0.); -} - -double CNS::sumit(const std::vector& v1, const std::vector& v2) { - return std::accumulate(v1.begin(), v1.end(), std::accumulate(v2.begin(), v2.end(), 0.)); -} - -int CNS::Counter::s_count; - -CNS::Counter::Counter() { - ++s_count; -} - -CNS::Counter::Counter(const Counter&) { - ++s_count; -} - -CNS::Counter& CNS::Counter::operator=(const Counter&) { - return *this; -} - -CNS::Counter::~Counter() { - --s_count; -} - -double CNS::myhowmany(const std::vector& v) { - return v.size(); -} - -double CNS::myhowmany(const std::vector& v1, const std::vector& v2) { - return v1.size() + v2.size(); -} - -int CNS::sumints(const std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0); -} - -int CNS::sumints(const std::vector& v1, const std::vector& v2) { - return std::accumulate(v1.begin(), v1.end(), std::accumulate(v2.begin(), v2.end(), 0)); -} - -double notallowed(std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0.); -} diff --git a/bindings/pyroot/cppyy/cppyy/test/conversions.xml b/bindings/pyroot/cppyy/cppyy/test/conversions.xml deleted file mode 100644 index e42409b276ad8..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/conversions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/cpp11features.cxx b/bindings/pyroot/cppyy/cppyy/test/cpp11features.cxx deleted file mode 100644 index edbe06978a7e8..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/cpp11features.cxx +++ /dev/null @@ -1,69 +0,0 @@ -#include "cpp11features.h" - - -// for std::shared/unique_ptr<> testing -int TestSmartPtr::s_counter = 0; - -std::shared_ptr create_shared_ptr_instance() { - return std::shared_ptr(new TestSmartPtr); -} - -std::unique_ptr create_unique_ptr_instance() { - return std::unique_ptr(new TestSmartPtr); -} - -int TestSmartPtr::get_value() { - return 17; -} - -int DerivedTestSmartPtr::get_value() { - return m_int + 76; -} - -int pass_shared_ptr(std::shared_ptr p) { - return p->get_value(); -} - -int move_shared_ptr(std::shared_ptr&& p) { - return p->get_value(); -} - -int move_unique_ptr(std::unique_ptr&& p) { - return p->get_value(); -} - -int move_unique_ptr_derived(std::unique_ptr&& p) { - return p->get_value(); -} - -TestSmartPtr create_TestSmartPtr_by_value() { - return TestSmartPtr{}; -} - -std::shared_ptr create_shared_ptr_to_derived() { - return std::shared_ptr(new PubDerivedTestSmartPtr); -} - -std::unique_ptr create_unique_ptr_to_derived() { - return std::unique_ptr(new PubDerivedTestSmartPtr); -} - -std::unique_ptr create_unique_ptr_to_offset_derived() { - return std::unique_ptr(new MultiDerivedTestSmartPtr); -} - - -// for move ctors etc. -int TestMoving1::s_move_counter = 0; -int TestMoving1::s_instance_counter = 0; -int TestMoving2::s_move_counter = 0; -int TestMoving2::s_instance_counter = 0; - -void implicit_converion_move(TestMoving2&&) { - /* empty */ -} - - -// for std::function testing -std::function FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } -std::function FunctionNS::FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } diff --git a/bindings/pyroot/cppyy/cppyy/test/cpp11features.h b/bindings/pyroot/cppyy/cppyy/test/cpp11features.h deleted file mode 100644 index 1ef186b60f01c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/cpp11features.h +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include - - -//=========================================================================== -class TestSmartPtr { // for std::shared/unique_ptr<> testing -public: - static int s_counter; - -public: - TestSmartPtr() { ++s_counter; } - TestSmartPtr(const TestSmartPtr&) { ++s_counter; } - virtual ~TestSmartPtr() { --s_counter; } - -public: - virtual int get_value(); -}; - -std::shared_ptr create_shared_ptr_instance(); -std::unique_ptr create_unique_ptr_instance(); - -class DerivedTestSmartPtr : TestSmartPtr { -public: - DerivedTestSmartPtr(int i) : m_int(i) {} - virtual int get_value(); - -public: - int m_int; -}; - -int pass_shared_ptr(std::shared_ptr p); -int move_shared_ptr(std::shared_ptr&& p); -int move_unique_ptr(std::unique_ptr&& p); -int move_unique_ptr_derived(std::unique_ptr&& p); - -TestSmartPtr create_TestSmartPtr_by_value(); - -// for auto-downcast of objects returned through a smart pointer -class PubDerivedTestSmartPtr : public TestSmartPtr { -public: - int only_in_derived() { return 27; } -}; - -// second base so that the cross-cast to the most derived type needs a -// non-zero pointer adjustment, which the smart pointer's dereferencer can -// not apply consistently (so no down-cast should happen in that case) -class TestSmartPtrIface { -public: - virtual ~TestSmartPtrIface() {} - long m_pad = 0; - int only_in_iface() { return 37; } -}; - -class MultiDerivedTestSmartPtr : public PubDerivedTestSmartPtr, public TestSmartPtrIface { -}; - -std::shared_ptr create_shared_ptr_to_derived(); -std::unique_ptr create_unique_ptr_to_derived(); -std::unique_ptr create_unique_ptr_to_offset_derived(); - -// sinks expecting a smart pointer to the *derived* type; a base-class smart -// pointer (even when its object was auto-down-cast) must not be accepted here -int pass_unique_ptr_to_derived(std::unique_ptr p) { - return p->only_in_derived(); -} - -int pass_shared_ptr_to_derived(std::shared_ptr p) { - return p->only_in_derived(); -} - -// Overloaded function to check if automatic downcasting is consistently -// applied for regular proxy objects and smart pointer proxies. -std::string pass_ptr_overloaded(TestSmartPtr *) { return "TestSmartPtr"; } -std::string pass_ptr_overloaded(PubDerivedTestSmartPtr *) { return "PubDerivedTestSmartPtr"; } -std::string pass_ref_overloaded(TestSmartPtr &) { return "TestSmartPtr"; } -std::string pass_ref_overloaded(PubDerivedTestSmartPtr &) { return "PubDerivedTestSmartPtr"; } -std::string pass_val_overloaded(TestSmartPtr) { return "TestSmartPtr"; } -std::string pass_val_overloaded(PubDerivedTestSmartPtr) { return "PubDerivedTestSmartPtr"; } - - -//=========================================================================== -class TestMoving1 { // for move ctors etc. -public: - static int s_move_counter; - static int s_instance_counter; - -public: - TestMoving1() { ++s_instance_counter; } - TestMoving1(TestMoving1&&) { ++s_move_counter; ++s_instance_counter; } - TestMoving1(const TestMoving1&) { ++s_instance_counter; } - TestMoving1& operator=(TestMoving1&&) { ++s_move_counter; return *this; } - TestMoving1& operator=(TestMoving1&) { return *this; } - ~TestMoving1() { --s_instance_counter; } -}; - -class TestMoving2 { // note opposite method order from TestMoving1 -public: - static int s_move_counter; - static int s_instance_counter; - -public: - TestMoving2() { ++s_instance_counter; } - TestMoving2(const TestMoving1&) { ++s_instance_counter; } - TestMoving2(const TestMoving2&) { ++s_instance_counter; } - TestMoving2(TestMoving2&& other) { ++s_move_counter; ++s_instance_counter; } - TestMoving2& operator=(TestMoving2&) { return *this; } - TestMoving2& operator=(TestMoving2&&) { ++s_move_counter; return *this; } - ~TestMoving2() { --s_instance_counter; } -}; - -void implicit_converion_move(TestMoving2&&); - - -//=========================================================================== -struct TestData { // for initializer list construction - TestData(int i=0) : m_int(i) {} - int m_int; -}; - -struct TestData2 { - TestData2(int i=0) : m_int(i) {} - virtual ~TestData2() {} - int m_int; -}; - -template -class WithInitList { -public: - WithInitList(std::initializer_list ll) : m_data(ll) {} - const T& operator[](int i) { return m_data[i]; } - - int size() { return m_data.size(); } - -private: - std::vector m_data; -}; - - -//=========================================================================== -struct FNTestStruct { // for std::function<> testing - FNTestStruct(int i) : t(i) {} - int t; -}; -std::function FNCreateTestStructFunc(); - -namespace FunctionNS { - struct FNTestStruct { FNTestStruct(int i) : t(i) {} int t; }; - std::function FNCreateTestStructFunc(); -} diff --git a/bindings/pyroot/cppyy/cppyy/test/cpp11features.xml b/bindings/pyroot/cppyy/cppyy/test/cpp11features.xml deleted file mode 100644 index e6cf2188bd803..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/cpp11features.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.cxx b/bindings/pyroot/cppyy/cppyy/test/crossinheritance.cxx deleted file mode 100644 index 8dacb1609e8ff..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.cxx +++ /dev/null @@ -1,130 +0,0 @@ -#include "crossinheritance.h" - - -// for overridden method checking -CrossInheritance::Base1::~Base1() {} - -int CrossInheritance::Base1::call_get_value(Base1* b) { - return b->get_value(); -} - -int CrossInheritance::Base1::call_sum_value(Base1* b, int i) { - return b->sum_value(i); -} - -int CrossInheritance::Base1::call_sum_all(Base1* b, int i) { - return b->sum_all(i); -} - -int CrossInheritance::Base1::call_sum_all(Base1* b, int i, int j) { - return b->sum_all(i, j); -} - -int CrossInheritance::Base1::sum_pass_value(Base1* b) { - int a = 0; - a += b->pass_value1(1); - int i = 2; - a += b->pass_value2(i); - a += b->pass_value3(3); - a += b->pass_value4(*b); - a += b->pass_value5(*b); - return a; -} - -int CrossInheritance::IBase2::call_get_value(IBase2* b) { - return b->get_value(); -} - -CrossInheritance::IBase3::IBase3(int i) { - m_int = i; -} - -int CrossInheritance::CBase2::get_value() { - return 42; -} - -int CrossInheritance::IBase4::call_get_value(IBase4* b) { - return b->get_value(); -} - -int CrossInheritance::CBase4::get_value() const { - return 27; -} - -int CrossInheritance::TDerived1::get_value() { - return 27; -} - -int CrossInheritance::CountableBase::s_count = 0; - -CrossInheritance::CountableBase::CountableBase() { - ++s_count; -} - -CrossInheritance::CountableBase::CountableBase(const CountableBase&) { - ++s_count; -} - -CrossInheritance::CountableBase& CrossInheritance::CountableBase::operator=(const CountableBase&) { - return *this; -} - -CrossInheritance::CountableBase::~CountableBase() { - --s_count; -} - -int CrossInheritance::CountableBase::call() { - return -1; -} - -CrossInheritance::Component::Component() { - ++s_count; -} - -CrossInheritance::Component::~Component() { - --s_count; -} - -int CrossInheritance::Component::get_count() { - return s_count; -} - -int CrossInheritance::Component::s_count = 0; - -namespace { - -class ComponentWithValue : public CrossInheritance::Component { -public: - ComponentWithValue(int value) : m_value(value) {} - int getValue() { return m_value; } - -protected: - int m_value; -}; - -} // unnamed namespace - -CrossInheritance::Component* CrossInheritance::build_component(int value) { - return new ComponentWithValue(value); -} - -CrossInheritance::Component* CrossInheritance::cycle_component(Component* c) { - return c; -} - -// for protected member testing -AccessProtected::MyBase::MyBase() : my_data(101) { - /* empty */ -} - -AccessProtected::MyBase::~MyBase() { - /* empty */ -} - -int AccessProtected::MyBase::get_data_v() { - return my_data; -} - -int AccessProtected::MyBase::get_data() { - return my_data; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.h b/bindings/pyroot/cppyy/cppyy/test/crossinheritance.h deleted file mode 100644 index a8c32002941bb..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef CPPYY_TEST_CROSSINHERITANCE_H -#define CPPYY_TEST_CROSSINHERITANCE_H - -#include - - -//=========================================================================== -namespace CrossInheritance { - -class Base1 { // for overridden method checking -public: - Base1() : m_int(42) {} - Base1(int i) : m_int(i) {} - virtual ~Base1(); - - virtual int get_value() { return m_int; } - static int call_get_value(Base1* b); - - virtual int sum_value(int i) { return m_int + i; } - static int call_sum_value(Base1* b, int); - - virtual int sum_all(int i) { return m_int + i; } - virtual int sum_all(int i, int j) { return m_int + i + j; } - static int call_sum_all(Base1* b, int); - static int call_sum_all(Base1* b, int, int); - - virtual int pass_value1(int a) { return a; } - virtual int pass_value2(int& a) { return a; } - virtual int pass_value3(const int& a) { return a; } - virtual int pass_value4(const Base1& b) { return b.m_int; } - virtual int pass_value5(Base1& b) { return b.m_int; } - static int sum_pass_value(Base1* b); - -public: - int m_int; -}; - -class IBase2 { -public: - IBase2() {} - virtual ~IBase2() {} - virtual int get_value() = 0; - static int call_get_value(IBase2* b); -}; - -class IBase3 : IBase2 { -public: - IBase3(int); - int m_int; -}; - -class CBase2 : public IBase2 { -public: - int get_value(); -}; - -class IBase4 { -public: - IBase4() {} - virtual ~IBase4() {} - virtual int get_value() const = 0; // <- const, as opposed to IBase2 - static int call_get_value(IBase4* b); -}; - -class CBase4 : public IBase4 { -public: - int get_value() const; -}; - -template -class TBase1 { -public: - virtual ~TBase1() {} - virtual int get_value() { - return 42; - } -}; - -class TDerived1 : public TBase1 { -public: - int get_value(); -}; - -using TBase1_I = TBase1; - -class CountableBase { -public: - CountableBase(); - CountableBase(const CountableBase&); - CountableBase& operator=(const CountableBase&); - virtual ~CountableBase(); - - virtual int call(); - - static int s_count; -}; - -class Component { -public: - Component(); - Component(const Component&) = delete; - Component& operator=(const Component&) = delete; - virtual ~Component(); - - static int get_count(); - -private: - static int s_count; -}; - -Component* build_component(int value); -Component* cycle_component(Component* c); - -} // namespace CrossInheritance - - -//=========================================================================== -namespace AccessProtected { // for protected member testing - -class MyBase { -public: - MyBase(); - virtual ~MyBase(); - -protected: - virtual int get_data_v(); - int get_data(); - -protected: - int my_data; -}; - -} // AccessProtected - -#endif // !CPPYY_TEST_CROSSINHERITANCE_H diff --git a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.xml b/bindings/pyroot/cppyy/cppyy/test/crossinheritance.xml deleted file mode 100644 index 12db5fcaa3b07..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/crossinheritance.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/datatypes.cxx b/bindings/pyroot/cppyy/cppyy/test/datatypes.cxx deleted file mode 100644 index f2c4bf3424901..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/datatypes.cxx +++ /dev/null @@ -1,1012 +0,0 @@ -#include "datatypes.h" - - -//=========================================================================== -std::vector vecFruits{kCitrus, kApple}; - - -//=========================================================================== -CppyyTestData::CppyyTestData() : m_const_int(17), m_owns_arrays(false) -{ - m_bool = false; - m_char = 'a'; - m_schar = 'b'; - m_uchar = 'c'; - m_wchar = L'D'; - m_char16 = u'\u00df'; - m_char32 = U'\u00df'; - m_byte = (std::byte)'d'; - m_int8 = -9; - m_uint8 = 9; - m_short = -11; - m_ushort = 11u; - m_int = -22; - m_uint = 22u; - m_long = -33l; - m_ulong = 33ul; - m_llong = -44ll; - m_ullong = 44ull; - m_long64 = -55ll; - m_ulong64 = 55ull; - m_float = -66.f; - m_double = -77.; - m_ldouble = -88.l; - m_complex = {99., 101.}; - m_icomplex = {121, 141}; - m_ccomplex = {151., 161.}; - m_enum = kNothing; - m_voidp = (void*)0; - - m_bool_array2 = new bool[N]; - m_schar_array2 = new signed char[N]; - m_uchar_array2 = new unsigned char[N]; - m_byte_array2 = new std::byte[N]; - m_int8_array2 = new int8_t[N]; - m_uint8_array2 = new uint8_t[N]; - m_short_array2 = new short[N]; - m_ushort_array2 = new unsigned short[N]; - m_int_array2 = new int[N]; - m_uint_array2 = new unsigned int[N]; - m_long_array2 = new long[N]; - m_ulong_array2 = new unsigned long[N]; - - m_float_array2 = new float[N]; - m_double_array2 = new double[N]; - m_complex_array2 = new complex_t[N]; - m_ccomplex_array2 = new ccomplex_t[N]; - - for (int i = 0; i < N; ++i) { - m_bool_array[i] = bool(i%2); - m_bool_array2[i] = bool((i+1)%2); - m_uchar_array[i] = 1u*i; - m_uchar_array2[i] = 2u*i; - m_byte_array[i] = (std::byte)(3u*i); - m_byte_array2[i] = (std::byte)(4u*i); - m_int8_array[i] = - 1*i; - m_int8_array2[i] = - 2*i; - m_uint8_array[i] = 3u*i; - m_uint8_array2[i] = 4u*i; - m_short_array[i] = - 5*i; - m_short_array2[i] = - 6*i; - m_ushort_array[i] = 7u*i; - m_ushort_array2[i] = 8u*i; - m_int_array[i] = - 9*i; - m_int_array2[i] = -10*i; - m_uint_array[i] = 11u*i; - m_uint_array2[i] = 23u*i; - m_long_array[i] = -13l*i; - m_long_array2[i] = -14l*i; - m_ulong_array[i] = 15ul*i; - m_ulong_array2[i] = 18ul*i; - - m_float_array[i] = -13.f*i; - m_float_array2[i] = -14.f*i; - m_double_array[i] = -15.*i; - m_double_array2[i] = -16.*i; - m_complex_array2[i] = {17.*i, 18.*i}; - m_ccomplex_array2[i] = {19.*i, 20.*i}; - } - - m_owns_arrays = true; - - m_pod.m_int = 888; - m_pod.m_double = 3.14; - - m_ppod = &m_pod; -}; - -CppyyTestData::~CppyyTestData() -{ - destroy_arrays(); -} - -void CppyyTestData::destroy_arrays() { - if (m_owns_arrays == true) { - delete[] m_bool_array2; - delete[] m_uchar_array2; - delete[] m_byte_array2; - delete[] m_int8_array2; - delete[] m_uint8_array2; - delete[] m_short_array2; - delete[] m_ushort_array2; - delete[] m_int_array2; - delete[] m_uint_array2; - delete[] m_long_array2; - delete[] m_ulong_array2; - - delete[] m_float_array2; - delete[] m_double_array2; - delete[] m_complex_array2; - delete[] m_ccomplex_array2; - - m_owns_arrays = false; - } -} - -//- getters ----------------------------------------------------------------- -bool CppyyTestData::get_bool() { return m_bool; } -char CppyyTestData::get_char() { return m_char; } -signed char CppyyTestData::get_schar() { return m_schar; } -unsigned char CppyyTestData::get_uchar() { return m_uchar; } -wchar_t CppyyTestData::get_wchar() { return m_wchar; } -char16_t CppyyTestData::get_char16() { return m_char16; } -char32_t CppyyTestData::get_char32() { return m_char32; } -std::byte CppyyTestData::get_byte() { return m_byte; } -int8_t CppyyTestData::get_int8() { return m_int8; } -uint8_t CppyyTestData::get_uint8() { return m_uint8; } -short CppyyTestData::get_short() { return m_short; } -unsigned short CppyyTestData::get_ushort() { return m_ushort; } -int CppyyTestData::get_int() { return m_int; } -unsigned int CppyyTestData::get_uint() { return m_uint; } -long CppyyTestData::get_long() { return m_long; } -unsigned long CppyyTestData::get_ulong() { return m_ulong; } -long long CppyyTestData::get_llong() { return m_llong; } -unsigned long long CppyyTestData::get_ullong() { return m_ullong; } -Long64_t CppyyTestData::get_long64() { return m_long64; } -ULong64_t CppyyTestData::get_ulong64() { return m_ulong64; } -float CppyyTestData::get_float() { return m_float; } -double CppyyTestData::get_double() { return m_double; } -long double CppyyTestData::get_ldouble() { return m_ldouble; } -long double CppyyTestData::get_ldouble_def(long double ld) { return ld; } -complex_t CppyyTestData::get_complex() { return m_complex; } -icomplex_t CppyyTestData::get_icomplex() { return m_icomplex; } -ccomplex_t CppyyTestData::get_ccomplex() { return m_ccomplex; } -CppyyTestData::EWhat CppyyTestData::get_enum() { return m_enum; } -void* CppyyTestData::get_voidp() { return m_voidp; } - -bool* CppyyTestData::get_bool_array() { return m_bool_array; } -bool* CppyyTestData::get_bool_array2() { return m_bool_array2; } -signed char* CppyyTestData::get_schar_array() { return m_schar_array; } -signed char* CppyyTestData::get_schar_array2() { return m_schar_array2; } -unsigned char* CppyyTestData::get_uchar_array() { return m_uchar_array; } -unsigned char* CppyyTestData::get_uchar_array2() { return m_uchar_array2; } -std::byte* CppyyTestData::get_byte_array() { return m_byte_array; } -std::byte* CppyyTestData::get_byte_array2() { return m_byte_array2; } -int8_t* CppyyTestData::get_int8_array() { return m_int8_array; } -int8_t* CppyyTestData::get_int8_array2() { return m_int8_array2; } -uint8_t* CppyyTestData::get_uint8_array() { return m_uint8_array; } -uint8_t* CppyyTestData::get_uint8_array2() { return m_uint8_array2; } -short* CppyyTestData::get_short_array() { return m_short_array; } -short* CppyyTestData::get_short_array2() { return m_short_array2; } -unsigned short* CppyyTestData::get_ushort_array() { return m_ushort_array; } -unsigned short* CppyyTestData::get_ushort_array2() { return m_ushort_array2; } -int* CppyyTestData::get_int_array() { return m_int_array; } -int* CppyyTestData::get_int_array2() { return m_int_array2; } -unsigned int* CppyyTestData::get_uint_array() { return m_uint_array; } -unsigned int* CppyyTestData::get_uint_array2() { return m_uint_array2; } -long* CppyyTestData::get_long_array() { return m_long_array; } -long* CppyyTestData::get_long_array2() { return m_long_array2; } -unsigned long* CppyyTestData::get_ulong_array() { return m_ulong_array; } -unsigned long* CppyyTestData::get_ulong_array2() { return m_ulong_array2; } - -float* CppyyTestData::get_float_array() { return m_float_array; } -float* CppyyTestData::get_float_array2() { return m_float_array2; } -double* CppyyTestData::get_double_array() { return m_double_array; } -double* CppyyTestData::get_double_array2() { return m_double_array2; } -complex_t* CppyyTestData::get_complex_array() { return m_complex_array; } -complex_t* CppyyTestData::get_complex_array2() { return m_complex_array2; } -ccomplex_t* CppyyTestData::get_ccomplex_array() { return m_ccomplex_array; } -ccomplex_t* CppyyTestData::get_ccomplex_array2() { return m_ccomplex_array2; } - -CppyyTestPod CppyyTestData::get_pod_val() { return m_pod; } -CppyyTestPod* CppyyTestData::get_pod_val_ptr() { return &m_pod; } -CppyyTestPod& CppyyTestData::get_pod_val_ref() { return m_pod; } -CppyyTestPod*& CppyyTestData::get_pod_ptrref() { return m_ppod; } - -CppyyTestPod* CppyyTestData::get_pod_ptr() { return m_ppod; } - -//- getters const-ref ------------------------------------------------------- -const bool& CppyyTestData::get_bool_cr() { return m_bool; } -const char& CppyyTestData::get_char_cr() { return m_char; } -const signed char& CppyyTestData::get_schar_cr() { return m_schar; } -const unsigned char& CppyyTestData::get_uchar_cr() { return m_uchar; } -const wchar_t& CppyyTestData::get_wchar_cr() { return m_wchar; } -const char16_t& CppyyTestData::get_char16_cr() { return m_char16; } -const char32_t& CppyyTestData::get_char32_cr() { return m_char32; } -const std::byte& CppyyTestData::get_byte_cr() { return m_byte; } -const int8_t& CppyyTestData::get_int8_cr() { return m_int8; } -const uint8_t& CppyyTestData::get_uint8_cr() { return m_uint8; } -const short& CppyyTestData::get_short_cr() { return m_short; } -const unsigned short& CppyyTestData::get_ushort_cr() { return m_ushort; } -const int& CppyyTestData::get_int_cr() { return m_int; } -const unsigned int& CppyyTestData::get_uint_cr() { return m_uint; } -const long& CppyyTestData::get_long_cr() { return m_long; } -const unsigned long& CppyyTestData::get_ulong_cr() { return m_ulong; } -const long long& CppyyTestData::get_llong_cr() { return m_llong; } -const unsigned long long& CppyyTestData::get_ullong_cr() { return m_ullong; } -const Long64_t& CppyyTestData::get_long64_cr() { return m_long64; } -const ULong64_t& CppyyTestData::get_ulong64_cr() { return m_ulong64; } -const float& CppyyTestData::get_float_cr() { return m_float; } -const double& CppyyTestData::get_double_cr() { return m_double; } -const long double& CppyyTestData::get_ldouble_cr() { return m_ldouble; } -const complex_t& CppyyTestData::get_complex_cr() { return m_complex; } -const icomplex_t& CppyyTestData::get_icomplex_cr() { return m_icomplex; } -const ccomplex_t& CppyyTestData::get_ccomplex_cr() { return m_ccomplex; } -const CppyyTestData::EWhat& CppyyTestData::get_enum_cr() { return m_enum; } - -//- getters ref ------------------------------------------------------------- -bool& CppyyTestData::get_bool_r() { return m_bool; } -char& CppyyTestData::get_char_r() { return m_char; } -signed char& CppyyTestData::get_schar_r() { return m_schar; } -unsigned char& CppyyTestData::get_uchar_r() { return m_uchar; } -wchar_t& CppyyTestData::get_wchar_r() { return m_wchar; } -char16_t& CppyyTestData::get_char16_r() { return m_char16; } -char32_t& CppyyTestData::get_char32_r() { return m_char32; } -std::byte& CppyyTestData::get_byte_r() { return m_byte; } -int8_t& CppyyTestData::get_int8_r() { return m_int8; } -uint8_t& CppyyTestData::get_uint8_r() { return m_uint8; } -short& CppyyTestData::get_short_r() { return m_short; } -unsigned short& CppyyTestData::get_ushort_r() { return m_ushort; } -int& CppyyTestData::get_int_r() { return m_int; } -unsigned int& CppyyTestData::get_uint_r() { return m_uint; } -long& CppyyTestData::get_long_r() { return m_long; } -unsigned long& CppyyTestData::get_ulong_r() { return m_ulong; } -long long& CppyyTestData::get_llong_r() { return m_llong; } -unsigned long long& CppyyTestData::get_ullong_r() { return m_ullong; } -Long64_t& CppyyTestData::get_long64_r() { return m_long64; } -ULong64_t& CppyyTestData::get_ulong64_r() { return m_ulong64; } -float& CppyyTestData::get_float_r() { return m_float; } -double& CppyyTestData::get_double_r() { return m_double; } -long double& CppyyTestData::get_ldouble_r() { return m_ldouble; } -complex_t& CppyyTestData::get_complex_r() { return m_complex; } -icomplex_t& CppyyTestData::get_icomplex_r() { return m_icomplex; } -ccomplex_t& CppyyTestData::get_ccomplex_r() { return m_ccomplex; } -CppyyTestData::EWhat& CppyyTestData::get_enum_r() { return m_enum; } - -//- setters ----------------------------------------------------------------- -void CppyyTestData::set_bool(bool b) { m_bool = b; } -void CppyyTestData::set_char(char c) { m_char = c; } -void CppyyTestData::set_schar(signed char sc) { m_schar = sc; } -void CppyyTestData::set_uchar(unsigned char uc) { m_uchar = uc; } -void CppyyTestData::set_wchar(wchar_t wc) { m_wchar = wc; } -void CppyyTestData::set_char16(char16_t c16) { m_char16 = c16; } -void CppyyTestData::set_char32(char32_t c32) { m_char32 = c32; } -void CppyyTestData::set_byte(std::byte b) { m_byte = b; } -void CppyyTestData::set_int8(int8_t s8) { m_int8 = s8; } -void CppyyTestData::set_uint8(uint8_t u8) { m_uint8 = u8; } -void CppyyTestData::set_short(short s) { m_short = s; } -void CppyyTestData::set_ushort(unsigned short us) { m_ushort = us; } -void CppyyTestData::set_int(int i) { m_int = i; } -void CppyyTestData::set_uint(unsigned int ui) { m_uint = ui; } -void CppyyTestData::set_long(long l) { m_long = l; } -void CppyyTestData::set_ulong(unsigned long ul) { m_ulong = ul; } -void CppyyTestData::set_llong(long long ll) { m_llong = ll; } -void CppyyTestData::set_ullong(unsigned long long ull) { m_ullong = ull; } -void CppyyTestData::set_long64(Long64_t l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64(ULong64_t ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float(float f) { m_float = f; } -void CppyyTestData::set_double(double d) { m_double = d; } -void CppyyTestData::set_ldouble(long double ld) { m_ldouble = ld; } -void CppyyTestData::set_complex(complex_t cd) { m_complex = cd; } -void CppyyTestData::set_icomplex(icomplex_t ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex(ccomplex_t cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum(EWhat w) { m_enum = w; } -void CppyyTestData::set_voidp(void* p) { m_voidp = p; } - -void CppyyTestData::set_pod_val(CppyyTestPod p) { m_pod = p; } -void CppyyTestData::set_pod_ptr_in(CppyyTestPod* pp) { m_pod = *pp; } -void CppyyTestData::set_pod_ptr_out(CppyyTestPod* pp) { *pp = m_pod; } -void CppyyTestData::set_pod_ref(const CppyyTestPod& rp) { m_pod = rp; } -void CppyyTestData::set_pod_ptrptr_in(CppyyTestPod** ppp) { m_pod = **ppp; } -void CppyyTestData::set_pod_void_ptrptr_in(void** pp) { m_pod = **((CppyyTestPod**)pp); } -void CppyyTestData::set_pod_ptrptr_out(CppyyTestPod** ppp) { delete *ppp; *ppp = new CppyyTestPod(m_pod); } -void CppyyTestData::set_pod_void_ptrptr_out(void** pp) { delete *((CppyyTestPod**)pp); - *((CppyyTestPod**)pp) = new CppyyTestPod(m_pod); } - -void CppyyTestData::set_pod_ptr(CppyyTestPod* pp) { m_ppod = pp; } - -//- setters const-ref ------------------------------------------------------- -void CppyyTestData::set_bool_cr(const bool& b) { m_bool = b; } -void CppyyTestData::set_char_cr(const char& c) { m_char = c; } -void CppyyTestData::set_schar_cr(const signed char& sc) { m_schar = sc; } -void CppyyTestData::set_uchar_cr(const unsigned char& uc) { m_uchar = uc; } -void CppyyTestData::set_wchar_cr(const wchar_t& wc) { m_wchar = wc; } -void CppyyTestData::set_char16_cr(const char16_t& c16) { m_char16 = c16; } -void CppyyTestData::set_char32_cr(const char32_t& c32) { m_char32 = c32; } -void CppyyTestData::set_byte_cr(const std::byte& b) { m_byte = b; } -void CppyyTestData::set_int8_cr(const int8_t& s8) { m_int8 = s8; } -void CppyyTestData::set_uint8_cr(const uint8_t& u8) { m_uint8 = u8; } -void CppyyTestData::set_short_cr(const short& s) { m_short = s; } -void CppyyTestData::set_ushort_cr(const unsigned short& us) { m_ushort = us; } -void CppyyTestData::set_int_cr(const int& i) { m_int = i; } -void CppyyTestData::set_uint_cr(const unsigned int& ui) { m_uint = ui; } -void CppyyTestData::set_long_cr(const long& l) { m_long = l; } -void CppyyTestData::set_ulong_cr(const unsigned long& ul) { m_ulong = ul; } -void CppyyTestData::set_llong_cr(const long long& ll) { m_llong = ll; } -void CppyyTestData::set_ullong_cr(const unsigned long long& ull) { m_ullong = ull; } -void CppyyTestData::set_long64_cr(const Long64_t& l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64_cr(const ULong64_t& ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float_cr(const float& f) { m_float = f; } -void CppyyTestData::set_double_cr(const double& d) { m_double = d; } -void CppyyTestData::set_ldouble_cr(const long double& ld) { m_ldouble = ld; } -void CppyyTestData::set_complex_cr(const complex_t& cd) { m_complex = cd; } -void CppyyTestData::set_icomplex_cr(const icomplex_t& ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex_cr(const ccomplex_t& cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum_cr(const EWhat& w) { m_enum = w; } - -//- setters ref ------------------------------------------------------------- -void CppyyTestData::set_bool_r(bool& b) { b = true; } -void CppyyTestData::set_char_r(char& c) { c = 'a'; } -void CppyyTestData::set_wchar_r(wchar_t& wc) { wc = 'b'; } -void CppyyTestData::set_char16_r(char16_t& c16) { c16 = u'\u6c24'; } -void CppyyTestData::set_char32_r(char32_t& c32) { c32 = U'\U0001f34e'; } -void CppyyTestData::set_schar_r(signed char& sc) { sc = 'c'; } -void CppyyTestData::set_uchar_r(unsigned char& uc) { uc = 'd'; } -void CppyyTestData::set_byte_r(std::byte& b) { b = (std::byte)'e'; } -void CppyyTestData::set_short_r(short& s) { s = -1; } -void CppyyTestData::set_ushort_r(unsigned short& us) { us = 2; } -void CppyyTestData::set_int_r(int& i) { i = -3; } -void CppyyTestData::set_uint_r(unsigned int& ui) { ui = 4; } -void CppyyTestData::set_long_r(long& l) { l = -5; } -void CppyyTestData::set_ulong_r(unsigned long& ul) { ul = 6; } -void CppyyTestData::set_llong_r(long long& ll) { ll = -7; } -void CppyyTestData::set_ullong_r(unsigned long long& ull) { ull = 8; } -void CppyyTestData::set_float_r(float& f) { f = 5.f; } -void CppyyTestData::set_double_r(double& d) { d = -5.; } -void CppyyTestData::set_ldouble_r(long double& ld) { ld = 10.l; } - -//- setters ptr ------------------------------------------------------------- -void CppyyTestData::set_bool_p(bool* b) { *b = true; } -void CppyyTestData::set_char_p(char* c) { *c = 'a'; } -void CppyyTestData::set_wchar_p(wchar_t* wc) { *wc = 'b'; } -void CppyyTestData::set_char16_p(char16_t* c16) { *c16 = u'\u6c24'; } -void CppyyTestData::set_char32_p(char32_t* c32) { *c32 = U'\U0001f34e'; } -void CppyyTestData::set_schar_p(signed char* sc) { *sc = 'c'; } -void CppyyTestData::set_uchar_p(unsigned char* uc) { *uc = 'd'; } -void CppyyTestData::set_byte_p(std::byte* b) { *b = (std::byte)'e'; } -void CppyyTestData::set_int8_p(int8_t* i8) { *i8 = -27; } -void CppyyTestData::set_uint8_p(uint8_t* ui8) { *ui8 = 28; } -void CppyyTestData::set_short_p(short* s) { *s = -1; } -void CppyyTestData::set_ushort_p(unsigned short* us) { *us = 2; } -void CppyyTestData::set_int_p(int* i) { *i = -3; } -void CppyyTestData::set_uint_p(unsigned int* ui) { *ui = 4; } -void CppyyTestData::set_long_p(long* l) { *l = -5; } -void CppyyTestData::set_ulong_p(unsigned long* ul) { *ul = 6; } -void CppyyTestData::set_llong_p(long long* ll) { *ll = -7; } -void CppyyTestData::set_ullong_p(unsigned long long* ull) { *ull = 8; } -void CppyyTestData::set_float_p(float* f) { *f = 5.f; } -void CppyyTestData::set_double_p(double* d) { *d = -5.; } -void CppyyTestData::set_ldouble_p(long double* ld) { *ld = 10.l; } - -//- setters ptrptr ---------------------------------------------------------- -void CppyyTestData::set_bool_ppa(bool** b) { - (*b) = new bool[3]; - (*b)[0] = true; (*b)[1] = false; (*b)[2] = true; -} -void CppyyTestData::set_char_ppa(char** c) { - (*c) = new char[3]; - (*c)[0] = 'a'; (*c)[1] = 'b'; (*c)[2] = 'c'; -} -void CppyyTestData::set_wchar_ppa(wchar_t** wc) { - (*wc) = new wchar_t[3]; - (*wc)[0] = 'd'; (*wc)[1] = 'e'; (*wc)[2] = 'f'; -} -void CppyyTestData::set_char16_ppa(char16_t** c16) { - (*c16) = new char16_t[3]; - (*c16)[0] = u'\u6c24'; (*c16)[1] = u'\u6c25'; (*c16)[2] = u'\u6c26'; -} -void CppyyTestData::set_char32_ppa(char32_t** c32) { - (*c32) = new char32_t[3]; - (*c32)[0] = U'\U0001f34d'; (*c32)[1] = U'\U0001f34e'; (*c32)[2] = U'\U0001f34f'; -} -void CppyyTestData::set_schar_ppa(signed char** sc) { - (*sc) = new signed char[3]; - (*sc)[0] = 'g'; (*sc)[1] = 'h'; (*sc)[2] = 'j'; -} -void CppyyTestData::set_uchar_ppa(unsigned char** uc) { - (*uc) = new unsigned char[3]; - (*uc)[0] = 'k'; (*uc)[1] = 'l'; (*uc)[2] = 'm'; -} -void CppyyTestData::set_byte_ppa(std::byte** b) { - (*b) = new std::byte[3]; - (*b)[0] = (std::byte)'n'; (*b)[1] = (std::byte)'o'; (*b)[2] = (std::byte)'p'; -} -void CppyyTestData::set_int8_ppa(int8_t** i8) { - (*i8) = new int8_t[3]; - (*i8)[0] = -27; (*i8)[1] = -28; (*i8)[2] = -29; -} -void CppyyTestData::set_uint8_ppa(uint8_t** ui8) { - (*ui8) = new uint8_t[3]; - (*ui8)[0] = 28; (*ui8)[1] = 29; (*ui8)[2] = 30; -} -void CppyyTestData::set_short_ppa(short** s) { - (*s) = new short[3]; - (*s)[0] = -1; (*s)[1] = -2; (*s)[2] = -3; -} -void CppyyTestData::set_ushort_ppa(unsigned short** us) { - (*us) = new unsigned short[3]; - (*us)[0] = 4; (*us)[1] = 5; (*us)[2] = 6; -} -void CppyyTestData::set_int_ppa(int** i) { - (*i) = new int[3]; - (*i)[0] = -7; (*i)[1] = -8; (*i)[2] = -9; -} -void CppyyTestData::set_uint_ppa(unsigned int** ui) { - (*ui) = new unsigned int[3]; - (*ui)[0] = 10; (*ui)[1] = 11; (*ui)[2] = 12; -} -void CppyyTestData::set_long_ppa(long** l) { - (*l) = new long[3]; - (*l)[0] = -13; (*l)[1] = -14; (*l)[2] = -15; -} -void CppyyTestData::set_ulong_ppa(unsigned long** ul) { - (*ul) = new unsigned long[3]; - (*ul)[0] = 16; (*ul)[1] = 17; (*ul)[2] = 18; -} -void CppyyTestData::set_llong_ppa(long long** ll) { - (*ll) = new long long[3]; - (*ll)[0] = -19; (*ll)[1] = -20; (*ll)[2] = -21; -} -void CppyyTestData::set_ullong_ppa(unsigned long long** ull) { - (*ull) = new unsigned long long[3]; - (*ull)[0] = 22; (*ull)[1] = 23; (*ull)[2] = 24; -} -void CppyyTestData::set_float_ppa(float** f) { - (*f) = new float[3]; - (*f)[0] = 5.f; (*f)[1] = 10.f; (*f)[2] = 20.f; -} -void CppyyTestData::set_double_ppa(double** d) { - (*d) = new double[3]; - (*d)[0] = -5; (*d)[1] = -10.; (*d)[2] = -20.; -} -void CppyyTestData::set_ldouble_ppa(long double** ld) { - (*ld) = new long double[3]; - (*ld)[0] = 5.l; (*ld)[1] = 10.f; (*ld)[2] = 20.l; -} - -intptr_t CppyyTestData::set_char_ppm(char** c) { - *c = (char*)malloc(4*sizeof(char)); - return (intptr_t)*c; -} -intptr_t CppyyTestData::set_cchar_ppm(const char** cc) { - *cc = (const char*)malloc(4*sizeof(char)); - return (intptr_t)*cc; -} -intptr_t CppyyTestData::set_wchar_ppm(wchar_t** w) { - *w = (wchar_t*)malloc(4*sizeof(wchar_t)); - return (intptr_t)*w; -} -intptr_t CppyyTestData::set_char16_ppm(char16_t** c16) { - *c16 = (char16_t*)malloc(4*sizeof(char16_t)); - return (intptr_t)*c16; -} -intptr_t CppyyTestData::set_char32_ppm(char32_t** c32) { - *c32 = (char32_t*)malloc(4*sizeof(char32_t)); - return (intptr_t)*c32; -} -intptr_t CppyyTestData::set_cwchar_ppm(const wchar_t** cw) { - *cw = (const wchar_t*)malloc(4*sizeof(wchar_t)); - return (intptr_t)*cw; -} -intptr_t CppyyTestData::set_cchar16_ppm(const char16_t** c16) { - *c16 = (const char16_t*)malloc(4*sizeof(char16_t)); - return (intptr_t)*c16; -} -intptr_t CppyyTestData::set_cchar32_ppm(const char32_t** c32) { - *c32 = (const char32_t*)malloc(4*sizeof(char32_t)); - return (intptr_t)*c32; -} -intptr_t CppyyTestData::set_void_ppm(void** v) { - *v = malloc(4*sizeof(void*)); - return (intptr_t)*v; -} - -intptr_t CppyyTestData::freeit(void* ptr) { - intptr_t out = (intptr_t)ptr; - free(ptr); - return out; -} - -//- setters r-value --------------------------------------------------------- -void CppyyTestData::set_bool_rv(bool&& b) { m_bool = b; } -void CppyyTestData::set_char_rv(char&& c) { m_char = c; } -void CppyyTestData::set_schar_rv(signed char&& sc) { m_schar = sc; } -void CppyyTestData::set_uchar_rv(unsigned char&& uc) { m_uchar = uc; } -void CppyyTestData::set_wchar_rv(wchar_t&& wc) { m_wchar = wc; } -void CppyyTestData::set_char16_rv(char16_t&& c16) { m_char16 = c16; } -void CppyyTestData::set_char32_rv(char32_t&& c32) { m_char32 = c32; } -void CppyyTestData::set_byte_rv(std::byte&& b) { m_byte = b; } -void CppyyTestData::set_int8_rv(int8_t&& s8) { m_int8 = s8; } -void CppyyTestData::set_uint8_rv(uint8_t&& u8) { m_uint8 = u8; } -void CppyyTestData::set_short_rv(short&& s) { m_short = s; } -void CppyyTestData::set_ushort_rv(unsigned short&& us) { m_ushort = us; } -void CppyyTestData::set_int_rv(int&& i) { m_int = i; } -void CppyyTestData::set_uint_rv(unsigned int&& ui) { m_uint = ui; } -void CppyyTestData::set_long_rv(long&& l) { m_long = l; } -void CppyyTestData::set_ulong_rv(unsigned long&& ul) { m_ulong = ul; } -void CppyyTestData::set_llong_rv(long long&& ll) { m_llong = ll; } -void CppyyTestData::set_ullong_rv(unsigned long long&& ull) { m_ullong = ull; } -void CppyyTestData::set_long64_rv(Long64_t&& l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64_rv(ULong64_t&& ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float_rv(float&& f) { m_float = f; } -void CppyyTestData::set_double_rv(double&& d) { m_double = d; } -void CppyyTestData::set_ldouble_rv(long double&& ld) { m_ldouble = ld; } -void CppyyTestData::set_complex_rv(complex_t&& cd) { m_complex = cd; } -void CppyyTestData::set_icomplex_rv(icomplex_t&& ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex_rv(ccomplex_t&& cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum_rv(EWhat&& w) { m_enum = w; } - -//- passers ----------------------------------------------------------------- -unsigned char* CppyyTestData::pass_array(unsigned char* a) { return a; } -short* CppyyTestData::pass_array(short* a) { return a; } -unsigned short* CppyyTestData::pass_array(unsigned short* a) { return a; } -int* CppyyTestData::pass_array(int* a) { return a; } -unsigned int* CppyyTestData::pass_array(unsigned int* a) { return a; } -long* CppyyTestData::pass_array(long* a) { return a; } -unsigned long* CppyyTestData::pass_array(unsigned long* a) { return a; } -float* CppyyTestData::pass_array(float* a) { return a; } -double* CppyyTestData::pass_array(double* a) { return a; } -complex_t* CppyyTestData::pass_array(complex_t* a) { return a; } -ccomplex_t* CppyyTestData::pass_array(ccomplex_t* a) { return a; } - -//- static data members ----------------------------------------------------- -bool CppyyTestData::s_bool = false; -char CppyyTestData::s_char = 'c'; -signed char CppyyTestData::s_schar = 's'; -unsigned char CppyyTestData::s_uchar = 'u'; -wchar_t CppyyTestData::s_wchar = L'U'; -char16_t CppyyTestData::s_char16 = u'\u6c29'; -char32_t CppyyTestData::s_char32 = U'\U0001f34b'; -std::byte CppyyTestData::s_byte = (std::byte)'b'; -int8_t CppyyTestData::s_int8 = - 87; -uint8_t CppyyTestData::s_uint8 = 87; -short CppyyTestData::s_short = -101; -unsigned short CppyyTestData::s_ushort = 255u; -int CppyyTestData::s_int = -202; -unsigned int CppyyTestData::s_uint = 202u; -long CppyyTestData::s_long = -303l; -unsigned long CppyyTestData::s_ulong = 303ul; -long long CppyyTestData::s_llong = -404ll; -unsigned long long CppyyTestData::s_ullong = 404ull; -Long64_t CppyyTestData::s_long64 = -505ll; -ULong64_t CppyyTestData::s_ulong64 = 505ull; -float CppyyTestData::s_float = -606.f; -double CppyyTestData::s_double = -707.; -long double CppyyTestData::s_ldouble = -808.l; -complex_t CppyyTestData::s_complex = {909., -909.}; -icomplex_t CppyyTestData::s_icomplex = {979, -979}; -ccomplex_t CppyyTestData::s_ccomplex = {919., -919.}; -CppyyTestData::EWhat CppyyTestData::s_enum = CppyyTestData::kNothing; -void* CppyyTestData::s_voidp = (void*)0; -std::string CppyyTestData::s_strv = "Hello"; -std::string* CppyyTestData::s_strp = nullptr; - -//- strings ----------------------------------------------------------------- -const char* CppyyTestData::get_valid_string(const char* in) { return in; } -const char* CppyyTestData::get_invalid_string() { return (const char*)0; } -const wchar_t* CppyyTestData::get_valid_wstring(const wchar_t* in) { return in; } -const wchar_t* CppyyTestData::get_invalid_wstring() { return (const wchar_t*)0; } -const char16_t* CppyyTestData::get_valid_string16(const char16_t* in) { return in; } -const char16_t* CppyyTestData::get_invalid_string16() { return (const char16_t*)0; } -const char32_t* CppyyTestData::get_valid_string32(const char32_t* in) { return in; } -const char32_t* CppyyTestData::get_invalid_string32() { return (const char32_t*)0; } - - -//= global functions ======================================================== -intptr_t get_pod_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod; -} - -intptr_t get_int_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod.m_int; -} - -intptr_t get_double_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod.m_double; -} - - -//= global variables/pointers =============================================== -bool g_bool = false; -char g_char = 'w'; -signed char g_schar = 'v'; -unsigned char g_uchar = 'u'; -wchar_t g_wchar = L'U'; -char16_t g_char16 = u'\u6c21'; -char32_t g_char32 = U'\u6c21'; -std::byte g_byte = (std::byte)'x'; -int8_t g_int8 = -66; -uint8_t g_uint8 = 66; -short g_short = -88; -unsigned short g_ushort = 88u; -int g_int = -188; -unsigned int g_uint = 188u; -long g_long = -288; -unsigned long g_ulong = 288ul; -long long g_llong = -388ll; -unsigned long long g_ullong = 388ull; -Long64_t g_long64 = -488ll; -ULong64_t g_ulong64 = 488ull; -float g_float = -588.f; -double g_double = -688.; -long double g_ldouble = -788.l; -complex_t g_complex = {808., -808.}; -icomplex_t g_icomplex = {909, -909}; -ccomplex_t g_ccomplex = {858., -858.}; -EFruit g_enum = kBanana; -void* g_voidp = nullptr; - - -//= global accessors ======================================================== -void set_global_int(int i) { - g_int = i; -} - -int get_global_int() { - return g_int; -} - -CppyyTestPod* g_pod = (CppyyTestPod*)0; - -bool is_global_pod(CppyyTestPod* t) { - return t == g_pod; -} - -void set_global_pod(CppyyTestPod* t) { - g_pod = t; -} - -CppyyTestPod* get_global_pod() { - return g_pod; -} - -CppyyTestPod* get_null_pod() { - return (CppyyTestPod*)0; -} - -std::string g_some_global_string = "C++"; -std::string get_some_global_string() { return g_some_global_string; } -std::string g_some_global_string2 = "C++"; -std::string get_some_global_string2() { return g_some_global_string2; } - -const char16_t* g_some_global_string16 = u"z\u00df\u6c34"; -const char32_t* g_some_global_string32 = U"z\u00df\u6c34\U0001f34c"; - -std::string SomeStaticDataNS::s_some_static_string = "C++"; -std::string SomeStaticDataNS::get_some_static_string() { return s_some_static_string; } -std::string SomeStaticDataNS::s_some_static_string2 = "C++"; -std::string SomeStaticDataNS::get_some_static_string2() { return s_some_static_string2; } - -StorableData gData{5.}; - - -//= special case of "byte" arrays =========================================== -int64_t sum_uc_data(unsigned char* data, int size) -{ - int64_t total = 0; - for (int i = 0; i < size; ++i) total += int64_t(data[i]); - return total; -} - -int64_t sum_byte_data(std::byte* data, int size) -{ - return sum_uc_data((unsigned char*)data, size); -} - - -//= function pointer passing ================================================ -int sum_of_int1(int i1, int i2) { - return i1+i2; -} - -int sum_of_int2(int i1, int i2) { - return 2*i1+i2; -} - -int (*sum_of_int_ptr)(int, int) = sum_of_int1; - -int call_sum_of_int(int i1, int i2) { - if (sum_of_int_ptr) - return (*sum_of_int_ptr)(i1, i2); - return -1; -} - -double sum_of_double(double d1, double d2) { - return d1+d2; -} - -double call_double_double(double (*f)(double, double), double d1, double d2) { - if (!f) return -1.; - return f(d1, d2); -} - - -//= callable passing ======================================================== -int call_int_int(int (*f)(int, int), int i1, int i2) { - return f(i1, i2); -} - -void call_void(void (*f)(int), int i) { - f(i); -} - -int call_refi(void (*f)(int&)) { - int i = -1; f(i); return i; -} - -int call_refl(void (*f)(long&)) { - long l = -1L; f(l); return l; -} - -int call_refd(void (*f)(double&)) { - double d = -1.; f(d); return d; -} - - -StoreCallable::StoreCallable(double (*f)(double, double)) : fF(f) { - /* empty */ -} - -void StoreCallable::set_callable(double (*f)(double, double)) { - fF = f; -} - -double StoreCallable::operator()(double d1, double d2) { - return fF(d1, d2); -} - -//= callable through std::function ========================================== -double call_double_double_sf(const std::function& f, double d1, double d2) { - return f(d1, d2); -} - -int call_int_int_sf(const std::function& f, int i1, int i2) { - return f(i1, i2); -} - -void call_void_sf(const std::function& f, int i) { - f(i); -} - -int call_refi_sf(const std::function& f) { - int i = -1; f(i); return i; -} - -int call_refl_sf(const std::function& f) { - long l = -1L; f(l); return l; -} - -int call_refd_sf(const std::function& f) { - double d = -1.; f(d); return d; -} - - -StoreCallable_sf::StoreCallable_sf(const std::function& f) : fF(f) { - /* empty */ -} - -void StoreCallable_sf::set_callable(const std::function& f) { - fF = f; -} - -double StoreCallable_sf::operator()(double d1, double d2) { - return fF(d1, d2); -} - - -//= array of C strings passing ============================================== -std::vector ArrayOfCStrings::takes_array_of_cstrings(const char* args[], int len) -{ - std::vector v; - v.reserve(len); - for (int i = 0; i < len; ++i) - v.emplace_back(args[i]); - - return v; -} - - -//= aggregate testing ====================================================== -int AggregateTest::Aggregate1::sInt = 17; -int AggregateTest::Aggregate2::sInt = 27; - - -//= multi-dim arrays ======================================================= -namespace MultiDimArrays { - -template -static inline T** allocate_2d(size_t N, size_t M) { - T** arr = (T**)malloc(sizeof(void*)*N); - for (size_t i = 0; i < N; ++i) - arr[i] = (T*)malloc(sizeof(T)*M); - return arr; -} - -static inline void free_2d(void** arr, size_t N) { - if (arr) { - for (size_t i = 0; i < N; ++i) - free(arr[i]); - free(arr); - } -} - -template -static inline T*** allocate_3d(size_t N, size_t M, size_t K) { - T*** arr = (T***)malloc(sizeof(void*)*N); - for (size_t i = 0; i < N; ++i) { - arr[i] = (T**)malloc(sizeof(void*)*M); - for (size_t j = 0; j < M; ++j) - arr[i][j] = (T*)malloc(sizeof(T)*K); - } - return arr; -} - -static inline void free_3d(void*** arr, size_t N, size_t M) { - if (arr) { - for (size_t i = 0; i < N; ++i) { - for (size_t j = 0; j < M; ++j) - free(arr[i][j]); - free(arr[i]); - } - free(arr); - } -} - -} // namespace MultiDimArrays - -MultiDimArrays::DataHolder::DataHolder() { - m_short2a = allocate_2d(5, 7); - m_unsigned_short2a = allocate_2d(5, 7); - m_int2a = allocate_2d(5, 7); - m_unsigned_int2a = allocate_2d(5, 7); - m_long2a = allocate_2d(5, 7); - m_unsigned_long2a = allocate_2d(5, 7); - m_long_long2a = allocate_2d(5, 7); - m_unsigned_long_long2a = allocate_2d(5, 7); - m_float2a = allocate_2d(5, 7); - m_double2a = allocate_2d(5, 7); - - for (size_t i = 0; i < 5; ++i) { - for (size_t j = 0; j < 7; ++j) { - size_t val = 5*i+j; - m_short2a[i][j] = (short)val; - m_unsigned_short2a[i][j] = (unsigned short)val; - m_int2a[i][j] = (int)val; - m_unsigned_int2a[i][j] = (unsigned int)val; - m_long2a[i][j] = (long)val; - m_unsigned_long2a[i][j] = (unsigned long)val; - m_long_long2a[i][j] = (long long)val; - m_unsigned_long_long2a[i][j] = (unsigned long long)val; - m_float2a[i][j] = (float)val; - m_double2a[i][j] = (double)val; - } - } - - m_short2b = nullptr; - m_unsigned_short2b = nullptr; - m_int2b = nullptr; - m_unsigned_int2b = nullptr; - m_long2b = nullptr; - m_unsigned_long2b = nullptr; - m_long_long2b = nullptr; - m_unsigned_long_long2b = nullptr; - m_float2b = nullptr; - m_double2b = nullptr; - - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 5; ++j) { - size_t val = 3*i+j; - m_short2c[i][j] = (short)val; - m_unsigned_short2c[i][j] = (unsigned short)val; - m_int2c[i][j] = (int)val; - m_unsigned_int2c[i][j] = (unsigned int)val; - m_long2c[i][j] = (long)val; - m_unsigned_long2c[i][j] = (unsigned long)val; - m_long_long2c[i][j] = (long long)val; - m_unsigned_long_long2c[i][j] = (unsigned long long)val; - m_float2c[i][j] = (float)val; - m_double2c[i][j] = (double)val; - - for (size_t k = 0; k < 7; ++k) { - val = 3*i+2*j+k; - m_short3c[i][j][k] = (short)val; - m_unsigned_short3c[i][j][k] = (unsigned short)val; - m_int3c[i][j][k] = (int)val; - m_unsigned_int3c[i][j][k] = (unsigned int)val; - m_long3c[i][j][k] = (long)val; - m_unsigned_long3c[i][j][k] = (unsigned long)val; - m_long_long3c[i][j][k] = (long long)val; - m_unsigned_long_long3c[i][j][k] = (unsigned long long)val; - m_float3c[i][j][k] = (float)val; - m_double3c[i][j][k] = (double)val; - } - } - } - - m_short3a = allocate_3d(5, 7, 11); - m_unsigned_short3a = allocate_3d(5, 7, 11); - m_int3a = allocate_3d(5, 7, 11); - m_unsigned_int3a = allocate_3d(5, 7, 11); - m_long3a = allocate_3d(5, 7, 11); - m_unsigned_long3a = allocate_3d(5, 7, 11); - m_long_long3a = allocate_3d(5, 7, 11); - m_unsigned_long_long3a = allocate_3d(5, 7, 11); - m_float3a = allocate_3d(5, 7, 11); - m_double3a = allocate_3d(5, 7, 11); - - for (size_t i = 0; i < 5; ++i) { - for (size_t j = 0; j < 7; ++j) { - for (size_t k = 0; k < 11; ++k) { - size_t val = 7*i+3*j+k; - m_short3a[i][j][k] = (short)val; - m_unsigned_short3a[i][j][k] = (unsigned short)val; - m_int3a[i][j][k] = (int)val; - m_unsigned_int3a[i][j][k] = (unsigned int)val; - m_long3a[i][j][k] = (long)val; - m_unsigned_long3a[i][j][k] = (unsigned long)val; - m_long_long3a[i][j][k] = (long long)val; - m_unsigned_long_long3a[i][j][k] = (unsigned long long)val; - m_float3a[i][j][k] = (float)val; - m_double3a[i][j][k] = (double)val; - } - } - } -} - -MultiDimArrays::DataHolder::~DataHolder() { - free_2d((void**)m_short2a, 5); - free_2d((void**)m_unsigned_short2a, 5); - free_2d((void**)m_int2a, 5); - free_2d((void**)m_unsigned_int2a, 5); - free_2d((void**)m_long2a, 5); - free_2d((void**)m_unsigned_long2a, 5); - free_2d((void**)m_long_long2a, 5); - free_2d((void**)m_unsigned_long_long2a, 5); - free_2d((void**)m_float2a, 5); - free_2d((void**)m_double2a, 5); - - free_2d((void**)m_short2b, 5); - free_2d((void**)m_unsigned_short2b, 5); - free_2d((void**)m_int2b, 5); - free_2d((void**)m_unsigned_int2b, 5); - free_2d((void**)m_long2b, 5); - free_2d((void**)m_unsigned_long2b, 5); - free_2d((void**)m_long_long2b, 5); - free_2d((void**)m_unsigned_long_long2b, 5); - free_2d((void**)m_float2b, 5); - free_2d((void**)m_double2b, 5); - - free_3d((void***)m_short3a, 5, 7); - free_3d((void***)m_unsigned_short3a, 5, 7); - free_3d((void***)m_int3a, 5, 7); - free_3d((void***)m_unsigned_int3a, 5, 7); - free_3d((void***)m_long3a, 5, 7); - free_3d((void***)m_unsigned_long3a, 5, 7); - free_3d((void***)m_long_long3a, 5, 7); - free_3d((void***)m_unsigned_long_long3a, 5, 7); - free_3d((void***)m_float3a, 5, 7); - free_3d((void***)m_double3a, 5, 7); -} - -#define MULTIDIM_ARRAYS_NEW2D(type, name) \ -type** MultiDimArrays::DataHolder::new_##name##2d(int N, int M) { \ - type** arr = allocate_2d(N, M); \ - for (size_t i = 0; i < N; ++i) { \ - for (size_t j = 0; j < M; ++j) { \ - size_t val = 7*i+j; \ - arr [i][j] = (type)val; \ - } \ - } \ - return arr; \ -} - -MULTIDIM_ARRAYS_NEW2D(short, short) -MULTIDIM_ARRAYS_NEW2D(unsigned short, ushort) -MULTIDIM_ARRAYS_NEW2D(int, int) -MULTIDIM_ARRAYS_NEW2D(unsigned int, uint) -MULTIDIM_ARRAYS_NEW2D(long, long) -MULTIDIM_ARRAYS_NEW2D(unsigned long, ulong) -MULTIDIM_ARRAYS_NEW2D(long long, llong) -MULTIDIM_ARRAYS_NEW2D(unsigned long long, ullong) -MULTIDIM_ARRAYS_NEW2D(float, float) -MULTIDIM_ARRAYS_NEW2D(double, double) - - -//=========================================================================== -namespace Int8_Uint8_Arrays { - int8_t test[6] = {-0x12, -0x34, -0x56, -0x78}; - uint8_t utest[6] = { 0x12, 0x34, 0x56, 0x78}; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/datatypes.h b/bindings/pyroot/cppyy/cppyy/test/datatypes.h deleted file mode 100644 index 5f1a65556e398..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/datatypes.h +++ /dev/null @@ -1,833 +0,0 @@ -#ifndef CPPYY_TEST_DATATYPES_H -#define CPPYY_TEST_DATATYPES_H - -#ifdef _WIN32 -typedef __int64 Long64_t; -typedef unsigned __int64 ULong64_t; -#else -typedef long long Long64_t; -typedef unsigned long long ULong64_t; -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -const int N = 5; - - -//=========================================================================== -struct CppyyTestPod { - int m_int; - double m_double; -}; - - -//=========================================================================== -enum EFruit {kApple=78, kBanana=29, kCitrus=34}; -extern std::vector vecFruits; - - -//=========================================================================== -enum class NamedClassEnum { E1 = 42 }; - -namespace EnumSpace { - enum E {E1 = 1, E2}; - class EnumClass { - public: - enum {E1 = -1}; - enum EE {E2 = -1}; - }; - - typedef enum { AA = 1, BB, CC, DD } letter_code; - - enum class NamedClassEnum { E1 = -42 }; -} - - -//=========================================================================== -class FourVector { -public: - FourVector(double x, double y, double z, double t) : - m_cc_called(false), m_x(x), m_y(y), m_z(z), m_t(t) {} - FourVector(const FourVector& s) : - m_cc_called(true), m_x(s.m_x), m_y(s.m_y), m_z(s.m_z), m_t(s.m_t) {} - - double operator[](int i) { - if (i == 0) return m_x; - if (i == 1) return m_y; - if (i == 2) return m_z; - if (i == 3) return m_t; - return -1; - } - - bool operator==(const FourVector& o) { - return (m_x == o.m_x && m_y == o.m_y && - m_z == o.m_z && m_t == o.m_t); - } - -public: - bool m_cc_called; - -private: - double m_x, m_y, m_z, m_t; -}; - - -//=========================================================================== -typedef std::complex complex_t; // maps to Py_complex -typedef std::complex icomplex_t; // no equivalent -#ifndef _WIN32 -typedef _Complex double ccomplex_t; // C-style complex, maps to Py_complex -#else -typedef _C_double_complex ccomplex_t; // id. -#endif - -class CppyyTestData { -public: - CppyyTestData(); - ~CppyyTestData(); - -// special cases - enum EWhat { kNothing=6, kSomething=111, kLots=42 }; - -// helper - void destroy_arrays(); - -// getters - bool get_bool(); - char get_char(); - signed char get_schar(); - unsigned char get_uchar(); - wchar_t get_wchar(); - char16_t get_char16(); - char32_t get_char32(); - std::byte get_byte(); - int8_t get_int8(); - uint8_t get_uint8(); - short get_short(); - unsigned short get_ushort(); - int get_int(); - unsigned int get_uint(); - long get_long(); - unsigned long get_ulong(); - long long get_llong(); - unsigned long long get_ullong(); - Long64_t get_long64(); - ULong64_t get_ulong64(); - float get_float(); - double get_double(); - long double get_ldouble(); - long double get_ldouble_def(long double ld = 1); - complex_t get_complex(); - icomplex_t get_icomplex(); - ccomplex_t get_ccomplex(); - EWhat get_enum(); - void* get_voidp(); - - bool* get_bool_array(); - bool* get_bool_array2(); - signed char* get_schar_array(); - signed char* get_schar_array2(); - unsigned char* get_uchar_array(); - unsigned char* get_uchar_array2(); - std::byte* get_byte_array(); - std::byte* get_byte_array2(); - int8_t* get_int8_array(); - int8_t* get_int8_array2(); - uint8_t* get_uint8_array(); - uint8_t* get_uint8_array2(); - short* get_short_array(); - short* get_short_array2(); - unsigned short* get_ushort_array(); - unsigned short* get_ushort_array2(); - int* get_int_array(); - int* get_int_array2(); - unsigned int* get_uint_array(); - unsigned int* get_uint_array2(); - long* get_long_array(); - long* get_long_array2(); - unsigned long* get_ulong_array(); - unsigned long* get_ulong_array2(); - - float* get_float_array(); - float* get_float_array2(); - double* get_double_array(); - double* get_double_array2(); - complex_t* get_complex_array(); - complex_t* get_complex_array2(); - ccomplex_t* get_ccomplex_array(); - ccomplex_t* get_ccomplex_array2(); - - CppyyTestPod get_pod_val(); // for m_pod - CppyyTestPod* get_pod_val_ptr(); - CppyyTestPod& get_pod_val_ref(); - CppyyTestPod*& get_pod_ptrref(); - - CppyyTestPod* get_pod_ptr(); // for m_ppod - -// getters const-ref - const bool& get_bool_cr(); - const char& get_char_cr(); - const signed char& get_schar_cr(); - const unsigned char& get_uchar_cr(); - const wchar_t& get_wchar_cr(); - const char16_t& get_char16_cr(); - const char32_t& get_char32_cr(); - const std::byte& get_byte_cr(); - const int8_t& get_int8_cr(); - const uint8_t& get_uint8_cr(); - const short& get_short_cr(); - const unsigned short& get_ushort_cr(); - const int& get_int_cr(); - const unsigned int& get_uint_cr(); - const long& get_long_cr(); - const unsigned long& get_ulong_cr(); - const long long& get_llong_cr(); - const unsigned long long& get_ullong_cr(); - const Long64_t& get_long64_cr(); - const ULong64_t& get_ulong64_cr(); - const float& get_float_cr(); - const double& get_double_cr(); - const long double& get_ldouble_cr(); - const complex_t& get_complex_cr(); - const icomplex_t& get_icomplex_cr(); - const ccomplex_t& get_ccomplex_cr(); - const EWhat& get_enum_cr(); - -// getters ref - bool& get_bool_r(); - char& get_char_r(); - signed char& get_schar_r(); - unsigned char& get_uchar_r(); - wchar_t& get_wchar_r(); - char16_t& get_char16_r(); - char32_t& get_char32_r(); - std::byte& get_byte_r(); - int8_t& get_int8_r(); - uint8_t& get_uint8_r(); - short& get_short_r(); - unsigned short& get_ushort_r(); - int& get_int_r(); - unsigned int& get_uint_r(); - long& get_long_r(); - unsigned long& get_ulong_r(); - long long& get_llong_r(); - unsigned long long& get_ullong_r(); - Long64_t& get_long64_r(); - ULong64_t& get_ulong64_r(); - float& get_float_r(); - double& get_double_r(); - long double& get_ldouble_r(); - complex_t& get_complex_r(); - icomplex_t& get_icomplex_r(); - ccomplex_t& get_ccomplex_r(); - EWhat& get_enum_r(); - -// setters - void set_bool(bool); - void set_char(char); - void set_schar(signed char); - void set_uchar(unsigned char); - void set_wchar(wchar_t); - void set_char16(char16_t); - void set_char32(char32_t); - void set_byte(std::byte); - void set_int8(int8_t); - void set_uint8(uint8_t); - void set_short(short); - void set_ushort(unsigned short); - void set_int(int); - void set_uint(unsigned int); - void set_long(long); - void set_ulong(unsigned long); - void set_llong(long long); - void set_ullong(unsigned long long); - void set_long64(Long64_t); - void set_ulong64(ULong64_t); - void set_float(float); - void set_double(double); - void set_ldouble(long double); - void set_complex(complex_t); - void set_icomplex(icomplex_t); - void set_ccomplex(ccomplex_t); - void set_enum(EWhat); - void set_voidp(void*); - - void set_pod_val(CppyyTestPod); // for m_pod - void set_pod_ptr_in(CppyyTestPod*); - void set_pod_ptr_out(CppyyTestPod*); - void set_pod_ref(const CppyyTestPod&); - void set_pod_ptrptr_in(CppyyTestPod**); - void set_pod_void_ptrptr_in(void**); - void set_pod_ptrptr_out(CppyyTestPod**); - void set_pod_void_ptrptr_out(void**); - - void set_pod_ptr(CppyyTestPod*); // for m_ppod - -// setters const-ref - void set_bool_cr(const bool&); - void set_char_cr(const char&); - void set_schar_cr(const signed char&); - void set_uchar_cr(const unsigned char&); - void set_wchar_cr(const wchar_t&); - void set_char16_cr(const char16_t&); - void set_char32_cr(const char32_t&); - void set_byte_cr(const std::byte&); - void set_int8_cr(const int8_t&); - void set_uint8_cr(const uint8_t&); - void set_short_cr(const short&); - void set_ushort_cr(const unsigned short&); - void set_int_cr(const int&); - void set_uint_cr(const unsigned int&); - void set_long_cr(const long&); - void set_ulong_cr(const unsigned long&); - void set_llong_cr(const long long&); - void set_ullong_cr(const unsigned long long&); - void set_long64_cr(const Long64_t&); - void set_ulong64_cr(const ULong64_t&); - void set_float_cr(const float&); - void set_double_cr(const double&); - void set_ldouble_cr(const long double&); - void set_complex_cr(const complex_t&); - void set_icomplex_cr(const icomplex_t&); - void set_ccomplex_cr(const ccomplex_t&); - void set_enum_cr(const EWhat&); - -// setters ref - void set_bool_r(bool&); - void set_char_r(char&); - void set_wchar_r(wchar_t&); - void set_char16_r(char16_t&); - void set_char32_r(char32_t&); - void set_schar_r(signed char&); - void set_uchar_r(unsigned char&); - void set_byte_r(std::byte&); - void set_short_r(short&); - void set_ushort_r(unsigned short&); - void set_int_r(int&); - void set_uint_r(unsigned int&); - void set_long_r(long&); - void set_ulong_r(unsigned long&); - void set_llong_r(long long&); - void set_ullong_r(unsigned long long&); - void set_float_r(float&); - void set_double_r(double&); - void set_ldouble_r(long double&); - -// setters ptr - void set_bool_p(bool*); - void set_char_p(char*); - void set_wchar_p(wchar_t*); - void set_char16_p(char16_t*); - void set_char32_p(char32_t*); - void set_schar_p(signed char*); - void set_uchar_p(unsigned char*); - void set_byte_p(std::byte*); - void set_int8_p(int8_t*); - void set_uint8_p(uint8_t*); - void set_short_p(short*); - void set_ushort_p(unsigned short*); - void set_int_p(int*); - void set_uint_p(unsigned int*); - void set_long_p(long*); - void set_ulong_p(unsigned long*); - void set_llong_p(long long*); - void set_ullong_p(unsigned long long*); - void set_float_p(float*); - void set_double_p(double*); - void set_ldouble_p(long double*); - -// setters ptrptr - void set_bool_ppa(bool**); - void set_char_ppa(char**); - void set_wchar_ppa(wchar_t**); - void set_char16_ppa(char16_t**); - void set_char32_ppa(char32_t**); - void set_schar_ppa(signed char**); - void set_uchar_ppa(unsigned char**); - void set_byte_ppa(std::byte**); - void set_int8_ppa(int8_t**); - void set_uint8_ppa(uint8_t**); - void set_short_ppa(short**); - void set_ushort_ppa(unsigned short**); - void set_int_ppa(int**); - void set_uint_ppa(unsigned int**); - void set_long_ppa(long**); - void set_ulong_ppa(unsigned long**); - void set_llong_ppa(long long**); - void set_ullong_ppa(unsigned long long**); - void set_float_ppa(float**); - void set_double_ppa(double**); - void set_ldouble_ppa(long double**); - - intptr_t set_char_ppm(char**); - intptr_t set_cchar_ppm(const char**); - intptr_t set_wchar_ppm(wchar_t**); - intptr_t set_char16_ppm(char16_t**); - intptr_t set_char32_ppm(char32_t**); - intptr_t set_cwchar_ppm(const wchar_t**); - intptr_t set_cchar16_ppm(const char16_t**); - intptr_t set_cchar32_ppm(const char32_t**); - intptr_t set_void_ppm(void**); - - intptr_t freeit(void*); - -// setters r-value - void set_bool_rv(bool&&); - void set_char_rv(char&&); - void set_schar_rv(signed char&&); - void set_uchar_rv(unsigned char&&); - void set_wchar_rv(wchar_t&&); - void set_char16_rv(char16_t&&); - void set_char32_rv(char32_t&&); - void set_byte_rv(std::byte&&); - void set_int8_rv(int8_t&&); - void set_uint8_rv(uint8_t&&); - void set_short_rv(short&&); - void set_ushort_rv(unsigned short&&); - void set_int_rv(int&&); - void set_uint_rv(unsigned int&&); - void set_long_rv(long&&); - void set_ulong_rv(unsigned long&&); - void set_llong_rv(long long&&); - void set_ullong_rv(unsigned long long&&); - void set_long64_rv(Long64_t&&); - void set_ulong64_rv(ULong64_t&&); - void set_float_rv(float&&); - void set_double_rv(double&&); - void set_ldouble_rv(long double&&); - void set_complex_rv(complex_t&&); - void set_icomplex_rv(icomplex_t&&); - void set_ccomplex_rv(ccomplex_t&&); - void set_enum_rv(EWhat&&); - -// passers - unsigned char* pass_array(unsigned char*); - short* pass_array(short*); - unsigned short* pass_array(unsigned short*); - int* pass_array(int*); - unsigned int* pass_array(unsigned int*); - long* pass_array(long*); - unsigned long* pass_array(unsigned long*); - float* pass_array(float*); - double* pass_array(double*); - complex_t* pass_array(complex_t*); - ccomplex_t* pass_array(ccomplex_t*); - - unsigned char* pass_void_array_B(void* a) { return pass_array((unsigned char*)a); } - short* pass_void_array_h(void* a) { return pass_array((short*)a); } - unsigned short* pass_void_array_H(void* a) { return pass_array((unsigned short*)a); } - int* pass_void_array_i(void* a) { return pass_array((int*)a); } - unsigned int* pass_void_array_I(void* a) { return pass_array((unsigned int*)a); } - long* pass_void_array_l(void* a) { return pass_array((long*)a); } - unsigned long* pass_void_array_L(void* a) { return pass_array((unsigned long*)a); } - float* pass_void_array_f(void* a) { return pass_array((float*)a); } - double* pass_void_array_d(void* a) { return pass_array((double*)a); } - complex_t* pass_void_array_Z(void* a) { return pass_array((complex_t*)a); } - ccomplex_t* pass_void_array_cZ(void* a) { return pass_array((ccomplex_t*)a); } - -// strings - const char* get_valid_string(const char* in); - const char* get_invalid_string(); - const wchar_t* get_valid_wstring(const wchar_t* in); - const wchar_t* get_invalid_wstring(); - const char16_t* get_valid_string16(const char16_t* in); - const char16_t* get_invalid_string16(); - const char32_t* get_valid_string32(const char32_t* in); - const char32_t* get_invalid_string32(); - -public: -// basic types - bool m_bool; - char m_char; - signed char m_schar; - unsigned char m_uchar; - wchar_t m_wchar; - char16_t m_char16; - char32_t m_char32; - std::byte m_byte; - int8_t m_int8; - uint8_t m_uint8; - short m_short; - unsigned short m_ushort; - int m_int; - const int m_const_int; // special case: const testing - unsigned int m_uint; - long m_long; - unsigned long m_ulong; - long long m_llong; - unsigned long long m_ullong; - Long64_t m_long64; - ULong64_t m_ulong64; - float m_float; - double m_double; - long double m_ldouble; - complex_t m_complex; - icomplex_t m_icomplex; - ccomplex_t m_ccomplex; - EWhat m_enum; - void* m_voidp; - -// array types - bool m_bool_array[N]; - bool* m_bool_array2; - signed char m_schar_array[N]; - signed char* m_schar_array2; - unsigned char m_uchar_array[N]; - unsigned char* m_uchar_array2; - std::byte m_byte_array[N]; - std::byte* m_byte_array2; - int8_t m_int8_array[N]; - int8_t* m_int8_array2; - uint8_t m_uint8_array[N]; - uint8_t* m_uint8_array2; - short m_short_array[N]; - short* m_short_array2; - unsigned short m_ushort_array[N]; - unsigned short* m_ushort_array2; - int m_int_array[N]; - int* m_int_array2; - unsigned int m_uint_array[N]; - unsigned int* m_uint_array2; - long m_long_array[N]; - long* m_long_array2; - unsigned long m_ulong_array[N]; - unsigned long* m_ulong_array2; - - float m_float_array[N]; - float* m_float_array2; - double m_double_array[N]; - double* m_double_array2; - complex_t m_complex_array[N]; - complex_t* m_complex_array2; - icomplex_t m_icomplex_array[N]; - icomplex_t* m_icomplex_array2; - ccomplex_t m_ccomplex_array[N]; - ccomplex_t* m_ccomplex_array2; - -// object types - CppyyTestPod m_pod; - CppyyTestPod* m_ppod; - -public: - static bool s_bool; - static char s_char; - static signed char s_schar; - static unsigned char s_uchar; - static wchar_t s_wchar; - static char16_t s_char16; - static char32_t s_char32; - static std::byte s_byte; - static int8_t s_int8; - static uint8_t s_uint8; - static short s_short; - static unsigned short s_ushort; - static int s_int; - static unsigned int s_uint; - static long s_long; - static unsigned long s_ulong; - static long long s_llong; - static unsigned long long s_ullong; - static Long64_t s_long64; - static ULong64_t s_ulong64; - static float s_float; - static double s_double; - static long double s_ldouble; - static complex_t s_complex; - static icomplex_t s_icomplex; - static ccomplex_t s_ccomplex; - static EWhat s_enum; - static void* s_voidp; - static std::string s_strv; - static std::string* s_strp; - -private: - bool m_owns_arrays; -}; - - -//= global functions ======================================================== -intptr_t get_pod_address(CppyyTestData& c); -intptr_t get_int_address(CppyyTestData& c); -intptr_t get_double_address(CppyyTestData& c); - - -//= global variables/pointers =============================================== -extern bool g_bool; -extern char g_char; -extern signed char g_schar; -extern unsigned char g_uchar; -extern wchar_t g_wchar; -extern char16_t g_char16; -extern char32_t g_char32; -extern std::byte g_byte; -extern int8_t g_int8; -extern uint8_t g_uint8; -extern short g_short; -extern unsigned short g_ushort; -extern int g_int; -extern unsigned int g_uint; -extern long g_long; -extern unsigned long g_ulong; -extern long long g_llong; -extern unsigned long long g_ullong; -extern Long64_t g_long64; -extern ULong64_t g_ulong64; -extern float g_float; -extern double g_double; -extern long double g_ldouble; -extern complex_t g_complex; -extern icomplex_t g_icomplex; -extern ccomplex_t g_ccomplex; -extern EFruit g_enum; -extern void* g_voidp; - -static const bool g_c_bool = true; -static const char g_c_char = 'z'; -static const signed char g_c_schar = 'y'; -static const unsigned char g_c_uchar = 'x'; -static const wchar_t g_c_wchar = L'U'; -static const char16_t g_c_char16 = u'\u6c34'; -static const char32_t g_c_char32 = U'\U0001f34c'; -static const std::byte g_c_byte = (std::byte)'u'; -static const int8_t g_c_int8 = -12; -static const uint8_t g_c_uint8 = 12; -static const short g_c_short = -99; -static const unsigned short g_c_ushort = 99u; -static const int g_c_int = -199; -static const unsigned int g_c_uint = 199u; -static const long g_c_long = -299; -static const unsigned long g_c_ulong = 299ul; -static const long long g_c_llong = -399ll; -static const unsigned long long g_c_ullong = 399ull; -static const Long64_t g_c_long64 = -499ll; -static const ULong64_t g_c_ulong64 = 499ull; -static const float g_c_float = -599.f; -static const double g_c_double = -699.; -static const long double g_c_ldouble = -799.l; -static const complex_t g_c_complex = {1., 2.}; -static const icomplex_t g_c_icomplex = {3, 4}; -static const ccomplex_t g_c_ccomplex = {5., 6.}; -static const EFruit g_c_enum = kApple; -static const void* g_c_voidp = nullptr; - - -//= global accessors ======================================================== -void set_global_int(int i); -int get_global_int(); - -extern CppyyTestPod* g_pod; -bool is_global_pod(CppyyTestPod* t); -void set_global_pod(CppyyTestPod* t); -CppyyTestPod* get_global_pod(); -CppyyTestPod* get_null_pod(); - -extern std::string g_some_global_string; -std::string get_some_global_string(); -extern std::string g_some_global_string2; -std::string get_some_global_string2(); - -extern const char16_t* g_some_global_string16; -extern const char32_t* g_some_global_string32; - -namespace SomeStaticDataNS { - extern std::string s_some_static_string; - std::string get_some_static_string(); - extern std::string s_some_static_string2; - std::string get_some_static_string2(); -} - -struct StorableData { - StorableData(double d) : fData(d) {} - double fData; -}; - -extern StorableData gData; - - -//= special case of "byte" arrays =========================================== -int64_t sum_uc_data(unsigned char* data, int size); -int64_t sum_byte_data(std::byte* data, int size); - - -//= function pointer passing ================================================ -int sum_of_int1(int i1, int i2); -int sum_of_int2(int i1, int i2); -extern int (*sum_of_int_ptr)(int, int); -int call_sum_of_int(int i1, int i2); - -double sum_of_double(double d1, double d2); -double call_double_double(double (*d)(double, double), double d1, double d2); - -struct sum_of_int_struct { - int (*sum_of_int_ptr)(int, int); -}; - -//= callable passing ======================================================== -int call_int_int(int (*)(int, int), int, int); -void call_void(void (*f)(int), int i); -int call_refi(void (*fcn)(int&)); -int call_refl(void (*fcn)(long&)); -int call_refd(void (*fcn)(double&)); - -class StoreCallable { - double (*fF)(double, double); -public: - StoreCallable(double (*)(double, double)); - void set_callable(double (*)(double, double)); - double operator()(double, double); -}; - - -//= callable through std::function ========================================== -double call_double_double_sf(const std::function&, double d1, double d2); - -int call_int_int_sf(const std::function&, int, int); -void call_void_sf(const std::function&, int i); -int call_refi_sf(const std::function&); -int call_refl_sf(const std::function&); -int call_refd_sf(const std::function&); - -class StoreCallable_sf { - std::function fF; -public: - StoreCallable_sf(const std::function&); - void set_callable(const std::function&); - double operator()(double, double); -}; - - -//= array of struct variants ================================================ -namespace ArrayOfStruct { - -struct Foo { - int fVal; -}; - -struct Bar1 { - Bar1() : fArr(new Foo[2]) { fArr[0].fVal = 42; fArr[1].fVal = 13; } - Bar1(const Bar1&) = delete; - Bar1& operator=(const Bar1&) = delete; - ~Bar1() { delete[] fArr; } - Foo* fArr; -}; - -struct Bar2 { - Bar2(int num_foo) : fArr(std::unique_ptr{new Foo[num_foo]}) { - for (int i = 0; i < num_foo; ++i) fArr[i].fVal = 2*i; - } - std::unique_ptr fArr; -}; - -} // namespace ArrayOfStruct - - -//= array of C strings passing ============================================== -namespace ArrayOfCStrings { - std::vector takes_array_of_cstrings(const char* args[], int len); -} - - -//= aggregate testing ====================================================== -namespace AggregateTest { - -struct Aggregate1 { - static int sInt; -}; - -struct Aggregate2 { - static int sInt; - int fInt = 42; -}; - -} // namespace AggregateTest - - -//= multi-dim arrays ======================================================= -namespace MultiDimArrays { - -struct DataHolder { - DataHolder(); - ~DataHolder(); - - short** m_short2a; - unsigned short** m_unsigned_short2a; - int** m_int2a; - unsigned int** m_unsigned_int2a; - long** m_long2a; - unsigned long** m_unsigned_long2a; - long long** m_long_long2a; - unsigned long long** m_unsigned_long_long2a; - float** m_float2a; - double** m_double2a; - - short** m_short2b; - short** new_short2d(int N, int M); - unsigned short** m_unsigned_short2b; - unsigned short** new_ushort2d(int N, int M); - int** m_int2b; - int** new_int2d(int N, int M); - unsigned int** m_unsigned_int2b; - unsigned int** new_uint2d(int N, int M); - long** m_long2b; - long** new_long2d(int N, int M); - unsigned long** m_unsigned_long2b; - unsigned long** new_ulong2d(int N, int M); - long long** m_long_long2b; - long long** new_llong2d(int N, int M); - unsigned long long** m_unsigned_long_long2b; - unsigned long long** new_ullong2d(int N, int M); - float** m_float2b; - float** new_float2d(int N, int M); - double** m_double2b; - double** new_double2d(int N, int M); - - short m_short2c[3][5]; - unsigned short m_unsigned_short2c[3][5]; - int m_int2c[3][5]; - unsigned int m_unsigned_int2c[3][5]; - long m_long2c[3][5]; - unsigned long m_unsigned_long2c[3][5]; - long long m_long_long2c[3][5]; - unsigned long long m_unsigned_long_long2c[3][5]; - float m_float2c[3][5]; - double m_double2c[3][5]; - - short*** m_short3a; - unsigned short*** m_unsigned_short3a; - int*** m_int3a; - unsigned int*** m_unsigned_int3a; - long*** m_long3a; - unsigned long*** m_unsigned_long3a; - long long*** m_long_long3a; - unsigned long long*** m_unsigned_long_long3a; - float*** m_float3a; - double*** m_double3a; - - short m_short3c[3][5][7]; - unsigned short m_unsigned_short3c[3][5][7]; - int m_int3c[3][5][7]; - unsigned int m_unsigned_int3c[3][5][7]; - long m_long3c[3][5][7]; - unsigned long m_unsigned_long3c[3][5][7]; - long long m_long_long3c[3][5][7]; - unsigned long long m_unsigned_long_long3c[3][5][7]; - float m_float3c[3][5][7]; - double m_double3c[3][5][7]; -}; - -} // namespace MultiDimArrays - - -//= int8_t/uint8_t arrays =================================================== -namespace Int8_Uint8_Arrays { - extern int8_t test[6]; - extern uint8_t utest[6]; -} - -#endif // !CPPYY_TEST_DATATYPES_H diff --git a/bindings/pyroot/cppyy/cppyy/test/datatypes.xml b/bindings/pyroot/cppyy/cppyy/test/datatypes.xml deleted file mode 100644 index 9281b656ad725..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/datatypes.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/doc_args_funcs.py b/bindings/pyroot/cppyy/cppyy/test/doc_args_funcs.py deleted file mode 100644 index 481c3a84334a0..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/doc_args_funcs.py +++ /dev/null @@ -1,5 +0,0 @@ -def ann_f1(arg: 'int') -> 'double': - return 3.1415*arg - -def ann_f2(arg1: 'int', arg2: 'int') -> 'int': - return 3*arg1*arg2 diff --git a/bindings/pyroot/cppyy/cppyy/test/doc_helper.cxx b/bindings/pyroot/cppyy/cppyy/test/doc_helper.cxx deleted file mode 100644 index 87bb2e37773f5..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/doc_helper.cxx +++ /dev/null @@ -1,6 +0,0 @@ -#include "doc_helper.h" - -void DocHelper::throw_an_error(int i) { - if (i) throw SomeError{"this is an error"}; - throw SomeOtherError{"this is another error"}; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/doc_helper.h b/bindings/pyroot/cppyy/cppyy/test/doc_helper.h deleted file mode 100644 index 9c6775eee2594..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/doc_helper.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CPPYY_TEST_DOCHELPER_H -#define CPPYY_TEST_DOCHELPER_H - -#include -#include - - -class SomeError : public std::exception { -public: - explicit SomeError(const std::string& msg) : fMsg(msg) {} - SomeError(const SomeError& s) : fMsg(s.fMsg) {} - - const char* what() const throw() override { return fMsg.c_str(); } - -private: - std::string fMsg; -}; - -class SomeOtherError : public SomeError { -public: - explicit SomeOtherError(const std::string& msg) : SomeError(msg) {} - SomeOtherError(const SomeOtherError& s) : SomeError(s) {} -}; - -namespace DocHelper { - -void throw_an_error(int i); - -} // namespace DocHelper - -#endif // !CPPYY_TEST_DOCHELPER_H diff --git a/bindings/pyroot/cppyy/cppyy/test/doc_helper.xml b/bindings/pyroot/cppyy/cppyy/test/doc_helper.xml deleted file mode 100644 index 79bc4f4c3d38c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/doc_helper.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/example01.cxx b/bindings/pyroot/cppyy/cppyy/test/example01.cxx deleted file mode 100644 index c70f23f47e6f6..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/example01.cxx +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include -#include -#include - -#include "example01.h" - -//=========================================================================== -payload::payload(double d) : m_data(d) { - count++; -} -payload::payload(const payload& p) : m_data(p.m_data) { - count++; -} -payload& payload::operator=(const payload& p) { - if (this != &p) { - m_data = p.m_data; - } - return *this; -} -payload::~payload() { - count--; -} - -double payload::getData() { return m_data; } -void payload::setData(double d) { m_data = d; } - -// class-level data -int payload::count = 0; - - -//=========================================================================== -example01::example01() : m_somedata(-99) { - count++; -} -example01::example01(int a) : m_somedata(a) { - count++; -} -example01::example01(const example01& e) : m_somedata(e.m_somedata) { - count++; -} -example01& example01::operator=(const example01& e) { - if (this != &e) { - m_somedata = e.m_somedata; - } - return *this; -} -example01::~example01() { - count--; -} - -// class-level methods -int example01::staticAddOneToInt(int a) { - return a + 1; -} -int example01::staticAddOneToInt(int a, int b) { - return a + b + 1; -} -double example01::staticAddToDouble(double a) { - return a + 0.01; -} -int example01::staticAtoi(const char* str) { - return ::atoi(str); -} -char* example01::staticStrcpy(const char* strin) { - char* strout = (char*)malloc(::strlen(strin)+1); - ::strcpy(strout, strin); - return strout; -} -void example01::staticSetPayload(payload* p, double d) { - p->setData(d); -} - -payload* example01::staticCyclePayload(payload* p, double d) { - staticSetPayload(p, d); - return p; -} - -payload example01::staticCopyCyclePayload(payload* p, double d) { - staticSetPayload(p, d); - return *p; -} - -int example01::getCount() { - return count; -} - -void example01::setCount(int value) { - count = value; -} - -// instance methods -int example01::addDataToInt(int a) { - return m_somedata + a; -} - -int example01::addDataToIntConstRef(const int& a) { - return m_somedata + a; -} - -int example01::overloadedAddDataToInt(int a, int b) { - return m_somedata + a + b; -} - -int example01::overloadedAddDataToInt(int a) { - return m_somedata + a; -} - -int example01::overloadedAddDataToInt(int a, int b, int c) { - return m_somedata + a + b + c; -} - -double example01::addDataToDouble(double a) { - return m_somedata + a; -} - -int example01::addDataToAtoi(const char* str) { - return ::atoi(str) + m_somedata; -} - -char* example01::addToStringValue(const char* str) { - int out = ::atoi(str) + m_somedata; - std::ostringstream ss; - ss << out << std::ends; - std::string result = ss.str(); - char* cresult = (char*)malloc(result.size()+1); - ::strcpy(cresult, result.c_str()); - return cresult; -} - -void example01::setPayload(payload* p) { - p->setData(m_somedata); -} - -payload* example01::cyclePayload(payload* p) { - setPayload(p); - return p; -} - -payload example01::copyCyclePayload(payload* p) { - setPayload(p); - return *p; -} - -// class-level data -int example01::count = 0; - - -// global -int globalAddOneToInt(int a) { - return a + 1; -} - -int ns_example01::globalAddOneToInt(int a) { - return ::globalAddOneToInt(a); -} - -int installableAddOneToInt(example01& e, int a) { - return e.staticAddOneToInt(a); -} - -int ns_example01::gMyGlobalInt = 99; - - -// argument passing -#define typeValueImp(itype, tname) \ -itype ArgPasser::tname##Value(itype arg0, int argn, itype arg1, itype arg2) \ -{ \ - switch (argn) { \ - case 0: \ - return arg0; \ - case 1: \ - return arg1; \ - case 2: \ - return arg2; \ - default: \ - break; \ - } \ - \ - return (itype)-1; \ -} - -typeValueImp(short, short) -typeValueImp(unsigned short, ushort) -typeValueImp(int, int) -typeValueImp(unsigned int, uint) -typeValueImp(long, long) -typeValueImp(unsigned long, ulong) - -typeValueImp(float, float) -typeValueImp(double, double) - -std::string ArgPasser::stringValue(std::string arg0, int argn, std::string arg1) -{ - switch (argn) { - case 0: - return arg0; - case 1: - return arg1; - default: - break; - } - - return "argn invalid"; -} - -std::string ArgPasser::stringRef(const std::string& arg0, int argn, const std::string& arg1) -{ - return stringValue(arg0, argn, arg1); -} - - -// special case naming -z_& z_::gime_z_(z_& z) { return z; } diff --git a/bindings/pyroot/cppyy/cppyy/test/example01.h b/bindings/pyroot/cppyy/cppyy/test/example01.h deleted file mode 100644 index 4ecdd6645ebf4..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/example01.h +++ /dev/null @@ -1,115 +0,0 @@ -#include - -class payload { -public: - payload(double d = 0.); - payload(const payload& p); - payload& operator=(const payload& e); - ~payload(); - - double getData(); - void setData(double d); - -public: // class-level data - static int count; - -private: - double m_data; -}; - - -class example01 { -public: - example01(); - example01(int a); - example01(const example01& e); - example01& operator=(const example01& e); - virtual ~example01(); - -public: // class-level methods - static int staticAddOneToInt(int a); - static int staticAddOneToInt(int a, int b); - static double staticAddToDouble(double a); - static int staticAtoi(const char* str); - static char* staticStrcpy(const char* strin); - static void staticSetPayload(payload* p, double d); - static payload* staticCyclePayload(payload* p, double d); - static payload staticCopyCyclePayload(payload* p, double d); - static int getCount(); - static void setCount(int); - -public: // instance methods - int addDataToInt(int a); - int addDataToIntConstRef(const int& a); - int overloadedAddDataToInt(int a, int b); - int overloadedAddDataToInt(int a); - int overloadedAddDataToInt(int a, int b, int c); - double addDataToDouble(double a); - int addDataToAtoi(const char* str); - char* addToStringValue(const char* str); - - void setPayload(payload* p); - payload* cyclePayload(payload* p); - payload copyCyclePayload(payload* p); - -public: // class-level data - static int count; - -public: // instance data - int m_somedata; -}; - - -// global functions and data -int globalAddOneToInt(int a); -namespace ns_example01 { - int globalAddOneToInt(int a); - extern int gMyGlobalInt; -} - -int installableAddOneToInt(example01&, int a); - -#define itypeValue(itype, tname) \ - itype tname##Value(itype arg0, int argn=0, itype arg1=1, itype arg2=2) - -#define ftypeValue(ftype) \ - ftype ftype##Value(ftype arg0, int argn=0, ftype arg1=1., ftype arg2=2.) - - -// argument passing -class ArgPasser { // use a class for now as methptrgetter not -public: // implemented for global functions - itypeValue(short, short); - itypeValue(unsigned short, ushort); - itypeValue(int, int); - itypeValue(unsigned int, uint); - itypeValue(long, long); - itypeValue(unsigned long, ulong); - - ftypeValue(float); - ftypeValue(double); - - std::string stringValue( - std::string arg0, int argn=0, std::string arg1 = "default"); - - std::string stringRef( - const std::string& arg0, int argn=0, const std::string& arg1="default"); -}; - - -// typedefs -typedef example01 example01_t; - - -// special case naming -class z_ { -public: - z_& gime_z_(z_& z); - int myint; -}; - -// for pythonization checking -class example01a : public example01 { -public: - example01a(int a) : example01(a) {} -}; diff --git a/bindings/pyroot/cppyy/cppyy/test/example01.xml b/bindings/pyroot/cppyy/cppyy/test/example01.xml deleted file mode 100644 index bc1dfcf2e4165..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/example01.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/fragile.cxx b/bindings/pyroot/cppyy/cppyy/test/fragile.cxx deleted file mode 100644 index 8ca3cf02cc6b3..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/fragile.cxx +++ /dev/null @@ -1,69 +0,0 @@ -#include "fragile.h" -#include - - -fragile::H::HH* fragile::H::HH::copy() { - return (HH*)0; -} - -fragile::I fragile::gI; - -void fragile::fglobal(int, double, char) { - /* empty; only used for doc-string testing */ -} - -namespace fragile { - - class Kderived : public K { - public: - virtual ~Kderived(); - }; - -} // namespace fragile - -fragile::Kderived::~Kderived() {} - -fragile::K::~K() {} - -fragile::K* fragile::K::GimeK(bool derived) { - if (!derived) return this; - else { - static Kderived kd; - return &kd; - } -}; - -fragile::K* fragile::K::GimeL() { - static L l; - return &l; -} - -fragile::L::~L() {} - - -int fragile::create_handle(OpaqueHandle_t* handle) { - *handle = (OpaqueHandle_t)0x01; - return 0x01; -} - -int fragile::destroy_handle(OpaqueHandle_t handle, intptr_t addr) { - if ((intptr_t)handle == addr) - return 1; - return 0; -} - - -// for signal -> exception testing -void fragile::segfault() { - int* i = 0; *i = 42; -} - -void fragile::sigabort() { - assert(0); -} - - -// for duplicate testing -int fragile::add42(int i) { - return i + 42; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/fragile.h b/bindings/pyroot/cppyy/cppyy/test/fragile.h deleted file mode 100644 index ea433d884755b..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/fragile.h +++ /dev/null @@ -1,151 +0,0 @@ -#include - - -namespace fragile { - -class no_such_class; - -class A { -public: - virtual int check() { return (int)'A'; } - virtual A* gime_null() { return (A*)0; } -}; - -class B { -public: - virtual int check() { return (int)'B'; } - no_such_class* gime_no_such() { return 0; } -}; - -class C { -public: - virtual int check() { return (int)'C'; } - void use_no_such(no_such_class*) {} -}; - -class D { -public: - virtual int check() { return (int)'D'; } - virtual int check(int, int) { return (int)'D'; } - void overload() {} - void overload(no_such_class*) {} - void overload(char, [[maybe_unused]] int i = 0) {} // Reflex requires a named arg - void overload(int, [[maybe_unused]] no_such_class* p = nullptr) {} -}; - - -static const uintptr_t dummy_location = 0xdead; - -class E { -public: - E() : m_pp_no_such((no_such_class**)&dummy_location), m_pp_a(0) {} - - virtual int check() { return (int)'E'; } - void overload(no_such_class**) {} - - no_such_class** m_pp_no_such; - A** m_pp_a; -}; - -class F { -public: - F() : m_int(0) {} - virtual int check() { return (int)'F'; } - int m_int; -}; - -class G { -public: - enum { unnamed1=24, unnamed2=96 }; - - class GG {}; -}; - -class H { -public: - class HH { - public: - HH* copy(); - }; - HH* m_h; -}; - -class I { -public: - operator bool() { return 0; } -}; - -extern I gI; - -class J { -public: - int method1(int, double) { return 0; } -}; - -void fglobal(int, double, char); - -namespace nested1 { - class A {}; - namespace nested2 { - class A {}; - namespace nested3 { - class A {}; - } // namespace nested3 - } // namespace nested2 -} // namespace nested1 - -class K { -public: - virtual ~K(); - K* GimeK(bool derived); - K* GimeL(); -}; - -class L : public K { -public: - virtual ~L(); - no_such_class* m_no_such; -}; - -class M { -public: - virtual ~M() {} - enum E1 { kOnce=42 }; - enum E2 { kTwice=12 }; -}; - -class N : public M { -public: - enum E2 { kTwice=12 }; -}; - -class O { -public: - virtual int abstract() = 0; - - // Abstract classes should declare a virtual destructor - virtual ~O() = default; - - // Destructor was declared, apply rule of five - O(const O &) = default; - O &operator=(const O &) = default; - O(O &&) = default; - O &operator=(O &&) = default; -}; - -class OpaqueType; -typedef OpaqueType* OpaqueHandle_t; - -int create_handle(OpaqueHandle_t* handle); -int destroy_handle(OpaqueHandle_t handle, intptr_t addr); - - -// for signal -> exception testing -void segfault(); -void sigabort(); - - -// for duplicate testing -int add42(int i); - -} // namespace fragile diff --git a/bindings/pyroot/cppyy/cppyy/test/fragile.xml b/bindings/pyroot/cppyy/cppyy/test/fragile.xml deleted file mode 100644 index 652b56a35aaa2..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/fragile.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/make_dict_win32.py b/bindings/pyroot/cppyy/cppyy/test/make_dict_win32.py deleted file mode 100755 index 6837f6b068c9c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/make_dict_win32.py +++ /dev/null @@ -1,108 +0,0 @@ -import glob, os, sys, subprocess - -USES_PYTHON_CAPI = set(('pythonizables',)) - -fn = sys.argv[1] - -if fn == 'all': - all_headers = glob.glob('*.h') - for header in all_headers: - res = os.system(" ".join(['python', sys.argv[0], header[:-2]]+sys.argv[2:])) - if res != 0: - sys.exit(res) - sys.exit(0) -else: - if fn[-4:] == '.cxx': fn = fn[:-4] - elif fn[-2:] == '.h': fn = fn[:-2] - if not os.path.exists(fn+'.h'): - print("file %s.h does not exist" % (fn,)) - sys.exit(1) - -uses_python_capi = False -if fn in USES_PYTHON_CAPI: - uses_python_capi = True - -if os.path.exists(fn+'Dict.dll'): - dct_time = os.stat(fn+'Dict.dll').st_mtime - if not '-f' in sys.argv: - mustbuild = False - for ext in ['.h', '.cxx', '.xml']: - if os.stat(fn+ext).st_mtime > dct_time: - mustbuild = True - break - if not mustbuild: - sys.exit(0) - - # cleanup - for fg in set(glob.glob(fn+"_rflx*") + glob.glob(fn+"Dict*") + \ - glob.glob("*.obj") + glob.glob(fn+"Linkdef.h")): - os.remove(fg) - -def _get_config_exec(): - return [sys.executable, '-m', 'cppyy_backend._cling_config'] - -def get_config(what): - config_exec_args = _get_config_exec() - config_exec_args.append('--'+what) - cli_arg = subprocess.check_output(config_exec_args) - return cli_arg.decode("utf-8").strip() - -def get_python_include_dir(): - incdir = subprocess.check_output([sys.executable, '-c', "import sysconfig; print(sysconfig.get_path('include'))"]) - return incdir.decode("utf-8").strip() - -def get_python_lib_dir(): - libdir = subprocess.check_output([sys.executable, '-c', "import sysconfig; print(sysconfig.get_path('stdlib'))"]) - return os.path.join(os.path.dirname(libdir.decode("utf-8").strip()), 'libs') - -# genreflex option -#DICTIONARY_CMD = "genreflex {fn}.h --selection={fn}.xml --rootmap={fn}Dict.rootmap --rootmap-lib={fn}Dict.dll".format(fn=fn) - -with open(fn+'Linkdef.h', 'w') as linkdef: - linkdef.write("#ifdef __CLING__\n\n") - linkdef.write("#pragma link C++ defined_in %s.h;\n" % fn) - linkdef.write("\n#endif") - -DICTIONARY_CMD = "python -m cppyy_backend._rootcling -f {fn}_rflx.cxx -rmf {fn}Dict.rootmap -rml {fn}Dict.dll {fn}.h {fn}Linkdef.h".format(fn=fn) -if os.system(DICTIONARY_CMD): - sys.exit(1) - -import platform -if '64' in platform.architecture()[0]: - PLATFORMFLAG = '-D_AMD64_' - MACHINETYPE = 'X64' -else: - PLATFORMFLAG = '-D_X86_' - MACHINETYPE = 'IX86' - -cppflags = get_config('cppflags') -if uses_python_capi: - cppflags += ' -I"' + get_python_include_dir() + '"' -BUILDOBJ_CMD_PART = "cl -O2 -nologo -TP -c -nologo " + cppflags + " -FIsehmap.h -MD -GR -D_WINDOWS -DWIN32 " + PLATFORMFLAG + " -EHsc- -W3 -wd4141 -wd4291 -wd4244 -wd4049 -D_XKEYCHECK_H -D_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS {fn}.cxx -Fo{fn}.obj" -BUILDOBJ_CMD = BUILDOBJ_CMD_PART.format(fn=fn) -if os.system(BUILDOBJ_CMD): - sys.exit(1) -BUILDOBJ_CMD = BUILDOBJ_CMD_PART.format(fn=fn+'_rflx') -if os.system(BUILDOBJ_CMD): - sys.exit(1) - -import cppyy_backend -CREATEDEF_CMD = "python bindexplib.py {fn} {fn}Dict".format(fn=fn) -if os.system(CREATEDEF_CMD): - sys.exit(1) - -ldflags = '' -if uses_python_capi: - ldflags = ' /LIBPATH:"' + get_python_lib_dir() + '" ' -CREATELIB_CMD = ("lib -nologo -MACHINE:" + MACHINETYPE + " -out:{fn}Dict.lib {fn}.obj {fn}_rflx.obj -def:{fn}Dict.def " + ldflags).format(fn=fn) -if os.system(CREATELIB_CMD): - sys.exit(1) - -ldflags += get_config('ldflags') -LINKDLL_CMD = ("link -nologo {fn}.obj {fn}_rflx.obj -DLL -out:{fn}Dict.dll {fn}Dict.exp " + ldflags).format(fn=fn) -if os.system(LINKDLL_CMD): - sys.exit(1) - -# cleanup -for fg in set(glob.glob(fn+"_rflx.cxx*") + glob.glob("*.obj") + glob.glob(fn+"Linkdef.h")): - os.remove(fg) diff --git a/bindings/pyroot/cppyy/cppyy/test/operators.cxx b/bindings/pyroot/cppyy/cppyy/test/operators.cxx deleted file mode 100644 index 4bd4c3e2e5c80..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/operators.cxx +++ /dev/null @@ -1,194 +0,0 @@ -#include "operators.h" - -// for testing the case of virtual operator== -v_opeq_base::v_opeq_base(int val) : m_val(val) {} -v_opeq_base::~v_opeq_base() {} - -bool v_opeq_base::operator==(const v_opeq_base& other) { - return m_val == other.m_val; -} - -v_opeq_derived::v_opeq_derived(int val) : v_opeq_base(val) {} -v_opeq_derived::~v_opeq_derived() {} - -bool v_opeq_derived::operator==(const v_opeq_derived& other) { - return m_val != other.m_val; -} - - -// for indexing tests -int& YAMatrix1::operator() (int, int) { - return m_val; -} - -const int& YAMatrix1::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix2::operator[] (int) { - return m_val; -} - -const int& YAMatrix2::operator[] (int) const { - return m_val; -} - -//- -int& YAMatrix3::operator() (int, int) { - return m_val; -} - -const int& YAMatrix3::operator() (int, int) const { - return m_val; -} - -int& YAMatrix3::operator[] (int) { - return m_val; -} - -const int& YAMatrix3::operator[] (int) const { - return m_val; -} - -//- -int& YAMatrix4::operator[] (int) { - return m_val; -} - -const int& YAMatrix4::operator[] (int) const { - return m_val; -} - -int& YAMatrix4::operator() (int, int) { - return m_val; -} - -const int& YAMatrix4::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix5::operator[] (int) { - return m_val; -} - -int YAMatrix5::operator[] (int) const { - return m_val; -} - -int& YAMatrix5::operator() (int, int) { - return m_val; -} - -int YAMatrix5::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix6::operator[] (int) { - return m_val; -} - -int YAMatrix6::operator[] (int) const { - return m_val; -} - -int& YAMatrix6::operator() (int, int) { - return m_val; -} - -//- -int& YAMatrix7::operator[] (int) { - return m_val; -} - -int& YAMatrix7::operator() (int, int) { - return m_val; -} - - -//- for __radd__/__rmul__, non-associative -AssocADD operator+(int n, AssocADD& a) { - return AssocADD(n+a.fx); -} - -AssocADD operator+(AssocADD& a, int n) { - return AssocADD(a.fx+n); -} - -NonAssocRADD operator+(int n, NonAssocRADD& a) { - return NonAssocRADD(n+a.fx); -} - -AssocMUL operator*(int n, AssocMUL& m) { - return AssocMUL(n*m.fx); -} - -AssocMUL operator*(AssocMUL& m, int n) { - return AssocMUL(m.fx*n); -} - -NonAssocRMUL operator*(int n, NonAssocRMUL& m) { - return NonAssocRMUL(n*m.fx); -} - - -//- for multi-lookup -double MultiLookup::operator*(const Vector2& v1, const Vector2& v2) { - return v1.x*v2.x + v1.y*v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator*(const Vector2& v, double a) { - return Vector2{v.x*a, v.y*a}; -} - -double MultiLookup::operator/(const Vector2& v1, const Vector2& v2) { - return v1.x/v2.x + v1.y/v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator/(const Vector2& v, double a) { - return Vector2{v.x/a, v.y/a}; -} - -double MultiLookup::operator+(const Vector2& v1, const Vector2& v2) { - return v1.x+v2.x + v1.y+v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator+(const Vector2& v, double a) { - return Vector2{v.x+a, v.y+a}; -} - -double MultiLookup::operator-(const Vector2& v1, const Vector2& v2) { - return v1.x-v2.x + v1.y-v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator-(const Vector2& v, double a) { - return Vector2{v.x-a, v.y-a}; -} - - -//- for unary functions -SomeGlobalNumber operator-(const SomeGlobalNumber& n) { - return SomeGlobalNumber{-n.i}; -} - -SomeGlobalNumber operator+(const SomeGlobalNumber& n) { - return SomeGlobalNumber{+n.i}; -} - -SomeGlobalNumber operator~(const SomeGlobalNumber& n) { - return SomeGlobalNumber{~n.i}; -} - -Unary::SomeNumber Unary::operator-(const SomeNumber& n) { - return SomeNumber{-n.i}; -} - -Unary::SomeNumber Unary::operator+(const SomeNumber& n) { - return SomeNumber{+n.i}; -} - -Unary::SomeNumber Unary::operator~(const SomeNumber& n) { - return SomeNumber{~n.i}; -} diff --git a/bindings/pyroot/cppyy/cppyy/test/operators.h b/bindings/pyroot/cppyy/cppyy/test/operators.h deleted file mode 100644 index 98d1123cdc1c2..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/operators.h +++ /dev/null @@ -1,332 +0,0 @@ -#ifndef CPPYY_TEST_OPERATORS_H -#define CPPYY_TEST_OPERATORS_H - -class number { -public: - number() { m_int = 0; } - number(int i) { m_int = i; } - - number operator+(const number& n) const { return number(m_int + n.m_int); } - number operator+(int n) const { return number(m_int + n); } - number operator-(const number& n) const { return number(m_int - n.m_int); } - number operator-(int n) const { return number(m_int - n); } - number operator*(const number& n) const { return number(m_int * n.m_int); } - number operator*(int n) const { return number(m_int * n); } - number operator/(const number& n) const { return number(m_int / n.m_int); } - number operator/(int n) const { return number(m_int / n); } - number operator%(const number& n) const { return number(m_int % n.m_int); } - number operator%(int n) const { return number(m_int % n); } - - number& operator+=(const number& n) { m_int += n.m_int; return *this; } - number& operator-=(const number& n) { m_int -= n.m_int; return *this; } - number& operator*=(const number& n) { m_int *= n.m_int; return *this; } - number& operator/=(const number& n) { m_int /= n.m_int; return *this; } - number& operator%=(const number& n) { m_int %= n.m_int; return *this; } - - number operator-() { return number( -m_int ); } - - bool operator<(const number& n) const { return m_int < n.m_int; } - bool operator>(const number& n) const { return m_int > n.m_int; } - bool operator<=(const number& n) const { return m_int <= n.m_int; } - bool operator>=(const number& n) const { return m_int >= n.m_int; } - bool operator!=(const number& n) const { return m_int != n.m_int; } - bool operator==(const number& n) const { return m_int == n.m_int; } - - operator bool() { return m_int != 0; } - - number operator&(const number& n) const { return number(m_int & n.m_int); } - number operator|(const number& n) const { return number(m_int | n.m_int); } - number operator^(const number& n) const { return number(m_int ^ n.m_int); } - - number& operator&=(const number& n) { m_int &= n.m_int; return *this; } - number& operator|=(const number& n) { m_int |= n.m_int; return *this; } - number& operator^=(const number& n) { m_int ^= n.m_int; return *this; } - - number operator<<(int i) const { return number(m_int << i); } - number operator>>(int i) const { return number(m_int >> i); } - -private: - int m_int; -}; - - -//---------------------------------------------------------------------------- -struct operator_char_star { // for testing user-defined implicit casts - operator_char_star() : m_str((char*)"operator_char_star") {} - operator char*() { return m_str; } - char* m_str; -}; - -struct operator_const_char_star { - operator_const_char_star() : m_str("operator_const_char_star" ) {} - operator const char*() { return m_str; } - const char* m_str; -}; - -struct operator_int { - operator int() { return m_int; } - int m_int; -}; - -struct operator_long { - operator long() { return m_long; } - long m_long; -}; - -struct operator_double { - operator double() { return m_double; } - double m_double; -}; - -struct operator_short { - operator short() { return m_short; } - unsigned short m_short; -}; - -struct operator_unsigned_int { - operator unsigned int() { return m_uint; } - unsigned int m_uint; -}; - -struct operator_unsigned_long { - operator unsigned long() { return m_ulong; } - unsigned long m_ulong; -}; - -struct operator_float { - operator float() { return m_float; } - float m_float; -}; - - -//---------------------------------------------------------------------------- -class v_opeq_base { -public: - v_opeq_base(int val); - virtual ~v_opeq_base(); - - virtual bool operator==(const v_opeq_base& other); - -protected: - int m_val; -}; - -class v_opeq_derived : public v_opeq_base { -public: - v_opeq_derived(int val); - virtual ~v_opeq_derived(); - - virtual bool operator==(const v_opeq_derived& other); -}; - - -//---------------------------------------------------------------------------- -class YAMatrix1 { // YetAnotherMatrix class for indexing tests -public: - YAMatrix1() : m_val(42) {} - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix2 { -public: - YAMatrix2() : m_val(42) {} - - int& operator[] (int i); - const int& operator[] (int i) const; - - int m_val; -}; - -class YAMatrix3 { -public: - YAMatrix3() : m_val(42) {} - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int& operator[] (int i); - const int& operator[] (int i) const; - - int m_val; -}; - -class YAMatrix4 { -public: - YAMatrix4() : m_val(42) {} - -// opposite order of method declarations from YAMatrix3 - int& operator[] (int i); - const int& operator[] (int i) const; - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix5 { -public: - YAMatrix5() : m_val(42) {} - - int& operator[] (int i); - int operator[] (int i) const; - - int& operator() (int i, int j); - int operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix6 { -public: - YAMatrix6() : m_val(42) {} - - int& operator[] (int i); - int operator[] (int i) const; - - int& operator() (int i, int j); - - int m_val; -}; - -class YAMatrix7 { -public: - YAMatrix7() : m_val(42) {} - - int& operator[] (int i); - int& operator() (int i, int j); - - int m_val; -}; - - -//---------------------------------------------------------------------------- -// templated operator -class TOIClass { -public: - template - bool operator<(T t) { return true; } -}; - - -//---------------------------------------------------------------------------- -// __radd__/__rmul__, (non-)associative -class AssocADD { -public: - AssocADD(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend AssocADD operator+(int n, AssocADD& a); - friend AssocADD operator+(AssocADD& a, int n); - -private: - double fx; -}; - -AssocADD operator+(int n, AssocADD& a); -AssocADD operator+(AssocADD& a, int n); - -class NonAssocRADD { -public: - NonAssocRADD(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend NonAssocRADD operator+(int n, NonAssocRADD& a); - -private: - double fx; -}; - -NonAssocRADD operator+(int n, NonAssocRADD& a); - -class AssocMUL { -public: - AssocMUL(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend AssocMUL operator*(int n, AssocMUL& m); - friend AssocMUL operator*(AssocMUL& m, int n); - -private: - double fx; -}; - -AssocMUL operator*(int n, AssocMUL& m); -AssocMUL operator*(AssocMUL& m, int n); - -class NonAssocRMUL { -public: - NonAssocRMUL(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend NonAssocRMUL operator*(int n, NonAssocRMUL& m); - -private: - double fx; -}; - -NonAssocRMUL operator*(int n, NonAssocRMUL& m); - - -//---------------------------------------------------------------------------- -// multi-lookup -namespace MultiLookup { - -struct Vector2 { - Vector2(double d1, double d2) : x(d1), y(d2) {} - double x, y; -}; - -double operator*(const Vector2&, const Vector2&); -Vector2 operator*(const Vector2&, double); - -double operator/(const Vector2&, const Vector2&); -Vector2 operator/(const Vector2&, double); - -double operator+(const Vector2&, const Vector2&); -Vector2 operator+(const Vector2&, double); - -double operator-(const Vector2&, const Vector2&); -Vector2 operator-(const Vector2&, double); - -} // namespace MultiLookup - - -//---------------------------------------------------------------------------- -// unary functions -struct SomeGlobalNumber { - SomeGlobalNumber(int n) : i(n) {} - int i; -}; - -SomeGlobalNumber operator-(const SomeGlobalNumber&); -SomeGlobalNumber operator+(const SomeGlobalNumber&); -SomeGlobalNumber operator~(const SomeGlobalNumber&); - -namespace Unary { - -struct SomeNumber { - SomeNumber(int n) : i(n) {} - int i; -}; - -SomeNumber operator-(const SomeNumber&); -SomeNumber operator+(const SomeNumber&); -SomeNumber operator~(const SomeNumber&); - -} - - -//---------------------------------------------------------------------------- -// comma operator (mapped to __comma__) -struct CommaOperator { - CommaOperator(int i) : fInt(i) {} - CommaOperator& operator,(int i) { fInt += i; return *this; } - int fInt; -}; - -#endif // !CPPYY_TEST_OPERATORS_H diff --git a/bindings/pyroot/cppyy/cppyy/test/operators.xml b/bindings/pyroot/cppyy/cppyy/test/operators.xml deleted file mode 100644 index 4bd7ab5b7362f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/operators.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/overloads.h b/bindings/pyroot/cppyy/cppyy/test/overloads.h deleted file mode 100644 index 96b5da1b54d46..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/overloads.h +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include - -class a_overload { -public: - a_overload(); - int i1, i2; -}; - -namespace ns_a_overload { - class a_overload { - public: - a_overload(); - int i1, i2; - }; - - class b_overload { - public: - int f(const std::vector* v); - }; -} - -namespace ns_b_overload { - class a_overload { - public: - a_overload(); - int i1, i2; - }; -} - -class b_overload { -public: - b_overload(); - int i1, i2; -}; - -class c_overload { -public: - c_overload(); - int get_int(a_overload* a); - int get_int(ns_a_overload::a_overload* a); - int get_int(ns_b_overload::a_overload* a); - int get_int(short* p); - int get_int(b_overload* b); - int get_int(int* p); -}; - -class d_overload { -public: - d_overload(); -// int get_int(void* p) { return *(int*)p; } - int get_int(int* p); - int get_int(b_overload* b); - int get_int(short* p); - int get_int(ns_b_overload::a_overload* a); - int get_int(ns_a_overload::a_overload* a); - int get_int(a_overload* a); -}; - - -class aa_ol {}; -class bb_ol; -bb_ol* get_bb_ol(); -class cc_ol {}; -class dd_ol; -dd_ol* get_dd_ol(); - - -class more_overloads { -public: - more_overloads(); - std::string call(const aa_ol&); - std::string call(const bb_ol&, void* n=0); - std::string call(const cc_ol&); - std::string call(const dd_ol&); - - std::string call_unknown(const dd_ol&); - - std::string call(double); - std::string call(int); - std::string call1(int); - std::string call1(double); -}; - -class more_overloads2 { -public: - more_overloads2(); - std::string call(const bb_ol&); - std::string call(const bb_ol*); - - std::string call(const dd_ol*, int); - std::string call(const dd_ol&, int); -}; - -template -double calc_mean(long n, const T* a) { - double sum = 0., sumw = 0.; - const T* end = a+n; - while (a != end) { - sum += *a++; - sumw += 1; - } - - return sum/sumw; -} - -double calc_mean(long n, const float* a); -double calc_mean(long n, const double* a); -double calc_mean(long n, const int* a); -double calc_mean(long n, const short* a); -double calc_mean(long n, const long* a); - -class more_overloads3 { -public: - std::string slice(size_t) const; - std::string slice(size_t); - std::string slice(size_t, size_t); - std::string slice(size_t, size_t) const; -}; diff --git a/bindings/pyroot/cppyy/cppyy/test/overloads.xml b/bindings/pyroot/cppyy/cppyy/test/overloads.xml deleted file mode 100644 index 418344baf8f56..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/overloads.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/pythonizables.cxx b/bindings/pyroot/cppyy/cppyy/test/pythonizables.cxx deleted file mode 100644 index c2263d9d93778..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/pythonizables.cxx +++ /dev/null @@ -1,53 +0,0 @@ -#include "pythonizables.h" - -//=========================================================================== -pyzables::NakedBuffers::NakedBuffers(int size, double valx, double valy) : m_size(size) { - m_Xbuf = new double[size]; - m_Ybuf = new double[size]; - - for (int i=0; i(new Countable); } - -pyzables::SharedCountable_t pyzables::gime_mine() { return mine; } -pyzables::SharedCountable_t* pyzables::gime_mine_ptr() { return &mine; } -pyzables::SharedCountable_t& pyzables::gime_mine_ref() { return mine; } - -unsigned int pyzables::pass_mine_sp(std::shared_ptr ptr) { return ptr->m_check; } -unsigned int pyzables::pass_mine_sp_ref(std::shared_ptr& ptr) { return ptr->m_check; } -unsigned int pyzables::pass_mine_sp_ptr(std::shared_ptr* ptr) { return (*ptr)->m_check; } - -unsigned int pyzables::pass_mine_rp(Countable c) { return c.m_check; } -unsigned int pyzables::pass_mine_rp_ref(const Countable& c) { return c.m_check; } -unsigned int pyzables::pass_mine_rp_ptr(const Countable* c) { return c->m_check; } - -pyzables::Countable* pyzables::gime_naked_countable() { return new Countable{}; } diff --git a/bindings/pyroot/cppyy/cppyy/test/pythonizables.h b/bindings/pyroot/cppyy/cppyy/test/pythonizables.h deleted file mode 100644 index abdb4c4a7de24..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/pythonizables.h +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include -#include - -// Python -struct _object; -typedef _object PyObject; - - -namespace pyzables { - -//=========================================================================== -class SomeDummy1 {}; -class SomeDummy2 {}; - - -//=========================================================================== -class NakedBuffers { -public: - NakedBuffers(int size, double valx, double valy); - NakedBuffers(const NakedBuffers&) = delete; - NakedBuffers& operator=(const NakedBuffers&) = delete; - ~NakedBuffers(); - -public: - int GetN(); - double* GetX(); - double* GetY(); - -private: - double* m_Xbuf; - double* m_Ybuf; - int m_size; -}; - -template -class NakedBuffers2 { -public: - NakedBuffers2(int size, double valx, double valy) : m_Xbuf(size), m_Ybuf(size) { - for (int i=0; i { -public: - Vector(int size) : std::vector(size) {} -}; - - -//=========================================================================== -class MyBase { -public: - virtual ~MyBase(); -}; -class MyDerived : public MyBase { -public: - virtual ~MyDerived(); -}; - -MyBase* GimeDerived(); - - -//=========================================================================== -class Countable { -public: - Countable() { ++sInstances; } - Countable(const Countable&) { ++sInstances; } - Countable& operator=(const Countable&) { return *this; } - ~Countable() { --sInstances; } - -public: - virtual const char* say_hi() { return "Hi!"; } - -public: - unsigned int m_check = 0xcdcdcdcd; - -public: - static int sInstances; -}; - -typedef std::shared_ptr SharedCountable_t; -extern SharedCountable_t mine; - -void renew_mine(); - -SharedCountable_t gime_mine(); -SharedCountable_t* gime_mine_ptr(); -SharedCountable_t& gime_mine_ref(); - -unsigned int pass_mine_sp(SharedCountable_t p); -unsigned int pass_mine_sp_ref(SharedCountable_t& p); -unsigned int pass_mine_sp_ptr(SharedCountable_t* p); - -unsigned int pass_mine_rp(Countable); -unsigned int pass_mine_rp_ref(const Countable&); -unsigned int pass_mine_rp_ptr(const Countable*); - -Countable* gime_naked_countable(); - - -//=========================================================================== -class unknown_iterator; -class IndexableBase { -public: - unknown_iterator* begin() { return nullptr; } - unknown_iterator* end() { return (unknown_iterator*)1; } - int operator[](int) { return 42; } - int size() { return 1; } -}; - -class IndexableDerived : public IndexableBase {}; - -//=========================================================================== -// for testing size() -> __len__ pythonization guards -class SizeReturnsInt { -public: - int size() { return 3; } - int *begin() { return m_data; } - int *end() { return m_data + 3; } - -private: - int m_data[3] = {1, 2, 3}; -}; - -class SizeReturnsNonInt { -public: - struct OptSize {}; - OptSize size() { return {}; } - int *begin() { return nullptr; } - int *end() { return nullptr; } -}; - -class SizeWithoutIterator { -public: - int size() { return 5; } - // no begin()/end() or operator[] -}; - -// for testing __len__ with fully inherited container interface -class ContainerBase { -public: - int size() { return 2; } - int *begin() { return m_data; } - int *end() { return m_data + 2; } - -private: - int m_data[2] = {10, 20}; -}; - -class InheritedContainer : public ContainerBase {}; - -} // namespace pyzables diff --git a/bindings/pyroot/cppyy/cppyy/test/pythonizables.xml b/bindings/pyroot/cppyy/cppyy/test/pythonizables.xml deleted file mode 100644 index 154869de9f10e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/pythonizables.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/std_streams.cxx b/bindings/pyroot/cppyy/cppyy/test/std_streams.cxx deleted file mode 100644 index 30a7940ea69eb..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/std_streams.cxx +++ /dev/null @@ -1,5 +0,0 @@ -#include "std_streams.h" - -#ifndef _WIN32 -template class std::basic_ios >; -#endif diff --git a/bindings/pyroot/cppyy/cppyy/test/std_streams.xml b/bindings/pyroot/cppyy/cppyy/test/std_streams.xml deleted file mode 100644 index b14aa884e1e48..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/std_streams.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/stltypes.cxx b/bindings/pyroot/cppyy/cppyy/test/stltypes.cxx deleted file mode 100644 index 970c83e7f05f8..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/stltypes.cxx +++ /dev/null @@ -1,98 +0,0 @@ -#include "stltypes.h" - -#include - - -//- adverse effect of implicit conversion on vector -int vectest_ol1(const std::vector&) { return 1; } -int vectest_ol1(std::string) { return 2; } -int vectest_ol2(std::string) { return 2; } -int vectest_ol2(const std::vector&) { return 1; } - - -//- helpers for testing array -int ArrayTest::get_pp_px(Point** p, int idx) { - return p[idx]->px; -} - -int ArrayTest::get_pp_py(Point** p, int idx) { - return p[idx]->py; -} - -int ArrayTest::get_pa_px(Point* p[], int idx) { - return p[idx]->px; -} - -int ArrayTest::get_pa_py(Point* p[], int idx) { - return p[idx]->py; -} - - -// helpers for string testing -std::string str_array_1[3] = {"a", "b", "c"}; -std::string str_array_2[] = {"d", "e", "f", "g"}; -std::string str_array_3[3][2] = {{"a", "b"}, {"c", "d"}, {"e", "f"}}; -std::string str_array_4[4][2][2] = { - {{"a", "b"}, {"c", "d"}}, - {{"e", "f"}, {"g", "h"}}, - {{"i", "j"}, {"k", "l"}}, - {{"m", "n"}, {"o", "p"}}, -}; - - -// helpers for mixing unicode and std::string -size_t UnicodeAndSTL::get_size(std::string s) { return s.size(); } -size_t UnicodeAndSTL::get_size_cr(const std::string& s ) { return s.size(); } -size_t UnicodeAndSTL::get_size_cc(const char* s) { return strlen(s); } -size_t UnicodeAndSTL::get_size_w(std::wstring s) { return s.size(); } -size_t UnicodeAndSTL::get_size_wcr(const std::wstring& s) { return s.size(); } -std::string UnicodeAndSTL::get_string(std::string s) { return s; } -std::string UnicodeAndSTL::get_string_cr(const std::string& s) { return s; } -std::string UnicodeAndSTL::get_string_cc(const char* s) { return s; } -std::wstring UnicodeAndSTL::get_string_w(std::wstring s) { return s; } -std::wstring UnicodeAndSTL::get_string_wcr(const std::wstring& s) { return s; } - - -// helpers for string_view testing -std::string_view::size_type StringViewTest::count(const std::string_view arg) { - return arg.size(); -} - -std::string_view::size_type StringViewTest::count_cr(const std::string_view& arg) { - return arg.size(); -} - -// helper for exception base class testing -int MyError::s_count = 0; -int MyError::get_count() { return s_count; } - -MyError::MyError(const std::string& msg) : fMsg(msg) { - s_count += 1; -} - -MyError::MyError(const MyError& other) : fMsg(other.fMsg) { - s_count += 1; -} - -MyError::~MyError() { - s_count -= 1; -} - -const char* MyError::what() const throw() { return fMsg.c_str(); } - -int GetMyErrorCount() { - return MyError::s_count; -} - -YourError::YourError(const std::string& msg) : MyError(msg) {} -YourError::YourError(const YourError& s) : MyError(s) {} - -ErrorNamespace::MyError::MyError(const std::string& msg) : fMsg(msg) {} -const char* ErrorNamespace::MyError::what() const throw() { return fMsg.c_str(); } - -void ErrorNamespace::throw_error(int i) { - if (i == 0) throw ::MyError("first error"); - else if (i == 1) throw ::YourError("second error"); - else if (i == 2) throw MyError("third error"); - throw YourError("fourth error"); -} diff --git a/bindings/pyroot/cppyy/cppyy/test/stltypes.h b/bindings/pyroot/cppyy/cppyy/test/stltypes.h deleted file mode 100644 index 6506b62f379e6..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/stltypes.h +++ /dev/null @@ -1,252 +0,0 @@ -#ifndef CPPYY_TEST_STLTYPES_H -#define CPPYY_TEST_STLTYPES_H - -#include -#include -#include -#include -#include -#include -#include -#include - -//- basic example class -class just_a_class { -public: - int m_i; -}; - -// enum for vector of enums setitem tests -enum VecTestEnum { - EVal1 = 1, EVal2 = 3 -}; - -namespace VecTestEnumNS { - enum VecTestEnum { EVal1 = 5, EVal2 = 42 }; -} - - -//- adverse effect of implicit conversion on vector -int vectest_ol1(const std::vector&); -int vectest_ol1(std::string); -int vectest_ol2(std::string); -int vectest_ol2(const std::vector&); - - -//- class with lots of std::[w]string handling -template -class stringy_class { -public: - stringy_class(const typename S::value_type* s) : m_string(s) {} - - S get_string1() { return m_string; } - void get_string2(S& s) { s = m_string; } - - void set_string1(const S& s) { m_string = s; } - void set_string2(S s) { m_string = s; } - - S m_string; -}; - -typedef stringy_class stringy_class_t; -typedef stringy_class wstringy_class_t; - - -//- class that has an STL-like interface -class no_dict_available; - -template -class stl_like_class { -public: - no_dict_available* begin() { return 0; } - no_dict_available* end() { return (no_dict_available*)1; } - int size() { return 4; } - int operator[](int i) { return i; } - std::string operator[](double) { return "double"; } - std::string operator[](const std::string&) { return "string"; } -}; - -namespace { - stl_like_class stlc_1; -} - - -//- similar, but now the iterators don't work b/c they don't compile -template -class stl_like_class2 { -protected: - value_type fData[sz]; - -public: - static size_t size() { return sz; } - value_type& operator[](ptrdiff_t i) { return fData[i]; } -}; - -class stl_like_class4 { -public: - struct iterator { - iterator(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const iterator& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - iterator begin() { return iterator{0}; } - iterator end() { return iterator{10}; } -}; - -class stl_like_class5 { -public: - struct iterator { - iterator(int p) : pos(p) {} - int operator++(int) { int val = pos; ++pos; return val; } - bool operator!=(const iterator& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - iterator begin() { return iterator{0}; } - iterator end() { return iterator{10}; } -}; - -class stl_like_class6 { -public: - struct some_name { - some_name(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const some_name& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - some_name begin() { return some_name{0}; } - some_name end() { return some_name{10}; } -}; - -struct some_name7 { - some_name7(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const some_name7& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; -}; - -class stl_like_class7 { -public: - some_name7 begin() { return some_name7{0}; } - some_name7 end() { return some_name7{10}; } -}; - - -// similar, but now with a base/derives split -class stl_like_class_base { -protected: - std::vector fVec; - -public: - void push_back(double d) { fVec.push_back(d); } - std::vector::size_type size() { return fVec.size(); } -}; - -class stl_like_class8 : public stl_like_class_base { -public: - double& operator[](int idx) { return fVec[idx]; } -}; - -class stl_like_class9 : public stl_like_class_base { -public: - double* begin() { return fVec.data(); } - double* end() { return fVec.data() + fVec.size(); } -}; - - -//- helpers for testing array -namespace ArrayTest { - -struct Point { - Point() : px(0), py(0) {} - Point(int x, int y) : px(x), py(y) {} - int px, py; -}; - -int get_pp_px(Point** p, int idx); -int get_pp_py(Point** p, int idx); -int get_pa_px(Point* p[], int idx); -int get_pa_py(Point* p[], int idx); - -} // namespace ArrayTest - - -// helpers for string testing -extern std::string str_array_1[3]; -extern std::string str_array_2[]; -extern std::string str_array_3[3][2]; -extern std::string str_array_4[4][2][2]; - - -// helpers for mixing unicode and std::string -namespace UnicodeAndSTL { - size_t get_size(std::string s); - size_t get_size_cr(const std::string& s); - size_t get_size_cc(const char* s); - size_t get_size_w(std::wstring s); - size_t get_size_wcr(const std::wstring& s); - std::string get_string(std::string s); - std::string get_string_cr(const std::string& s); - std::string get_string_cc(const char* s); - std::wstring get_string_w(std::wstring s); - std::wstring get_string_wcr(const std::wstring& s); -} - - -// helpers for string_view testing -namespace StringViewTest { - std::string_view::size_type count(const std::string_view arg); - std::string_view::size_type count_cr(const std::string_view& arg); -} - - -// helper for exception base class testing -class MyError : public std::exception { -public: - static int s_count; - static int get_count(); - -public: - explicit MyError(const std::string& msg); - MyError(const MyError&); - virtual ~MyError(); - MyError& operator=(const MyError&) = default; - const char* what() const throw() override; - -private: - std::string fMsg; -}; - -int GetMyErrorCount(); - -class YourError : public MyError { -public: - explicit YourError(const std::string& msg); - YourError(const YourError& s); -}; - -namespace ErrorNamespace { - -class MyError : public std::exception { -public: - explicit MyError(const std::string& msg); - const char* what() const throw() override; - -private: - std::string fMsg; -}; - -class YourError : public MyError { -public: - using MyError::MyError; -}; - -void throw_error(int i); - -} // ErrorNamespace - -#endif // !CPPYY_TEST_STLTYPES_H diff --git a/bindings/pyroot/cppyy/cppyy/test/stltypes.xml b/bindings/pyroot/cppyy/cppyy/test/stltypes.xml deleted file mode 100644 index 4a5c2c00e1095..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/stltypes.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/support.py b/bindings/pyroot/cppyy/cppyy/test/support.py deleted file mode 100644 index f8a1bccbd593e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/support.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import print_function -import os, py, sys, subprocess - -currpath = py.path.local(__file__).dirpath() - - -def setup_make(targetname): - if sys.platform == 'win32': - popen = subprocess.Popen([sys.executable, "make_dict_win32.py", targetname], cwd=str(currpath), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - else: - popen = subprocess.Popen(["make", targetname+"Dict.so"], cwd=str(currpath), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout, _ = popen.communicate() - if popen.returncode: - raise OSError("'make' failed:\n%s" % (stdout,)) - -if sys.hexversion >= 0x3000000: - pylong = int - pyunicode = str - maxvalue = sys.maxsize -else: - pylong = long - pyunicode = unicode - maxvalue = sys.maxint - -IS_WINDOWS = 0 -if 'win32' in sys.platform: - import platform - if '64' in platform.architecture()[0]: - IS_WINDOWS = 64 - maxvalue = 2**31-1 - else: - IS_WINDOWS = 32 - -IS_MAC = False -IS_MAC_ARM = 0 -if 'darwin' in sys.platform: - import platform - IS_MAC = True - if 'arm64' in platform.machine(): - IS_MAC_ARM = 64 - os.environ["CPPYY_UNCAUGHT_QUIET"] = "1" - -try: - import __pypy__ - ispypy = True -except ImportError: - ispypy = False - -def has_cpp_20(): - import cppyy - cppyy.cppdef(""" - #ifndef CPPYY_CPP_STANDARD_PROBE - #define CPPYY_CPP_STANDARD_PROBE - constexpr long cppyy_cpp_standard = __cplusplus; - #endif - """) - return cppyy.gbl.cppyy_cpp_standard >= 202002 diff --git a/bindings/pyroot/cppyy/cppyy/test/templ_args_funcs.py b/bindings/pyroot/cppyy/cppyy/test/templ_args_funcs.py deleted file mode 100644 index b8fc6178a2e9e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/templ_args_funcs.py +++ /dev/null @@ -1,9 +0,0 @@ -import cppyy - -def ann_adapt(node: 'FPTA::Node&') -> cppyy.gbl.FPTA.EventId: - return cppyy.gbl.FPTA.EventId(node.fData) - -def ann_ref_mod(node: 'FPTA::Node&') -> cppyy.gbl.FPTA.EventId: - ev_id = cppyy.gbl.FPTA.EventId(node.fData) - node.fData = 81 - return ev_id diff --git a/bindings/pyroot/cppyy/cppyy/test/templates.h b/bindings/pyroot/cppyy/cppyy/test/templates.h deleted file mode 100644 index b3c38d2514e42..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/templates.h +++ /dev/null @@ -1,538 +0,0 @@ -#ifndef CPPYY_TEST_TEMPLATES_H -#define CPPYY_TEST_TEMPLATES_H - -#include -#include -#include -#include -#include - -#ifndef _WIN32 -#include -inline std::string demangle_it(const char* name, const char* errmsg) { - int status; - std::string res = abi::__cxa_demangle(name, 0, 0, &status); - if (status != 0) throw std::runtime_error(errmsg); - return res; -} -#else -inline std::string demangle_it(const char* name, const char*) { - return name; // typeinfo's name() is already demangled -} -#endif - - -//=========================================================================== -class MyTemplatedMethodClass { // template methods -public: - template long get_size(A&); - template long get_size(const A&); - - long get_size(); - template long get_size(); - - long get_char_size(); - long get_int_size(); - long get_long_size(); - long get_float_size(); - long get_double_size(); - - long get_self_size(); - -private: - double m_data[3]; -}; - -template -long MyTemplatedMethodClass::get_size(A&) { - return sizeof(A); -} - -template -long MyTemplatedMethodClass::get_size(const A&) { - return sizeof(A)+1; -} - -template -inline long MyTemplatedMethodClass::get_size() { - return sizeof(B); -} - -// -typedef MyTemplatedMethodClass MyTMCTypedef_t; - -// explicit instantiation -template long MyTemplatedMethodClass::get_size(); -template long MyTemplatedMethodClass::get_size(); - -// "lying" specialization -template<> -inline long MyTemplatedMethodClass::get_size() { - return 42; -} - - -//=========================================================================== -// global templated functions -template -long global_get_size() { - return sizeof(T); -} - -template -int global_some_foo(T) { - return 42; -} - -template -int global_some_bar() { - return T; -} - -template -int global_some_bar_var(T) { - return 13; -} - -template -struct SomeResult { - F m_retval; -}; - -template -SomeResult global_get_some_result(const I& carrier) { - SomeResult r{}; - r.m_retval = O(carrier[0]); - return r; -} - - -//=========================================================================== -// variadic functions -inline bool isSomeInt(int) { return true; } -inline bool isSomeInt(double) { return false; } -template -inline bool isSomeInt(Args...) { return false; } - -namespace AttrTesting { - -struct Obj1 { int var1; }; -struct Obj2 { int var2; }; - -template -constexpr auto has_var1(T t) -> decltype(t.var1, true) { return true; } - -template -constexpr bool has_var1(Args...) { return false; } - -template -constexpr bool call_has_var1(T&& t) { return AttrTesting::has_var1(std::forward(t)); } - -template -struct select_template_arg {}; - -template -struct select_template_arg<0, T0, T...> { - typedef T0 type; - typedef type argument; -}; - -template -struct select_template_arg { - typedef typename select_template_arg::type argument; -}; - -} // AttrTesting - - -namespace SomeNS { - -template -int some_foo(T) { - return 42; -} - -template -int some_bar() { - return T; -} - -struct SomeStruct { - template - static int some_bar() { - return T; - } -}; - -inline std::string tuplify(std::ostringstream& out) { - out << "NULL)"; - return out.str(); -} - -template -std::string tuplify(std::ostringstream& out, T value, Args... args) -{ - out << value << ", "; - return tuplify(out, args...); -} - -} // namespace SomeNS - - -//=========================================================================== -// using of static data -// TODO: this should live here instead of in test_templates.test08 -/* -template struct BaseClassWithStatic { - static T const ref_value; -}; - -template -T const BaseClassWithStatic::ref_value = 42; - -template -struct DerivedClassUsingStatic : public BaseClassWithStatic { - using BaseClassWithStatic::ref_value; - - explicit DerivedClassUsingStatic(T x) : BaseClassWithStatic() { - m_value = x > ref_value ? ref_value : x; - } - - T m_value; -}; -*/ - - -//=========================================================================== -// templated callable -class TemplatedCallable { -public: - template - O operator() (const I& in) const { return O(in); } -}; - - -//=========================================================================== -// templated typedefs -namespace TemplatedTypedefs { - -template -struct BaseWithEnumAndTypedefs { - enum { vsize = _vsize }; - typedef TYPE_IN in_type; - typedef TYPE_OUT out_type; -}; - -template -struct DerivedWithUsing : public BaseWithEnumAndTypedefs -{ - typedef BaseWithEnumAndTypedefs base_type; - using base_type::vsize; - using typename base_type::in_type; - typedef typename base_type::in_type in_type_tt; - using typename base_type::out_type; -}; - -struct SomeDummy {}; - -} // namespace TemplatedTypedefs - - -//=========================================================================== -// hiding templated methods -namespace TemplateHiding { - -struct Base { - template - int callme(T t = T(1)) { return 2*t; } -}; - -struct Derived : public Base { - int callme(int t = 2) { return t; } -}; - -} // namespace TemplateHiding - - -//=========================================================================== -// 'using' of templates -template using DA_vector = std::vector; - -namespace using_problem { - -template -class Base { -public: - template - R get1(T t) { return t + R{5}; } - T get2() { return T{5}; } - template - R get3(T t) { return t + R{5}; } - T get3() { return T{5}; } -}; - -template -class Derived : public Base { -public: - typedef Base _Mybase; - using _Mybase::get1; - using _Mybase::get2; - using _Mybase::get3; -}; - -template class Foo { T x; }; - -namespace Bar { - using ::using_problem::Foo; -} - -} // namespace using_problem - - -//=========================================================================== -// template with r-value -namespace T_WithRValue { - -template -bool is_valid(T&& new_value) { - return new_value != T{}; -} - -} // namespace T_WithRValue - - -//=========================================================================== -// variadic templates -namespace some_variadic { - -#ifdef _WIN32 -#ifdef __CLING__ -extern __declspec(dllimport) std::string gTypeName; -#else -extern __declspec(dllexport) std::string gTypeName; -#endif -#else -extern std::string gTypeName; -#endif - -template -class A { -public: - A() { - gTypeName = demangle_it(typeid(A).name(), "A::A"); - } - A(const A&) = default; - A(A&&) = default; - A& operator=(const A&) = default; - A& operator=(A&&) = default; - - template - void a(FArgs&&... args) { - gTypeName = demangle_it(typeid(&A::a).name(), "A::a-2"); - } - - template - T a_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(&A::a_T).name(), "A::a_T-2"); - return T{}; - } - - template - static void sa(FArgs&&... args) { - gTypeName = demangle_it(typeid(A).name(), "A::sa-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(A::sa).name(), "A::sa-2"); - } - - template - static T sa_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(A).name(), "A::sa_T-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(A::sa_T).name(), "A::sa_T-2"); - return T{}; - } -}; - -class B { -public: - B() { - gTypeName = demangle_it(typeid(B).name(), "B::B"); - } - B(const B&) = default; - B(B&&) = default; - B& operator=(const B&) = default; - B& operator=(B&&) = default; - - template - void b(FArgs&&... args) { - gTypeName = demangle_it(typeid(&B::b).name(), "B::b-2"); - } - - template - T b_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(&B::b_T).name(), "B::b_T-2"); - return T{}; - } - - template - static void sb(FArgs&&... args) { - gTypeName = demangle_it(typeid(B).name(), "B::sb-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(B::sb).name(), "B::sb-2"); - } - - template - static T sb_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(B).name(), "B::sb_T-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(B::sb_T).name(), "B::sb_T-2"); - return T{}; - } -}; - -template -void fn(Args&&... args) { - gTypeName = demangle_it(typeid(fn).name(), "fn"); -} - -template -T fn_T(Args&&... args) { - gTypeName = demangle_it(typeid(fn).name(), "fn_T"); - return T{}; -} - -} // namespace some_variadic - - -//=========================================================================== -// template with empty body -namespace T_WithEmptyBody { - -#ifdef _WIN32 -#ifdef __CLING__ -extern __declspec(dllimport) std::string side_effect; -#else -extern __declspec(dllexport) std::string side_effect; -#endif -#else -extern std::string side_effect; -#endif - -template -void some_empty(); - -} // namespace T_WithEmptyBody - - -//=========================================================================== -// template with catch-all (void*, void**)overloads -namespace T_WithGreedyOverloads { - -class SomeClass { - double fD; -}; - -class WithGreedy1 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(void*, bool force=false) { return -1; } -}; - -class WithGreedy2 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(void**, bool force=false) { return -1; } -}; - -class DoesNotExist; - -class WithGreedy3 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(DoesNotExist*, bool force=false) { return -1; } -}; - -} // namespace T_WithGreedyOverloads - - -//=========================================================================== -// template with overloaded non-templated and templated setitem -namespace TemplateWithSetItem { - -template -class MyVec { -private: - std::vector fData; - -public: - using size_type = typename std::vector::size_type; - - MyVec(size_type count) : fData(count) {} - - T & operator[](size_type index) { return fData[index]; } - - // The definition of this templated operator causes the issue - template - MyVec operator[](const MyVec &conds) const { return MyVec(2); } -}; - -} // namespace TemplateWithSetItem - - -//=========================================================================== -// type reduction examples on gmpxx-like template expressions -namespace TypeReduction { - -template -struct BinaryExpr; - -template -struct Expr { - Expr() {} - Expr(const BinaryExpr&) {} -}; - -template -struct BinaryExpr { - BinaryExpr(const Expr&, const Expr&) {} -}; - -template -BinaryExpr operator+(const Expr& e1, const Expr& e2) { - return BinaryExpr(e1, e2); -} - -} // namespace TypeReduction - - -//=========================================================================== -// type deduction examples -namespace FailedTypeDeducer { - -template -class B { -public: - auto result() { return 5.; } -}; - -extern template class B; - -} - -namespace UsingPtr { - -struct Test {}; -using testptr = Test *; - -template -bool testfun(T x) -{ - return !(bool)x; -} - -} - -#endif // !CPPYY_TEST_TEMPLATES_H diff --git a/bindings/pyroot/cppyy/cppyy/test/templates.xml b/bindings/pyroot/cppyy/cppyy/test/templates.xml deleted file mode 100644 index 7cb33f67a0051..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/templates.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py b/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py deleted file mode 100644 index 3a096069e4204..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest, os -from pytest import raises, mark -from support import setup_make, IS_WINDOWS - -test_dct = "libexample_cxx" - - -class TestACLASSLOADER: - - def setup_class(cls): - import cppyy - - @mark.xfail(strict=True, condition=not IS_WINDOWS, reason="rootmap files are a legacy feature") - def test01_class_autoloading(self): - """Test whether a class can be found through .rootmap.""" - import cppyy - example01_class = cppyy.gbl.example01 - assert example01_class - cl2 = cppyy.gbl.example01 - assert cl2 - assert example01_class is cl2 - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py deleted file mode 100644 index 03307937c8ab1..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py +++ /dev/null @@ -1,964 +0,0 @@ -import pytest, os -from pytest import mark, raises, skip -from support import setup_make, pylong, IS_WINDOWS, ispypy - -test_dct = "advancedcpp_cxx" - - -class TestADVANCEDCPP: - def setup_class(cls): - cls.test_dct = test_dct - import cppyy - cls.advanced = cppyy.load_reflection_info(cls.test_dct) - - def test01_default_arguments(self): - """Test usage of default arguments""" - - import cppyy - def test_defaulter(n, t): - defaulter = getattr(cppyy.gbl, '%s_defaulter' % n) - - d = defaulter() - assert d.m_a == t(11) - assert d.m_b == t(22) - assert d.m_c == t(33) - d.__destruct__() - - d = defaulter(0) - assert d.m_a == t(0) - assert d.m_b == t(22) - assert d.m_c == t(33) - d.__destruct__() - - d = defaulter(1, 2) - assert d.m_a == t(1) - assert d.m_b == t(2) - assert d.m_c == t(33) - d.__destruct__() - - d = defaulter(3, 4, 5) - assert d.m_a == t(3) - assert d.m_b == t(4) - assert d.m_c == t(5) - d.__destruct__() - - defaulter_func = getattr(cppyy.gbl, '%s_defaulter_func' %n) - answers = [11, 22, 33, 3] - for idx in range(4): - assert defaulter_func(idx) == answers[idx] - - test_defaulter('short', int) - test_defaulter('ushort', int) - test_defaulter('int', int) - test_defaulter('uint', int) - test_defaulter('long', pylong) - test_defaulter('ulong', pylong) - test_defaulter('llong', pylong) - test_defaulter('ullong', pylong) - test_defaulter('float', float) - test_defaulter('double', float) - - assert cppyy.gbl.string_defaulter_func(0) == "aap" - assert cppyy.gbl.string_defaulter_func(0, "zus") == "zus" - assert cppyy.gbl.string_defaulter_func(1) == "noot" - assert cppyy.gbl.string_defaulter_func(1, "zus") == "noot" - assert cppyy.gbl.string_defaulter_func(1, "zus", "jet") == "jet" - assert cppyy.gbl.string_defaulter_func(2) == "mies" - - def test02_simple_inheritance(self): - """Test binding of a basic inheritance structure""" - - import cppyy - base_class = cppyy.gbl.base_class - derived_class = cppyy.gbl.derived_class - - assert issubclass(derived_class, base_class) - assert not issubclass(base_class, derived_class) - - b = base_class() - assert isinstance(b, base_class) - assert not isinstance(b, derived_class) - - assert b.m_b == 1 - assert b.get_value() == 1 - assert b.m_db == 1.1 - assert b.get_base_value() == 1.1 - - b.m_b, b.m_db = 11, 11.11 - assert b.m_b == 11 - assert b.get_value() == 11 - assert b.m_db == 11.11 - assert b.get_base_value() == 11.11 - - b.__destruct__() - - d = derived_class() - assert isinstance(d, derived_class) - assert isinstance(d, base_class) - - assert d.m_d == 2 - assert d.get_value() == 2 - assert d.m_dd == 2.2 - assert d.get_derived_value() == 2.2 - - assert d.m_b == 1 - assert d.m_db == 1.1 - assert d.get_base_value() == 1.1 - - d.m_b, d.m_db = 11, 11.11 - d.m_d, d.m_dd = 22, 22.22 - - assert d.m_d == 22 - assert d.get_value() == 22 - assert d.m_dd == 22.22 - assert d.get_derived_value() == 22.22 - - assert d.m_b == 11 - assert d.m_db == 11.11 - assert d.get_base_value() == 11.11 - - d.__destruct__() - - def test03_namespaces(self): - """Test access to namespaces and inner classes""" - - import cppyy - gbl = cppyy.gbl - - assert gbl.a_ns is gbl.a_ns - assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - - assert gbl.a_ns.b_class is gbl.a_ns.b_class - assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class - assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class - assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class - - assert gbl.a_ns.g_a == 11 - assert gbl.a_ns.get_g_a() == 11 - assert gbl.a_ns.b_class.s_b == 22 - assert gbl.a_ns.b_class().m_b == -2 - assert gbl.a_ns.b_class.c_class.s_c == 33 - assert gbl.a_ns.b_class.c_class().m_c == -3 - assert gbl.a_ns.d_ns.g_d == 44 - assert gbl.a_ns.d_ns.get_g_d() == 44 - assert gbl.a_ns.d_ns.e_class.s_e == 55 - assert gbl.a_ns.d_ns.e_class().m_e == -5 - assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 - assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 - - raises(TypeError, gbl.a_ns) - - def test03a_namespace_lookup_on_update(self): - """Test whether namespaces can be shared across dictionaries.""" - - import cppyy - gbl = cppyy.gbl - - lib2 = cppyy.load_reflection_info("advancedcpp2_cxx") - - assert gbl.a_ns is gbl.a_ns - assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - - assert gbl.a_ns.g_class is gbl.a_ns.g_class - assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class - assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class - assert gbl.a_ns.d_ns.i_class.j_class is gbl.a_ns.d_ns.i_class.j_class - - assert gbl.a_ns.g_g == 77 - assert gbl.a_ns.get_g_g() == 77 - assert gbl.a_ns.g_class.s_g == 88 - assert gbl.a_ns.g_class().m_g == -7 - assert gbl.a_ns.g_class.h_class.s_h == 99 - assert gbl.a_ns.g_class.h_class().m_h == -8 - assert gbl.a_ns.d_ns.g_i == 111 - assert gbl.a_ns.d_ns.get_g_i() == 111 - assert gbl.a_ns.d_ns.i_class.s_i == 222 - assert gbl.a_ns.d_ns.i_class().m_i == -9 - assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 - assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 - - def test04_template_types(self): - """Test bindings of templated types""" - - import cppyy - gbl = cppyy.gbl - - assert gbl.T1 is gbl.T1 - assert gbl.T2 is gbl.T2 - assert gbl.T3 is gbl.T3 - assert not gbl.T1 is gbl.T2 - assert not gbl.T2 is gbl.T3 - - assert gbl.T1('int') is gbl.T1('int') - assert gbl.T1(int) is gbl.T1('int') - assert gbl.T2('T1') is gbl.T2('T1') - assert gbl.T2(gbl.T1('int')) is gbl.T2('T1') - assert gbl.T2(gbl.T1(int)) is gbl.T2('T1') - assert gbl.T3('int,double') is gbl.T3('int,double') - assert gbl.T3('int', 'double') is gbl.T3('int,double') - assert gbl.T3(int, 'double') is gbl.T3('int,double') - assert gbl.T3('T1,T2 >') is gbl.T3('T1,T2 >') - assert gbl.T3('T1', gbl.T2(gbl.T1(int))) is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4(int) is gbl.a_ns.T4('int') - assert gbl.a_ns.T4('a_ns::T4 >')\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- mix in some of the alternative syntax - assert gbl.T1['int'] is gbl.T1('int') - assert gbl.T1[int] is gbl.T1('int') - assert gbl.T2['T1'] is gbl.T2('T1') - assert gbl.T2[gbl.T1('int')] is gbl.T2('T1') - assert gbl.T2[gbl.T1(int)] is gbl.T2('T1') - assert gbl.T3['int,double'] is gbl.T3('int,double') - assert gbl.T3['int', 'double'] is gbl.T3('int,double') - assert gbl.T3[int, 'double'] is gbl.T3('int,double') - assert gbl.T3['T1,T2 >'] is gbl.T3('T1,T2 >') - assert gbl.T3['T1', gbl.T2[gbl.T1[int]]] is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4[int] is gbl.a_ns.T4('int') - assert gbl.a_ns.T4['a_ns::T4 >']\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- - t1 = gbl.T1(int)() - assert t1.m_t1 == 1 - assert t1.get_value() == 1 - t1.__destruct__() - - #----- - t1 = gbl.T1(int)(11) - assert t1.m_t1 == 11 - assert t1.get_value() == 11 - t1.m_t1 = 111 - assert t1.get_value() == 111 - assert t1.m_t1 == 111 - t1.__destruct__() - - #----- - t2 = gbl.T2(gbl.T1(int))(gbl.T1(int)(32)) - t2.m_t2.m_t1 = 32 - assert t2.m_t2.get_value() == 32 - assert t2.m_t2.m_t1 == 32 - t2.__destruct__() - - - def test05_abstract_classes(self): - """Test non-instatiatability of abstract classes""" - - import cppyy - gbl = cppyy.gbl - - raises(TypeError, gbl.a_class) - raises(TypeError, gbl.some_abstract_class) - - assert issubclass(gbl.some_concrete_class, gbl.some_abstract_class) - - c = gbl.some_concrete_class() - assert isinstance(c, gbl.some_concrete_class) - assert isinstance(c, gbl.some_abstract_class) - - def test06_datamembers(self): - """Test data member access when using virtual inheritence""" - - import cppyy - a_class = cppyy.gbl.a_class - b_class = cppyy.gbl.b_class - c_class_1 = cppyy.gbl.c_class_1 - c_class_2 = cppyy.gbl.c_class_2 - d_class = cppyy.gbl.d_class - - assert issubclass(b_class, a_class) - assert issubclass(c_class_1, a_class) - assert issubclass(c_class_1, b_class) - assert issubclass(c_class_2, a_class) - assert issubclass(c_class_2, b_class) - assert issubclass(d_class, a_class) - assert issubclass(d_class, b_class) - assert issubclass(d_class, c_class_2) - - #----- - b = b_class() - assert b.m_a == 1 - assert b.m_da == 1.1 - assert b.m_b == 2 - assert b.m_db == 2.2 - - b.m_a = 11 - assert b.m_a == 11 - assert b.m_b == 2 - - b.m_da = 11.11 - assert b.m_da == 11.11 - assert b.m_db == 2.2 - - b.m_b = 22 - assert b.m_a == 11 - assert b.m_da == 11.11 - assert b.m_b == 22 - assert b.get_value() == 22 - - b.m_db = 22.22 - assert b.m_db == 22.22 - - b.__destruct__() - - #----- - c1 = c_class_1() - assert c1.m_a == 1 - assert c1.m_b == 2 - assert c1.m_c == 3 - - c1.m_a = 11 - assert c1.m_a == 11 - - c1.m_b = 22 - assert c1.m_a == 11 - assert c1.m_b == 22 - - c1.m_c = 33 - assert c1.m_a == 11 - assert c1.m_b == 22 - assert c1.m_c == 33 - assert c1.get_value() == 33 - - c1.__destruct__() - - #----- - d = d_class() - assert d.m_a == 1 - assert d.m_b == 2 - assert d.m_c == 3 - assert d.m_d == 4 - - d.m_a = 11 - assert d.m_a == 11 - - d.m_b = 22 - assert d.m_a == 11 - assert d.m_b == 22 - - d.m_c = 33 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 - - d.m_d = 44 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 - assert d.m_d == 44 - assert d.get_value() == 44 - - d.__destruct__() - - def test07_pass_by_reference(self): - """Test reference passing when using virtual inheritance""" - - import cppyy - gbl = cppyy.gbl - b_class = gbl.b_class - c_class = gbl.c_class_2 - d_class = gbl.d_class - - #----- - b = b_class() - b.m_a, b.m_b = 11, 22 - assert gbl.get_a(b) == 11 - assert gbl.get_b(b) == 22 - b.__destruct__() - - #----- - c = c_class() - c.m_a, c.m_b, c.m_c = 11, 22, 33 - assert gbl.get_a(c) == 11 - assert gbl.get_b(c) == 22 - assert gbl.get_c(c) == 33 - c.__destruct__() - - #----- - d = d_class() - d.m_a, d.m_b, d.m_c, d.m_d = 11, 22, 33, 44 - assert gbl.get_a(d) == 11 - assert gbl.get_b(d) == 22 - assert gbl.get_c(d) == 33 - assert gbl.get_d(d) == 44 - d.__destruct__() - - def test08_void_pointer_passing(self): - """Test passing of variants of void pointer arguments""" - - import cppyy - pointer_pass = cppyy.gbl.pointer_pass - some_concrete_class = cppyy.gbl.some_concrete_class - - pp = pointer_pass() - o = some_concrete_class() - - assert cppyy.addressof(o) == pp.gime_address_ptr(o) - assert cppyy.addressof(o) == pp.gime_address_ptr_ptr(o) - assert cppyy.addressof(o) == pp.gime_address_ptr_ref(o) - - if IS_WINDOWS != 64: - # there is no 8-byte integer type array on Windows 64b - import array - addressofo = array.array('l', [cppyy.addressof(o)]) - assert addressofo[0] == pp.gime_address_ptr_ptr(addressofo) - - assert 0 == pp.gime_address_ptr(0) - raises(TypeError, pp.gime_address_ptr, None) - - ptr = cppyy.bind_object(0, some_concrete_class) - assert cppyy.addressof(ptr) == 0 - pp.set_address_ptr_ref(ptr) - assert cppyy.addressof(ptr) == 0x1234 - - # alternate path through static method handling, which does NOT - # provide 'pp' as argument (and thus need no removing) - sf = pp.set_address_ptr_ref - sf(ptr) - assert cppyy.addressof(ptr) == 0x1234 - - pp.set_address_ptr_ptr(ptr) - assert cppyy.addressof(ptr) == 0x4321 - - assert cppyy.addressof(cppyy.nullptr) == 0 - raises(TypeError, cppyy.addressof, None) - assert cppyy.addressof(0) == 0 - - def test09_opaque_pointer_passing(self): - """Test passing around of opaque pointers""" - - import cppyy - some_concrete_class = cppyy.gbl.some_concrete_class - - o = some_concrete_class() - - # TODO: figure out the PyPy equivalent of CObject (may have to do this - # through the C-API from C++) - - #cobj = cppyy.as_cobject(o) - addr = cppyy.addressof(o) - - #assert o == cppyy.bind_object(cobj, some_concrete_class) - #assert o == cppyy.bind_object(cobj, type(o)) - #assert o == cppyy.bind_object(cobj, o.__class__) - #assert o == cppyy.bind_object(cobj, "some_concrete_class") - assert cppyy.addressof(o) == cppyy.addressof(cppyy.bind_object(addr, some_concrete_class)) - assert o is cppyy.bind_object(addr, some_concrete_class) - assert o is cppyy.bind_object(addr, type(o)) - assert o is cppyy.bind_object(addr, o.__class__) - assert o is cppyy.bind_object(addr, "some_concrete_class") - raises(TypeError, cppyy.bind_object, addr, "does_not_exist") - raises(TypeError, cppyy.bind_object, addr, 1) - - def test10_object_identity(self): - """Test object identity""" - - import cppyy - some_concrete_class = cppyy.gbl.some_concrete_class - some_class_with_data = cppyy.gbl.some_class_with_data - - o = some_concrete_class() - addr = cppyy.addressof(o) - - o2 = cppyy.bind_object(addr, some_concrete_class) - assert o is o2 - - o3 = cppyy.bind_object(addr, some_class_with_data) - assert not o is o3 - - d1 = some_class_with_data() - d2 = d1.gime_copy() - assert not d1 is d2 - - dd1a = d1.gime_data() - dd1b = d1.gime_data() - assert dd1a is dd1b - - dd2 = d2.gime_data() - assert not dd1a is dd2 - assert not dd1b is dd2 - - d2.__destruct__() - d1.__destruct__() - - RTS = cppyy.gbl.refers_to_self - - r1 = RTS() - r2 = RTS() - r1.m_other = r2 - - r3 = r1.m_other - r4 = r1.m_other - assert r3 is r4 - - assert r3 == r2 - assert r3 is r2 - - r3.extra = 42 - assert r2.extra == 42 - assert r4.extra == 42 - - def test11_multi_methods(self): - """Test calling of methods from multiple inheritance""" - - import cppyy - multi = cppyy.gbl.multi - - assert cppyy.gbl.multi1 is multi.__bases__[0] - assert cppyy.gbl.multi2 is multi.__bases__[1] - - dict_keys = list(multi.__dict__.keys()) - assert dict_keys.count('get_my_own_int') == 1 - assert dict_keys.count('get_multi1_int') == 0 - assert dict_keys.count('get_multi2_int') == 0 - - m = multi(1, 2, 3) - assert m.get_multi1_int() == 1 - assert m.get_multi2_int() == 2 - assert m.get_my_own_int() == 3 - - def test12_actual_type(self): - """Test that a pointer to base return does an auto-downcast""" - - import cppyy - base_class = cppyy.gbl.base_class - base_class.clone.__creates__ = True - derived_class = cppyy.gbl.derived_class - derived_class.clone.__creates__ = True - - b = base_class() - d = derived_class() - - assert b is b.cycle(b) - assert id(b) == id(b.cycle(b)) - assert b is d.cycle(b) - assert id(b) == id(d.cycle(b)) - assert d is b.cycle(d) - assert id(d) == id(b.cycle(d)) - assert d is d.cycle(d) - assert id(d) == id(d.cycle(d)) - - assert isinstance(b.cycle(b), base_class) - assert isinstance(d.cycle(b), base_class) - assert isinstance(b.cycle(d), derived_class) - assert isinstance(d.cycle(d), derived_class) - - base_class.clone.__creates__ = True - assert isinstance(b.clone(), base_class) - derived_class.clone.__creates__ = True - assert isinstance(d.clone(), derived_class) - - # special case when round-tripping through a void* ptr - voidp = b.mask(d) - assert not isinstance(voidp, base_class) - assert not isinstance(voidp, derived_class) - - d1 = cppyy.bind_object(voidp, base_class, cast=True) - assert isinstance(d1, derived_class) - assert d1 is d - - b1 = cppyy.bind_object(voidp, base_class) - assert isinstance(b1, base_class) - assert cppyy.addressof(b1) == cppyy.addressof(d) - assert not (b1 is d) - - def test13_actual_type_virtual_multi(self): - """Test auto-downcast in adverse inheritance situation""" - - import cppyy - - cppyy.gbl.create_c1.__creates__ = True - c1 = cppyy.gbl.create_c1() - assert type(c1) == cppyy.gbl.c_class_1 - assert c1.m_c == 3 - c1.__destruct__() - - cppyy.gbl.create_c2.__creates__ = True - c2 = cppyy.gbl.create_c2() - assert type(c2) == cppyy.gbl.c_class_2 - assert c2.m_c == 3 - c2.__destruct__() - - def test14_new_overloader(self): - """Verify that class-level overloaded new/delete are called""" - - import cppyy - - assert cppyy.gbl.new_overloader.s_instances == 0 - nl = cppyy.gbl.new_overloader() - assert cppyy.gbl.new_overloader.s_instances == 1 - nl.__destruct__() - - import gc - gc.collect() - assert cppyy.gbl.new_overloader.s_instances == 0 - - def test15_template_instantiation_with_vector_of_float(self): - """Test template instantiation with a std::vector""" - - import cppyy - - # the following will simply fail if there is a naming problem (e.g. - # std::, allocator, etc., etc.); note the parsing required ... - b = cppyy.gbl.my_templated_class(cppyy.gbl.std.vector(float))() - - for i in range(5): - b.m_b.push_back(i) - assert round(b.m_b[i], 5) == float(i) - - def test16_template_global_functions(self): - """Test template global function lookup and calls""" - - import cppyy - - f = cppyy.gbl.my_templated_function - - assert f('c') == 'c' - assert type(f('c')) == type('c') - assert f(3.) == 3. - assert type(f(4.)) == type(4.) - - def test17_assign_to_return_byref(self): - """Test assignment to an instance returned by reference""" - - if ispypy: - skip('segfaults in pypy') - - from cppyy import gbl - - a = gbl.std.vector(gbl.ref_tester)() - a.push_back(gbl.ref_tester(42)) - - assert len(a) == 1 - assert a[0].m_i == 42 - - a[0] = gbl.ref_tester(33) - assert len(a) == 1 - assert a[0].m_i == 33 - - def test18_math_converters(self): - """Test operator int/long/double incl. typedef""" - - from cppyy import gbl - - a = gbl.some_convertible() - a.m_i = 1234 - a.m_d = 4321. - - assert int(a) == 1234 - assert int(a) == a.m_i - assert pylong(a) == a.m_i - - assert float(a) == 4321. - assert float(a) == a.m_d - - def test19_comparator(self): - """Check that the global operator!=/== is picked up""" - - from cppyy import gbl - - a, b = gbl.some_comparable(), gbl.some_comparable() - - assert a == b - assert b == a - assert a.__eq__(b) - assert b.__eq__(a) - assert a.__ne__(a) - assert b.__ne__(b) - assert a.__eq__(b) == True - assert b.__eq__(a) == True - assert a.__eq__(a) == False - assert b.__eq__(b) == False - - def test20_overload_order_with_proper_return(self): - """Test return type against proper overload w/ const and covariance""" - - import cppyy - - assert cppyy.gbl.overload_one_way().gime() == 1 - assert cppyy.gbl.overload_the_other_way().gime() == "aap" - - @mark.xfail(strict=True) - def test21_access_to_global_variables(self): - """Access global_variables_and_pointers""" - - import cppyy - - assert cppyy.gbl.my_global_double == 12. - assert len(cppyy.gbl.my_global_array) == 500 - assert cppyy.gbl.my_global_string1 == "aap noot mies" - assert cppyy.gbl.my_global_string2 == "zus jet teun" - assert list(cppyy.gbl.my_global_string3) == ["aap", "noot", "mies"] - assert cppyy.gbl.my_global_ptr[0] == 1234. - - v = cppyy.gbl.my_global_int_holders - assert len(v) == 5 - expected_vals = [13, 42, 88, -1, 17] - for i in range(len(v)): - assert v[i].m_val == expected_vals[i] - - o = cppyy.gbl.some_concrete_class() - assert type(o) != type(cppyy.gbl.g_abstract_ptr) - cppyy.gbl.g_abstract_ptr = o - assert type(o) != type(cppyy.gbl.g_abstract_ptr) - assert cppyy.gbl.g_abstract_ptr == o - cppyy.gbl.g_abstract_ptr = cppyy.nullptr - - cppyy.cppexec("std::vector* gtestv1 = nullptr;") - pyv = cppyy.gbl.std.vector[int]() - cppyy.gbl.gtestv1 = pyv - cppyy.cppexec("auto* gtestv2 = gtestv1;") - pyv.push_back(42) - assert len(cppyy.gbl.gtestv1) == 1 - assert len(cppyy.gbl.gtestv2) == 1 - - @mark.xfail(run=False, condition=IS_WINDOWS, reason="Crashes with some versions of Visual Studio") - def test22_exceptions(self): - """Catching of C++ exceptions""" - - import cppyy - Thrower = cppyy.gbl.Thrower - - Thrower.throw_anything.__useffi__ = False - Thrower.throw_exception.__useffi__ = False - - t = Thrower() - - assert raises(Exception, t.throw_anything) - assert raises(Exception, t.throw_exception) - - caught = False - try: - t.throw_exception() - except Exception as e: - assert "C++ function failed" in str(e) - caught = True - assert caught == True - - def test23_using(self): - """Accessibility of using declarations""" - - import cppyy - - assert cppyy.gbl.UsingBase1().vcheck() == 'A' - - D1 = cppyy.gbl.UsingDerived1 - assert not 'UsingBase1' in D1.__init__.__doc__ - - d1a = D1() - assert d1a.m_int == 13 - assert d1a.m_int2 == 42 - assert d1a.vcheck() == 'B' - - d1b = D1(10) - assert d1b.m_int == 10 - assert d1b.m_int2 == 42 - assert d1b.vcheck() == 'B' - - d1c = D1(d1b) - assert d1c.m_int == 10 - assert d1c.m_int2 == 42 - assert d1c.vcheck() == 'B' - - D2 = cppyy.gbl.UsingDerived2 - assert 'vcheck(int)' in D2.vcheck.__doc__ - assert 'vcheck()' in D2.vcheck.__doc__ - - d2 = D2() - assert d2.vcheck() == 'A' - assert d2.vcheck(1) == 'B' - - @mark.xfail(strict=True) - def test24_typedef_to_private_class(self): - """Typedefs to private classes should not resolve""" - - import cppyy - - assert cppyy.gbl.TypedefToPrivateClass().f().m_val == 42 - - @mark.xfail(strict=True) - def test25_ostream_printing(self): - """Mapping of __str__ through operator<<(ostream&)""" - - import cppyy - - ns = cppyy.gbl.Cpp2PyPrinting - - assert str(ns.Printable1()) == "Printable1::operator<<" - for tst in [(ns.Printable2, "Cpp2PyPrinting::operator<<"), - (ns.Printable3, "::operator<<(3)"), - (cppyy.gbl.Printable4, "::operator<<(4)"), - (cppyy.gbl.Printable6, "Printable6")]: - assert str(tst[0]()) == tst[1] - if '__lshiftc__' in tst[0].__dict__: - # only cached for global functions and in principle should - # not be needed anymore ... - assert tst[0].__lshiftc__ - del tst[0].__lshiftc__ - assert str(tst[0]()) == tst[1] - assert tst[0].__lshiftc__ - s = cppyy.gbl.std.ostringstream() - tst[0].__lshiftc__(s, tst[0]()) - assert s.str() == tst[1] - - # print through base class (used to fail with compilation error) - assert str(cppyy.gbl.Printable5()) == "Ok." - - # print through friend - cppyy.cppdef("""\ - namespace PrintingNS { - class X { - friend std::ostream& operator<<(std::ostream& os, const X&) { return os << "X"; } - }; - - class Y { - }; - - std::ostream& operator<<(std::ostream& os, const Y&) { return os << "Y"; } - } """) - - x = cppyy.gbl.PrintingNS.X() - assert str(x) == 'X' - - y = cppyy.gbl.PrintingNS.Y() - assert str(y) == 'Y' - - def test26_using_directive(self): - """Test using directive in namespaces""" - - import cppyy - - assert cppyy.gbl.UserDirs.foo1() == cppyy.gbl.UsedSpace1.foo1() - assert cppyy.gbl.UserDirs.bar() == cppyy.gbl.UsedSpace2.bar() - assert cppyy.gbl.UserDirs.foo2() == cppyy.gbl.UsedSpace1.inner.foo2() - - def test27_shadowed_typedef(self): - """Test that typedefs are not shadowed""" - - import cppyy - - cppyy.cppdef(""" - namespace ShadowedTypedef { - struct A { - typedef std::shared_ptr Ptr; - typedef int Val; - }; - - struct B : public A { - typedef std::shared_ptr Ptr; - typedef double Val; - }; - - struct C : public A { - /* empty */ - }; }""") - - ns = cppyy.gbl.ShadowedTypedef - - ns.A.Ptr # pull A::Ptr first - ns.A.Val # id. A::Val - ns.B.Ptr # used to be A.Ptr through python-side dict lookup - ns.B.Val # id. B::Val - ns.C.Ptr # is A.Ptr - ns.C.Val # is A.Val - - assert ns.A.Ptr == ns.A.Ptr - assert ns.B.Ptr == ns.B.Ptr - assert ns.A.Ptr != ns.B.Ptr - assert ns.A.Ptr == ns.C.Ptr - assert ns.A.Val == ns.C.Val - - # TODO: currently only classes are checked; typedefs of builtin types are - # mapped through the type mapper and as such can be anything - #assert ns.A.Val != ns.B.Val - #assert type(ns.A.Val(1)) == int - #assert type(ns.B.Val(1)) == float - - @mark.xfail(strict=True) - def test28_extern_C_in_namespace(self): - """Access to extern "C" declared functions in namespaces""" - - import cppyy - - cppyy.cppdef("""\ - namespace extern_c_in_ns { - extern "C" int some_func_xc(void) { return 21; } - int some_func() { return some_func_xc(); } - - namespace deeper { - extern "C" int some_other_func_xc(void) { return 42; } - } }""") - - ns = cppyy.gbl.extern_c_in_ns - - assert ns.some_func() == 21 - assert ns.some_func_xc() == 21 - - assert ns.deeper.some_other_func_xc() == 42 - - def test29_castcpp(self): - """Allow casting a Python class to a C++ one""" - - import cppyy - import math - - cppyy.cppdef("""\ - namespace castcpp { - struct MyPoint { - double x, y; - }; - - double norm_cr(const MyPoint& p) { - return std::sqrt(p.x*p.x + p.y*p.y); - } - - double norm_r(MyPoint& p) { - return std::sqrt(p.x*p.x + p.y*p.y); - } - - double norm_m(MyPoint&& p) { - return std::sqrt(p.x*p.x + p.y*p.y); - } - - double norm_v(MyPoint p) { - return std::sqrt(p.x*p.x + p.y*p.y); - } - - double norm_p(MyPoint* p) { - return std::sqrt(p->x*p->x + p->y*p->y); - } }""") - - ns = cppyy.gbl.castcpp - - class MyPyPoint1: - def __init__(self, x, y): - self.x = x - self.y = y - - class MyPyPoint2(MyPyPoint1): - def __cast_cpp__(self): - return ns.MyPoint(self.x, self.y) - - class MyPyPoint3(MyPyPoint1): - def __cast_cpp__(self): - return (self.x, self.y) - - p1 = MyPyPoint1(5, 10) - p2 = MyPyPoint2(p1.x, p1.y) - p3 = MyPyPoint3(p1.x, p1.y) - pynorm = math.sqrt(p2.x**2+p2.y**2) - - for norm in [ns.norm_cr, ns.norm_r, ns.norm_v, ns.norm_p]: - with raises(TypeError): - norm(p1) - - assert round(norm(p2) - pynorm, 8) == 0 - - for norm in [ns.norm_cr, ns.norm_m, ns.norm_v]: - assert round(norm(p3) - pynorm, 8) == 0 - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_api.py b/bindings/pyroot/cppyy/cppyy/test/test_api.py deleted file mode 100644 index f30bc4c65d3fe..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_api.py +++ /dev/null @@ -1,228 +0,0 @@ -import pytest -from pytest import raises, skip -from support import ispypy - - -class TestAPI: - def setup_class(cls): - if ispypy: - skip('C++ API only available on CPython') - - import cppyy - cppyy.include('CPyCppyy/API.h') - - def test01_type_checking(self): - """Python class type checks""" - - import cppyy - cpp = cppyy.gbl - API = cpp.CPyCppyy - - cppyy.cppdef(""" - class APICheck { - public: - void some_method() {} - };""") - - assert API.Scope_Check(cpp.APICheck) - assert not API.Scope_CheckExact(cpp.APICheck) - - a = cpp.APICheck() - assert API.Instance_Check(a) - assert not API.Instance_CheckExact(a) - - m = a.some_method - assert API.Overload_Check(m) - assert API.Overload_CheckExact(m) - - def test02_interpreter_access(self): - """Access to the python interpreter""" - - import cppyy - API = cppyy.gbl.CPyCppyy - - assert API.Exec('import sys') - - def test03_instance_conversion(self): - """Proxy object conversions""" - - import cppyy - cpp = cppyy.gbl - API = cpp.CPyCppyy - - cppyy.cppdef(""" - class APICheck2 { - public: - virtual ~APICheck2() {} - };""") - - m = cpp.APICheck2() - - voidp = API.Instance_AsVoidPtr(m) - m2 = API.Instance_FromVoidPtr(voidp, 'APICheck2') - assert m is m2 - - def test04_custom_converter(self): - """Custom type converter""" - - import cppyy - - cppyy.cppdef(""" - #include "CPyCppyy/API.h" - - class APICheck3 { - int fFlags; - public: - APICheck3() : fFlags(0) {} - virtual ~APICheck3() {} - - void setSetArgCalled() { fFlags |= 0x01; } - bool wasSetArgCalled() { return fFlags & 0x01; } - void setFromMemoryCalled() { fFlags |= 0x02; } - bool wasFromMemoryCalled() { return fFlags & 0x02; } - void setToMemoryCalled() { fFlags |= 0x04; } - bool wasToMemoryCalled() { return fFlags & 0x04; } - }; - - class APICheck3Converter : public CPyCppyy::Converter { - public: - virtual bool SetArg(PyObject* pyobject, CPyCppyy::Parameter& para, CPyCppyy::CallContext* = nullptr) { - APICheck3* a3 = (APICheck3*)CPyCppyy::Instance_AsVoidPtr(pyobject); - a3->setSetArgCalled(); - para.fValue.fVoidp = a3; - para.fTypeCode = 'V'; - return true; - } - - virtual PyObject* FromMemory(void* address) { - APICheck3* a3 = (APICheck3*)address; - a3->setFromMemoryCalled(); - return CPyCppyy::Instance_FromVoidPtr(a3, "APICheck3"); - } - - virtual bool ToMemory(PyObject* value, void* address) { - APICheck3* a3 = (APICheck3*)address; - a3->setToMemoryCalled(); - *a3 = *(APICheck3*)CPyCppyy::Instance_AsVoidPtr(value); - return true; - } - }; - - typedef CPyCppyy::ConverterFactory_t cf_t; - void register_a3() { - CPyCppyy::RegisterConverter("APICheck3", (cf_t)+[](CPyCppyy::cdims_t) { static APICheck3Converter c{}; return &c; }); - CPyCppyy::RegisterConverter("APICheck3&", (cf_t)+[](CPyCppyy::cdims_t) { static APICheck3Converter c{}; return &c; }); - } - void unregister_a3() { - CPyCppyy::UnregisterConverter("APICheck3"); - CPyCppyy::UnregisterConverter("APICheck3&"); - } - - APICheck3 gA3a, gA3b; - void CallWithAPICheck3(APICheck3&) {} - """) - - cppyy.gbl.register_a3() - - gA3a = cppyy.gbl.gA3a - assert gA3a - assert type(gA3a) == cppyy.gbl.APICheck3 - assert gA3a.wasFromMemoryCalled() - - assert not gA3a.wasSetArgCalled() - cppyy.gbl.CallWithAPICheck3(gA3a) - assert gA3a.wasSetArgCalled() - - cppyy.gbl.unregister_a3() - - gA3b = cppyy.gbl.gA3b - assert gA3b - assert type(gA3b) == cppyy.gbl.APICheck3 - assert not gA3b.wasFromMemoryCalled() - - def test05_custom_executor(self): - """Custom type executor""" - - import cppyy - - cppyy.cppdef(""" - #include "CPyCppyy/API.h" - - class APICheck4 { - int fFlags; - public: - APICheck4() : fFlags(0) {} - virtual ~APICheck4() {} - - void setExecutorCalled() { fFlags |= 0x01; } - bool wasExecutorCalled() { return fFlags & 0x01; } - }; - - class APICheck4Executor : public CPyCppyy::Executor { - public: - virtual PyObject* Execute(Cppyy::TCppMethod_t meth, Cppyy::TCppObject_t obj, CPyCppyy::CallContext* ctxt) { - APICheck4* a4 = (APICheck4*)CPyCppyy::CallVoidP(meth, obj, ctxt); - a4->setExecutorCalled(); - return CPyCppyy::Instance_FromVoidPtr(a4, "APICheck4", true); - } - }; - - typedef CPyCppyy::ExecutorFactory_t ef_t; - void register_a4() { - CPyCppyy::RegisterExecutor("APICheck4*", (ef_t)+[](CPyCppyy::cdims_t) { static APICheck4Executor c{}; return &c; }); - } - void unregister_a4() { - CPyCppyy::UnregisterExecutor("APICheck4*"); - } - - APICheck4* CreateAPICheck4() { return new APICheck4{}; } - APICheck4* CreateAPICheck4b() { return new APICheck4{}; } - """) - - cppyy.gbl.register_a4() - - a4 = cppyy.gbl.CreateAPICheck4() - assert a4 - assert type(a4) == cppyy.gbl.APICheck4 - assert a4.wasExecutorCalled(); - del a4 - - cppyy.gbl.unregister_a4() - - a4 = cppyy.gbl.CreateAPICheck4b() - assert a4 - assert type(a4) == cppyy.gbl.APICheck4 - assert not a4.wasExecutorCalled(); - - def test06_custom_executor(self): - """Custom type executor""" - - import cppyy - - cppyy.cppdef(""" - #include "CPyCppyy/API.h" - - namespace ArrayLike { - class MyClass{}; - MyClass* my = nullptr; - MyClass myA[5]; - - class MyArray { - public: - int operator[](int) { return 42; } - }; }""") - - ns = cppyy.gbl.ArrayLike; - Sequence_Check = cppyy.gbl.CPyCppyy.Sequence_Check - - assert not Sequence_Check(ns.my) - assert Sequence_Check(ns.myA) - assert not Sequence_Check(ns.MyClass()) - assert Sequence_Check(ns.MyArray()) - assert Sequence_Check(tuple()) - assert Sequence_Check(cppyy.gbl.std.vector[ns.MyClass]()) - assert not Sequence_Check(cppyy.gbl.std.list[ns.MyClass]()) - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_boost.py b/bindings/pyroot/cppyy/cppyy/test/test_boost.py deleted file mode 100644 index 11678bb6062f8..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_boost.py +++ /dev/null @@ -1,172 +0,0 @@ -import os, pytest -from pytest import mark, raises, skip -from support import setup_make - -noboost = False -if not (os.path.exists(os.path.join(os.path.sep, 'usr', 'include', 'boost')) or \ - os.path.exists(os.path.join(os.path.sep, 'usr', 'local', 'include', 'boost'))): - noboost = True - - -@mark.skipif(noboost == True, reason="boost not found") -class TestBOOSTANY: - def setup_class(cls): - import cppyy - - cppyy.include('boost/any.hpp') - - def test01_any_class(self): - """Availability of boost::any""" - - import cppyy - - assert cppyy.gbl.boost.any - - from cppyy.gbl import std - from cppyy.gbl.boost import any - - assert std.list[any] - - def test02_any_usage(self): - """boost::any assignment and casting""" - - import cppyy - - assert cppyy.gbl.boost - - from cppyy.gbl import std, boost - - val = boost.any() - # test both by-ref and by rvalue - v = std.vector[int]() - val.__assign__(v) - val.__assign__(std.move(std.vector[int](range(100)))) - assert val.type() == cppyy.typeid(std.vector[int]) - - extract = boost.any_cast[std.vector[int]](val) - assert type(extract) is std.vector[int] - assert len(extract) == 100 - extract += range(100) - assert len(extract) == 200 - - val.__assign__(std.move(extract)) # move forced - #assert len(extract) == 0 # not guaranteed by the standard - - # TODO: we hit boost::any_cast(boost::any* operand) instead - # of the reference version which raises - boost.any_cast.__useffi__ = False - try: - # raises(Exception, boost.any_cast[int], val) - assert not boost.any_cast[int](val) - except Exception: - # getting here is good, too ... - pass - - extract = boost.any_cast[std.vector[int]](val) - assert len(extract) == 200 - - -@mark.skipif(noboost == True, reason="boost not found") -class TestBOOSTOPERATORS: - def setup_class(cls): - import cppyy - - cppyy.include('boost/operators.hpp') - - def test01_ordered(self): - """ordered_field_operators as base used to crash""" - - import cppyy - - try: - cppyy.include("gmpxx.h") - except ImportError: - skip("gmpxx not installed") - cppyy.cppdef(""" - namespace boost_test { - class Derived : boost::ordered_field_operators, boost::ordered_field_operators {}; - } - """) - - assert cppyy.gbl.boost_test.Derived - - -@mark.skipif(noboost == True, reason="boost not found") -class TestBOOSTVARIANT: - def setup_class(cls): - import cppyy - - cppyy.include("boost/variant/variant.hpp") - cppyy.include("boost/variant/get.hpp") - - def test01_variant_usage(self): - """boost::variant usage""" - - # as posted on stackoverflow as example - import cppyy - - cpp = cppyy.gbl - std = cpp.std - boost = cpp.boost - - cppyy.cppdef("""namespace BV { - class A { }; - class B { }; - class C { }; } """) - - VariantType = boost.variant['BV::A, BV::B, BV::C'] - VariantTypeList = std.vector[VariantType] - - v = VariantTypeList() - - v.push_back(VariantType(cpp.BV.A())) - assert v.back().which() == 0 - v.push_back(VariantType(cpp.BV.B())) - assert v.back().which() == 1 - v.push_back(VariantType(cpp.BV.C())) - assert v.back().which() == 2 - - assert type(boost.get['BV::A'](v[0])) == cpp.BV.A - raises(Exception, boost.get['BV::B'], v[0]) - assert type(boost.get['BV::B'](v[1])) == cpp.BV.B - assert type(boost.get['BV::C'](v[2])) == cpp.BV.C - - -@mark.skipif(noboost == True, reason="boost not found") -class TestBOOSTERASURE: - def setup_class(cls): - import cppyy - - cppyy.include("boost/type_erasure/any.hpp") - cppyy.include("boost/type_erasure/member.hpp") - cppyy.include("boost/mpl/vector.hpp") - - @mark.xfail(run=False, reason="TClassInfo for 'Lengths' is missing & triggering Cling assertions during unloading") - def test01_erasure_usage(self): - """boost::type_erasure usage""" - - import cppyy - - cppyy.cppdef(""" - BOOST_TYPE_ERASURE_MEMBER((has_member_f), f, 0) - - using LengthsInterface = boost::mpl::vector< - boost::type_erasure::copy_constructible<>, - has_member_f() const>>; - - using Lengths = boost::type_erasure::any; - - struct Unerased { - std::vector f() const { return std::vector{}; } - }; - - Lengths lengths() { - return Unerased{}; - } - """) - - assert cppyy.gbl.lengths() is not None - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py b/bindings/pyroot/cppyy/cppyy/test/test_conversions.py deleted file mode 100644 index 201569539e5d4..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py +++ /dev/null @@ -1,128 +0,0 @@ -import pytest, os -from pytest import raises, mark -from support import setup_make, IS_WINDOWS - -test_dct = "conversions_cxx" - - -class TestCONVERSIONS: - def setup_class(cls): - cls.test_dct = test_dct - import cppyy - cls.conversion = cppyy.load_reflection_info(cls.test_dct) - - def test01_implicit_vector_conversions(self): - """Test implicit conversions of std::vector""" - - import cppyy - CNS = cppyy.gbl.CNS - - N = 10 - total = float(sum(range(N))) - - v = cppyy.gbl.std.vector['double'](range(N)) - assert CNS.sumit(v) == total - assert sum(v) == total - assert CNS.sumit(range(N)) == total - - M = 5 - total = float(sum(range(N)) + sum(range(M, N))) - v1 = cppyy.gbl.std.vector['double'](range(N)) - v2 = cppyy.gbl.std.vector['double'](range(M, N)) - assert CNS.sumit(v1, v2) == total - assert sum(v1)+sum(v2) == total - assert CNS.sumit(v1, range(M, N)) == total - assert CNS.sumit(range(N), v2) == total - assert CNS.sumit(range(N), range(M, N)) == total - - def test02_memory_handling_of_temporaries(self): - """Verify that memory of temporaries is properly cleaned up""" - - import cppyy, gc - CNS, CC = cppyy.gbl.CNS, cppyy.gbl.CNS.Counter - - assert CC.s_count == 0 - c = CC() - assert c.__python_owns__ - assert CC.s_count == 1 - del c; gc.collect() - assert CC.s_count == 0 - - assert CNS.myhowmany((CC(), CC(), CC())) == 3 - gc.collect() - assert CC.s_count == 0 - - assert CNS.myhowmany((CC(), CC(), CC()), [CC(), CC()]) == 5 - gc.collect() - assert CC.s_count == 0 - - def test03_error_handling(self): - """Verify error handling""" - - import cppyy, gc - CNS, CC = cppyy.gbl.CNS, cppyy.gbl.CNS.Counter - - N = 13 - total = sum(range(N)) - assert CNS.sumints(range(N)) == total - assert CNS.sumit([float(x) for x in range(N)]) == float(total) - raises(TypeError, CNS.sumints, [float(x) for x in range(N)]) - raises(TypeError, CNS.sumints, list(range(N))+[0.]) - - assert CC.s_count == 0 - - raises(TypeError, CNS.sumints, list(range(N))+[CC()]) - gc.collect() - assert CC.s_count == 0 - - raises(TypeError, CNS.sumints, range(N), [CC()]) - gc.collect() - - assert CC.s_count == 0 - raises(TypeError, CNS.sumints, [CC()], range(N)) - gc.collect() - assert CC.s_count == 0 - - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") - def test04_implicit_conversion_from_tuple(self): - """Allow implicit conversions from tuples as arguments {}-like""" - - # Note: fails on windows b/c the assignment operator for strings is - # template, which ("operator=(std::string)") doesn't instantiate - import cppyy - - m = cppyy.gbl.std.map[str, str]() - m.insert(('a', 'b')) # implicit conversion to std::pair - - assert m['a'] == 'b' - - def test05_bool_conversions(self): - """Test operator bool() and null pointer behavior""" - - import cppyy - - cppyy.cppdef("""\ - namespace BoolConversions { - struct Test1 {}; - struct Test2 { - Test2(bool b) : m_b(b) {} - explicit operator bool() const { return m_b; } - bool m_b; - }; - - Test1* CreateNullTest1() { return nullptr; } - Test2* CreateNullTest2() { return nullptr; } - }""") - - ns = cppyy.gbl.BoolConversions - - for t in [ns.CreateNullTest1(), ns.CreateNullTest2()]: - assert not t - - assert ns.Test1() - assert ns.Test2(True) - assert not ns.Test2(False) - - -if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py deleted file mode 100644 index 71e26dd1d9b22..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py +++ /dev/null @@ -1,2460 +0,0 @@ -import sys, pytest, os -from pytest import mark, raises, skip -from support import setup_make, pylong, pyunicode, IS_MAC, IS_MAC_ARM, IS_WINDOWS - -test_dct = "datatypes_cxx" - - -class TestDATATYPES: - def setup_class(cls): - import cppyy - - cls.test_dct = test_dct - cls.datatypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N - - @mark.xfail(strict=True) - def test01_instance_data_read_access(self): - """Read access to instance public data and verify values""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # reading boolean type - assert c.m_bool == False - assert not c.get_bool(); assert not c.get_bool_cr(); assert not c.get_bool_r() - - # reading char types - assert c.m_char == 'a' - assert c.m_schar == 'b' - assert c.m_uchar == 'c' - assert type(c.m_wchar) == pyunicode - assert c.m_wchar == u'D' - assert type(c.m_char16) == pyunicode - assert c.m_char16 == u'\u00df' - assert type(c.m_char32) == pyunicode - assert c.m_char32 == u'\u00df' - - # reading integer types - assert c.m_int8 == - 9; assert c.get_int8_cr() == - 9; assert c.get_int8_r() == - 9 - assert c.m_uint8 == 9; assert c.get_uint8_cr() == 9; assert c.get_uint8_r() == 9 - assert c.m_byte == ord('d'); assert c.get_byte_cr() == ord('d'); assert c.get_byte_r() == ord('d') - assert c.m_short == -11; assert c.get_short_cr() == -11; assert c.get_short_r() == -11 - assert c.m_ushort == 11; assert c.get_ushort_cr() == 11; assert c.get_ushort_r() == 11 - assert c.m_int == -22; assert c.get_int_cr() == -22; assert c.get_int_r() == -22 - assert c.m_uint == 22; assert c.get_uint_cr() == 22; assert c.get_uint_r() == 22 - assert c.m_long == -33; assert c.get_long_cr() == -33; assert c.get_long_r() == -33 - assert c.m_ulong == 33; assert c.get_ulong_cr() == 33; assert c.get_ulong_r() == 33 - assert c.m_llong == -44; assert c.get_llong_cr() == -44; assert c.get_llong_r() == -44 - assert c.m_ullong == 44; assert c.get_ullong_cr() == 44; assert c.get_ullong_r() == 44 - assert c.m_long64 == -55; assert c.get_long64_cr() == -55; assert c.get_long64_r() == -55 - assert c.m_ulong64 == 55; assert c.get_ulong64_cr() == 55; assert c.get_ulong64_r() == 55 - - # reading floating point types - assert round(c.m_float + 66., 5) == 0 - assert round(c.get_float_cr() + 66., 5) == 0 - assert round(c.get_float_r() + 66., 5) == 0 - assert round(c.m_double + 77., 11) == 0 - assert round(c.get_double_cr() + 77., 11) == 0 - assert round(c.get_double_r() + 77., 11) == 0 - assert round(c.m_ldouble + 88., 24) == 0 - assert round(c.get_ldouble_cr() + 88., 24) == 0 - assert round(c.get_ldouble_r() + 88., 24) == 0 - assert round(c.get_ldouble_def() -1., 24) == 0 - assert round(c.get_ldouble_def(2) -2., 24) == 0 - - # complex type - assert type(c.get_complex()) == complex - assert round(c.get_complex().real - 99., 11) == 0 - assert round(c.get_complex().imag - 101., 11) == 0 - assert repr(c.get_complex()) == '(99+101j)' - assert round(c.get_complex_cr().real - 99., 11) == 0 - assert round(c.get_complex_cr().imag - 101., 11) == 0 - assert round(c.get_complex_r().real - 99., 11) == 0 - assert round(c.get_complex_r().imag - 101., 11) == 0 - assert complex(cppyy.gbl.std.complex['double'](1, 2)) == complex(1, 2) - assert repr(cppyy.gbl.std.complex['double'](1, 2)) == '(1+2j)' - - # complex retains C++ type in all cases (but includes pythonization to - # resemble Python's complex more closely - assert type(c.get_icomplex()) == cppyy.gbl.std.complex[int] - assert round(c.get_icomplex().real - 121., 11) == 0 - assert round(c.get_icomplex().imag - 141., 11) == 0 - assert repr(c.get_icomplex()) == '(121+141j)' - assert round(c.get_icomplex_cr().real - 121., 11) == 0 - assert round(c.get_icomplex_cr().imag - 141., 11) == 0 - assert type(c.get_icomplex_r()) == cppyy.gbl.std.complex[int] - assert round(c.get_icomplex_r().real - 121., 11) == 0 - assert round(c.get_icomplex_r().imag - 141., 11) == 0 - assert complex(cppyy.gbl.std.complex['int'](1, 2)) == complex(1, 2) - - # _Complex double type - assert type(c.get_ccomplex()) == complex - assert round(c.get_ccomplex().real - 151., 11) == 0 - assert round(c.get_ccomplex().imag - 161., 11) == 0 - assert repr(c.get_ccomplex()) == '(151+161j)' - assert round(c.get_ccomplex_cr().real - 151., 11) == 0 - assert round(c.get_ccomplex_cr().imag - 161., 11) == 0 - assert round(c.get_ccomplex_r().real - 151., 11) == 0 - assert round(c.get_ccomplex_r().imag - 161., 11) == 0 - - # complex overloads - cppyy.cppdef(""" - namespace ComplexOverload { - template - struct CO { - CO(std::size_t sz) : m_size(sz), m_cplx(std::complex(7,42)) {} - CO(std::complex cplx) : m_size(42), m_cplx(cplx) {} - - std::size_t m_size; - std::complex m_cplx; - }; - }""") - - COd = cppyy.gbl.ComplexOverload.CO['double'] - COf = cppyy.gbl.ComplexOverload.CO['float'] - scf = cppyy.gbl.std.complex['float'] - - assert COd(2).m_size == 2 - assert COd(2).m_cplx == 7.+42j - assert COd(3.14).m_size == 42 - assert COd(3.14).m_cplx == 3.14+0j - assert COd(9.+7j).m_size == 42 - assert COd(9.+7j).m_cplx == 9.+7j - - assert COf(2).m_size == 2 - assert COf(2).m_cplx == scf(7, 42) - assert COf(3.14).m_size == 42 - assert COf(3.14).m_cplx == scf(3.14, 0) - assert COf(9.+7j).m_size == 42 - assert COf(9.+7j).m_cplx == scf(9., 7.) - - # reading of enum types - assert c.m_enum == CppyyTestData.kNothing - assert c.m_enum == c.kNothing - - # reading of boolean array - for i in range(self.N): - assert c.m_bool_array[i] == bool(i%2) - assert c.get_bool_array()[i] == bool(i%2) - assert c.m_bool_array2[i] == bool((i+1)%2) - assert c.get_bool_array2()[i] == bool((i+1)%2) - - # reading of integer array types - names = ["schar", "uchar", "int8", "uint8", "short", "ushort", "int", "uint", "long", "ulong", "byte"] - alpha = [(1, 2), (1, 2), (-1, -2), (3, 4), (-5, -6), (7, 8), (-9, -10), (11, 12), (-13, -14), (15, 16), (3, 4)] - - for j in range(self.N): - assert getattr(c, 'm_%s_array' % names[i])[i] == alpha[i][0]*i - assert getattr(c, 'get_%s_array' % names[i])()[i] == alpha[i][0]*i - assert getattr(c, 'm_%s_array2' % names[i])[i] == alpha[i][1]*i - assert getattr(c, 'get_%s_array2' % names[i])()[i] == alpha[i][1]*i - - # reading of floating point array types - for k in range(self.N): - assert round(c.m_float_array[k] + 13.*k, 5) == 0 - assert round(c.m_float_array2[k] + 14.*k, 5) == 0 - assert round(c.m_double_array[k] + 15.*k, 8) == 0 - assert round(c.m_double_array2[k] + 16.*k, 8) == 0 - - # out-of-bounds checks - raises(IndexError, c.m_schar_array.__getitem__, self.N) - raises(IndexError, c.m_uchar_array.__getitem__, self.N) - raises(IndexError, c.m_byte_array.__getitem__, self.N) - raises(IndexError, c.m_int8_array.__getitem__, self.N) - raises(IndexError, c.m_uint8_array.__getitem__, self.N) - raises(IndexError, c.m_short_array.__getitem__, self.N) - raises(IndexError, c.m_ushort_array.__getitem__, self.N) - raises(IndexError, c.m_int_array.__getitem__, self.N) - raises(IndexError, c.m_uint_array.__getitem__, self.N) - raises(IndexError, c.m_long_array.__getitem__, self.N) - raises(IndexError, c.m_ulong_array.__getitem__, self.N) - raises(IndexError, c.m_float_array.__getitem__, self.N) - raises(IndexError, c.m_double_array.__getitem__, self.N) - - # can not access an instance member on the class - raises(AttributeError, getattr, CppyyTestData, 'm_bool') - raises(AttributeError, getattr, CppyyTestData, 'm_int') - - assert not hasattr(CppyyTestData, 'm_bool') - assert not hasattr(CppyyTestData, 'm_int') - - c.__destruct__() - - @mark.xfail(strict=True) - def test02_instance_data_write_access(self): - """Test write access to instance public data and verify values""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # boolean types through functions - c.set_bool(True); assert c.get_bool() == True - c.set_bool(0); assert c.get_bool() == False - - # boolean types through data members - c.m_bool = True; assert c.get_bool() == True - c.set_bool(True); assert c.m_bool == True - c.m_bool = 0; assert c.get_bool() == False - c.set_bool(0); assert c.m_bool == False - raises(ValueError, c.set_bool, 10) - - # char types through functions - c.set_char('c'); assert c.get_char() == 'c' - c.set_uchar('e'); assert c.get_uchar() == 'e' - c.set_wchar(u'F'); assert c.get_wchar() == u'F' - assert type(c.get_wchar()) == pyunicode - c.set_char16(u'\u00f2'); assert c.get_char16() == u'\u00f2' - c.set_char32(u'\U0001f31c'); assert c.get_char32() == u'\U0001f31c' - - # char types through data members - c.m_char = 'b'; assert c.get_char() == 'b' - c.m_char = 40; assert c.get_char() == chr(40) - c.set_char('c'); assert c.m_char == 'c' - c.set_char(41); assert c.m_char == chr(41) - c.m_uchar = 'd'; assert c.get_uchar() == 'd' - c.m_uchar = 42; assert c.get_uchar() == chr(42) - c.set_uchar('e'); assert c.m_uchar == 'e' - c.set_uchar(43); assert c.m_uchar == chr(43) - c.m_wchar = u'G'; assert c.get_wchar() == u'G' - c.set_wchar(u'H'); assert c.m_wchar == u'H' - c.m_char16 = u'\u00f3'; assert c.get_char16() == u'\u00f3' - c.set_char16(u'\u00f4'); assert c.m_char16 == u'\u00f4' - c.m_char32 = u'\U0001f31d'; assert c.get_char32() == u'\U0001f31d' - c.set_char32(u'\U0001f31e'); assert c.m_char32 == u'\U0001f31e' - - raises(ValueError, c.set_char, "string") - raises(ValueError, c.set_char, 500) - raises(ValueError, c.set_uchar, "string") - raises(ValueError, c.set_uchar, -1) - raises(ValueError, c.set_wchar, "string") - raises(ValueError, c.set_char16, "string") - raises(ValueError, c.set_char32, "string") - - # integer types - names = ["int8", "uint8", "short", "ushort", "int", "uint", "long", "ulong", "llong", "ullong", "byte"] - - for i in range(len(names)): - setattr(c, 'm_'+names[i], i) - assert eval('c.get_%s()' % names[i]) == i - - for i in range(len(names)): - getattr(c, 'set_'+names[i])(2*i) - assert eval('c.m_%s' % names[i]) == 2*i - - for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(3*i) - assert eval('c.m_%s' % names[i]) == 3*i - - for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_rv')(4*i) - assert eval('c.m_%s' % names[i]) == 4*i - - for i in range(len(names)): - setattr(c, 'm_'+names[i], cppyy.default) - assert eval('c.get_%s()' % names[i]) == 0 - - for i in range(len(names)): - getattr(c, 'set_'+names[i])(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0 - - for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0 - - # float types through functions - c.set_float(0.123); assert round(c.get_float() - 0.123, 5) == 0 - c.set_double(0.456); assert round(c.get_double() - 0.456, 8) == 0 - c.set_ldouble(0.789); assert round(c.get_ldouble() - 0.789, 8) == 0 - - # float types through data members - c.m_float = 0.123; assert round(c.get_float() - 0.123, 5) == 0 - c.set_float(0.234); assert round(c.m_float - 0.234, 5) == 0 - c.set_float_cr(0.456); assert round(c.m_float - 0.456, 5) == 0 - c.m_double = 0.678; assert round(c.get_double() - 0.678, 8) == 0 - c.set_double(0.890); assert round(c.m_double - 0.890, 8) == 0 - c.set_double_cr(0.012); assert round(c.m_double - 0.012, 8) == 0 - c.m_ldouble = 0.876; assert round(c.get_ldouble() - 0.876, 8) == 0 - c.set_ldouble(0.098); assert round(c.m_ldouble - 0.098, 8) == 0 - c.set_ldouble_cr(0.210); assert round(c.m_ldouble - 0.210, 8) == 0 - - names = ['float', 'double', 'ldouble'] - for i in range(len(names)): - setattr(c, 'm_'+names[i], cppyy.default) - assert eval('c.get_%s()' % names[i]) == 0. - - for i in range(len(names)): - getattr(c, 'set_'+names[i])(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0. - - for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0. - - # (non-)writing of enum types - raises(TypeError, setattr, CppyyTestData, 'kNothing', 42) - - # arrays; there will be pointer copies, so destroy the current ones - c.destroy_arrays() - - # integer arrays (skip int8_t and uint8_t as these are presented as (unsigned) char still) - names = ["uchar", "short", "ushort", "int", "uint", "long", "ulong", "byte"] - - import array - a = range(self.N) - atypes = ['B', 'h', 'H', 'i', 'I', 'l', 'L', 'B'] - for j in range(len(names)): - b = array.array(atypes[j], a) - setattr(c, 'm_'+names[j]+'_array', b) # buffer copies - for i in range(self.N): - assert eval('c.m_%s_array[i]' % names[j]) == b[i] - - setattr(c, 'm_'+names[j]+'_array2', b) # pointer copies - assert 3 < self.N - b[3] = 28 - for i in range(self.N): - assert eval('c.m_%s_array2[i]' % names[j]) == b[i] - - # can not write to constant data - assert c.m_const_int == 17 - raises(TypeError, setattr, c, 'm_const_int', 71) - - c.__destruct__() - - def test03_array_passing(self): - """Test passing of array arguments""" - - import cppyy, array, sys - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - a = range(self.N) - # test arrays in mixed order, to give overload resolution a workout - for t in ['d', 'i', 'f', 'H', 'I', 'h', 'L', 'l']: - b = array.array(t, a) - - # typed passing - ca = c.pass_array(b) - if t != 'l': assert type(ca[0]) == type(b[0]) - else: assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 - assert len(b) == self.N - for i in range(self.N): - assert ca[i] == b[i] - - # void* passing - ca = eval('c.pass_void_array_%s(b)' % t) - if t != 'l': assert type(ca[0]) == type(b[0]) - else: assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 - assert len(b) == self.N - for i in range(self.N): - assert ca[i] == b[i] - - # NULL/nullptr passing (will use short*) - assert not c.pass_array(0) - raises(Exception, c.pass_array(0).__getitem__, 0) # raises SegfaultException - assert raises(TypeError, c.pass_array, None) - assert not c.pass_array(cppyy.nullptr) - raises(Exception, c.pass_array(cppyy.nullptr).__getitem__, 0) # id. id. - - c.__destruct__() - - def test04_class_read_access(self): - """Test read access to class public data and verify values""" - - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # char types - assert CppyyTestData.s_char == 'c' - assert c.s_char == 'c' - assert CppyyTestData.s_uchar == 'u' - assert c.s_uchar == 'u' - assert CppyyTestData.s_wchar == u'U' - assert c.s_wchar == u'U' - assert CppyyTestData.s_char16 == u'\u6c29' - assert c.s_char16 == u'\u6c29' - assert CppyyTestData.s_char32 == u'\U0001f34b' - assert c.s_char32 == u'\U0001f34b' - - assert type(c.s_wchar) == pyunicode - assert type(CppyyTestData.s_wchar) == pyunicode - assert type(c.s_char16) == pyunicode - assert type(CppyyTestData.s_char16) == pyunicode - assert type(c.s_char32) == pyunicode - assert type(CppyyTestData.s_char32) == pyunicode - - # integer types - assert CppyyTestData.s_byte == ord('b') - assert c.s_byte == ord('b') - assert CppyyTestData.s_int8 == - 87 - assert c.s_int8 == - 87 - assert CppyyTestData.s_uint8 == 87 - assert c.s_uint8 == 87 - assert CppyyTestData.s_short == -101 - assert c.s_short == -101 - assert c.s_ushort == 255 - assert CppyyTestData.s_ushort == 255 - assert CppyyTestData.s_int == -202 - assert c.s_int == -202 - assert c.s_uint == 202 - assert CppyyTestData.s_uint == 202 - assert CppyyTestData.s_long == -pylong(303) - assert c.s_long == -pylong(303) - assert c.s_ulong == pylong(303) - assert CppyyTestData.s_ulong == pylong(303) - assert CppyyTestData.s_llong == -pylong(404) - assert c.s_llong == -pylong(404) - assert c.s_ullong == pylong(404) - assert CppyyTestData.s_ullong == pylong(404) - - # floating point types - assert round(CppyyTestData.s_float + 606., 5) == 0 - assert round(c.s_float + 606., 5) == 0 - assert round(CppyyTestData.s_double + 707., 8) == 0 - assert round(c.s_double + 707., 8) == 0 - assert round(CppyyTestData.s_ldouble + 808., 8) == 0 - assert round(c.s_ldouble + 808., 8) == 0 - - c.__destruct__() - - def test05_class_data_write_access(self): - """Test write access to class public data and verify values""" - - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # char types - CppyyTestData.s_char = 'a' - assert c.s_char == 'a' - c.s_char = 'b' - assert CppyyTestData.s_char == 'b' - CppyyTestData.s_uchar = 'c' - assert c.s_uchar == 'c' - c.s_uchar = 'd' - assert CppyyTestData.s_uchar == 'd' - raises(ValueError, setattr, CppyyTestData, 's_uchar', -1) - raises(ValueError, setattr, c, 's_uchar', -1) - CppyyTestData.s_wchar = u'K' - assert c.s_wchar == u'K' - c.s_wchar = u'L' - assert CppyyTestData.s_wchar == u'L' - CppyyTestData.s_char16 = u'\u00df' - assert c.s_char16 == u'\u00df' - c.s_char16 = u'\u00ef' - assert CppyyTestData.s_char16 == u'\u00ef' - CppyyTestData.s_char32 = u'\u00df' - assert c.s_char32 == u'\u00df' - c.s_char32 = u'\u00ef' - assert CppyyTestData.s_char32 == u'\u00ef' - - # integer types - c.s_byte = 66 - assert CppyyTestData.s_byte == 66 - CppyyTestData.s_byte = 66 - assert c.s_byte == 66 - c.s_short = -102 - assert CppyyTestData.s_short == -102 - CppyyTestData.s_short = -203 - assert c.s_short == -203 - c.s_ushort = 127 - assert CppyyTestData.s_ushort == 127 - CppyyTestData.s_ushort = 227 - assert c.s_ushort == 227 - CppyyTestData.s_int = -234 - assert c.s_int == -234 - c.s_int = -321 - assert CppyyTestData.s_int == -321 - CppyyTestData.s_uint = 1234 - assert c.s_uint == 1234 - c.s_uint = 4321 - assert CppyyTestData.s_uint == 4321 - raises(ValueError, setattr, c, 's_uint', -1) - raises(ValueError, setattr, CppyyTestData, 's_uint', -1) - CppyyTestData.s_long = -pylong(87) - assert c.s_long == -pylong(87) - c.s_long = pylong(876) - assert CppyyTestData.s_long == pylong(876) - CppyyTestData.s_ulong = pylong(876) - assert c.s_ulong == pylong(876) - c.s_ulong = pylong(678) - assert CppyyTestData.s_ulong == pylong(678) - raises(ValueError, setattr, CppyyTestData, 's_ulong', -1) - raises(ValueError, setattr, c, 's_ulong', -1) - - # floating point types - CppyyTestData.s_float = -3.1415 - assert round(c.s_float, 5) == -3.1415 - c.s_float = 3.1415 - assert round(CppyyTestData.s_float, 5) == 3.1415 - import math - c.s_double = -math.pi - assert CppyyTestData.s_double == -math.pi - CppyyTestData.s_double = math.pi - assert c.s_double == math.pi - c.s_ldouble = -math.pi - assert CppyyTestData.s_ldouble == -math.pi - CppyyTestData.s_ldouble = math.pi - assert c.s_ldouble == math.pi - - c.__destruct__() - - def test06_range_access(self): - """Test the ranges of integer types""" - - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # TODO: should these be TypeErrors, or should char/bool raise - # ValueErrors? In any case, consistency is needed ... - raises(ValueError, setattr, c, 'm_uint', -1) - raises(ValueError, setattr, c, 'm_ulong', -1) - - c.__destruct__() - - def test07_type_conversions(self): - """Test conversions between builtin types""" - - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - c.m_double = -1 - assert round(c.m_double + 1.0, 8) == 0 - - raises(TypeError, setattr, c.m_double, 'c') - raises(TypeError, setattr, c.m_int, -1.) - raises(TypeError, setattr, c.m_int, 1.) - raises(TypeError, setattr, c.m_long, 3.14) - raises(TypeError, setattr, c.m_ulong, 3.14) - raises(TypeError, setattr, c.m_llong, 3.14) - raises(TypeError, setattr, c.m_ullong, 3.14) - - raises(TypeError, c.set_int, 3.14) - raises(TypeError, c.set_long, 3.14) - raises(TypeError, c.set_ulong, 3.14) - raises(TypeError, c.set_llong, 3.14) - raises(TypeError, c.set_ullong, 3.14) - - raises(TypeError, c.set_int_cr, 3.14) - raises(TypeError, c.set_long_cr, 3.14) - raises(TypeError, c.set_ulong_cr, 3.14) - raises(TypeError, c.set_llong_cr, 3.14) - raises(TypeError, c.set_ullong_cr, 3.14) - - c.__destruct__() - - def test08_global_builtin_type(self): - """Test access to a global builtin type""" - - import cppyy - gbl = cppyy.gbl - - assert gbl.g_int == gbl.get_global_int() - - gbl.set_global_int(32) - assert gbl.get_global_int() == 32 - assert gbl.g_int == 32 - - gbl.g_int = 22 - assert gbl.get_global_int() == 22 - assert gbl.g_int == 22 - - # if setting before checking, the change must be reflected on the C++ side - gbl.g_some_global_string = "Python" - assert gbl.get_some_global_string() == "Python" - assert gbl.g_some_global_string2 == "C++" - assert gbl.get_some_global_string2() == "C++" - gbl.g_some_global_string2 = "Python" - assert gbl.get_some_global_string2() == "Python" - - assert gbl.g_some_global_string16 == u'z\u00df\u6c34' - assert gbl.g_some_global_string32 == u'z\u00df\u6c34\U0001f34c' - - NS = gbl.SomeStaticDataNS - NS.s_some_static_string = "Python" - assert NS.get_some_static_string() == "Python" - assert NS.s_some_static_string2 == "C++" - assert NS.get_some_static_string2() == "C++" - NS.s_some_static_string2 = "Python" - assert NS.get_some_static_string2() == "Python" - - # verify that non-C++ data can still be set - gbl.g_python_only = "Python" - assert gbl.g_python_only == "Python" - - NS.s_python_only = "Python" - assert NS.s_python_only == "Python" - - def test08a_global_object(self): - """Test access to global objects by value""" - - import cppyy - gbl = cppyy.gbl - - assert gbl.gData.fData == 5. - - def test09_global_ptr(self): - """Test access of global objects through a pointer""" - - import cppyy - gbl = cppyy.gbl - - with raises(ReferenceError): - gbl.g_pod.m_int - - c = gbl.CppyyTestPod() - c.m_int = 42 - c.m_double = 3.14 - - gbl.set_global_pod(c) - assert gbl.is_global_pod(c) - assert gbl.g_pod.m_int == 42 - assert gbl.g_pod.m_double == 3.14 - - d = gbl.get_global_pod() - assert gbl.is_global_pod(d) - assert c is d - assert id(c) == id(d) - - e = gbl.CppyyTestPod() - e.m_int = 43 - e.m_double = 2.14 - - gbl.g_pod = e - assert gbl.is_global_pod(e) - assert gbl.g_pod.m_int == 43 - assert gbl.g_pod.m_double == 2.14 - - def test10_enum(self): - """Test access to enums""" - - import cppyy - gbl = cppyy.gbl - - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - # test that the enum is accessible as a type - assert CppyyTestData.EWhat - - assert CppyyTestData.kNothing == 6 - assert CppyyTestData.kSomething == 111 - assert CppyyTestData.kLots == 42 - - assert CppyyTestData.EWhat(CppyyTestData.kNothing) == CppyyTestData.kNothing - assert CppyyTestData.EWhat(6) == CppyyTestData.kNothing - # TODO: only allow instantiations with correct values (C++11) - - assert c.get_enum() == CppyyTestData.kNothing - assert c.m_enum == CppyyTestData.kNothing - - c.m_enum = CppyyTestData.kSomething - assert c.get_enum() == CppyyTestData.kSomething - assert c.m_enum == CppyyTestData.kSomething - - c.set_enum(CppyyTestData.kLots) - assert c.get_enum() == CppyyTestData.kLots - assert c.m_enum == CppyyTestData.kLots - - assert c.s_enum == CppyyTestData.s_enum - assert c.s_enum == CppyyTestData.kNothing - assert CppyyTestData.s_enum == CppyyTestData.kNothing - - c.s_enum = CppyyTestData.kSomething - assert c.s_enum == CppyyTestData.s_enum - assert c.s_enum == CppyyTestData.kSomething - assert CppyyTestData.s_enum == CppyyTestData.kSomething - - # global enums - assert gbl.EFruit # test type accessible - assert gbl.kApple == 78 - assert gbl.kBanana == 29 - assert gbl.kCitrus == 34 - assert gbl.EFruit.__name__ == 'EFruit' - assert gbl.EFruit.__cpp_name__ == 'EFruit' - - assert gbl.EFruit.kApple == 78 - assert gbl.EFruit.kBanana == 29 - assert gbl.EFruit.kCitrus == 34 - - assert gbl.NamedClassEnum.E1 == 42 - assert gbl.NamedClassEnum.__name__ == 'NamedClassEnum' - assert gbl.NamedClassEnum.__cpp_name__ == 'NamedClassEnum' - - assert gbl.EnumSpace.E - assert gbl.EnumSpace.EnumClass.E1 == -1 # anonymous - assert gbl.EnumSpace.EnumClass.E2 == -1 # named type - - assert gbl.EnumSpace.NamedClassEnum.E1 == -42 - assert gbl.EnumSpace.NamedClassEnum.__name__ == 'NamedClassEnum' - assert gbl.EnumSpace.NamedClassEnum.__cpp_name__ == 'EnumSpace::NamedClassEnum' - - raises(TypeError, setattr, gbl.EFruit, 'kBanana', 42) - - assert gbl.g_enum == gbl.EFruit.kBanana - gbl.g_enum = gbl.EFruit.kCitrus - assert gbl.g_enum == gbl.EFruit.kCitrus - - # typedef enum - assert gbl.EnumSpace.letter_code - assert gbl.EnumSpace.AA == 1 - assert gbl.EnumSpace.BB == 2 - - @mark.xfail(strict=True) - def test11_typed_enums(self): - """Determine correct types of enums""" - - import cppyy - - cppyy.cppdef("""\ - namespace TrueEnumTypes { - class Test { - enum nums { ZERO = 0, ONE = 1 }; - enum dir : char { left = 'l', right = 'r' }; - enum rank : long { FIRST = 1, SECOND }; - enum vraioufaux : bool { faux = false, vrai = true }; - }; }""") - - sc = cppyy.gbl.TrueEnumTypes.Test - - assert sc.nums.ZERO == 0 - assert sc.nums.ONE == 1 - assert type(sc.nums.ZERO) == sc.nums - assert isinstance(sc.nums.ZERO, int) - assert 'int' in repr(sc.nums.ZERO) - assert str(sc.nums.ZERO) == '0' - - assert sc.dir.left == 'l' - assert sc.dir.right == 'r' - assert type(sc.dir.left) == sc.dir - assert isinstance(sc.dir.left, str) - assert 'char' in repr(sc.dir.left) - assert str(sc.dir.left) == "'l'" - - assert sc.rank.FIRST == 1 - assert sc.rank.SECOND == 2 - assert type(sc.rank.FIRST) == sc.rank - assert isinstance(sc.rank.FIRST, pylong) - assert 'long' in repr(sc.rank.FIRST) - assert str(sc.rank.FIRST) == '1' or str(sc.rank.FIRST) == '1L' - - assert sc.vraioufaux.faux == False - assert sc.vraioufaux.vrai == True - assert type(sc.vraioufaux.faux) == bool # no bool as base class - assert isinstance(sc.vraioufaux.faux, bool) - - @mark.xfail(strict=True) - def test12_enum_scopes(self): - """Enum accessibility and scopes""" - - import cppyy - - cppyy.cppdef("""\ - enum { g_one = 1, g_two = 2 }; - enum GEnum1 { g_three = 3, g_four = 4 }; - enum class GEnum2 { g_five = 5, g_six = 6 }; - - namespace EnumScopes { - enum { n_one = 1, n_two = 2 }; - enum NEnum1 { n_three = 3, n_four = 4 }; - enum class NEnum2 { n_five = 5, n_six = 6 }; - } - - class EnumClass { - public: - enum { c_one = 1, c_two = 2 }; - enum CEnum1 { c_three = 3, c_four = 4 }; - enum class CEnum2 { c_five = 5, c_six = 6 }; - }; """) - - gn = cppyy.gbl - assert not hasattr(gn, 'n_one') - assert not hasattr(gn, 'c_one') - assert gn.g_two == 2 - assert gn.g_four == 4 - assert gn.GEnum1.g_three == 3 - assert gn.GEnum1.g_three == gn.g_three - assert type(gn.GEnum1.g_three) == type(gn.g_three) - assert not hasattr(gn, 'g_five') - assert gn.GEnum2.g_six == 6 - - ns = cppyy.gbl.EnumScopes - assert not hasattr(ns, 'g_one') - assert not hasattr(ns, 'c_one') - assert ns.n_two == 2 - assert ns.n_four == 4 - assert ns.NEnum1.n_three == 3 - assert ns.NEnum1.n_three == ns.n_three - assert type(ns.NEnum1.n_three) == type(ns.n_three) - assert not hasattr(ns, 'n_five') - assert ns.NEnum2.n_six == 6 - - cl = cppyy.gbl.EnumClass - assert not hasattr(cl, 'g_one') - assert not hasattr(cl, 'n_one') - assert cl.c_two == 2 - assert cl.c_four == 4 - assert cl.CEnum1.c_three == 3 - assert cl.CEnum1.c_three == cl.c_three - assert type(cl.CEnum1.c_three) == type(cl.c_three) - assert not hasattr(cl, 'c_five') - assert cl.CEnum2.c_six == 6 - - def test13_string_passing(self): - """Test passing/returning of a const char*""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert c.get_valid_string('aap') == 'aap' - assert c.get_invalid_string() == '' - - assert c.get_valid_wstring(u'aap') == u'aap' - assert c.get_invalid_wstring() == u'' - - assert c.get_valid_string16(u'z\u00df\u6c34') == u'z\u00df\u6c34' - assert c.get_invalid_string16() == u'' - - assert c.get_valid_string32(u'z\u00df\u6c34\U0001f34c') == u'z\u00df\u6c34\U0001f34c' - assert c.get_invalid_string32() == u'' - - def test14_copy_constructor(self): - """Test copy constructor""" - - import cppyy - FourVector = cppyy.gbl.FourVector - - t1 = FourVector(1., 2., 3., -4.) - t2 = FourVector(0., 0., 0., 0.) - t3 = FourVector(t1) - - assert t1 == t3 - assert t1 != t2 - - for i in range(4): - assert t1[i] == t3[i] - - def test15_object_returns(self): - """Test access to and return of PODs""" - - import cppyy - - c = cppyy.gbl.CppyyTestData() - - assert c.m_pod.m_int == 888 - assert c.m_pod.m_double == 3.14 - - pod = c.get_pod_val() - assert pod.m_int == 888 - assert pod.m_double == 3.14 - - assert c.get_pod_val_ptr().m_int == 888 - assert c.get_pod_val_ptr().m_double == 3.14 - c.get_pod_val_ptr().m_int = 777 - assert c.get_pod_val_ptr().m_int == 777 - - assert c.get_pod_val_ref().m_int == 777 - assert c.get_pod_val_ref().m_double == 3.14 - c.get_pod_val_ref().m_int = 666 - assert c.get_pod_val_ref().m_int == 666 - - assert c.get_pod_ptrref().m_int == 666 - assert c.get_pod_ptrref().m_double == 3.14 - - def test16_object_arguments(self): - """Test setting and returning of a POD through arguments""" - - import cppyy - - c = cppyy.gbl.CppyyTestData() - assert c.m_pod.m_int == 888 - assert c.m_pod.m_double == 3.14 - - p = cppyy.gbl.CppyyTestPod() - p.m_int = 123 - assert p.m_int == 123 - p.m_double = 321. - assert p.m_double == 321. - - c.set_pod_val(p) - assert c.m_pod.m_int == 123 - assert c.m_pod.m_double == 321. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_ptr_in(p) - assert c.m_pod.m_int == 123 - assert c.m_pod.m_double == 321. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_ptr_out(p) - assert p.m_int == 888 - assert p.m_double == 3.14 - - p.m_int = 555 - p.m_double = 666. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_ref(p) - assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_ptrptr_in(p) - assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. - assert p.m_int == 555 - assert p.m_double == 666. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_void_ptrptr_in(p) - assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. - assert p.m_int == 555 - assert p.m_double == 666. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_ptrptr_out(p) - assert c.m_pod.m_int == 888 - assert c.m_pod.m_double == 3.14 - assert p.m_int == 888 - assert p.m_double == 3.14 - - p.m_int = 777 - p.m_double = 888. - - c = cppyy.gbl.CppyyTestData() - c.set_pod_void_ptrptr_out(p) - assert c.m_pod.m_int == 888 - assert c.m_pod.m_double == 3.14 - assert p.m_int == 888 - assert p.m_double == 3.14 - - def test17_nullptr_passing(self): - """Integer 0 ('NULL') and nullptr allowed to pass through instance*""" - - import cppyy - - for o in (0, cppyy.nullptr): - c = cppyy.gbl.CppyyTestData() - assert c.m_pod.m_int == 888 - assert c.m_pod.m_double == 3.14 - assert not not c.m_ppod - - c.set_pod_ptr(o) - assert not c.m_ppod - assert not c.get_pod_ptr() - - def test18_respect_privacy(self): - """Test that privacy settings are respected""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - assert isinstance(c, CppyyTestData) - - raises(AttributeError, getattr, c, 'm_owns_arrays') - - c.__destruct__() - - def test19_object_and_pointer_comparisons(self): - """Verify object and pointer comparisons""" - - import cppyy - gbl = cppyy.gbl - - c1 = cppyy.bind_object(0, gbl.CppyyTestData) - assert not c1 - - c2 = cppyy.bind_object(0, gbl.CppyyTestData) - assert c1 == c2 - assert c2 == c1 - - # FourVector overrides operator== - l1 = cppyy.bind_object(0, gbl.FourVector) - assert not l1 - - assert c1 != l1 - assert l1 != c1 - - l2 = cppyy.bind_object(0, gbl.FourVector) - assert l1 == l2 - assert l2 == l1 - - l3 = gbl.FourVector(1, 2, 3, 4) - l4 = gbl.FourVector(1, 2, 3, 4) - l5 = gbl.FourVector(4, 3, 2, 1) - assert l3 == l4 - assert l4 == l3 - - assert l3 - assert l3 != l5 # like this to ensure __ne__ is called - assert l5 != l3 # id. - - def test20_object_comparisons_with_cpp__eq__(self): - """Comparisons with C++ providing __eq__/__ne__""" - - import cppyy - - cppyy.cppdef(""" - namespace MoreComparisons { - struct Comparable1 { - Comparable1(int i) : fInt(i) {} - int fInt; - static bool __eq__(const Comparable1& self, const Comparable1& other) { - return self.fInt == other.fInt; - } - static bool __ne__(const Comparable1& self, const Comparable1& other) { - return self.fInt != other.fInt; - } - }; - - struct Comparable2 { - Comparable2(int i) : fInt(i) {} - int fInt; - bool __eq__(const Comparable2& other) { - return fInt == other.fInt; - } - bool __ne__(const Comparable2& other) { - return fInt != other.fInt; - } - }; }""") - - ns = cppyy.gbl.MoreComparisons - - c1_1 = ns.Comparable1(42) - c1_2 = ns.Comparable1(42) - c1_3 = ns.Comparable1(43) - - assert ns.Comparable1.__dict__['__eq__'](c1_1, c1_2) - assert not ns.Comparable1.__dict__['__eq__'](c1_1, c1_3) - assert not ns.Comparable1.__dict__['__ne__'](c1_1, c1_2) - assert ns.Comparable1.__dict__['__ne__'](c1_1, c1_3) - - # the following works as a side-effect of a workaround for vector calls and - # it is probably preferable to have it working, so leave the discrepancy for - # now: python's aggressive end-of-life schedule will catch up soon enough - if 0x3080000 <= sys.hexversion: - assert c1_1 == c1_2 - assert not c1_1 != c1_2 - else: - with raises(TypeError): - c1_1 == c1_2 - with raises(TypeError): - c1_1 != c1_2 - - c2_1 = ns.Comparable2(27) - c2_2 = ns.Comparable2(27) - c2_3 = ns.Comparable2(28) - - assert c2_1 == c2_1 - assert c2_1 == c2_2 - assert not c2_1 == c2_3 - assert not c2_1 != c2_1 - assert not c2_1 != c2_2 - assert c2_1 != c2_3 - - def test21_object_validity(self): - """Test object validity checking""" - - from cppyy import gbl - - d = gbl.CppyyTestPod() - - assert d - assert not not d - - d2 = gbl.get_null_pod() - - assert not d2 - - @mark.xfail(strict=True) - def test22_buffer_shapes(self): - """Correctness of declared buffer shapes""" - - import cppyy - - cppyy.cppdef("""\ - namespace ShapeTester { - enum Enum{One, Two, Three}; - - template - struct Foo { - T a[5]; - T aa[5][4]; - T aaa[5][4][3]; - }; }""") - - ns = cppyy.gbl.ShapeTester - - for dtype in ["int", "double", "long", "bool", "char", "void*", "ShapeTester::Enum"]: - foo = ns.Foo[dtype]() - if dtype != 'char': - assert foo.a.shape == (5,) - else: - # TODO: verify the following is for historic reasons and should be modified - # once bug #344 (bitbucket) is fixed - assert len(foo.a) == 5 - assert foo.aa.shape == (5, 4) - assert foo.aaa.shape == (5, 4, 3) - - def test23_buffer_reshaping(self): - """Test usage of buffer sizing""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - for func in ['get_bool_array', 'get_bool_array2', - 'get_uchar_array', 'get_uchar_array2', - 'get_int8_array', 'get_int8_array2', - 'get_uint8_array', 'get_uint8_array2', - 'get_short_array', 'get_short_array2', - 'get_ushort_array', 'get_ushort_array2', - 'get_int_array', 'get_int_array2', - 'get_uint_array', 'get_uint_array2', - 'get_long_array', 'get_long_array2', - 'get_ulong_array', 'get_ulong_array2', - 'get_byte_array', 'get_byte_array2']: - arr = getattr(c, func)() - arr.reshape((self.N,)) - assert len(arr) == self.N - - raises(ValueError, arr.reshape, (1, 2)) - assert len(arr) == self.N - - raises(TypeError, arr.reshape, 2*self.N) - assert len(arr) == self.N - - l = list(arr) - for i in range(self.N): - assert arr[i] == l[i] - - def test24_voidp(self): - """Test usage of void* data""" - - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - - assert not cppyy.nullptr - - assert c.s_voidp is cppyy.nullptr - assert CppyyTestData.s_voidp is cppyy.nullptr - - assert c.m_voidp is cppyy.nullptr - assert c.get_voidp() is cppyy.nullptr - - c2 = CppyyTestData() - assert c2.m_voidp is cppyy.nullptr - c.set_voidp(c2.m_voidp) - assert c.m_voidp is cppyy.nullptr - c.set_voidp(c2.get_voidp()) - assert c.m_voidp is cppyy.nullptr - c.set_voidp(cppyy.nullptr) - assert c.m_voidp is cppyy.nullptr - - c.set_voidp(c2) - def address_equality_test(a, b): - assert cppyy.addressof(a) == cppyy.addressof(b) - b2 = cppyy.bind_object(a, CppyyTestData) - assert b is b2 # memory regulator recycles - b3 = cppyy.bind_object(cppyy.addressof(a), CppyyTestData) - assert b is b3 # likewise - - address_equality_test(c.m_voidp, c2) - address_equality_test(c.get_voidp(), c2) - - def null_test(null): - c.m_voidp = null - assert c.m_voidp is cppyy.nullptr - map(null_test, [0, cppyy.nullptr]) - - c.m_voidp = c2 - address_equality_test(c.m_voidp, c2) - address_equality_test(c.get_voidp(), c2) - - c.s_voidp = c2 - address_equality_test(c.s_voidp, c2) - - cppyy.cppdef("""\ - namespace VoidP { - void* vvv[3][5][7]; - struct Init { - Init() { - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 5; ++j) { - for (size_t k = 0; k < 7; ++k) - vvv[i][j][k] = (void*)(i+j+k); - } - } - } - } _init; }""") - - ns = cppyy.gbl.VoidP - for i in range(3): - for j in range(5): - for k in range(7): - assert int(ns.vvv[i,j,k]) == i+j+k - - @mark.xfail(run=False, reason="segmentation violation") - def test25_byte_arrays(self): - """Usage of unsigned char* as byte array and std::byte*""" - - import array, cppyy, ctypes - - buf = b'123456789' - total = 0 - for c in buf: - try: - total += ord(c) # p2 - except TypeError: - total += c # p3 - - def run(self, f, buf, total): - - # The following create a unique type for fixed-size C arrays: ctypes.c_char_Array_9 - # and neither inherits from a non-sized type nor implements the buffer interface. - # As such, it can't be handled. TODO? - #pbuf = ctypes.create_string_buffer(len(buf), buf) - #assert f(pbuf, len(buf)) == total - - pbuf = array.array('B', buf) - assert f(pbuf, len(buf)) == total - - pbuf = (ctypes.c_ubyte * len(buf)).from_buffer_copy(buf) - assert f(pbuf, len(buf)) == total - - pbuf = ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte * len(buf)))[0] - assert f(pbuf, len(buf)) == total - - run(self, cppyy.gbl.sum_uc_data, buf, total) - run(self, cppyy.gbl.sum_byte_data, buf, total) - - @mark.xfail(strict=True, run=not IS_MAC and not IS_WINDOWS, reason="Fails on all platforms; crashes on macOS with " \ - "libc++abi: terminating due to uncaught exception") - def test26_function_pointers(self): - """Function pointer passing""" - - import cppyy - - fi1 = cppyy.gbl.sum_of_int1 - fi2 = cppyy.gbl.sum_of_int2 - fd = cppyy.gbl.sum_of_double - fdd = cppyy.gbl.call_double_double - - assert 5 == fi1(2, 3) - assert 5. == fd(5., 0.) - - raises(TypeError, fdd, fi1, 2, 3) - - assert 5. == fdd(fd, 5., 0.) - assert -1. == fdd(cppyy.nullptr, 5., 0.) - - fip = cppyy.gbl.sum_of_int_ptr - assert 5 == fip(2, 3) - - cppyy.gbl.sum_of_int_ptr = cppyy.gbl.sum_of_int2 - assert 7 == cppyy.gbl.sum_of_int_ptr(2, 3) - - cppyy.gbl.sum_of_int_ptr = cppyy.nullptr - assert not cppyy.gbl.sum_of_int_ptr - with raises(cppyy.gbl.std.bad_function_call): - cppyy.gbl.sum_of_int_ptr(2, 3) - with raises(AttributeError): - cppyy.gbl.sim_of_int_ptr # incorrect spelling - - cppyy.gbl.sum_of_int_ptr = cppyy.gbl.sum_of_int1 - assert fip is cppyy.gbl.sum_of_int_ptr # b/c cached - - o = cppyy.gbl.sum_of_int_struct() - o.sum_of_int_ptr = cppyy.gbl.sum_of_int1 - assert 5 == o.sum_of_int_ptr(2, 3) - - o.sum_of_int_ptr = cppyy.gbl.sum_of_int2 - assert 7 == o.sum_of_int_ptr(2, 3) - - def sum_in_python(i1, i2): - return i1-i2 - cppyy.gbl.sum_of_int_ptr = sum_in_python - assert 1 == cppyy.gbl.call_sum_of_int(3, 2) - - def sum_in_python(i1, i2, i3): - return i1+i2+i3 - cppyy.gbl.sum_of_int_ptr = sum_in_python - with raises(TypeError): - cppyy.gbl.call_sum_of_int(3, 2) - - cppyy.cppdef(r"""\ - namespace FuncPtrReturn { - typedef std::string (*func_t)(void); - std::string hello() { return "Hello, World!"; } - func_t foo() { return hello; } - }""") - - ns = cppyy.gbl.FuncPtrReturn - assert ns.foo()() == "Hello, World!" - - def test27_callable_passing(self): - """Passing callables through function pointers""" - - import cppyy, gc - - fdd = cppyy.gbl.call_double_double - fii = cppyy.gbl.call_int_int - fv = cppyy.gbl.call_void - fri = cppyy.gbl.call_refi - frl = cppyy.gbl.call_refl - frd = cppyy.gbl.call_refd - - assert 'call_double_double' in str(fdd) - assert 'call_refd' in str(frd) - - def pyf(arg0, arg1): - return arg0+arg1 - - assert type(fdd(pyf, 2, 3)) == float - assert fdd(pyf, 2, 3) == 5. - - assert type(fii(pyf, 2, 3)) == int - assert fii(pyf, 2, 3) == 5 - - def pyf(arg0, arg1): - return arg0*arg1 - - assert fdd(pyf, 2, 3) == 6. - assert fii(pyf, 2, 3) == 6 - - # call of void function - global retval - retval = None - def voidf(i): - global retval - retval = i - - assert retval is None - assert not fv(voidf, 5) - assert retval == 5 - - # call of function with reference argument - def reff(ref): - ref.value = 5 - assert fri(reff) == 5 - assert frl(reff) == pylong(5) - assert frd(reff) == 5. - - # callable that does not accept weak-ref - import math - assert fdd(math.atan2, 0, 3.) == 0. - - # error testing - raises(TypeError, fii, None, 2, 3) - - def pyf(arg0, arg1): - return arg0/arg1 - - raises(ZeroDivisionError, fii, pyf, 1, 0) - - def pyd(arg0, arg1): - return arg0*arg1 - c = cppyy.gbl.StoreCallable(pyd) - assert c(3, 3) == 9. - - c.set_callable(lambda x, y: x*y) - assert c(3, 3) == 9. # life line protected - - c.__dict__.clear() # destroys life lines - gc.collect() - raises(TypeError, c, 3, 3) # lambda gone out of scope - - def test28_callable_through_function_passing(self): - """Passing callables through std::function""" - - import cppyy, gc - - fdd = cppyy.gbl.call_double_double_sf - fii = cppyy.gbl.call_int_int_sf - fv = cppyy.gbl.call_void_sf - fri = cppyy.gbl.call_refi_sf - frl = cppyy.gbl.call_refl_sf - frd = cppyy.gbl.call_refd_sf - - assert 'call_double_double_sf' in str(fdd) - assert 'call_refd_sf' in str(frd) - - def pyf(arg0, arg1): - return arg0+arg1 - - assert type(fdd(pyf, 2, 3)) == float - assert fdd(pyf, 2, 3) == 5. - - assert type(fii(pyf, 2, 3)) == int - assert fii(pyf, 2, 3) == 5 - - def pyf(arg0, arg1): - return arg0*arg1 - - assert fdd(pyf, 2, 3) == 6. - assert fii(pyf, 2, 3) == 6 - - # call of void function - global retval - retval = None - def voidf(i): - global retval - retval = i - - assert retval is None - assert not fv(voidf, 5) - assert retval == 5 - - # call of function with reference argument - def reff(ref): - ref.value = 5 - assert fri(reff) == 5 - assert frl(reff) == pylong(5) - assert frd(reff) == 5. - - # callable that does not accept weak-ref - import math - assert fdd(math.atan2, 0, 3.) == 0. - - # error testing - raises(TypeError, fii, None, 2, 3) - - def pyf(arg0, arg1): - return arg0/arg1 - - raises(ZeroDivisionError, fii, pyf, 1, 0) - - def pyd(arg0, arg1): - return arg0*arg1 - c = cppyy.gbl.StoreCallable(pyd) - assert c(3, 3) == 9. - - c.set_callable(lambda x, y: x*y) - assert c(3, 3) == 9. # life line protected - - c.__dict__.clear() # destroys life lines - gc.collect() - raises(TypeError, c, 3, 3) # lambda gone out of scope - - def test29_std_function_life_lines(self): - """Life lines to std::function data members""" - - import cppyy, gc - - cppyy.cppdef("""\ - namespace BoundMethod2StdFunction { - class Base { - public: - virtual ~Base() {} - - std::function execute; - std::string do_execute() { - return execute(); - } - }; } """) - - ns = cppyy.gbl.BoundMethod2StdFunction - - class Derived(ns.Base): - def __init__(self): - super(Derived, self).__init__() - self.execute = self.xyz - - def xyz(self): - return "xyz" - - d = Derived() - assert d.do_execute() == "xyz" - assert d.do_execute() == "xyz" - - gc.collect() - assert d.do_execute() == "xyz" - - d.execute = d.xyz - assert d.do_execute() == "xyz" - - def test30_multi_dim_arrays_of_builtins(test): - """Multi-dim arrays of builtins""" - - import cppyy, ctypes - - cppyy.cppdef(""" - template - struct MultiDimTest { - T* layers[nlayers]; - - MultiDimTest(int width, int height) { - for (int i=0; i= cppyy.sizeof(AoS.Foo) - arr = bar.fArr - arr.size = 1 - for f in arr: - assert type(f) == AoS.Foo - assert type(bar.fArr[0]) == AoS.Foo - - def test34_object_pointers(self): - """Read/write access to objects through pointers""" - - import cppyy - - c = cppyy.gbl.CppyyTestData() - - assert cppyy.gbl.CppyyTestData.s_strv == "Hello" - assert c.s_strv == "Hello" - assert not cppyy.gbl.CppyyTestData.s_strp - assert not c.s_strp - - c.s_strv = "World" - assert cppyy.gbl.CppyyTestData.s_strv == "World" - - # assign on nullptr is a pointer copy - sn = cppyy.gbl.std.string("aap") - cppyy.gbl.CppyyTestData.s_strp = sn - assert c.s_strp == "aap" - - # assign onto the existing object - cppyy.gbl.CppyyTestData.s_strp.__assign__(cppyy.gbl.std.string("noot")) - assert c.s_strp == "noot" - assert sn == "noot" # set through pointer - - def test35_restrict(self): - """Strip __restrict keyword from use""" - - import cppyy - - cppyy.cppdef("std::string restrict_call(const char*__restrict s) { return s; }") - - assert cppyy.gbl.restrict_call("aap") == "aap" - - def test36_legacy_matrix(self): - """Handling of legacy matrix""" - - import cppyy - - cppyy.cppdef("""\ - namespace Pointer2D { - int** g_matrix; - - int** create_matrix(int n, int m) { - int** mat = (int**)malloc(n*sizeof(int*)); - int* arr = (int*)malloc(n*m*sizeof(int)); - for (int i = 0; i < n; ++i) { - mat[i] = arr + i*m; - for(int j = 0; j < m; ++j) { - mat[i][j] = 13+i*n+j; - } - } - g_matrix = mat; - return mat; - } - - bool destroy_matrix(int** mat, int n, int m) { - for (int i = 0; i < n; ++i) { - for (int j = 0; j < m; ++j) { - if (mat[i][j] != 13+i*n+j) - return false; - } - } - g_matrix = nullptr; - free(mat[0]); - free(mat); - return true; - } }""") - - ns = cppyy.gbl.Pointer2D; - - N, M = 2, 3 - m = ns.create_matrix(N, M) - g = ns.g_matrix; - - for i in range(N): - for j in range(M): - assert m[i][j] == 13+i*N+j - assert g[i][j] == 13+i*N+j - - assert ns.destroy_matrix(m, N, M) - - m = ns.create_matrix(N, M) - assert ns.destroy_matrix(ns.g_matrix, N, M) - - def test37_legacy_matrix_of_structs(self): - """Handling of legacy matrix of structs""" - - import cppyy - - cppyy.cppdef("""\ - namespace StructPointer2D { - typedef struct { - int x,y,z; // 'z' so that sizeof(xy) != sizeof(void*) - } xy; - - xy** g_matrix; - - xy** create_matrix(int n, int m) { - xy** mat = (xy**)malloc(n*sizeof(xy*)); - xy* arr = (xy*)malloc(n*m*sizeof(xy)); - for(int i=0; i fArr; }; - struct SomePOD_E { int* fPtrInt; }; - struct SomePOD_F { std::array* fPtrArr; }; - - namespace LotsOfPODS { - struct SomePOD_A { }; - struct SomePOD_B { int fInt; }; - struct SomePOD_C { int fInt; double fDouble; }; - struct SomePOD_D { std::array fArr; }; - struct SomePOD_E { int* fPtrInt; }; - struct SomePOD_F { std::array* fPtrArr; }; - }""") - - for ns in [cppyy.gbl, cppyy.gbl.LotsOfPODS]: - # no data member POD - assert ns.SomePOD_A() - - # single data member POD - b0 = ns.SomePOD_B() - assert b0.__python_owns__ - assert b0.fInt == 0 - b1 = ns.SomePOD_B(42) - assert b1.__python_owns__ - assert b1.fInt == 42 - b2 = ns.SomePOD_B(fInt = 17) - assert b2.__python_owns__ - assert b2.fInt == 17 - - # dual data member POD - c0 = ns.SomePOD_C() - assert c0.__python_owns__ - assert c0.fInt == 0 - assert c0.fDouble == 0. - c1a = ns.SomePOD_C(42) - assert c1a.__python_owns__ - assert c1a.fInt == 42 - assert c1a.fDouble == 0. - c1b = ns.SomePOD_C(fInt = 17) - assert c1b.__python_owns__ - assert c1b.fInt == 17 - assert c1b.fDouble == 0. - c1c = ns.SomePOD_C(fDouble = 5.) - assert c1c.__python_owns__ - assert c1c.fInt == 0 - assert c1c.fDouble == 5. - c2a = ns.SomePOD_C(88, 10.) - assert c2a.__python_owns__ - assert c2a.fInt == 88 - assert c2a.fDouble == 10. - c2b = ns.SomePOD_C(fDouble=5., fInt=77) - assert c2b.__python_owns__ - assert c2b.fInt == 77 - assert c2b.fDouble ==5. - - # object type data member POD - d0 = ns.SomePOD_D() - assert d0.__python_owns__ - assert len(d0.fArr) == 3 - assert d0.fArr[0] == 0. - d1 = ns.SomePOD_D((1., 2., 3.)) - assert d1.__python_owns__ - assert len(d1.fArr) == 3 - assert list(d1.fArr) == [1., 2., 3] - - # ptr type data member POD - e0 = ns.SomePOD_E() - assert e0.__python_owns__ - - # ptr to object type data member pOD - f0 = ns.SomePOD_F() - assert f0.__python_owns__ - arr = cppyy.gbl.std.array['double', 3]((1., 2., 3.)) - f1 = ns.SomePOD_F(arr) - assert f1.__python_owns__ - assert len(f1.fPtrArr) == 3 - assert list(f1.fPtrArr) == [1., 2., 3] - - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Test doesn't work on Windows") - def test39_aggregates(self): - """Initializer construction of aggregates""" - - import cppyy - - cppyy.cppdef("""\ - namespace AggregateTest { - class Atom { - public: - using size_type = std::size_t; - - struct AtomicNumber { - size_type a_n = 0; - }; - - std::array coords; - }; }""") - - ns = cppyy.gbl.AggregateTest - - Z = ns.Atom.AtomicNumber(5) - assert Z.a_n == 5 - - a = ns.Aggregate1() - assert a.sInt == 17 - - a = ns.Aggregate2() - assert a.sInt == 27 - assert a.fInt == 42 - - a = ns.Aggregate2(fInt=13) - assert a.fInt == 13 - - cppyy.cppdef("""\ - namespace AggregateTest { - - typedef enum _TYPE { DATA=0, SHAPE } TYPE; - - typedef struct _Buf { - int val; - const char *name; - TYPE buf_type; - } Buf; }""") - - ns = cppyy.gbl.AggregateTest - - b = ns.Buf(val=10, name="aap", buf_type=ns.SHAPE) - - assert b.val == 10 - assert b.name == "aap" - assert b.buf_type == ns.SHAPE - - @mark.xfail(run=False, reason="error code: Subprocess aborted") - def test40_more_aggregates(self, capfd): - """More aggregate testings (used to fail/report errors)""" - - import cppyy - - cppyy.cppdef("""\ - namespace AggregateTest { - enum E { A=1 }; - - struct R1 { E e; }; - R1 make_R1() { return {A}; } - - struct S { - S(int x): x(x) {} - S(const S&) = delete; - int x; - }; }""") - - ns = cppyy.gbl.AggregateTest - - r1 = ns.make_R1() - assert r1.e == ns.E.A - - cppyy.cppdef("""\ - namespace AggregateTest { - struct R2 { - std::optional s = {}; - }; - - R2 make_R2() { - return {1}; - } }""") - - r2 = ns.make_R2() - assert r2.s.x == 1 - - # Fail if there was an interpreter error - captured = capfd.readouterr() - output = (captured.out + captured.err).lower() - assert "error:" not in output - - @mark.xfail(strict=True) - def test41_complex_numpy_arrays(self, capfd): - """Usage of complex numpy arrays""" - - import cppyy - - try: - import numpy as np - except ImportError: - skip('numpy is not installed') - - cppyy.cppdef("""\ - namespace ComplexArrays { - typedef std::complex fcomp; - fcomp fcompdot(const fcomp* arr1, const fcomp* arr2, const int N) { - fcomp res{0.f, 0.f}; - for (int i=0; i dcomp; - dcomp dcompdot(const dcomp* arr1, const dcomp* arr2, const int N) { - dcomp res{0., 0.}; - for (int i=0; i