From 57e5c258c1c120aaeda996dcac39a9bb8c1649e0 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 16:38:45 +0200 Subject: [PATCH 01/53] [cling] Never resolve JIT symbols against legacy MSVC runtimes On Windows, the Dyld's library scan (used as last resort for symbols that resolve neither in the JIT nor in the process) walks the search path, which includes System32. Windows still ships the Visual C++ 6 runtime there (msvcp60.dll), which exports std::basic_string and other std:: members whose mangled names match modern lookups - with a pre-C++11, refcounted ABI. Binding JIT'd code to those exports silently corrupts every object they touch. Seen on the Windows CI of the CppInterOp migration branch: JIT'd wrapper code calling outlined MSVC STL string methods (msvcp140.dll exports almost none of basic_string, it is header-inline) landed in msvcp60.dll, producing strings with NULL data pointers but positive sizes (test_crossinheritance test37: "NULL string with positive size" from call_fs building its result via operator+=), access violations in string concatenation, streaming and default-argument materialization (test_pythonify test10/test19, test_regression test18, test_stltypes test09, test_streams, test_fragile test20, test_templates test16), and a direct stack trace with msvcp60!basic_string::find under JIT frames in tutorial-roofit-roostats-StandardHypoTestInvDemo-py. Permanently ignore msvcp*/msvcr*/msvcirt* in the scan. The runtimes the process actually links are already loaded and their exports are found in-process before this scan is consulted, so this only removes the ABI-incompatible legacy candidates; genuinely unresolved symbols now fail with a clear "symbol unresolved" error instead of corrupting memory. --- .../Interpreter/DynamicLibraryManagerSymbol.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/interpreter/cling/lib/Interpreter/DynamicLibraryManagerSymbol.cpp b/interpreter/cling/lib/Interpreter/DynamicLibraryManagerSymbol.cpp index a54962d76b1d2..76f800cc23a15 100644 --- a/interpreter/cling/lib/Interpreter/DynamicLibraryManagerSymbol.cpp +++ b/interpreter/cling/lib/Interpreter/DynamicLibraryManagerSymbol.cpp @@ -1141,6 +1141,21 @@ namespace cling { if (!cling::DynamicLibraryManager::isSharedLibrary(FileName)) return true; +#ifdef _WIN32 + // Never bind JIT'd code to legacy MSVC runtimes (msvcp60.dll & co, still + // shipped in System32, which is part of the search path): they export + // std::basic_string and friends with mangled names that match modern + // lookups but with an incompatible ABI, silently corrupting any object + // they touch. The runtimes this process was built against are already + // loaded, so their exports are found in-process before this library scan + // is ever consulted. + std::string Stem = llvm::sys::path::stem(FileName).lower(); + if (StringRef(Stem).starts_with("msvcp") || + StringRef(Stem).starts_with("msvcr") || + StringRef(Stem).starts_with("msvcirt")) + return true; +#endif + // No need to check linked libraries, as this function is only invoked // for symbols that cannot be found (neither by dlsym nor in the JIT). if (m_DynamicLibraryManager.isLibraryLoaded(FileName)) From f80cf5b8a208a343ffab3c922096617064903595 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 1 Oct 2026 09:10:39 +0200 Subject: [PATCH 02/53] [cppinterop] Upgrade to upstream e731565e32ae --- interpreter/CppInterOp/CMakeLists.txt | 3 + .../check_no_clangCppInterOp_link.sh | 0 interpreter/CppInterOp/cppinterop-version.tag | 2 +- .../include/CppInterOp/CppInterOpTypes.h | 25 ++-- .../CppInterOp/include/CppInterOp/Dispatch.h | 14 ++- .../CppInterOp/lib/CppInterOp/BakedArgs.h.in | 5 + .../CppInterOp/lib/CppInterOp/CMakeLists.txt | 47 +++++++ .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 115 +++++++++++++++++- .../lib/CppInterOp/InterpreterInfo.h | 6 + .../CppInterOp/VariableReflectionTest.cpp | 68 +++++++++++ 10 files changed, 273 insertions(+), 12 deletions(-) mode change 100755 => 100644 interpreter/CppInterOp/bazel-support/check_no_clangCppInterOp_link.sh create mode 100644 interpreter/CppInterOp/lib/CppInterOp/BakedArgs.h.in diff --git a/interpreter/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/CMakeLists.txt index e65c162e29d9b..75c19caf24c32 100644 --- a/interpreter/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/CMakeLists.txt @@ -23,6 +23,9 @@ option(CPPINTEROP_INSTALL "Generate install rules for CppInterOp" ON) # would otherwise materialize the SOVERSION namelink as a second full copy. option(CPPINTEROP_SHARED_LIBRARY_VERSIONING "Attach the LLVM SOVERSION and namelink to shared clangCppInterOp" ON) + +set(CPPINTEROP_BAKED_INTERPRETER_ARGS "" CACHE STRING + "Space-separated arguments passed at interpreter creation (no quoting: arguments containing spaces are not supported)") if(EMSCRIPTEN) set(CPPINTEROP_EXTRA_WASM_FLAGS "-fwasm-exceptions" CACHE STRING "Extra flags for wasm") endif() diff --git a/interpreter/CppInterOp/bazel-support/check_no_clangCppInterOp_link.sh b/interpreter/CppInterOp/bazel-support/check_no_clangCppInterOp_link.sh old mode 100755 new mode 100644 diff --git a/interpreter/CppInterOp/cppinterop-version.tag b/interpreter/CppInterOp/cppinterop-version.tag index 9cd89f6911ecf..e1ea93b25377f 100644 --- a/interpreter/CppInterOp/cppinterop-version.tag +++ b/interpreter/CppInterOp/cppinterop-version.tag @@ -1 +1 @@ -261bebbf25c8f709d2e2490fe6e3f876f6ec5920 +e731565e32ae82cf0e5b1a3f9d66ff457136e0e0 diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h index 6bc5724325695..278ca3094474b 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h @@ -26,6 +26,18 @@ #endif #endif +// Visibility of friend redeclarations of API functions. MSVC treats a friend +// declaration without the dllimport/dllexport attribute as a +// different-linkage redeclaration, so it needs CPPINTEROP_API. Elsewhere the +// visibility comes from the API declaration itself; leaving it off here lets +// Dispatch.h define these functions with hidden visibility (Clang rejects a +// visibility that differs from a previous declaration). +#if defined _WIN32 || defined __CYGWIN__ +#define CPPINTEROP_FRIEND_API CPPINTEROP_API +#else +#define CPPINTEROP_FRIEND_API +#endif + // Cross-platform deprecation attribute. Older Clang versions (Cling) // mis-parse C++11 `[[deprecated]]` near the return type, so we use // the vendor-specific spelling on each compiler. @@ -456,8 +468,8 @@ enum class ValueKind : std::uint8_t { /// function, constructor or destructor. class JitCall { public: - friend CPPINTEROP_API JitCall MakeFunctionCallable(InterpRef I, - ConstFuncRef func); + friend CPPINTEROP_FRIEND_API JitCall MakeFunctionCallable(InterpRef I, + ConstFuncRef func); enum Kind : char { kUnknown = 0, kGenericCall, @@ -498,16 +510,13 @@ class JitCall { : m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {} // Trace-hook impls need private m_FD for the function-name lookup. - // CPPINTEROP_API matches the X-macro-generated decl in CppInterOpDecl.inc; - // MSVC treats a friend decl without the dllimport/dllexport attribute as - // a different-linkage redeclaration. - friend CPPINTEROP_API void + friend CPPINTEROP_FRIEND_API void CppInterOpTraceJitCallInvokeImpl(const JitCall*, void* result, void** args, std::size_t nargs, void* self); - friend CPPINTEROP_API void + friend CPPINTEROP_FRIEND_API void CppInterOpTraceJitCallInvokeDestructorImpl(const JitCall*, void* object, unsigned long nary, int withFree); - friend CPPINTEROP_API void + friend CPPINTEROP_FRIEND_API void CppInterOpTraceJitCallInvokeReturnImpl(const JitCall*, void* result); /// Checks if the passed arguments are valid for the given function. diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h index 9a7b5a0c06864..069886a174c32 100644 --- a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -119,10 +119,22 @@ using namespace Cpp; // exclusive (#error guard at top) so only one body of Cpp::* exists per TU. // CN (CppName) may differ from DN (DispatchName) for overloaded functions, // e.g. CN=GetFunctionAddress, DN=GetFunctionAddress_fn. +// +// The wrappers have hidden visibility so that the funtion is not exported +// from the client. Otherwise the dynamic linker could bind libclangCppInterOp's +// own references to Cpp::* +#if defined(__GNUC__) && !defined(_WIN32) && !defined(__CYGWIN__) +#define CPPINTEROP_DISPATCH_HIDDEN __attribute__((__visibility__("hidden"))) +#else +#define CPPINTEROP_DISPATCH_HIDDEN +#endif + namespace Cpp { #define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ - inline Ret CN DeclArgs { return ::CppInternal::DispatchRaw::DN CallArgs; } + CPPINTEROP_DISPATCH_HIDDEN inline Ret CN DeclArgs { \ + return ::CppInternal::DispatchRaw::DN CallArgs; \ + } #include "CppInterOp/CppInterOpAPI.inc" /// Initialize all CppInterOp API from the dynamically loaded library. diff --git a/interpreter/CppInterOp/lib/CppInterOp/BakedArgs.h.in b/interpreter/CppInterOp/lib/CppInterOp/BakedArgs.h.in new file mode 100644 index 0000000000000..70bbf819569e2 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/BakedArgs.h.in @@ -0,0 +1,5 @@ +// Generated by CMake from BakedArgs.h.in. Do not edit. +// Carry configure-time values into CppInterOp.cpp through a header instead +// of -D definitions, so only one escaping layer (the C string literal) ever +// applies — with -D, the shell/ninja/packaging layers mangle backslashes. +@_baked_defines@ diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index 68391bab9a54b..5349299aca1a5 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -303,7 +303,54 @@ if(TARGET CppInterOpTableGen) endif() target_compile_definitions(clangCppInterOp PUBLIC "_CINDEX_LIB_") # workaround for the use of `CINDEX_LINKAGE` +# Where clang keeps its builtin headers. MakeResourcesPath() derives this +# from LLVM_BINARY_DIR at runtime, which misses wherever clang's headers are +# installed under a different prefix than LLVM's libraries, leaving a probe +# for a `clang` on PATH as the only fallback. Ask the clang this build links +# against instead, while the answer is still knowable. The cache entry is +# the override; the detected value stays local so it cannot outlive a +# reconfigure against a different Clang_DIR. +set(CPPINTEROP_CLANG_RESOURCE_DIR "" CACHE PATH + "Clang resource directory compiled into the library; detected when empty") + +set(_clang_resource_dir "${CPPINTEROP_CLANG_RESOURCE_DIR}") +# Auto-detection is skipped for coverage-instrumented builds: a baked-in +# directory makes MakeResourcesPath() return early at runtime, leaving the +# runtime fallback paths below it uncovered. An explicit +# CPPINTEROP_CLANG_RESOURCE_DIR override is always honored. +if(NOT _clang_resource_dir AND NOT CODE_COVERAGE + AND EXISTS "${CLANG_INSTALL_PREFIX}/bin/clang") + execute_process( + COMMAND "${CLANG_INSTALL_PREFIX}/bin/clang" -print-resource-dir + OUTPUT_VARIABLE _detected_resource_dir OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(EXISTS "${_detected_resource_dir}/include") + set(_clang_resource_dir "${_detected_resource_dir}") + endif() +endif() +message(STATUS "Clang resource directory: ${_clang_resource_dir}") + string(REPLACE ";" "\;" _VER CPPINTEROP_VERSION) set_source_files_properties(CppInterOp.cpp PROPERTIES COMPILE_DEFINITIONS "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\";CPPINTEROP_VERSION=\"${_VAR}\"" ) + +# The values land in C string literals, so escape for exactly that layer. +# They are delivered through a generated header (see BakedArgs.h.in) instead +# of -D definitions: with -D, the shell would apply another unescape layer +# and corrupt literal backslashes on the way to the preprocessor. +function(_baked_string_define OUT NAME VALUE) + string(REPLACE "\\" "\\\\" _v "${VALUE}") + string(REPLACE "\"" "\\\"" _v "${_v}") + set(${OUT} "${${OUT}}#define ${NAME} \"${_v}\"\n" PARENT_SCOPE) +endfunction() +set(_baked_defines "") +if(CPPINTEROP_BAKED_INTERPRETER_ARGS) + _baked_string_define(_baked_defines CPPINTEROP_BAKED_INTERPRETER_ARGS + "${CPPINTEROP_BAKED_INTERPRETER_ARGS}") +endif() +if(_clang_resource_dir) + _baked_string_define(_baked_defines CPPINTEROP_CLANG_RESOURCE_DIR + "${_clang_resource_dir}") +endif() +configure_file(BakedArgs.h.in ${CMAKE_CURRENT_BINARY_DIR}/BakedArgs.h @ONLY) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 20f5988bd50b1..401237c9a3f5b 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -11,6 +11,9 @@ #include "Unwrap.h" #include "CppInterOp/Error.h" +// Generated at configure time; defines CPPINTEROP_BAKED_INTERPRETER_ARGS +// and CPPINTEROP_CLANG_RESOURCE_DIR when the build baked them in. +#include "BakedArgs.h" #include "Compatibility.h" #include "ErrorInternal.h" #include "InterpreterInfo.h" @@ -76,6 +79,7 @@ #include "clang/Sema/Sema.h" #include "clang/Sema/TemplateDeduction.h" +#include "llvm/ADT/APInt.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -113,9 +117,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -3385,6 +3389,56 @@ TypeRef GetVariableType(ConstDeclRef var) { return INTEROP_RETURN(nullptr); } +// Lay an evaluated constant array initializer out contiguously in +// ASTContext-owned memory (stable for the interpreter's lifetime) and return +// its address. Integral element types only; anything else returns 0 so the +// caller falls back to the regular JIT lookup/codegen path. Cache is the +// per-interpreter ConstArrayValueStore, so repeated queries return the same +// address instead of re-evaluating and re-allocating on every call. +static intptr_t +MaterializeConstArrayValue(ASTContext& C, const VarDecl* VD, const APValue& Val, + std::map& Cache) { + const clang::ArrayType* AT = + Val.isArray() ? C.getAsArrayType(VD->getType()) : nullptr; + const uint64_t EltBytes = + AT ? C.getTypeSizeInChars(AT->getElementType()).getQuantity() : 0; + const size_t Count = AT ? Val.getArraySize() : 0; + if (!EltBytes || !Count || + Count > std::numeric_limits::max() / EltBytes) + return 0; + QualType EltTy = AT->getElementType(); + + // The buffer lives in the ASTContext bump allocator for the interpreter's + // lifetime, so re-laying out on every call would hand out unstable + // addresses (unlike the scalar path, which returns the cached APValue + // storage) and grow the allocator unboundedly. + auto [It, Inserted] = Cache.try_emplace(VD->getCanonicalDecl(), 0); + if (!Inserted) + return It->second; + + uint8_t* Buf = static_cast(C.Allocate( + EltBytes * Count, (unsigned)C.getTypeAlignInChars(EltTy).getQuantity())); + for (size_t I = 0; I < Count; ++I) { + const APValue* Elt = nullptr; + if (I < Val.getArrayInitializedElts()) + Elt = &Val.getArrayInitializedElt(I); + else if (Val.hasArrayFiller()) + Elt = &Val.getArrayFiller(); + if (!Elt || !Elt->isInt()) + return 0; // cached 0: not fully evaluable, fall back to the JIT path + const llvm::APInt& Int = Elt->getInt(); + // The raw layout only works if the APInt fills the element exactly; + // padded storage like _BitInt(24) (4-byte storage, 3-byte APInt) would + // trip StoreIntToMemory's width assert in assertions-enabled builds. + if (Int.getBitWidth() != EltBytes * 8) + return 0; + llvm::StoreIntToMemory(Int, Buf + (I * EltBytes), (unsigned)EltBytes); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + It->second = reinterpret_cast(Buf); + return It->second; +} + intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, CXXRecordDecl* BaseCXXRD) { if (!D) @@ -3461,6 +3515,32 @@ intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, void* address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( mangledName.c_str()); + // A const variable whose constant initializer is in the AST: serve the + // evaluated value before the JIT lookup. This must stay *after* the + // search in the loaded binaries above: a symbol that a binary does + // export keeps its real address. A missed lookup falls through to the + // symbol autoloader, which scans and parses every library on the + // search path (over a thousand file opens with ROOT, ~0.5 s), only to + // end up in the evaluate fallback below anyway: in-class initialized + // static members like TString::kNPOS are exported by no binary. Unlike + // that fallback, don't instantiate templates here, so no Sema work is + // added for variables the lookup would have found. + if (!address) { + const VarDecl* InitVD = VD->hasInit() ? VD : VD->getDefinition(); + if (InitVD && InitVD->hasInit() && + !InitVD->getType().isVolatileQualified() && + (InitVD->isConstexpr() || InitVD->getType().isConstQualified())) { + if (const APValue* val = InitVD->evaluateValue()) { + if (InitVD->getType()->isIntegralType(C)) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(val->getInt().getRawData()); + if (intptr_t ArrAddr = MaterializeConstArrayValue( + C, InitVD, *val, getInterpInfo(&I).ConstArrayValueStore)) + return ArrAddr; + } + } + } + if (!address) address = I.getAddressOfGlobal(GD); if (!address) { @@ -5349,6 +5429,13 @@ bool DefineAbsoluteSymbol(compat::Interpreter& I, const char* unmangled_name, #endif static std::string MakeResourcesPath() { +#ifdef CPPINTEROP_CLANG_RESOURCE_DIR + // Recorded at configure time from the clang this library links against; + // the LLVM_BINARY_DIR derivation below misses whenever clang's headers + // are installed under a different prefix than LLVM's libraries. + if (sys::fs::is_directory(CPPINTEROP_CLANG_RESOURCE_DIR)) + return CPPINTEROP_CLANG_RESOURCE_DIR; +#endif StringRef Dir; #ifdef LLVM_BINARY_DIR Dir = LLVM_BINARY_DIR; @@ -5441,13 +5528,37 @@ InterpRef CreateInterpreter(const std::vector& Args /*={}*/, // copies and move them into the interpreter's InterpreterInfo entry. std::vector ArgvStorage; ArgvStorage.push_back(sys::fs::getMainExecutable(nullptr, nullptr)); + // Arguments compiled in at configure time, e.g. a packager recording the + // toolchain header paths the in-process clang cannot probe for. They + // precede the embedder's arguments so they act as defaults: conflicts in + // the clang invocation are resolved later-wins, and the embedder's + // -resource-dir stays ahead of the baked one (ExtractArgument below). + // CPPINTEROP_EXTRA_INTERPRETER_ARGS from the environment follows both. + std::vector AllArgs; +#ifdef CPPINTEROP_BAKED_INTERPRETER_ARGS + // Storage for the split arguments; must outlive the pointers in AllArgs. + std::vector BakedStorage; + StringRef Baked(CPPINTEROP_BAKED_INTERPRETER_ARGS); + while (!Baked.empty()) { + StringRef Arg; + std::tie(Arg, Baked) = Baked.split(' '); + if (!Arg.empty()) + BakedStorage.push_back(Arg.str()); + } + for (const std::string& Arg : BakedStorage) + AllArgs.push_back(Arg.c_str()); +#endif + AllArgs.insert(AllArgs.end(), Args.begin(), Args.end()); // In some systems, CppInterOp cannot manually detect the correct resource. // Then the -resource-dir passed by the user is assumed to be the correct // location. Prioritising it over detecting it within CppInterOp. Extracting // the resource-dir from the arguments is required because we set the // necessary library search location explicitly below. Because by default, // linker flags are ignored in repl (issue #748) + // The embedder's -resource-dir wins over any baked one. std::string ResourceDir = ExtractArgument(Args, "-resource-dir"); + if (ResourceDir.empty()) + ResourceDir = ExtractArgument(AllArgs, "-resource-dir"); if (ResourceDir.empty()) ResourceDir = MakeResourcesPath(); llvm::Triple T(llvm::sys::getProcessTriple()); @@ -5489,7 +5600,7 @@ InterpRef CreateInterpreter(const std::vector& Args /*={}*/, } } #endif - ArgvStorage.insert(ArgvStorage.end(), Args.begin(), Args.end()); + ArgvStorage.insert(ArgvStorage.end(), AllArgs.begin(), AllArgs.end()); // To keep the Interpreter creation interface between cling and clang-repl // to some extent compatible we should put Args and GpuArgs together. On the // receiving end we should check for -xcuda to know. diff --git a/interpreter/CppInterOp/lib/CppInterOp/InterpreterInfo.h b/interpreter/CppInterOp/lib/CppInterOp/InterpreterInfo.h index f8869b7671e8a..006521d24c4e3 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/InterpreterInfo.h +++ b/interpreter/CppInterOp/lib/CppInterOp/InterpreterInfo.h @@ -23,6 +23,7 @@ #include "llvm/ADT/StringMap.h" +#include #include #include #include @@ -40,6 +41,11 @@ struct InterpreterInfo { // interpreter, so the caches must be destroyed together with it. std::map WrapperStore; std::map DtorWrapperStore; + // Constant-array initializers materialized by GetVariableOffset, keyed on + // the AST node so each variable is laid out once (stable address per + // query, no bump-allocator growth). A 0 entry records a deterministic + // materialization failure. + std::map ConstArrayValueStore; // A deque keeps element addresses stable so DiagnosticRef::data // survives push_back. std::deque StoredDiags; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp index 0adf9dcbc6c70..a02442e14a18a 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp @@ -394,6 +394,74 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, EXPECT_FALSE(Cpp::GetVariableOffset(var2)); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + VariableReflection_GetVariableOffset_InClassConstInit) { + // External-linkage static members initialized in-class, which no binary + // exports (TString::kNPOS is the in-the-wild shape). The value is served + // from the evaluated initializer, without a JIT lookup that would fall + // through to scanning all libraries for the symbol. + TestFixture::CreateInterpreter(); + Cpp::Declare(R"( + struct InClassConstInit { + static const long kNPOS = ~(long)0; + static constexpr int kN = 100; + static constexpr short kArr[3] = {1, -2, 3}; + static constexpr short kFill[4] = {7, 8}; // zero-filler tail + // Not materializable from the evaluated value: non-integral element + // type, and _BitInt storage padded past the APInt width. The query + // must fail cleanly (0), not crash in JIT codegen for the array. + static constexpr double kF[2] = {1.5, -2.5}; + static constexpr _BitInt(24) kBit[2] = {1, -2}; + }; + )"); + Cpp::DeclRef klass = Cpp::GetNamed("InClassConstInit"); + EXPECT_TRUE(klass); + + // GetVariableOffset returns addresses as intptr_t; dereferencing them is + // the point of these assertions. + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast, + // performance-no-int-to-ptr) + intptr_t npos = Cpp::GetVariableOffset(Cpp::GetNamed("kNPOS", klass)); + EXPECT_TRUE(npos); + EXPECT_EQ(*reinterpret_cast(npos), ~(long)0); + + intptr_t n = Cpp::GetVariableOffset(Cpp::GetNamed("kN", klass)); + EXPECT_TRUE(n); + EXPECT_EQ(*reinterpret_cast(n), 100); + + intptr_t arr = Cpp::GetVariableOffset(Cpp::GetNamed("kArr", klass)); + EXPECT_TRUE(arr); + EXPECT_EQ(reinterpret_cast(arr)[0], 1); + EXPECT_EQ(reinterpret_cast(arr)[1], -2); + EXPECT_EQ(reinterpret_cast(arr)[2], 3); + // The materialization is cached per variable: a repeated query must return + // the same address, not a fresh bump allocation. + EXPECT_EQ(Cpp::GetVariableOffset(Cpp::GetNamed("kArr", klass)), arr); + + Cpp::DeclRef filldecl = Cpp::GetNamed("kFill", klass); + EXPECT_TRUE(filldecl); + intptr_t fill = Cpp::GetVariableOffset(filldecl); + EXPECT_TRUE(fill); + EXPECT_EQ(reinterpret_cast(fill)[0], 7); + EXPECT_EQ(reinterpret_cast(fill)[1], 8); + EXPECT_EQ(reinterpret_cast(fill)[2], 0); // APValue filler + EXPECT_EQ(reinterpret_cast(fill)[3], 0); + EXPECT_EQ(Cpp::GetVariableOffset(filldecl), fill); + + // Unsupported fast-path shapes fall back to JIT codegen of the constexpr + // inline static. The outcome is platform-dependent — resolvable on some + // targets (then the values must read back correctly), failing cleanly + // with 0 on others. Never a crash either way. + if (intptr_t f = Cpp::GetVariableOffset(Cpp::GetNamed("kF", klass))) { + EXPECT_EQ(reinterpret_cast(f)[0], 1.5); + EXPECT_EQ(reinterpret_cast(f)[1], -2.5); + } + + Cpp::GetVariableOffset(Cpp::GetNamed("kBit", klass)); // outcome varies + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast, + // performance-no-int-to-ptr) +} + TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetVariableOffset_NoStaleUsedHandle) { #ifdef __EMSCRIPTEN__ From 5880a0eaef77e3b592ee2113cf3e40431b64cef0 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 19 Aug 2026 21:29:39 +0200 Subject: [PATCH 03/53] [cppinterop] Do not expose inherited special-member constructors [upstream] --- interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp | 9 +++++++++ .../unittests/CppInterOp/FunctionReflectionTest.cpp | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 401237c9a3f5b..e463d0ed8b086 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1616,6 +1616,15 @@ static void GetClassDecls(ConstDeclRef DRef, std::vector& methods) { } if (CXXCD->isDeleted()) continue; + // Do not expose the base's parameterless default/copy/move + // constructors: callers (e.g. cppyy) treat the class' own special + // members as authoritative, and the call layer refuses to invoke + // special members injected by a using declaration (see + // make_narg_call_with_return). Note that a constructor with defaulted + // parameters, e.g. Base(int n = 0), must stay: its non-default form is + // a genuinely inherited constructor. + if (CXXCD->getNumParams() == 0 || CXXCD->isCopyOrMoveConstructor()) + continue; // Result is appended to the decls, i.e. CXXRD, iterator // non-shadowed decl will be push_back later diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index 5d41fc3cfae38..3d67f127d264e 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -103,7 +103,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassMethods) { std::vector methods3; Cpp::GetClassMethods(Decls[4], methods3); - EXPECT_EQ(methods3.size(), 9); + // the parameterless default/copy/move constructors of B, though nominally + // inherited by the using declaration, are not exposed: C's own special + // members are authoritative (and the call layer refuses to invoke special + // members injected by a using declaration) + EXPECT_EQ(methods3.size(), 7); EXPECT_EQ(get_method_name(methods3[0]), "inline C::C()"); EXPECT_EQ(get_method_name(methods3[1]), "inline constexpr C::C(const C &)"); EXPECT_EQ(get_method_name(methods3[2]), "inline constexpr C::C(C &&)"); @@ -111,7 +115,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassMethods) { EXPECT_EQ(get_method_name(methods3[4]), "inline C &C::operator=(C &&)"); EXPECT_EQ(get_method_name(methods3[5]), "inline C::~C()"); EXPECT_EQ(get_method_name(methods3[6]), "inline C::B(int)"); - EXPECT_EQ(get_method_name(methods3[7]), "inline constexpr C::B(const B &)"); // Should not crash. std::vector methods4; From 140563dd8472b8d31bd09149cc6cf581f221ad09 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 24 Aug 2026 12:54:32 +0200 Subject: [PATCH 04/53] [cppinterop] Require public path between same-template base instantiations [upstream] --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 41 +++++++++++++- .../CppInterOp/ScopeReflectionTest.cpp | 55 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index e463d0ed8b086..c8778c62782af 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1474,8 +1474,45 @@ bool IsSubclass(ConstDeclRef derived, ConstDeclRef base) { const auto* Derived = cast(derived_D); const auto* Base = cast(base_D); - return INTEROP_RETURN( - IsTypeDerivedFrom(GetTypeFromScope(Derived), GetTypeFromScope(Base))); + + // This interface historically ignores access specifiers: interactive and + // binding use cases pass derived objects where a base is expected also + // for non-public bases. The exception is when both classes are + // instantiations of the same class template: there, non-public + // derivation is a recursive implementation detail (e.g. MSVC's + // std::tuple derives privately from the tuple of its tail elements) and + // accepting the derived instantiation for its base instantiation would + // silently reinterpret the object - a tuple must never be passed as its + // own tail. Require a public path in that case only. + if (!IsTypeDerivedFrom(GetTypeFromScope(Derived), GetTypeFromScope(Base))) + return INTEROP_RETURN(false); + + const auto* DerivedCTSD = llvm::dyn_cast(Derived); + const auto* BaseCTSD = llvm::dyn_cast(Base); + if (!DerivedCTSD || !BaseCTSD || + DerivedCTSD->getSpecializedTemplate()->getCanonicalDecl() != + BaseCTSD->getSpecializedTemplate()->getCanonicalDecl()) + return INTEROP_RETURN(true); + + // distinct handles may refer to the same class (Sema::IsDerivedFrom above + // accepts identical types, the base-path walk below would not) + if (Derived->getCanonicalDecl() == Base->getCanonicalDecl()) + return INTEROP_RETURN(true); + + const CXXRecordDecl* Def = Derived->getDefinition(); + if (!Def) + return INTEROP_RETURN(false); + + CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + if (!Def->isDerivedFrom(Base, Paths)) + return INTEROP_RETURN(false); + + for (const CXXBasePath& Path : Paths) + if (Path.Access == AS_public) + return INTEROP_RETURN(true); + + return INTEROP_RETURN(false); } // Copied from VTableBuilder.cpp diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 304912b3c50c3..4371ec90ebd8d 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -1132,6 +1132,61 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsSubclass) { EXPECT_FALSE(Cpp::IsSubclass(Decls[4], nullptr)); } +// IsSubclass deliberately ignores access specifiers so bindings can pass +// objects across non-public bases - except between two instantiations of the +// same class template, where non-public derivation is a recursive +// implementation detail (MSVC's std::tuple derives privately from the tuple +// of its tail) and acceptance would reinterpret a tuple as its own tail. +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsSubclassSameTemplate) { + std::vector Decls; + std::string code = R"( + struct PrivBase { int b; }; + struct PrivDerived : private PrivBase {}; + + template struct RTup; + template <> struct RTup<> {}; + template + struct RTup : private RTup { H head; }; + + template struct PubTup; + template <> struct PubTup<> {}; + template + struct PubTup : public PubTup { H head; }; + + RTup rt3; + RTup rt2; + PubTup pt2; + PubTup pt1; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto scope_of = [](const char* var) { + return Cpp::GetScopeFromType(Cpp::GetVariableType(Cpp::GetNamed(var))); + }; + Cpp::DeclRef rt3 = scope_of("rt3"); + Cpp::DeclRef rt2 = scope_of("rt2"); + Cpp::DeclRef pt2 = scope_of("pt2"); + Cpp::DeclRef pt1 = scope_of("pt1"); + ASSERT_TRUE(rt3); + ASSERT_TRUE(rt2); + ASSERT_TRUE(pt2); + ASSERT_TRUE(pt1); + + // Distinct classes keep the access-blind behavior. + EXPECT_TRUE(Cpp::IsSubclass(Cpp::GetScope("PrivDerived"), + Cpp::GetScope("PrivBase"))); + + // Same template, public derivation chain: still a subclass. + EXPECT_TRUE(Cpp::IsSubclass(pt2, pt1)); + + // Same template, only a private path: a tuple is not its own tail. + EXPECT_FALSE(Cpp::IsSubclass(rt3, rt2)); + + // Identity between same-template handles is unaffected. + EXPECT_TRUE(Cpp::IsSubclass(rt3, rt3)); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetBaseClassOffset) { std::vector Decls; #define Stringify(s) Stringifyx(s) From a3def769431459f6149bfebef0511117b3291df2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 5 Sep 2026 20:28:53 +0200 Subject: [PATCH 05/53] [cppinterop] Use a spellable return type in wrapper codegen [upstream] --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 35 ++++++++++ .../CppInterOp/FunctionReflectionTest.cpp | 66 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index c8778c62782af..0a538beaf6218 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -4595,6 +4595,35 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, } } +// A wrapper can only name a type whose spelling is reachable from file +// scope: the head of the sugared name and every record enclosing it must be +// public, and decltype/typeof sugar is spelled as its expression, whose +// names may only resolve in the declaring scope. Builtins and compound types +// keep the status quo. +static bool isWrapperSpellable(QualType QT) { + for (const clang::Type* T = QT.getTypePtr();;) { + if (isa(T)) + return false; + if (isa(T)) + break; + QualType Next = T->getLocallyUnqualifiedSingleStepDesugaredType(); + if (Next.getTypePtr() == T) + break; + T = Next.getTypePtr(); + } + const NamedDecl* D = nullptr; + if (const auto* TT = QT->getAs()) + D = TT->getDecl(); + else if (const auto* RD = QT->getAsRecordDecl()) + D = RD; + while (D) { + if (D->getAccess() != AS_public && D->getAccess() != AS_none) + return false; + D = llvm::dyn_cast(D->getDeclContext()); + } + return true; +} + void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD, const unsigned N, const std::string& class_name, std::ostringstream& buf, int indent_level) { @@ -4624,6 +4653,12 @@ void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD, return; } QualType QT = FD->getReturnType(); + // A return type spelled through a non-public path (e.g. a typedef nested + // in a private helper class) cannot be named in the wrapper; fall back to + // the canonical type when that one is spellable (the inverse also exists: + // a public typedef of a private class must keep its sugared name). + if (!isWrapperSpellable(QT) && isWrapperSpellable(QT.getCanonicalType())) + QT = QT.getCanonicalType(); if (QT->isVoidType()) { std::ostringstream typedefbuf; std::ostringstream callbuf; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index 3d67f127d264e..14e98c19e083b 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -5435,3 +5435,69 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, EXPECT_EQ(ra, 7); // HeavyZero<1>::tls.id set by the NonTrivial ctor EXPECT_EQ(rb, 7); } + +// The wrapper spells the return type in its placement-new; a typedef nested +// in a private member class is not spellable there (and, unlike a typedef of +// a builtin, is not desugared by get_type_as_string), the canonical type is. +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_PrivateTypedefReturn) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + std::vector Decls; + std::string code = R"( + struct Obj {}; + struct Res { + private: + struct Helper { typedef Obj Iterator_t; }; + public: + Helper::Iterator_t get() { return {}; } + }; + )"; + + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + /*interpreter_args=*/{"-include", "new"}); + ASSERT_EQ(Decls.size(), 2); + auto Fns = Cpp::GetFunctionsUsingName(Decls[1], "get"); + ASSERT_EQ(Fns.size(), 1); + EXPECT_EQ(Cpp::MakeFunctionCallable(Fns[0]).getKind(), + Cpp::JitCall::kGenericCall); +} + +// decltype sugar is spelled as its expression: a call in it keeps its +// qualifier as written (detail::helper), which does not resolve at file scope; +// the canonical type is spellable. A typedef of a class type underneath keeps +// the sugar from being desugared when the type is printed (a builtin would be). +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DecltypeReturn) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + std::vector Decls; + std::string code = R"( + namespace ns { + namespace detail { inline int helper() { return 0; } } + struct Obj { int v; }; + typedef Obj Res; + auto get(int i) -> decltype(detail::helper(), Res()) { return Res{7 * i}; } + } + )"; + + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + /*interpreter_args=*/{"-include", "new"}); + ASSERT_EQ(Decls.size(), 1); + auto Fns = Cpp::GetFunctionsUsingName(Decls[0], "get"); + ASSERT_EQ(Fns.size(), 1); + Cpp::JitCall JC = Cpp::MakeFunctionCallable(Fns[0]); + ASSERT_EQ(JC.getKind(), Cpp::JitCall::kGenericCall); + + int i = 3; + struct { int v; } result = {0}; + std::array args = {(void*)&i}; + JC.Invoke(&result, {args.data(), /*args_size=*/1}, /*self=*/nullptr); + EXPECT_EQ(result.v, 21); +} From 71974c5fa6bfed709770c348ff5cc8a99d0c8fe8 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 5 Sep 2026 21:05:43 +0200 Subject: [PATCH 06/53] [cppinterop] Keep template arguments qualified in GetCompleteName [upstream] --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 14 ++++++++++++++ .../unittests/CppInterOp/ScopeReflectionTest.cpp | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 0a538beaf6218..9b5a2da2b618c 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1031,6 +1031,20 @@ static std::string GetCompleteNameImpl(ConstDeclRef DRef, bool qualified) { if (const auto* TD = llvm::dyn_cast(ND)) { std::string type_name; + if (!qualified && TD->getIdentifier()) { + // The name must be unqualified only for the tag itself; template + // arguments have to keep their scopes (SuppressScope would strip + // those too). getNameForDiagnostic prints the bare name and the + // template arguments under the policy, dropping defaulted ones. + Policy.SuppressScope = false; + Policy.FullyQualifiedName = true; + Policy.Suppress_Elab = true; + Policy.SuppressDefaultTemplateArgs = true; + llvm::raw_string_ostream name_stream(type_name); + TD->getNameForDiagnostic(name_stream, Policy, /*Qualified=*/false); + name_stream.flush(); + return type_name; + } QualType QT = compat::GetTypeFromDecl(TD); QT.getAsStringInternal(type_name, Policy); return type_name; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 4371ec90ebd8d..d687f1d776254 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -582,6 +582,21 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetCompleteName) { EXPECT_EQ(Cpp::GetCompleteName(fn), "fn"); } +// Template arguments keep their own scope in the complete name; defaulted +// arguments are dropped and only the tag's enclosing scope is stripped. +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetCompleteNameQualifiedArgs) { + std::vector Decls; + std::string code = R"( + namespace NS { struct S {}; } + template struct Tpl {}; + Tpl t; + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::GetCompleteName( + Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[2]))), + "Tpl"); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetQualifiedName) { std::vector Decls; std::string code = R"(namespace N { From e7d988a1bb2e90c29bdd5ef1b3326c8a79acd2dd Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 5 Sep 2026 21:06:00 +0200 Subject: [PATCH 07/53] [cppinterop] Print type and scope names platform-independently [upstream] --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 27 ++++++++++++++++++- .../CppInterOp/ScopeReflectionTest.cpp | 23 ++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 9b5a2da2b618c..397767dd17071 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1018,6 +1018,15 @@ static std::string GetCompleteNameImpl(ConstDeclRef DRef, bool qualified) { if (const auto* ND = llvm::dyn_cast_or_null(D)) { PrintingPolicy Policy = C.getPrintingPolicy(); Policy.SuppressUnwrittenScope = true; + // Keep scope and type names platform-independent: always drop inline + // namespaces (libc++'s std::__1, libstdc++'s std::__cxx11) instead of + // relying on the lookup-based "redundant qualifier" heuristic, and do + // not substitute preferred names (libc++ tags basic_string with + // preferred_name(string)). cppyy matches these names against string + // keys and uses them as Python scope paths. + Policy.SuppressInlineNamespace = + llvm::to_underlying(PrintingPolicy::SuppressInlineNamespaceMode::All); + Policy.UsePreferredNames = false; if (qualified) { Policy.FullyQualifiedName = true; Policy.Suppress_Elab = true; @@ -1057,7 +1066,14 @@ static std::string GetCompleteNameImpl(ConstDeclRef DRef, bool qualified) { return func_name; } - return qualified ? ND->getQualifiedNameAsString() : ND->getNameAsString(); + if (qualified) { + std::string qual_name; + llvm::raw_string_ostream OS(qual_name); + ND->printQualifiedName(OS, Policy); + OS.flush(); + return qual_name; + } + return ND->getNameAsString(); } if (llvm::isa_and_nonnull(D)) { @@ -3844,6 +3860,15 @@ std::string GetTypeAsString(ConstTypeRef var) { Policy.SuppressTagKeyword = true; // Do not print `class std::string`. Policy.Suppress_Elab = true; Policy.FullyQualifiedName = true; + // Type names are matched against string keys on the cppyy side (e.g. the + // std::string return-value executor), so their spelling must not depend on + // the platform: always drop inline namespaces (libc++'s std::__1, + // libstdc++'s std::__cxx11) instead of relying on the lookup-based + // "redundant qualifier" heuristic, and do not substitute preferred names + // (libc++ tags basic_string with preferred_name(string)). + Policy.SuppressInlineNamespace = + llvm::to_underlying(PrintingPolicy::SuppressInlineNamespaceMode::All); + Policy.UsePreferredNames = false; return INTEROP_RETURN(QT.getAsString(Policy)); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index d687f1d776254..09c3aacb875bc 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -597,6 +597,29 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetCompleteNameQualifiedArgs) { "Tpl"); } +// Printed names must not depend on platform naming features: preferred-name +// substitution is off (libc++ tags basic_string as std::string) and +// inline namespaces are dropped unconditionally. +TYPED_TEST(CPPINTEROP_TEST_MODE, + ScopeReflection_GetCompleteNamePlatformIndependent) { + std::vector Decls; + std::string code = R"( + namespace Outer { inline namespace v1 { struct T {}; } } + Outer::T t; + template struct BS; + typedef BS MyStr; + template struct [[clang::preferred_name(MyStr)]] BS {}; + BS v; + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::GetQualifiedCompleteName( + Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[1]))), + "Outer::T"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName( + Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[5]))), + "BS"); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetQualifiedName) { std::vector Decls; std::string code = R"(namespace N { From 401eb0d389464a5329b29f6f336376532be38ff5 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 14 Jul 2026 19:20:12 +0000 Subject: [PATCH 08/53] [cppinterop] Reject ill-formed instantiations in BestOverloadFunctionMatch [upstream] When the best viable function is a not-yet-instantiated template specialization, force the instantiation of its body with diagnostics suppressed and reject the match if the body turns out ill-formed. Callers use this function to probe whether a valid instantiation exists (e.g. cpyrt's __cppjit_internal::is_equal fallback for comparison operators), so an ill-formed body must surface as "no viable function" rather than as a stream of hard errors when the wrapper is compiled at call time. This mirrors cling's LookupHelper::overloadFunctionSelector. Note that probing with an expression-SFINAE helper instead is not equivalent: C++20 rewritten (reversed) operator candidates make comparisons like a member operator==(const Base&) ambiguous, which is a hard substitution failure in a SFINAE context but only a warning in a function body. InstantiateTemplate equally refuses to hand out a specialization whose attempted instantiation marked it invalid: any use of it, such as compiling a call wrapper, can only fail. With the error spam gone, test21_access_to_global_variables of test_advancedcpp passes and loses its xfail marker. --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 397767dd17071..f156b622bb7f6 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -2856,6 +2856,34 @@ BestOverloadFunctionMatch(const std::vector& candidates, Overloads.BestViableFunction(S, Loc, Best); FunctionDecl* Result = Best != Overloads.end() ? Best->Function : nullptr; + + // If the winner is a not-yet-instantiated template specialization, force + // the instantiation of its body now, with diagnostics suppressed, and + // reject the match if the body turns out to be ill-formed. Callers use + // this function to probe whether a valid instantiation exists (e.g. + // CPyCppyy's __cppyy_internal::is_equal fallback for comparison + // operators), so an ill-formed body must surface as "no viable function" + // here rather than as a hard error when the wrapper is compiled at call + // time. This mirrors cling's LookupHelper::overloadFunctionSelector. Note + // that probing with an expression-SFINAE helper instead is not equivalent: + // C++20 rewritten (reversed) operator candidates make comparisons like a + // member operator==(const Base&) ambiguous, which is a hard substitution + // failure in a SFINAE context but only a warning in a function body. + // DefinitionRequired stays false: a pattern without a body (defined in a + // library) is still a valid match. + if (Result && Result->isTemplateInstantiation() && !Result->isDefined()) { + DiagnosticsEngine& Diags = S.getDiagnostics(); + bool OldSuppress = Diags.getSuppressAllDiagnostics(); + Diags.setSuppressAllDiagnostics(true); + S.InstantiateFunctionDefinition(Loc, Result, /*Recursive=*/true); + Diags.setSuppressAllDiagnostics(OldSuppress); + if (Result->isInvalidDecl()) { + Diags.Reset(/*soft=*/true); + Diags.getClient()->clear(); + Result = nullptr; + } + } + delete[] Exprs; return INTEROP_RETURN(Result); } @@ -6224,6 +6252,11 @@ static Decl* InstantiateTemplate(TemplateDecl* TemplateD, // FIXME: Diagnose what happened. (void)Result; } + // Never hand out a specialization whose (attempted) instantiation turned + // out to be ill-formed, e.g. one rejected by BestOverloadFunctionMatch: + // any use of it, such as compiling a call wrapper, can only fail. + if (Specialization && Specialization->isInvalidDecl()) + return nullptr; if (instantiate_body) InstantiateFunctionDefinition(Specialization); return Specialization; From 28ac889f780adff66b5ac9686845ad507f5c3316 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 17:47:14 +0200 Subject: [PATCH 09/53] [cppinterop] Resolve globals whose symbol exists in no loaded binary [upstream] Layered onto upstream's GetVariableOffset (which since carries the prefer-definition and instantiation-pattern guards, the evaluation of const initializers found in the AST ahead of the JIT lookup with constant arrays materialized into a per-interpreter buffer, and odr-use emission for discardable-ODR linkage): - Serve module-local (internal-linkage) const/constexpr values from the evaluated initializer before any symbol lookup: no library symbol can legitimately be such an entity, and a missed JIT lookup falls to the symbol autoloader, which loads any library on the dynamic path exporting the name (a REPL constexpr int S pulled in msys-2.0.dll on the Windows CI, corrupting the host process at exit) - Keep the initializer reads (getInit/evaluateValue) inside one SynthesizingCodeRAII, since they may lazily deserialize; the final symbol lookup and the evaluate fallback stay outside it, or execution is deferred - Also ForceCodeGen strong definitions whose initializer is in the AST but whose home binary does not export the symbol (Windows: bindexplib does not export read-only data) - On symbol-lookup failure, fall back to odr-using the variable in an interpreter-evaluated expression, as TClingDataMemberInfo::Offset does --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 196 ++++++++++++------ 1 file changed, 133 insertions(+), 63 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index f156b622bb7f6..106b381ba6b1a 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -3616,80 +3616,150 @@ intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, auto GD = GlobalDecl(VD); std::string mangledName; compat::maybeMangleDeclName(GD, mangledName); - void* address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( - mangledName.c_str()); - - // A const variable whose constant initializer is in the AST: serve the - // evaluated value before the JIT lookup. This must stay *after* the - // search in the loaded binaries above: a symbol that a binary does - // export keeps its real address. A missed lookup falls through to the - // symbol autoloader, which scans and parses every library on the - // search path (over a thousand file opens with ROOT, ~0.5 s), only to - // end up in the evaluate fallback below anyway: in-class initialized - // static members like TString::kNPOS are exported by no binary. Unlike - // that fallback, don't instantiate templates here, so no Sema work is - // added for variables the lookup would have found. - if (!address) { - const VarDecl* InitVD = VD->hasInit() ? VD : VD->getDefinition(); - if (InitVD && InitVD->hasInit() && - !InitVD->getType().isVolatileQualified() && - (InitVD->isConstexpr() || InitVD->getType().isConstQualified())) { - if (const APValue* val = InitVD->evaluateValue()) { - if (InitVD->getType()->isIntegralType(C)) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - return reinterpret_cast(val->getInt().getRawData()); - if (intptr_t ArrAddr = MaterializeConstArrayValue( - C, InitVD, *val, getInterpInfo(&I).ConstArrayValueStore)) - return ArrAddr; + void* address = nullptr; + { + // scoped: the final symbol lookup and the evaluate fallback below must + // run outside of any nested transaction, or execution is deferred; the + // initializer reads (getInit/evaluateValue) may lazily deserialize and + // so need an open transaction + compat::SynthesizingCodeRAII RAII(&getInterp()); + + // A name without external linkage is module-local: no library symbol + // can legitimately be this entity, and a missed JIT lookup falls + // through to the symbol autoloader, which loads any library on the + // dynamic path exporting the name (a REPL "constexpr int S" pulled in + // msys-2.0.dll on Windows). Serve the evaluated value directly. + if (!VD->hasExternalFormalLinkage()) { + if (!VD->hasInit()) { + if (VarDecl* Def = VD->getDefinition()) { + VD = Def; + } else if (VD->getTemplateInstantiationPattern()) { + getSema().InstantiateVariableDefinition(SourceLocation(), VD); + if (VarDecl* Inst = VD->getDefinition()) + VD = Inst; + } + } + if (VD->hasInit() && + (VD->isConstexpr() || VD->getType().isConstQualified())) { + if (const APValue* val = VD->evaluateValue()) { + if (VD->getType()->isIntegralType(C)) + return (intptr_t)val->getInt().getRawData(); + if (intptr_t ArrAddr = MaterializeConstArrayValue( + C, VD, *val, getInterpInfo(&I).ConstArrayValueStore)) + return ArrAddr; + } } } - } - if (!address) - address = I.getAddressOfGlobal(GD); - if (!address) { - if (!VD->hasInit()) { - // The initializer feeding the constexpr fast path below may live on - // an already-parsed out-of-line definition (a non-template class's - // static data member, e.g. std::partial_ordering::less): prefer it, - // with no Sema work at all. Only a variable instantiated from a - // template can need Sema::InstantiateVariableDefinition — and - // without an instantiation pattern that call dereferences a null - // VarDecl in release builds. - if (VarDecl* Def = VD->getDefinition()) { - VD = Def; - } else if (VD->getTemplateInstantiationPattern()) { - compat::SynthesizingCodeRAII RAII(&getInterp()); - getSema().InstantiateVariableDefinition(SourceLocation(), VD); - if (VarDecl* Inst = VD->getDefinition()) - VD = Inst; + address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( + mangledName.c_str()); + + // A const variable whose constant initializer is in the AST: serve the + // evaluated value before the JIT lookup. This must stay *after* the + // search in the loaded binaries above: a symbol that a binary does + // export keeps its real address. A missed lookup falls through to the + // symbol autoloader, which scans and parses every library on the + // search path (over a thousand file opens with ROOT, ~0.5 s), only to + // end up in the evaluate fallback below anyway: in-class initialized + // static members like TString::kNPOS are exported by no binary. Unlike + // that fallback, don't instantiate templates here, so no Sema work is + // added for variables the lookup would have found. + if (!address) { + const VarDecl* InitVD = VD->hasInit() ? VD : VD->getDefinition(); + if (InitVD && InitVD->hasInit() && + !InitVD->getType().isVolatileQualified() && + (InitVD->isConstexpr() || InitVD->getType().isConstQualified())) { + if (const APValue* val = InitVD->evaluateValue()) { + if (InitVD->getType()->isIntegralType(C)) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(val->getInt().getRawData()); + if (intptr_t ArrAddr = MaterializeConstArrayValue( + C, InitVD, *val, getInterpInfo(&I).ConstArrayValueStore)) + return ArrAddr; + } } } - if (VD->hasInit() && - (VD->isConstexpr() || VD->getType().isConstQualified())) { - if (const APValue* val = VD->evaluateValue()) { - if (VD->getType()->isIntegralType(C)) { - return (intptr_t)val->getInt().getRawData(); + + if (!address) + address = I.getAddressOfGlobal(GD); + if (!address) { + if (!VD->hasInit()) { + // The initializer feeding the constexpr fast path below may live on + // an already-parsed out-of-line definition (a non-template class's + // static data member, e.g. std::partial_ordering::less): prefer it, + // with no Sema work at all. Only a variable instantiated from a + // template can need Sema::InstantiateVariableDefinition — and + // without an instantiation pattern that call dereferences a null + // VarDecl in release builds. + if (VarDecl* Def = VD->getDefinition()) { + VD = Def; + } else if (VD->getTemplateInstantiationPattern()) { + getSema().InstantiateVariableDefinition(SourceLocation(), VD); + if (VarDecl* Inst = VD->getDefinition()) + VD = Inst; + } + } + if (VD->hasInit() && + (VD->isConstexpr() || VD->getType().isConstQualified())) { + if (const APValue* val = VD->evaluateValue()) { + if (VD->getType()->isIntegralType(C)) { + return (intptr_t)val->getInt().getRawData(); + } + if (intptr_t ArrAddr = MaterializeConstArrayValue( + C, VD, *val, getInterpInfo(&I).ConstArrayValueStore)) + return ArrAddr; } } } - } - if (!address) { - auto Linkage = C.GetGVALinkageForVariable(VD); - // Odr-use emission only for discardable-ODR entities (inline/constexpr - // statics) — the class the used-list crash traced to. Internal-linkage - // variables cannot be odr-used from a later PTU (module-local symbol: - // the reference duplicates or misses the entity), and an - // available-externally definition would not be emitted by a mere - // reference; both stay on the stock UsedAttr path. - if (isDiscardableGVALinkage(Linkage) && - (Linkage != GVA_DiscardableODR || !EmitVariableViaOdrUse(I, VD))) - ForceCodeGen(VD, I); + if (!address) { + auto Linkage = C.GetGVALinkageForVariable(VD); + // Odr-use emission only for discardable-ODR entities (inline/constexpr + // statics) — the class the used-list crash traced to. Internal-linkage + // variables cannot be odr-used from a later PTU (module-local symbol: + // the reference duplicates or misses the entity), and an + // available-externally definition would not be emitted by a mere + // reference; both stay on the stock UsedAttr path. + // Also emit non-discardable strong definitions whose initializer is + // available in the AST but whose home binary does not export the + // symbol: e.g. on Windows, bindexplib deliberately does not export + // read-only data from DLLs, but the dictionary provides the + // initializer, so an emitted copy has the same value. Writable + // globals are exported and found above, so they cannot end up with a + // diverging JIT copy here. A discardable entity already emitted via + // odr-use must never also take the UsedAttr route: the attr sticks on + // the AST decl and grows used-list residue in every later PTU. + bool discardable = isDiscardableGVALinkage(Linkage); + if ((discardable && + (Linkage != GVA_DiscardableODR || !EmitVariableViaOdrUse(I, VD))) || + (!discardable && VD->isThisDeclarationADefinition() && + VD->hasInit())) + ForceCodeGen(VD, I); + } } auto VDAorErr = compat::getSymbolAddress(I, StringRef(mangledName)); if (!VDAorErr) { - llvm::logAllUnhandledErrors(VDAorErr.takeError(), llvm::errs(), - "Failed to GetVariableOffset:"); + // Last resort: ODR-use the variable in an interpreter-evaluated + // expression. This forces CodeGen to emit inline/constexpr variables + // that exist in no loaded library (e.g. std::nullopt) and that the + // deferred-decl handling above did not emit. Mirrors what + // TClingDataMemberInfo::Offset does in ROOT. + // This can only work when CodeGen can emit the variable, i.e. when its + // definition or at least an initializer is available in the AST (e.g. + // std::ios_base::__noreplace, a static const member initialized in the + // class). For a variable defined in a binary that does not export it, + // the emitted reference could never be resolved, and executing the + // expression would be undefined behavior. + llvm::consumeError(VDAorErr.takeError()); + if (VD->getDefinition() || VD->hasInit()) { + compat::Value V; + const std::string addr_of = + "&::" + VD->getQualifiedNameAsString() + ";"; + if (I.evaluate(addr_of.c_str(), V) == compat::Interpreter::kSuccess && + V.hasValue()) + return (intptr_t)compat::convertTo(V); + } + llvm::errs() << "Failed to GetVariableOffset: symbol '" << mangledName + << "' not found and its definition is not available\n"; return 0; } return (intptr_t)jitTargetAddressToPointer(VDAorErr.get()); From b91addf4e0d0153598d32a02d8b5c09b9c05a28c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 16:38:45 +0200 Subject: [PATCH 10/53] [cppinterop] Test smart-pointer detection of a templated operator-> [upstream] Models MSVC's std::shared_ptr::operator->, a member template with a defaulted, SFINAE-constrained template parameter. With no call arguments BestOverloadFunctionMatch must return an instantiation with a concrete return type rather than the template pattern, which smart-pointer detection relies on to find the pointee type. --- .../CppInterOp/FunctionReflectionTest.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index 14e98c19e083b..5a562a8599957 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -2698,6 +2698,40 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, "template<> A A::operator-(A rhs)"); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_TemplatedOperatorArrow) { + // Model of MSVC's std::shared_ptr::operator->, which is a member template + // with a defaulted template parameter (SFINAE-constrained on the element + // type). Smart-pointer detection has to instantiate it with no call + // arguments to determine the pointee type. + std::vector Decls; + std::string code = R"( + struct TheData { int fData; }; + template struct SmartLike { + T* ptr; + template U* operator->() { return ptr; } + }; + SmartLike gSmart{nullptr}; + )"; + GetAllTopLevelDecls(code, Decls); + + Cpp::DeclRef Scope = + Cpp::GetScopeFromType(Cpp::GetVariableType(Cpp::GetNamed("gSmart"))); + ASSERT_TRUE(Scope.data); + + std::vector ops; + Cpp::GetOperator(Scope, Cpp::Operator::OP_Arrow, ops); + ASSERT_EQ(ops.size(), 1); + EXPECT_TRUE(Cpp::IsTemplatedFunction(ops[0])); + + Cpp::FuncRef Deref = Cpp::BestOverloadFunctionMatch(ops, {}, {}); + ASSERT_TRUE(Deref); + // The match is an instantiation with the defaulted template parameter, not + // the template pattern itself: its return type is concrete. + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Deref)), + "TheData *"); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_BestOverloadFunctionMatch4) { std::vector Decls, SubDecls; From cb2b1cb0d07421b561a2c3dc44150f433cdab25d Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 18:25:30 +0200 Subject: [PATCH 11/53] [cppinterop] Guard lazily deserialized reflection queries with SynthesizingCodeRAII [upstream] SizeOf, GetNumBases, GetBaseClass and IsLambdaClass read declarations that a PCH or module may still have to deserialize, which on Cling requires an open transaction; without one the lazy loads hit assertion failures and crashes. Open the RAII scope right before the deserializing call. GetNamed's guard now also covers the parent's typedef unwrap, which is what GetScope needed, and GetClassDecls' guard moves before its own typedef unwrap. The same guard covers GetEnumConstantDatamembers, whose enumerator iteration deserializes as well. --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 106b381ba6b1a..25265e30902c0 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -783,6 +783,7 @@ size_t SizeOf(ConstDeclRef DRef) { if (const auto* RD = dyn_cast(unwrap(DRef))) { ASTContext& Context = RD->getASTContext(); + compat::SynthesizingCodeRAII RAII(&getInterp()); const ASTRecordLayout& Layout = Context.getASTRecordLayout(RD); return INTEROP_RETURN(Layout.getSize().getQuantity()); } @@ -1358,12 +1359,12 @@ bool HasReachableUsingDirective(const clang::DeclContext* DC) { DeclRef GetNamed(const std::string& name, ConstDeclRef parent /*= nullptr*/) { INTEROP_TRACE(name, parent); - clang::DeclContext* Within = 0; + clang::DeclContext* Within = nullptr; + compat::SynthesizingCodeRAII RAII(&getInterp()); if (parent) { auto* D = unwrap(GetUnderlyingScope(parent)); Within = llvm::dyn_cast(D); } - compat::SynthesizingCodeRAII RAII(&getInterp()); if (Within) Within->getPrimaryContext()->buildLookup(); @@ -1465,6 +1466,7 @@ size_t GetNumBases(ConstDeclRef DRef) { compat::InstantiateClassTemplateSpecialization( getInterp(), const_cast(CTSD)); if (const auto* CXXRD = llvm::dyn_cast_or_null(D)) { + compat::SynthesizingCodeRAII RAII(&getInterp()); if (CXXRD->hasDefinition()) return INTEROP_RETURN(CXXRD->getNumBases()); } @@ -1476,6 +1478,7 @@ DeclRef GetBaseClass(ConstDeclRef DRef, size_t ibase) { INTEROP_TRACE(DRef, ibase); const auto* D = unwrap(DRef); const auto* CXXRD = llvm::dyn_cast_or_null(D); + compat::SynthesizingCodeRAII RAII(&getInterp()); if (!CXXRD || CXXRD->getNumBases() <= ibase) return INTEROP_RETURN(nullptr); @@ -1640,6 +1643,7 @@ static void GetClassDecls(ConstDeclRef DRef, std::vector& methods) { // operation on the AST, logically const for the caller. Decl* D = const_cast(unwrap(DRef)); + compat::SynthesizingCodeRAII RAII(&getInterp()); if (auto* TD = dyn_cast(D)) { DeclRef Scope = GetScopeFromType(TD->getUnderlyingType()); D = unwrap(Scope); @@ -1649,7 +1653,6 @@ static void GetClassDecls(ConstDeclRef DRef, std::vector& methods) { return; auto* CXXRD = dyn_cast(D); - compat::SynthesizingCodeRAII RAII(&getInterp()); if (auto* CTSD = dyn_cast(CXXRD)) { QualType QT = compat::GetTypeFromDecl(CTSD); if (!getSema().isCompleteType(CTSD->getLocation(), QT)) @@ -3417,6 +3420,9 @@ void GetEnumConstantDatamembers(ConstDeclRef DRef, std::vector& datamembers, bool include_enum_class) { INTEROP_TRACE(DRef, INTEROP_OUT(datamembers), include_enum_class); + // Iterating the enumerators may lazily deserialize them (PCH/modules), + // which requires an open transaction. + compat::SynthesizingCodeRAII RAII(&getInterp()); std::vector EDs; GetClassDecls(DRef, EDs); for (DeclRef i : EDs) { @@ -3454,6 +3460,7 @@ DeclRef LookupDatamember(const std::string& name, ConstDeclRef parent) { bool IsLambdaClass(ConstTypeRef TyRef) { INTEROP_TRACE(TyRef); QualType QT = QualType::getFromOpaquePtr(TyRef.data); + compat::SynthesizingCodeRAII RAII(&getInterp()); if (auto* CXXRD = QT->getAsCXXRecordDecl()) { return INTEROP_RETURN(CXXRD->isLambda()); } From 1f1b75571741abfe4a18a79148cba2048e095cf9 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 30 Sep 2026 12:18:32 +0200 Subject: [PATCH 12/53] [cppinterop] Add Cling-specific lookup and codegen patches [ROOT-patch] Adapts the vendored CppInterOp to ROOT's Cling: - ExistsFunctionTemplate, LookupDatamember, GetFunctionsUsingName and GetClassTemplatedMethods call buildLookup on the parent context before the qualified lookup, as GetNamed does, so that declarations Cling registers lazily are visible to it - make_narg_call drops the class_name:: qualification of the callee so that the wrapper dispatches virtually (TInterpreter::Declare is pure virtual) - GetTypeAsString builds its PrintingPolicy from default LangOptions instead of the ASTContext's policy, which keeps the std::basic_string spelling the cpyrt type factory expects - GetReferencedType returns a null type for a null input instead of dereferencing it --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 25265e30902c0..92e19b46d1a32 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1804,8 +1804,11 @@ std::vector GetFunctionsUsingName(ConstDeclRef DRef, clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, RedeclarationKind::ForVisibleRedeclaration); + auto* Within = Decl::castToDeclContext(D); compat::SynthesizingCodeRAII RAII(&getInterp()); - CppInternal::utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + if (Within) + Within->getPrimaryContext()->buildLookup(); + CppInternal::utils::Lookup::Named(&S, R, Within); if (R.empty()) return INTEROP_RETURN(funcs); @@ -2656,6 +2659,8 @@ bool ExistsFunctionTemplate(const std::string& name, ConstDeclRef parent) { } compat::SynthesizingCodeRAII RAII(&getInterp()); + if (Within) + const_cast(Within->getPrimaryContext())->buildLookup(); auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if ((intptr_t)ND == (intptr_t)0) @@ -2726,6 +2731,8 @@ bool GetClassTemplatedMethods(const std::string& name, ConstDeclRef parent, auto* DC = clang::Decl::castToDeclContext(DU); compat::SynthesizingCodeRAII RAII(&getInterp()); + if (DC) + DC->getPrimaryContext()->buildLookup(); CppInternal::utils::Lookup::Named(&S, R, DC); if (R.getResultKind() == clang_LookupResult_Not_Found && funcs.empty()) @@ -3447,6 +3454,8 @@ DeclRef LookupDatamember(const std::string& name, ConstDeclRef parent) { } compat::SynthesizingCodeRAII RAII(&getInterp()); + if (Within) + const_cast(Within->getPrimaryContext())->buildLookup(); auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { if (llvm::isa_and_nonnull(ND)) { @@ -3920,6 +3929,8 @@ TypeRef GetPointerType(ConstTypeRef TyRef) { TypeRef GetReferencedType(ConstTypeRef TyRef, bool rvalue) { INTEROP_TRACE(TyRef, rvalue); + if (!TyRef.data) + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(TyRef.data); if (rvalue) return INTEROP_RETURN( @@ -3960,7 +3971,8 @@ TypeRef GetUnderlyingType(ConstTypeRef TyRef) { std::string GetTypeAsString(ConstTypeRef var) { INTEROP_TRACE(var); QualType QT = QualType::getFromOpaquePtr(var.data); - PrintingPolicy Policy(getASTContext().getPrintingPolicy()); + // FIXME: Get the default printing policy from the ASTContext. + PrintingPolicy Policy((LangOptions())); Policy.Bool = true; // Print bool instead of _Bool. Policy.SuppressTagKeyword = true; // Do not print `class std::string`. Policy.Suppress_Elab = true; @@ -4541,8 +4553,6 @@ void make_narg_call(const FunctionDecl* FD, const std::string& return_type, else callbuf << "((" << class_name << "*)obj)->"; - if (op_flag) - callbuf << class_name << "::"; } else if (isa(get_non_transparent_decl_context(FD))) { // This is a namespace member. if (op_flag || N <= 1) From 183b30d9de5a507baadaeee17b355f4159a0305d Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 17:54:12 +0200 Subject: [PATCH 13/53] [cppinterop] Compile call wrappers without access control [upstream] cppyy-backend's TClingCallFunc::compile_wrapper hardcodes withAccessControl=false for every wrapper: the callee was already selected (and public-filtered) by the caller, and the generated code may legitimately name entities that are private at the call site. The wrapper for RResultPtr>::begin() spells its return type through the private member template RIterationHelper; with access control on it fails to compile and iterating a Take result raises (rdf_define_pyz.test_cpp_free_function). Subsumes the narrower relaxAccessControl escape hatch for using-shadows; the parameter stays in the signature for its callers. --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 20 ++++------ .../CppInterOp/FunctionReflectionTest.cpp | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 92e19b46d1a32..0947afc3e6d89 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -5370,18 +5370,14 @@ JitCall::GenericCall make_wrapper(compat::Interpreter& I, // // Compile the wrapper code. // - bool withAccessControl = true; - // We should be able to call private default constructors. - if (auto Ctor = dyn_cast(FD)) - withAccessControl = !Ctor->isDefaultConstructor(); - // Members introduced into a derived class with a public using-declaration - // are reachable through the derived class, but the generated wrapper still - // calls the target through its original (e.g. protected) qualified name. - // Disable access control for this specific case so the wrapper compiles. - if (relaxAccessControl) - withAccessControl = false; - void* wrapper = - compile_wrapper(I, wrapper_name, wrapper_code, withAccessControl); + // Access control must be off, matching cppyy-backend's TClingCallFunc: the + // callee was already selected (and public-filtered) by the caller, and + // compiling the call may lazily instantiate template bodies that are only + // valid with checks relaxed (e.g. a member template accessing a private + // member of another specialization of its own class template). This + // subsumes the narrower relaxAccessControl escape hatch for using-shadows. + void* wrapper = compile_wrapper(I, wrapper_name, wrapper_code, + /*withAccessControl=*/false); if (wrapper) { WrapperStore.insert(std::make_pair(FD, wrapper)); } else { diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index 5a562a8599957..38b35ae27c323 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -320,6 +320,43 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Cpp::Destruct(object, Decls[1]); } +// The wrapper spells the callee's return type. When that spelling runs through +// a private member template, as RResultPtr>::begin() does with +// RIterationHelper::Iterator_t, the wrapper only compiles with access +// control off, as cppyy-backend's TClingCallFunc always compiled it. +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_MakeFunctionCallable_PrivateReturnSpelling) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + std::vector Decls; + std::string code = R"( + struct Value { int v; }; + class Holder { + template struct Helper { using Value_t = void; }; + template struct Helper { using Value_t = V; }; + public: + Helper::Value_t get() { return Value{42}; } + }; + )"; + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + /*interpreter_args=*/{"-include", "new"}); + + Cpp::JitCall Call = Cpp::MakeFunctionCallable( + Cpp::ConstFuncRef{Cpp::GetNamed("get", Decls[1]).data}); + ASSERT_EQ(Call.getKind(), Cpp::JitCall::kGenericCall); + + Cpp::ObjectRef object = Cpp::Construct(Decls[1]); + ASSERT_TRUE(object); + int result = 0; + Call.Invoke(&result, {}, object.data); + EXPECT_EQ(result, 42); + Cpp::Destruct(object, Decls[1]); +} + // A using-promoted method still belongs to its declaring base class. When that // base sits at a non-zero offset inside the derived object (multiple // inheritance), callers adjust `this` with From 0b6092d539abf4f289cf3371c37278d72850840b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 10 Sep 2026 15:20:55 +0200 Subject: [PATCH 14/53] [cppinterop] Share one dispatch table across Windows DLLs [upstream] Consumers of the dispatch API spread across several shared libraries must share a single pointer table. On ELF the extern pointers resolve across module boundaries by default; on Windows data symbols are not auto-exported (WINDOWS_EXPORT_ALL_SYMBOLS covers functions only), so every non-defining DLL failed to link. Mark the table dllexport in the module that compiles the definition TU (CPPINTEROP_DISPATCH_EXPORTS) and dllimport everywhere else. --- .../include/CppInterOp/CppInterOpTypes.h | 35 ++++++++++++++++--- .../CppInterOp/include/CppInterOp/Dispatch.h | 5 ++- .../CppInterOp/lib/CppInterOp/CMakeLists.txt | 3 ++ .../CppInterOp/lib/CppInterOp/Tracing.cpp | 17 ++++----- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h index 278ca3094474b..68958c554af9d 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h @@ -38,6 +38,27 @@ #define CPPINTEROP_FRIEND_API #endif +// Storage class for the dispatch-table slots (CppInternal::DispatchRaw). +// Consumers spread across several shared libraries must share ONE table: on +// ELF the extern pointers resolve across module boundaries by default, but +// on Windows data symbols need explicit annotations (unlike CPPINTEROP_API, +// which is unconditionally dllexport, promising a same-module definition). +// The module that compiles a table definition defines +// CPPINTEROP_DISPATCH_EXPORTS for all of its TUs; every other module imports. +#ifndef CPPINTEROP_DISPATCH_STORAGE +#if defined(_WIN32) +#if defined(CPPINTEROP_DISPATCH_EXPORTS) +#define CPPINTEROP_DISPATCH_STORAGE __declspec(dllexport) +#else +#define CPPINTEROP_DISPATCH_STORAGE __declspec(dllimport) +#endif +#elif defined(__GNUC__) +#define CPPINTEROP_DISPATCH_STORAGE __attribute__((__visibility__("default"))) +#else +#define CPPINTEROP_DISPATCH_STORAGE +#endif +#endif + // Cross-platform deprecation attribute. Older Clang versions (Cling) // mis-parse C++11 `[[deprecated]]` near the return type, so we use // the vendor-specific spelling on each compiler. @@ -237,13 +258,17 @@ namespace DispatchRaw { // Trace-hook slot forward decls; the X-macro expansion of // CppInterOpAPI.inc re-declares these with identical types. They're // here so JitCall::Invoke's inline body below can reference them. -extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeImpl)( +extern CPPINTEROP_DISPATCH_STORAGE void (*CppInterOpTraceJitCallInvokeImpl)( const Cpp::JitCall* JC, void* result, void** args, std::size_t nargs, void* self); -extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeDestructorImpl)( - const Cpp::JitCall* JC, void* object, unsigned long nary, int withFree); -extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeReturnImpl)( - const Cpp::JitCall* JC, void* result); +extern CPPINTEROP_DISPATCH_STORAGE void ( + *CppInterOpTraceJitCallInvokeDestructorImpl)(const Cpp::JitCall* JC, + void* object, + unsigned long nary, + int withFree); +extern CPPINTEROP_DISPATCH_STORAGE void ( + *CppInterOpTraceJitCallInvokeReturnImpl)(const Cpp::JitCall* JC, + void* result); } // namespace DispatchRaw } // namespace CppInternal diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h index 069886a174c32..87aabbe37dbe3 100644 --- a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -28,6 +28,9 @@ #ifdef _WIN32 #include #undef LoadLibrary +// windows.h aliases GetObject to GetObjectA/W, mangling any consumer method +// of that name compiled after this header (e.g. TBranch::GetObject in ROOT). +#undef GetObject #else #include #endif @@ -107,7 +110,7 @@ namespace CppInternal::DispatchRaw { using namespace Cpp; // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) #define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ - extern Ret(*DN) RawTypes; + extern CPPINTEROP_DISPATCH_STORAGE Ret(*DN) RawTypes; #include "CppInterOp/CppInterOpAPI.inc" // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) } // namespace CppInternal::DispatchRaw diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index 5349299aca1a5..db8f81ebed03a 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -302,6 +302,9 @@ if(TARGET CppInterOpTableGen) add_dependencies(clangCppInterOp CppInterOpTableGen) endif() target_compile_definitions(clangCppInterOp PUBLIC "_CINDEX_LIB_") # workaround for the use of `CINDEX_LINKAGE` +# The library defines its own dispatch trace-hook slots (Tracing.cpp): mark +# them dllexport on Windows via the CPPINTEROP_DISPATCH_STORAGE declarations. +target_compile_definitions(clangCppInterOp PRIVATE CPPINTEROP_DISPATCH_EXPORTS) # Where clang keeps its builtin headers. MakeResourcesPath() derives this # from LLVM_BINARY_DIR at runtime, which misses wherever clang's headers are diff --git a/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp index 0e4e0ebf5367d..d5194bcc97980 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp @@ -27,16 +27,17 @@ // libclangCppInterOp's own trace-hook slot storage; Dispatch.h // consumers carry their per-DSO copies, populated by LoadDispatchAPI. +// Storage class (dllexport/visibility) comes from the declarations in +// CppInterOpTypes.h; this TU's module defines CPPINTEROP_DISPATCH_EXPORTS. namespace CppInternal { namespace DispatchRaw { -CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeImpl)(const Cpp::JitCall*, - void*, void**, - std::size_t, - void*) = nullptr; -CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeDestructorImpl)( - const Cpp::JitCall*, void*, unsigned long, int) = nullptr; -CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeReturnImpl)( - const Cpp::JitCall*, void*) = nullptr; +void (*CppInterOpTraceJitCallInvokeImpl)(const Cpp::JitCall*, void*, void**, + std::size_t, void*) = nullptr; +void (*CppInterOpTraceJitCallInvokeDestructorImpl)(const Cpp::JitCall*, void*, + unsigned long, + int) = nullptr; +void (*CppInterOpTraceJitCallInvokeReturnImpl)(const Cpp::JitCall*, + void*) = nullptr; } // namespace DispatchRaw } // namespace CppInternal From 5d23277889f776b0e796655ee595853f7188e859 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 16:38:46 +0200 Subject: [PATCH 15/53] [cppinterop] Print template arguments without literal suffixes [ROOT-patch] GetCompleteName spelled non-type template arguments with their literal suffix (array). ROOT and cppyy always exposed the suffix-free form, which the names cpyrt matches against expect. --- interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 0947afc3e6d89..4d6fa0494d1a9 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -1050,6 +1050,9 @@ static std::string GetCompleteNameImpl(ConstDeclRef DRef, bool qualified) { Policy.FullyQualifiedName = true; Policy.Suppress_Elab = true; Policy.SuppressDefaultTemplateArgs = true; + // no literal suffixes on non-type arguments ("array", + // not "...3UL>"), matching the names ROOT and cppyy always exposed + Policy.AlwaysIncludeTypeForTemplateArgument = false; llvm::raw_string_ostream name_stream(type_name); TD->getNameForDiagnostic(name_stream, Policy, /*Qualified=*/false); name_stream.flush(); From 3a0351f446aa74c3184b99fba73da2a6e0ee713a Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 14:20:37 +0200 Subject: [PATCH 16/53] [cppjit] Vendor upstream cppjit at 7dc6bf0ff Full tree of compiler-research/cppjit at the pinned revision, minus .github and zizmor.yml; cppjit-version.tag records the pin. Not yet wired into the build. --- bindings/pyroot/cppjit/.clang-format | 15 + bindings/pyroot/cppjit/.gitignore | 33 + bindings/pyroot/cppjit/CMakeLists.txt | 163 + bindings/pyroot/cppjit/README.md | 225 + .../pyroot/cppjit/cmake/AddCppInterOp.cmake | 252 + .../cppjit/cmake/VerifyCppInterOp.cmake | 14 + bindings/pyroot/cppjit/cppjit-version.tag | 1 + bindings/pyroot/cppjit/docs/ReleaseNotes.md | 42 + bindings/pyroot/cppjit/docs/ReleaseProcess.md | 78 + bindings/pyroot/cppjit/pyproject.toml | 122 + .../pyroot/cppjit/python/cppjit/__init__.py | 484 ++ .../cppjit/python/cppjit/_cpython_cppjit.py | 227 + .../cppjit/python/cppjit/_pythonization.py | 360 ++ .../cppjit/python/cppjit/_stdcpp_fix.py | 14 + .../pyroot/cppjit/python/cppjit/_typemap.py | 139 + .../pyroot/cppjit/python/cppjit/_version.py | 1 + bindings/pyroot/cppjit/python/cppjit/ll.py | 174 + .../pyroot/cppjit/python/cppjit/numba_ext.py | 691 +++ .../pyroot/cppjit/python/cppjit/reflex.py | 26 + bindings/pyroot/cppjit/python/cppjit/types.py | 32 + bindings/pyroot/cppjit/requirements.txt | 10 + bindings/pyroot/cppjit/src/cpyrt/API.cxx | 477 ++ bindings/pyroot/cppjit/src/cpyrt/API.h | 252 + .../cppjit/src/cpyrt/CPPClassMethod.cxx | 60 + .../pyroot/cppjit/src/cpyrt/CPPClassMethod.h | 24 + .../cppjit/src/cpyrt/CPPConstructor.cxx | 392 ++ .../pyroot/cppjit/src/cpyrt/CPPConstructor.h | 93 + .../pyroot/cppjit/src/cpyrt/CPPDataMember.cxx | 450 ++ .../pyroot/cppjit/src/cpyrt/CPPDataMember.h | 72 + bindings/pyroot/cppjit/src/cpyrt/CPPEnum.cxx | 263 + bindings/pyroot/cppjit/src/cpyrt/CPPEnum.h | 17 + .../cppjit/src/cpyrt/CPPExcInstance.cxx | 253 + .../pyroot/cppjit/src/cpyrt/CPPExcInstance.h | 34 + .../pyroot/cppjit/src/cpyrt/CPPFunction.cxx | 138 + .../pyroot/cppjit/src/cpyrt/CPPFunction.h | 44 + .../pyroot/cppjit/src/cpyrt/CPPGetSetItem.cxx | 103 + .../pyroot/cppjit/src/cpyrt/CPPGetSetItem.h | 34 + .../pyroot/cppjit/src/cpyrt/CPPInstance.cxx | 1133 +++++ .../pyroot/cppjit/src/cpyrt/CPPInstance.h | 149 + .../pyroot/cppjit/src/cpyrt/CPPMethod.cxx | 1122 ++++ bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h | 126 + .../pyroot/cppjit/src/cpyrt/CPPOperator.cxx | 62 + .../pyroot/cppjit/src/cpyrt/CPPOperator.h | 28 + .../pyroot/cppjit/src/cpyrt/CPPOverload.cxx | 1161 +++++ .../pyroot/cppjit/src/cpyrt/CPPOverload.h | 122 + bindings/pyroot/cppjit/src/cpyrt/CPPScope.cxx | 739 +++ bindings/pyroot/cppjit/src/cpyrt/CPPScope.h | 117 + .../pyroot/cppjit/src/cpyrt/CallContext.cxx | 61 + .../pyroot/cppjit/src/cpyrt/CallContext.h | 166 + bindings/pyroot/cppjit/src/cpyrt/CommonDefs.h | 34 + .../pyroot/cppjit/src/cpyrt/Compatibility.h | 48 + .../pyroot/cppjit/src/cpyrt/Converters.cxx | 4501 +++++++++++++++++ bindings/pyroot/cppjit/src/cpyrt/Converters.h | 103 + .../pyroot/cppjit/src/cpyrt/CustomPyTypes.cxx | 456 ++ .../pyroot/cppjit/src/cpyrt/CustomPyTypes.h | 70 + .../cppjit/src/cpyrt/DeclareConverters.h | 542 ++ .../cppjit/src/cpyrt/DeclareExecutors.h | 204 + bindings/pyroot/cppjit/src/cpyrt/Dimensions.h | 90 + .../pyroot/cppjit/src/cpyrt/DispatchPtr.cxx | 114 + .../pyroot/cppjit/src/cpyrt/DispatchPtr.h | 80 + .../pyroot/cppjit/src/cpyrt/Dispatcher.cxx | 567 +++ bindings/pyroot/cppjit/src/cpyrt/Dispatcher.h | 17 + .../pyroot/cppjit/src/cpyrt/Executors.cxx | 1471 ++++++ bindings/pyroot/cppjit/src/cpyrt/Executors.h | 52 + .../pyroot/cppjit/src/cpyrt/LowLevelViews.cxx | 1283 +++++ .../pyroot/cppjit/src/cpyrt/LowLevelViews.h | 102 + .../cppjit/src/cpyrt/MemoryRegulator.cxx | 256 + .../pyroot/cppjit/src/cpyrt/MemoryRegulator.h | 48 + .../pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx | 1060 ++++ .../pyroot/cppjit/src/cpyrt/ProxyWrappers.h | 37 + bindings/pyroot/cppjit/src/cpyrt/PyCallable.h | 55 + .../pyroot/cppjit/src/cpyrt/PyException.cxx | 137 + .../pyroot/cppjit/src/cpyrt/PyException.h | 55 + .../pyroot/cppjit/src/cpyrt/PyObjectDir27.inc | 177 + .../pyroot/cppjit/src/cpyrt/PyStrings.cxx | 302 ++ bindings/pyroot/cppjit/src/cpyrt/PyStrings.h | 92 + .../pyroot/cppjit/src/cpyrt/Pythonize.cxx | 2218 ++++++++ bindings/pyroot/cppjit/src/cpyrt/Pythonize.h | 14 + bindings/pyroot/cppjit/src/cpyrt/Reflex.h | 30 + .../pyroot/cppjit/src/cpyrt/TemplateProxy.cxx | 987 ++++ .../pyroot/cppjit/src/cpyrt/TemplateProxy.h | 101 + .../cppjit/src/cpyrt/TupleOfInstances.cxx | 266 + .../cppjit/src/cpyrt/TupleOfInstances.h | 32 + .../pyroot/cppjit/src/cpyrt/TypeManip.cxx | 279 + bindings/pyroot/cppjit/src/cpyrt/TypeManip.h | 28 + bindings/pyroot/cppjit/src/cpyrt/Utility.cxx | 1587 ++++++ bindings/pyroot/cppjit/src/cpyrt/Utility.h | 139 + bindings/pyroot/cppjit/src/cpyrt/cpyrt.h | 221 + .../pyroot/cppjit/src/cpyrt/cpyrtModule.cxx | 1021 ++++ .../pyroot/cppjit/src/interop/callcontext.h | 54 + .../src/interop/cppinterop_dispatch.cxx | 11 + .../cppjit/src/interop/cppjit_interop.h | 396 ++ .../cppjit/src/interop/interop_wrapper.cxx | 1776 +++++++ .../pyroot/cppjit/src/interop/precommondefs.h | 74 + bindings/pyroot/cppjit/test/Makefile | 50 + bindings/pyroot/cppjit/test/conftest.py | 66 + .../pyroot/cppjit/test/cpp/advancedcpp.cxx | 173 + bindings/pyroot/cppjit/test/cpp/advancedcpp.h | 522 ++ .../pyroot/cppjit/test/cpp/advancedcpp2.cxx | 12 + .../pyroot/cppjit/test/cpp/advancedcpp2.h | 36 + .../pyroot/cppjit/test/cpp/conversions.cxx | 44 + bindings/pyroot/cppjit/test/cpp/conversions.h | 27 + .../pyroot/cppjit/test/cpp/cpp11features.cxx | 61 + .../pyroot/cppjit/test/cpp/cpp11features.h | 182 + .../cppjit/test/cpp/crossinheritance.cxx | 100 + .../pyroot/cppjit/test/cpp/crossinheritance.h | 130 + bindings/pyroot/cppjit/test/cpp/datatypes.cxx | 1042 ++++ bindings/pyroot/cppjit/test/cpp/datatypes.h | 835 +++ .../pyroot/cppjit/test/cpp/doc_helper.cxx | 7 + bindings/pyroot/cppjit/test/cpp/doc_helper.h | 30 + bindings/pyroot/cppjit/test/cpp/example01.cxx | 164 + bindings/pyroot/cppjit/test/cpp/example01.h | 115 + bindings/pyroot/cppjit/test/cpp/fragile.cxx | 61 + bindings/pyroot/cppjit/test/cpp/fragile.h | 143 + bindings/pyroot/cppjit/test/cpp/operators.cxx | 140 + bindings/pyroot/cppjit/test/cpp/operators.h | 350 ++ bindings/pyroot/cppjit/test/cpp/overloads.cxx | 77 + bindings/pyroot/cppjit/test/cpp/overloads.h | 116 + .../pyroot/cppjit/test/cpp/pythonizables.cxx | 112 + .../pyroot/cppjit/test/cpp/pythonizables.h | 164 + .../pyroot/cppjit/test/cpp/std_streams.cxx | 5 + bindings/pyroot/cppjit/test/cpp/std_streams.h | 15 + bindings/pyroot/cppjit/test/cpp/stltypes.cxx | 83 + bindings/pyroot/cppjit/test/cpp/stltypes.h | 261 + bindings/pyroot/cppjit/test/cpp/templates.cxx | 50 + bindings/pyroot/cppjit/test/cpp/templates.h | 478 ++ bindings/pyroot/cppjit/test/support.py | 166 + .../pyroot/cppjit/test/test_aclassloader.py | 27 + .../pyroot/cppjit/test/test_advancedcpp.py | 1032 ++++ bindings/pyroot/cppjit/test/test_api.py | 231 + bindings/pyroot/cppjit/test/test_basic_api.py | 93 + bindings/pyroot/cppjit/test/test_boost.py | 213 + .../pyroot/cppjit/test/test_concurrent.py | 333 ++ .../pyroot/cppjit/test/test_conversions.py | 156 + .../pyroot/cppjit/test/test_cpp11features.py | 697 +++ .../pyroot/cppjit/test/test_cpp23features.py | 428 ++ .../cppjit/test/test_crossinheritance.py | 1920 +++++++ bindings/pyroot/cppjit/test/test_datatypes.py | 2802 ++++++++++ .../pyroot/cppjit/test/test_doc_features.py | 1348 +++++ bindings/pyroot/cppjit/test/test_eigen.py | 173 + bindings/pyroot/cppjit/test/test_fragile.py | 937 ++++ bindings/pyroot/cppjit/test/test_leakcheck.py | 292 ++ bindings/pyroot/cppjit/test/test_lowlevel.py | 1180 +++++ bindings/pyroot/cppjit/test/test_main.py | 6 + bindings/pyroot/cppjit/test/test_numba.py | 854 ++++ bindings/pyroot/cppjit/test/test_operators.py | 423 ++ bindings/pyroot/cppjit/test/test_overloads.py | 477 ++ bindings/pyroot/cppjit/test/test_pythonify.py | 648 +++ .../pyroot/cppjit/test/test_pythonization.py | 324 ++ .../pyroot/cppjit/test/test_regression.py | 1714 +++++++ bindings/pyroot/cppjit/test/test_stltypes.py | 2503 +++++++++ bindings/pyroot/cppjit/test/test_streams.py | 66 + bindings/pyroot/cppjit/test/test_templates.py | 1653 ++++++ 153 files changed, 58329 insertions(+) create mode 100644 bindings/pyroot/cppjit/.clang-format create mode 100644 bindings/pyroot/cppjit/.gitignore create mode 100644 bindings/pyroot/cppjit/CMakeLists.txt create mode 100644 bindings/pyroot/cppjit/README.md create mode 100644 bindings/pyroot/cppjit/cmake/AddCppInterOp.cmake create mode 100644 bindings/pyroot/cppjit/cmake/VerifyCppInterOp.cmake create mode 100644 bindings/pyroot/cppjit/cppjit-version.tag create mode 100644 bindings/pyroot/cppjit/docs/ReleaseNotes.md create mode 100644 bindings/pyroot/cppjit/docs/ReleaseProcess.md create mode 100644 bindings/pyroot/cppjit/pyproject.toml create mode 100644 bindings/pyroot/cppjit/python/cppjit/__init__.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/_pythonization.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/_typemap.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/_version.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/ll.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/numba_ext.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/reflex.py create mode 100644 bindings/pyroot/cppjit/python/cppjit/types.py create mode 100644 bindings/pyroot/cppjit/requirements.txt create mode 100644 bindings/pyroot/cppjit/src/cpyrt/API.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/API.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPClassMethod.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPConstructor.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPEnum.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPEnum.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPFunction.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPFunction.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPGetSetItem.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPInstance.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPMethod.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPOperator.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPOperator.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPOverload.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPOverload.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPScope.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CPPScope.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CallContext.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CallContext.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CommonDefs.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Compatibility.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Converters.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Converters.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/CustomPyTypes.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/DeclareConverters.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/DeclareExecutors.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Dimensions.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/DispatchPtr.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Dispatcher.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Dispatcher.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Executors.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Executors.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/LowLevelViews.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/MemoryRegulator.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyCallable.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyException.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyException.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyStrings.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/PyStrings.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Pythonize.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Pythonize.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Reflex.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TupleOfInstances.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TypeManip.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/TypeManip.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Utility.cxx create mode 100644 bindings/pyroot/cppjit/src/cpyrt/Utility.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/cpyrt.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx create mode 100644 bindings/pyroot/cppjit/src/interop/callcontext.h create mode 100644 bindings/pyroot/cppjit/src/interop/cppinterop_dispatch.cxx create mode 100644 bindings/pyroot/cppjit/src/interop/cppjit_interop.h create mode 100644 bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx create mode 100644 bindings/pyroot/cppjit/src/interop/precommondefs.h create mode 100644 bindings/pyroot/cppjit/test/Makefile create mode 100644 bindings/pyroot/cppjit/test/conftest.py create mode 100644 bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/advancedcpp.h create mode 100644 bindings/pyroot/cppjit/test/cpp/advancedcpp2.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/advancedcpp2.h create mode 100644 bindings/pyroot/cppjit/test/cpp/conversions.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/conversions.h create mode 100644 bindings/pyroot/cppjit/test/cpp/cpp11features.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/cpp11features.h create mode 100644 bindings/pyroot/cppjit/test/cpp/crossinheritance.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/crossinheritance.h create mode 100644 bindings/pyroot/cppjit/test/cpp/datatypes.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/datatypes.h create mode 100644 bindings/pyroot/cppjit/test/cpp/doc_helper.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/doc_helper.h create mode 100644 bindings/pyroot/cppjit/test/cpp/example01.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/example01.h create mode 100644 bindings/pyroot/cppjit/test/cpp/fragile.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/fragile.h create mode 100644 bindings/pyroot/cppjit/test/cpp/operators.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/operators.h create mode 100644 bindings/pyroot/cppjit/test/cpp/overloads.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/overloads.h create mode 100644 bindings/pyroot/cppjit/test/cpp/pythonizables.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/pythonizables.h create mode 100644 bindings/pyroot/cppjit/test/cpp/std_streams.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/std_streams.h create mode 100644 bindings/pyroot/cppjit/test/cpp/stltypes.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/stltypes.h create mode 100644 bindings/pyroot/cppjit/test/cpp/templates.cxx create mode 100644 bindings/pyroot/cppjit/test/cpp/templates.h create mode 100644 bindings/pyroot/cppjit/test/support.py create mode 100644 bindings/pyroot/cppjit/test/test_aclassloader.py create mode 100644 bindings/pyroot/cppjit/test/test_advancedcpp.py create mode 100644 bindings/pyroot/cppjit/test/test_api.py create mode 100644 bindings/pyroot/cppjit/test/test_basic_api.py create mode 100644 bindings/pyroot/cppjit/test/test_boost.py create mode 100644 bindings/pyroot/cppjit/test/test_concurrent.py create mode 100644 bindings/pyroot/cppjit/test/test_conversions.py create mode 100644 bindings/pyroot/cppjit/test/test_cpp11features.py create mode 100644 bindings/pyroot/cppjit/test/test_cpp23features.py create mode 100644 bindings/pyroot/cppjit/test/test_crossinheritance.py create mode 100644 bindings/pyroot/cppjit/test/test_datatypes.py create mode 100644 bindings/pyroot/cppjit/test/test_doc_features.py create mode 100644 bindings/pyroot/cppjit/test/test_eigen.py create mode 100644 bindings/pyroot/cppjit/test/test_fragile.py create mode 100644 bindings/pyroot/cppjit/test/test_leakcheck.py create mode 100644 bindings/pyroot/cppjit/test/test_lowlevel.py create mode 100644 bindings/pyroot/cppjit/test/test_main.py create mode 100644 bindings/pyroot/cppjit/test/test_numba.py create mode 100644 bindings/pyroot/cppjit/test/test_operators.py create mode 100644 bindings/pyroot/cppjit/test/test_overloads.py create mode 100644 bindings/pyroot/cppjit/test/test_pythonify.py create mode 100644 bindings/pyroot/cppjit/test/test_pythonization.py create mode 100644 bindings/pyroot/cppjit/test/test_regression.py create mode 100644 bindings/pyroot/cppjit/test/test_stltypes.py create mode 100644 bindings/pyroot/cppjit/test/test_streams.py create mode 100644 bindings/pyroot/cppjit/test/test_templates.py 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/cppjit/__init__.py b/bindings/pyroot/cppjit/python/cppjit/__init__.py new file mode 100644 index 0000000000000..8df1b926f1670 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/__init__.py @@ -0,0 +1,484 @@ +"""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 ctypes +import importlib.util +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__ == "std::basic_string": + + 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() + + +# --- 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() + + +def cppdef(src, verbose=True): + """Declare C++ source to Cling.""" + with _stderr_capture() as err: + 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: + 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 + + if not errcode == 0: + 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): + 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.""" + reason = gbl.std.string() + result = gbl.Cpp.LoadLibrary(name, True, reason) + if result == False: # noqa: E712 + raise RuntimeError('Could not load library "%s": %s' % (name, reason)) + + return True + + +def include(header): + """Load (and JIT) header file
into Cling.""" + with _stderr_capture() as err: + 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: + 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) + 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.Cpp.AddSearchPath(path, True, False) + + +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) + + # add access to the cpyrt dispatcher API headers, which install next to the + # extension module; anchoring on the extension resolves editable and regular + # installs alike. CPPJIT_API_PATH overrides ("none" disables the lookup). + if not _ispypy: + apipath_extra = os.environ.get("CPPJIT_API_PATH") + if apipath_extra: + if os.path.basename(apipath_extra) == "cpyrt": + apipath_extra = os.path.dirname(apipath_extra) + else: + spec = importlib.util.find_spec("cppjit.libcppjit") + if spec is not None and spec.origin: + apipath_extra = os.path.join( + os.path.dirname(spec.origin), "interop", "include" + ) + + if apipath_extra and apipath_extra.lower() != "none": + if os.path.isdir(os.path.join(apipath_extra, "cpyrt")): + add_include_path(apipath_extra) + else: + warnings.warn( + "cpyrt API not found (tried: %s); set CPPJIT_API_PATH envar to the 'cpyrt' API directory to fix" + % apipath_extra + ) + + 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..50a32f374cf5a --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py @@ -0,0 +1,227 @@ +"""CPython-specific touch-ups""" + +import ctypes +import importlib.util + +from . import _stdcpp_fix # noqa: F401 + +__all__ = [ + "gbl", + "load_reflection_info", + "addressof", + "bind_object", + "nullptr", + "default", + "_backend", + "_begin_capture_stderr", + "_end_capture_stderr", +] + + +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 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): + # with _stderr_capture() as err: + # FIXME: Remove the .so and add logic in libcppinterop + name = name + ".so" + result = Cpp.LoadLibrary(name, True) + if name.endswith("Dict.so"): + header = name[:-7] + ".h" + Cpp.Declare('#include "' + header + '"', False) + + if result == False: # noqa: E712 + raise RuntimeError('Could not load library "%s"' % (name)) + + return True + + +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/cppjit/python/cppjit/_pythonization.py b/bindings/pyroot/cppjit/python/cppjit/_pythonization.py new file mode 100644 index 0000000000000..a3d1e74a24dd8 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_pythonization.py @@ -0,0 +1,360 @@ +"""Pythonization API.""" + +import re + +__all__ = [ + "add_pythonization", + "remove_pythonization", + "pin_type", + "add_type_reducer", +] + + +def _set_backend(backend): + global _backend + _backend = backend + + +# user-provided, general pythonizations +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 .""" + return _backend.remove_pythonization(pythonizor, scope) + + +# prevent auto-casting (e.g. for interfaces) +def pin_type(klass): + return _backend._pin_type(klass) + + +# mapper to reduce template expression trees +def add_type_reducer(reducable, reduced): + """Reduce to type on returns from function calls.""" + return _backend._add_type_reducer(reducable, reduced) + + +# exception pythonizations +def add_exception_mapping(cpp_exception, py_exception): + _backend.UserExceptions[cpp_exception] = py_exception + + +# --- 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) + ) + + +def set_ownership_policy(match_class, match_method, 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 +# make do with the hackier version here. +def rename_attribute(match_class, orig_attribute, new_attribute, keep_orig=False): + 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) + + def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): + self.match_class = re.compile(match_class) + self.match_attr = re.compile(orig_attribute) + self.new_attr = new_attribute + self.keep_orig = keep_orig + + def __call__(self, obj, name): + if not self.match_class.match(name): + return + 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) + + 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): +# self.match_class = re.compile(match_class) +# self.match_attr = re.compile(orig_attribute) +# self.new_attr = new_attribute +# self.keep_orig = keep_orig + +# def __call__(self, obj, name): +# import sys +# if not self.match_class.match(name): +# return +# sys.stderr.write("%s %s %s %s" % ("!!!", obj, name, "\n")) +# for k in dir(obj): #obj.__dict__: +# if not self.match_attr.match(k): continue +# try: +# tmp = getattr(obj, k) +# except Exception as e: +# continue +# setattr(obj, self.new_attr, tmp) +# if not self.keep_orig: delattr(obj, k) +# return method_pythonizor(match_class, orig_attribute, new_attribute, keep_orig) + + +# Shared with PyPy: + + +def add_overload(match_class, match_method, overload): + class method_pythonizor(object): + def __init__(self, match_class, match_method, overload): + self.match_class = re.compile(match_class) + self.match_method = re.compile(match_method) + self.overload = overload + + def __call__(self, obj, name): + if not self.match_class.match(name): + return + for k in dir(obj): # .__dict__: + try: + tmp = getattr(obj, k) + except: + continue + if self.match_method.match(k): + try: + tmp.__add_overload__(overload) + except AttributeError: + pass + + return method_pythonizor(match_class, match_method, overload) + + +def compose_method(match_class, match_method, g): + class composition_pythonizor(object): + def __init__(self, match_class, match_method, g): + self.match_class = re.compile(match_class) + self.match_method = re.compile(match_method) + self.g = g + + def __call__(self, obj, name): + if not self.match_class.match(name): + return + g = self.g + for k in obj.__dict__: + if not self.match_method.match(k): + continue + try: + f = getattr(obj, k) + 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) + + +def set_method_property(match_class, match_method, prop, value): + class method_pythonizor(object): + def __init__(self, match_class, match_method, prop, value): + self.match_class = re.compile(match_class) + self.match_method = re.compile(match_method) + self.prop = prop + self.value = value + + def __call__(self, obj, name): + if not self.match_class.match(name): + return + for k in dir(obj): # .__dict__: + try: + tmp = getattr(obj, k) + 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 +): + class property_pythonizor(object): + def __init__(self, match_class, match_get, match_set, match_del, prop_name): + self.match_class = re.compile(match_class) + + self.match_get = re.compile(match_get) + match_many_getters = self.match_get.groups == 1 + + if match_set: + 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") + else: + self.match_set = None + + if match_del: + 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") + 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 self.match_many and 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" + ) + + self.prop_name = prop_name + + def make_get_del_proxy(self, getter): + class proxy(object): + def __init__(self, getter): + self.getter = getter + + def __call__(self, obj): + return getattr(obj, self.getter)() + + return proxy(getter) + + def make_set_proxy(self, setter): + class proxy(object): + def __init__(self, setter): + self.setter = setter + + def __call__(self, obj, arg): + return getattr(obj, self.setter)(arg) + + return proxy(setter) + + def __call__(self, obj, name): + if not self.match_class.match(name): + return + + names = [] + named_getters = {} + named_setters = {} + named_deleters = {} + + if not self.match_many: + fget, fset, fdel = None, None, None + + for k in dir(obj): # .__dict__: + match = self.match_get.match(k) + try: + tmp = getattr(obj, k) + except: + continue + if match and hasattr(tmp, "__call__"): + if self.match_many: + name = match.group(1) + named_getters[name] = k + else: + fget = self.make_get_del_proxy(k) + break + + if self.match_set: + for k in dir(obj): # .__dict__: + match = self.match_set.match(k) + try: + tmp = getattr(obj, k) + except: + continue + if match and hasattr(tmp, "__call__"): + if self.match_many: + name = match.group(1) + named_setters[name] = k + else: + fset = self.make_set_proxy(k) + break + + if self.match_del: + for k in dir(obj): # .__dict__: + match = self.match_del.match(k) + try: + tmp = getattr(obj, k) + except: + continue + if match and hasattr(tmp, "__call__"): + if self.match_many: + name = match.group(1) + named_deleters[name] = k + else: + fdel = self.make_get_del_proxy(k) + break + + if not self.match_many: + new_prop = property(fget, fset, fdel) + setattr(obj, self.prop_name, new_prop) + return + + names += list(named_getters.keys()) + names += list(named_setters.keys()) + 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]) + else: + fget = None + + if name in named_setters: + fset = self.make_set_proxy(named_setters[name]) + else: + fset = None + + if name in named_deleters: + fdel = self.make_get_del_proxy(named_deleters[name]) + else: + fdel = None + + new_prop = property(fget, fset, fdel) + if self.prop_name: + prop_name = self.prop_name.format(name) + else: + prop_name = 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/cppjit/python/cppjit/_stdcpp_fix.py b/bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py new file mode 100644 index 0000000000000..7b66014ea3261 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_stdcpp_fix.py @@ -0,0 +1,14 @@ +import sys + +# 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: + # TODO: check executable to see whether linking indeed didn't happen + import ctypes + + try: + 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/cppjit/python/cppjit/_typemap.py b/bindings/pyroot/cppjit/python/cppjit/_typemap.py new file mode 100644 index 0000000000000..4a182bfaba977 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/_typemap.py @@ -0,0 +1,139 @@ +"""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 = "cppjit.gbl." + scope + else: + cppname = name + 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 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# 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 + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +# --- end from six.py + + +class _BoolMeta(type): + def __call__(self, val=bool()): + if val: + return True + else: + return False + + +class _Bool(with_metaclass(_BoolMeta, object)): + pass + + +def initialize(backend): + 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) + + # char types + str_tm = _create_mapper(str) + for tp in ["char", "unsigned char", "signed char"]: + tm[tp] = str_tm + if sys.hexversion < 0x3000000: + tm["wchar_t"] = _create_mapper(unicode) # noqa: F821 + else: + tm["wchar_t"] = str_tm + + # integer types + int_tm = _create_mapper(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) # noqa: F821 + else: + 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"]: + tm[tp] = float_tm + + # void* + def voidp_init(self, 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}) 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/cppjit/python/cppjit/numba_ext.py b/bindings/pyroot/cppjit/python/cppjit/numba_ext.py new file mode 100644 index 0000000000000..0f4a556098191 --- /dev/null +++ b/bindings/pyroot/cppjit/python/cppjit/numba_ext.py @@ -0,0 +1,691 @@ +"""cppjit extensions for numba""" + +import re + +import numba +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.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 + +import cppjit +import cppjit.reflex as cpp_refl +import cppjit.types as cpp_types + + +# setuptools entry point for Numba +def _init_extension(): + pass + + +class Qualified: + 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(cppjit.sizeof("void*") * 8) + +# special case access to unboxing/boxing APIs +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, +} + + +def resolve_std_vector(val): + return re.match(r"std::vector<(.+?)>", val).group(1) + + +def resolve_const_types(val): + return re.match(r"const\s+(.+)\s*\*", val).group(1) + + +def cpp2numba(val): + if not isinstance(val, str): + # TODO: distinguish ptr/ref/byval + # TODO: Only metaclasses/proxies end up here since + # ref cases makes the RETURN_TYPE from reflex a string + return typeof_scope(val, nb_typing.typeof.Purpose.argument, Qualified.value) + 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"): + return nb_types.CPointer(cpp2numba(resolve_const_types(val))) + 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" + + +def numba2cpp(val): + if hasattr(val, "literal_type"): + val = val.literal_type + 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 + elif isinstance(val, numba.types.CPointer): + return _numba2cpp[val.dtype] + elif isinstance(val, numba.types.RawPointer): + return _numba2cpp[nb_types.voidptr] + elif isinstance(val, numba.types.Array): + return "std::vector<" + _numba2cpp[val.dtype] + ">" + elif isinstance(val, CppClassNumbaType): + return val._scope.__cpp_name__ + else: + try: + return _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): + # If the user explicitly passes an argument using numba CPointer, the regex match is used + # to detect the pass by reference since the dispatcher always returns typeref[val*] + match = re.search(r"typeref\[(.*?)\*\]", str(arg)) + if match: + literal_val = match.group(1) + arg_type = numba.typeof(eval(literal_val)) + args_cpp.append(to_ref(numba2cpp(arg_type))) + else: + args_cpp.append(numba2cpp(arg)) + return tuple(args_cpp) + + +def to_ref(type_list): + ref_list = [] + 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(), +} + + +def cpp2ir(val): + try: + return _cpp2ir[val] + except KeyError: + if val.startswith("std::vector"): + ## 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"): + return ir.PointerType(cpp2ir(resolve_const_types(val))) + type_2 = _cpp2ir[val[:-2]] + return ir.PointerType(type_2) + + +# +# C++ function pointer -> Numba +# +class CppFunctionNumbaType(nb_types.Callable): + targetdescr = nb_reg.cpu_target + requires_gil = False + + def __init__(self, func, is_method=False): + super(CppFunctionNumbaType, self).__init__("CppFunction(%s)" % str(func)) + + self.sig = None + self._func = func + self._is_method = is_method + + self._signatures = list() + self._impl_keys = dict() + self._arg_set_matched = tuple() + self.ret_type = None + + def is_precise(self): + return True # by definition + + def get_call_type(self, context, args, kwds): + try: + return self._impl_keys[args].sig + except KeyError: + pass + + ol = CppFunctionNumbaType( + self._func.__overload__(numba_arg_convertor(args)), self._is_method + ) + + thistype = None + if self._is_method: + thistype = nb_types.voidptr + + 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 + ) + + 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 + ) + + 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, + ): + ptrty = context.get_function_pointer_type(ty) + ptrval = context.add_dynamic_addr( + builder, ty.get_pointer(pyval), info=str(pyval) + ) + fptr = builder.bitcast(ptrval, ptrty) + return context.call_function_pointer(builder, fptr, args) + + return ol.sig + + def get_call_signatures(self): + return list(self._signatures), False + + def get_impl_key(self, sig): + return self._impl_keys[sig.args] + + # TODO: Remove the redundancy of __overload__ matching and use this function + # to only obtain the address given the matched overload + def get_pointer(self, func): + if func is None: + func = self._func + + ol = func.__overload__(numba_arg_convertor(self.sig.args)) + + address = cppjit.addressof(ol) + if not address: + raise RuntimeError("unresolved address for %s" % str(ol)) + return address + + @property + def key(self): + return self._func + + +@nb_ext.typeof_impl.register(cpp_types.Function) +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__"): + 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* + 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 + # actual overload is handled dynamically. + return + + +# +# C++ method / data member -> Numba +# +class CppDataMemberInfo(object): + __slots__ = ["f_name", "f_offset", "f_nbtype", "f_irtype"] + + def __init__(self, name, offset, cpptype): + self.f_name = name + self.f_offset = offset + self.f_nbtype = cpp2numba(cpptype) + self.f_irtype = cpp2ir(cpptype) + + +# +# C++ class -> Numba +# +class CppClassNumbaType(CppFunctionNumbaType): + def __init__(self, scope, qualifier): + addr = None + cppinstance_val = None + if qualifier == Qualified.instance: + 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._qualifier = qualifier + self._cppinstanceval = cppinstance_val + self._addr = addr + + def get_scope(self): + return self._scope + + def get_qualifier(self): + return self._qualifier + + def get_call_type(self, context, args, kwds): + sig = super(CppClassNumbaType, self).get_call_type(context, args, kwds) + self.sig = sig + return sig + + def is_precise(self): + return True + + @property + def key(self): + return (self._scope, self._qualifier) + + +@nb_tmpl.infer_getattr +class CppClassFieldResolver(nb_tmpl.AttributeTemplate): + key = CppClassNumbaType + + def generic_resolve(self, typ, attr): + ft = typ.__dict__.get(attr, None) + if ft is not None: + return ft + + try: + f = getattr(typ._scope, attr) + if isinstance(f, cpp_types.Function): + ft = CppFunctionNumbaType(f, is_method=True) + except AttributeError: + pass + + try: + f = typ._scope.__dict__[attr] + if isinstance(f, cpp_types.DataMember): + ct = f.__cpp_reflex__(cpp_refl.TYPE) + ft = cpp2numba(ct) + except AttributeError: + pass + + if ft is not None: + typ.__dict__[attr] = ft + + 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 + ): + dm = typ._scope.__dict__[attr] + ct = dm.__cpp_reflex__(cpp_refl.TYPE) + offset = dm.__cpp_reflex__(cpp_refl.OFFSET) + + q = typ.get_qualifier() + if q == Qualified.default: + llval = builder.bitcast(val, ir_byteptr) + pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, offset)]) + pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) + return builder.load(pf) + + elif q == Qualified.value: + model = nb_dm.default_manager.lookup(typ) + return model.get(builder, val, attr) + + else: + assert not "unknown qualified type" + + # TODO: easier with inttoptr and ptrtoint (cgutils.pointer_add)? + llval = builder.bitcast(val, ir_byteptr) + pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, offset)]) + pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) + return builder.load(pf) + + # assume this is a method + q = typ.get_qualifier() + if q == Qualified.default: + return builder.bitcast(val, ir_voidptr) + + elif q == Qualified.value: + return None + + assert not "unknown qualified type" + return None + + +class ImplAggregateValueModel(nb_dm.models.StructModel): + def get(self, builder, val, pos): + """Get a field at the given position/field name""" + + 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. + dmi = self._data_members[pos] + + stack = nb_cgu.alloca_once(builder, self.get_data_type()) + builder.store(val, stack) + + llval = builder.bitcast(stack, ir_byteptr) + pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, dmi.f_offset)]) + pf = builder.bitcast(pfc, ir.PointerType(dmi.f_irtype)) + + 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. + def get_value_type(self): + return self.get_data_type() + + # 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. + if self._data_type is None: + 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. + 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): + is_instance = False + cppinstance_val = None + if q == Qualified.instance: + cppinstance_val = val + val = type(val) + q = Qualified.default + is_instance = True + + global scope_numbatypes + + try: + return scope_numbatypes[q][val] + except KeyError: + pass + + if val.__cpp_reflex__(cpp_refl.IS_NAMESPACE): + cnt = nb_types.Module(val) + scope_numbatypes[Qualified.default][val] = cnt + return cnt + + class ImplClassType(CppClassNumbaType): + pass + + if is_instance: + cnt = ImplClassType(cppinstance_val, Qualified.instance) + else: + cnt = ImplClassType(val, q) + + scope_numbatypes[q][val] = cnt + + # 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), + ) + ) + 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 ... + nb_reg.cpu_target.typing_context.refresh() + + # 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 + 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 + def traverse(self, builder): + return [] + + def traverse_models(self): + return [] + + def traverse_types(self): + return [self._fe_type] # from StructModel + + # 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. + 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 + + # 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. + def get_argument_type(self): + return self.get_value_type() + + def as_argument(self, builder, value): + return value + + def from_argument(self, builder, value): + return value + + # return: representation used for return argument. + # TODO ... + + # access to public data members + def get(self, builder, val, pos): + """Get a field at the given position/field name""" + + if isinstance(pos, str): + pos = self.get_field_position(pos) + + dmi = self._data_members[pos] + + llval = builder.bitcast(val, ir_byteptr) + pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, dmi.f_offset)]) + pf = builder.bitcast(pfc, ir.PointerType(dmi.f_irtype)) + + return builder.load(pf) + + 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=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 + members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] + nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) + + ImplClassModel.__init__ = init + + else: + assert not "unknown qualified type" + + # Python proxy unwrapping for arguments into the Numba trace + @nb_ext.unbox(ImplClassType) + def unbox_instance(typ, obj, c): + global cppjit_as_voidptr + + ptrty = ir.PointerType(ir.FunctionType(ir_voidptr, [ir_voidptr])) + 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]) + model = nb_dm.default_manager.lookup(typ) + pobj = c.builder.bitcast(vptr, model.get_argument_type()) + + return nb_ext.NativeValue(pobj, is_error=None, cleanup=None) + + 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 + @nb_ext.box(ImplClassType) + def box_instance(typ, val, c): + assert not "requires object model and passing of intact object, not memberwise copy" + + 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 # 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 # 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_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: + c.pyapi.decref(i) + + return box_res + + return cnt + + +# +# C++ instance -> Numba +# +@nb_ext.typeof_impl.register(cpp_types.Instance) +def typeof_instance(val, c): + global scope_numbatypes + + try: + return scope_numbatypes[Qualified.default][type(val)] + except KeyError: + pass + # Pass the val itself to obtain Cling address of the CPPInstance for reference to C++ objects + return typeof_scope(val, c, Qualified.instance) 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/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..045666929214c --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx @@ -0,0 +1,1133 @@ +// 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) { + 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) { + const std::string& pv = + *(std::string*)((CPPInstance*)pretty)->GetObject(); + if (!pv.empty() && pv.find("@0x") == std::string::npos) + 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..2278733296cf8 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPMethod.h @@ -0,0 +1,126 @@ +#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; + +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..aecc711816b55 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CPPOverload.cxx @@ -0,0 +1,1161 @@ +// 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__"); +} + +//---------------------------------------------------------------------------- +static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*) { + // Get '_mempolicy' enum, which determines ownership of call arguments. + if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics) + return PyInt_FromLong(CallContext::kUseHeuristics); + + if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict) + return PyInt_FromLong(CallContext::kUseStrict); + + return PyInt_FromLong(-1); +} + +//---------------------------------------------------------------------------- +static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*) { + // Set '_mempolicy' enum, which determines ownership of call arguments. + long mempolicy = PyLong_AsLong(value); + if (mempolicy == CallContext::kUseHeuristics) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict; + } else if (mempolicy == CallContext::kUseStrict) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics; + } else { + PyErr_SetString(PyExc_ValueError, + "expected kMemoryStrict or kMemoryHeuristics as value for " + "__mempolicy__"); + return -1; + } + + return 0; +} + +//---------------------------------------------------------------------------- +#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*)"For argument ownership rules: like global, either heuristic or " + "strict", + 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; + const auto mempolicy = + (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict)); + ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy; + 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 + if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && + name.find("Clone") != std::string::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..f8d077e04cd28 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CallContext.cxx @@ -0,0 +1,61 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CallContext.h" + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { + +CallContext::ECallFlags CallContext::sMemoryPolicy = CallContext::kUseStrict; +// this is just a data holder for linking; actual value is set in +// cpyrtModule.cxx +CallContext::ECallFlags CallContext::sSignalPolicy = CallContext::kNone; + +} // namespace cppjit::cpyrt + +//----------------------------------------------------------------------------- +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::SetMemoryPolicy(ECallFlags e) { + // Set the global memory policy, which affects object ownership when objects + // are passed as function arguments. + if (kUseHeuristics == e || e == kUseStrict) { + sMemoryPolicy = e; + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CallContext::SetGlobalSignalPolicy(bool setProtected) { + // Set the global signal policy, which determines whether a jmp address + // should be saved to return to after a C++ segfault. + bool old = sSignalPolicy == kProtected; + sSignalPolicy = setProtected ? kProtected : kNone; + 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..b52915b6da127 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/CallContext.h @@ -0,0 +1,166 @@ +#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 + kUseStrict = 0x040000, // if method applies strict memory policy + }; + + // memory handling + static ECallFlags sMemoryPolicy; + static bool SetMemoryPolicy(ECallFlags e); + + void AddTemporary(PyObject* pyobj); + void Cleanup(); + + // signal safety + static ECallFlags sSignalPolicy; + static bool SetGlobalSignalPolicy(bool setProtected); + + 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(CallContext* ctxt) { + if (ctxt && (ctxt->fFlags & CallContext::kUseStrict)) + return true; + if (ctxt && (ctxt->fFlags & CallContext::kUseHeuristics)) + return false; + + return CallContext::sMemoryPolicy == CallContext::kUseStrict; +} + +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..df2a87957ec7d --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx @@ -0,0 +1,4501 @@ +// 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 +#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; + +// regular expression for matching function pointer +static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); +} // 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; + + // 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(ctxt)) + 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 && CallContext::sMemoryPolicy != CallContext::kUseStrict) + 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(ctxt)) + 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() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + ((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(ctxt)) + 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() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + 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 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); + + // FIXME: avoid string comparisons and parsing + std::string true_signature = signature; + + if (true_signature.rfind("(void)") != std::string::npos) + true_signature = true_signature.substr(0, true_signature.size() - 6) + "()"; + + 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) { + PyObject* sig = m->GetSignature(false); + bool found = true_signature == cpyrt_PyText_AsString(sig); + Py_DECREF(sig); + if (found) { + void* fptr = (void*)m->GetFunctionAddress(); + if (fptr) + return fptr; + break; // fall-through, with calling through Python + } + } + // FIXME: maybe we should try BestOverloadFunctionMatch before failing + // FIXME: Should we fall-through, with calling through Python + 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; + interop::TCppMethod_t cppmeth = + interop::GetMethodTemplate(scope, fullname, true_signature); + if (cppmeth) { + void* fptr = (void*)interop::GetFunctionAddress(cppmeth, false); + if (fptr) + return fptr; + } + // FIXME: Should we fall-through, with calling through Python + 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; + + // re-use existing wrapper if possible + auto key = rettype + true_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(true_signature); + 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 " << rettype << " " + << 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(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 (!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, fRetType, fSignature, 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) + return Utility::FuncPtr2StdFunction(fRetType, fSignature, *(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, fRetType, fSignature, 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(ctxt)) + ((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 (!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 + if (!isConst && 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"}; + } + + // 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, 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 { + 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)); + } + } + 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 == "*&") { + 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, + 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 (resolvedTypeStr.find("(*)") != std::string::npos || + (resolvedTypeStr.find("::*)") != std::string::npos)) { + // this is a function function pointer + // TODO: find better way of finding the type + auto pos1 = resolvedTypeStr.find('('); + auto pos2 = resolvedTypeStr.find("*)"); + auto pos3 = resolvedTypeStr.rfind(')'); + std::string return_type = resolvedTypeStr.substr(0, pos1); + result = new FunctionPointerConverter( + return_type.erase(return_type.find_last_not_of(" ") + 1), + resolvedTypeStr.substr(pos2 + 2, pos3 - pos2 - 1)); + } 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..b1b655fd26a87 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/DeclareConverters.h @@ -0,0 +1,542 @@ +#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(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; } + std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; + +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; + std::string GetFailureMsg() override { return "[StdFunctionConverter]"; }; + +protected: + 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..1fafb4867d7f1 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Dispatcher.cxx @@ -0,0 +1,567 @@ +// 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); + PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); + 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..5465a1e9e4da0 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Executors.cxx @@ -0,0 +1,1471 @@ +// 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 + static interop::TCppScope_t sSTLStringScope = + interop::GetFullScope("std::string"); + + std::string* result = (std::string*)GILCallR(method, self, ctxt); + if (!fAssignable) { + std::string* rescp = new std::string{*result}; + return BindCppObjectNoCast((void*)rescp, sSTLStringScope, + CPPInstance::kIsOwner); + } + + 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) + result = new std::string{}; + else if (PyErr_Occurred()) { + delete result; + return nullptr; + } + + return BindCppObjectNoCast((void*)result, sSTLStringScope, + CPPInstance::kIsOwner); +} + +//---------------------------------------------------------------------------- +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..4503502b8aedf --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/ProxyWrappers.cxx @@ -0,0 +1,1060 @@ +// 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 + if (!interop::IsPublicMethod(method)) + 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..4b79ab003f5f8 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyCallable.h @@ -0,0 +1,55 @@ +#ifndef CPYRT_PYCALLABLE_H +#define CPYRT_PYCALLABLE_H + +#include + +// Bindings +#include "CallContext.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; } + +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/cppjit/src/cpyrt/PyException.h b/bindings/pyroot/cppjit/src/cpyrt/PyException.h new file mode 100644 index 0000000000000..e18e93ac499ee --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyException.h @@ -0,0 +1,55 @@ +#ifndef CPYRT_PYEXCEPTION_H +#define CPYRT_PYEXCEPTION_H + +////////////////////////////////////////////////////////////////////////////// +// // +// PyException // +// // +// 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. // +// // +// The situation is: // +// - We're calling C++ code from python. // +// - 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 cpyrt calls C++. // +// To do that we throw a TPyException. // +// We can then catch this exception when we do the C++ call. // +// // +// Note that we don't need to save any state in the exception -- it's // +// already in the python error info variables. // +// (??? Actually, if the program is multithreaded, this is dangerous // +// if the code has released and reacquired the lock along the call chain. // +// Punt on this for now, though.) // +// // +////////////////////////////////////////////////////////////////////////////// + +// Standard +#include +#include + +// Bindings +#include "cpyrt/CommonDefs.h" + +namespace cppjit::cpyrt { + +class CPYRT_CLASS_EXTERN PyException : public std::exception { +public: + PyException(); + ~PyException() 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; + +private: + std::string fMsg; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYEXCEPTION_H diff --git a/bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc b/bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc new file mode 100644 index 0000000000000..88f276215b3fd --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/PyObjectDir27.inc @@ -0,0 +1,177 @@ +/* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved + +Copied from Objects/object.c under the PSF License Version 2. + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are retained +in Python alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + +*/ + +/* Helper for PyObject_Dir. + Merge the __dict__ of aclass into dict, and recursively also all + the __dict__s of aclass's base classes. The order of merging isn't + defined, as it's expected that only the final set of dict keys is + interesting. + Return 0 on success, -1 on error. +*/ + +static int merge_class_dict(PyObject* dict, PyObject* aclass) +{ + PyObject *classdict; + PyObject *bases; + + assert(PyDict_Check(dict)); + assert(aclass); + + /* Merge in the type's dict (if any). */ + classdict = PyObject_GetAttrString(aclass, "__dict__"); + if (classdict == NULL) + PyErr_Clear(); + else { + int status = PyDict_Update(dict, classdict); + Py_DECREF(classdict); + if (status < 0) + return -1; + } + + /* Recursively merge in the base types' (if any) dicts. */ + bases = PyObject_GetAttrString(aclass, "__bases__"); + if (bases == NULL) + PyErr_Clear(); + else { + /* We have no guarantee that bases is a real tuple */ + Py_ssize_t i, n; + n = PySequence_Size(bases); /* This better be right */ + if (n < 0) + PyErr_Clear(); + else { + for (i = 0; i < n; i++) { + int status; + PyObject *base = PySequence_GetItem(bases, i); + if (base == NULL) { + Py_DECREF(bases); + return -1; + } + status = merge_class_dict(dict, base); + Py_DECREF(base); + if (status < 0) { + Py_DECREF(bases); + return -1; + } + } + } + Py_DECREF(bases); + } + return 0; +} + +/* 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. +*/ +static PyObject * +_specialized_dir_type(PyObject *obj) +{ + PyObject *result = NULL; + PyObject *dict = PyDict_New(); + + if (dict != NULL && merge_class_dict(dict, obj) == 0) + result = PyDict_Keys(dict); + + Py_XDECREF(dict); + return result; +} + +static PyObject* _generic_dir(PyObject *obj) +{ + if (PyType_Check(obj) || PyClass_Check(obj)) + return _specialized_dir_type(obj); + + PyObject *result = NULL; + PyObject *dict = NULL; + PyObject *itsclass = NULL; + + /* Get __dict__ (which may or may not be a real dict...) */ + dict = PyObject_GetAttrString(obj, "__dict__"); + if (dict == NULL) { + PyErr_Clear(); + dict = PyDict_New(); + } + else if (!PyDict_Check(dict)) { + Py_DECREF(dict); + dict = PyDict_New(); + } + else { + /* Copy __dict__ to avoid mutating it. */ + PyObject *temp = PyDict_Copy(dict); + Py_DECREF(dict); + dict = temp; + } + + if (dict == NULL) + goto error; + + /* Merge in attrs reachable from its class. */ + itsclass = PyObject_GetAttrString(obj, "__class__"); + if (itsclass == NULL) + /* XXX(tomer): Perhaps fall back to obj->ob_type if no + __class__ exists? */ + PyErr_Clear(); + else { + if (merge_class_dict(dict, itsclass) != 0) + goto error; + } + + result = PyDict_Keys(dict); + /* fall through */ +error: + Py_XDECREF(itsclass); + Py_XDECREF(dict); + return result; +} 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..d94cde52a3be1 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Pythonize.cxx @@ -0,0 +1,2218 @@ +// 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 + +//- 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; +} + +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; +} + +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 || cpyrt_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 || cpyrt_PyText_Check(res)) + return res; + PyObject* str_res = PyObject_Str(res); + Py_DECREF(res); + return str_res; +} + +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". + if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBecpyrt=*/true)) { + 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& 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); + } + + 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); + } + + if (interop::IsAggregate(((CPPClass*)pyclass)->fCppType) && + name.compare(0, 5, "std::", 5) != 0) { + // 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, "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::__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::__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); + } + + else if (name == "std::basic_string,std::" + "allocator >" || + 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..e5f7970758dc2 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/TemplateProxy.cxx @@ -0,0 +1,987 @@ +// 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 = ""; + + // 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); + + 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) { // 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..616ef6a83a6b8 --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx @@ -0,0 +1,1587 @@ +// 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); + if (!pyfunc) + if ((scope = interop::GetScope(TypeManip::extract_namespace(lcname)))) + pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); + + if (!pyfunc && + scope != interop::GetGlobalScope()) // search in global scope anyway + pyfunc = + BuildOperator(lcname, rcname, op, interop::GetGlobalScope(), 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*)&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*)&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..dd54099f3a3af --- /dev/null +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx @@ -0,0 +1,1021 @@ +// 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; +} + +//---------------------------------------------------------------------------- +static PyObject* SetMemoryPolicy(PyObject*, PyObject* args) { + // Set the global memory policy, which affects object ownership when objects + // are passed as function arguments. + PyObject* policy = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!"), &PyInt_Type, &policy)) + return nullptr; + + long old = (long)CallContext::sMemoryPolicy; + + long l = PyInt_AS_LONG(policy); + if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) { + return PyInt_FromLong(old); + } + + PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args) { + // Set the global signal policy, which determines whether a jmp address + // should be saved to return to after a C++ segfault. + PyObject* setProtected = 0; + if (!PyArg_ParseTuple(args, const_cast("O"), &setProtected)) + return nullptr; + + if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +//---------------------------------------------------------------------------- +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 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*)"SetMemoryPolicy", (PyCFunction)SetMemoryPolicy, METH_VARARGS, + (char*)"Determines object ownership model."}, + {(char*)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, + METH_VARARGS, + (char*)"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."}, + {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 { + +//---------------------------------------------------------------------------- +extern "C" PyObject* PyInit_libcppjit() { + // 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); + + // policy labels + PyModule_AddObject(gThisModule, (char*)"kMemoryHeuristics", + PyInt_FromLong((int)CallContext::kUseHeuristics)); + PyModule_AddObject(gThisModule, (char*)"kMemoryStrict", + PyInt_FromLong((int)CallContext::kUseStrict)); + + // 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/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..883f0a38c6c88 --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/cppjit_interop.h @@ -0,0 +1,396 @@ +#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); +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 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..735e207c39567 --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx @@ -0,0 +1,1776 @@ +#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" + +typedef cppjit::cpyrt::Parameter Parameter; + +static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } + +#ifndef _WIN32 +#include +#endif + +// Standard +#include // for std::count, std::remove +#include +#include +#include // for getenv +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::recursive_mutex 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 +}; + +// One set of coordinates, two anchors: prefer CppInterOp next to our own +// load location so wheels relocate; fall back to the build-time install +// prefix. An absolute coordinate (an external CppInterOp) replaces the +// anchor in the join ([fs.path.append]). +static InterOpPaths cppinterop_paths() { + std::filesystem::path anchor = CPPINTEROP_INSTALL_PREFIX; +#ifndef _WIN32 + Dl_info info; + if (dladdr((void*)&cppinterop_paths, &info) && info.dli_fname) { + const std::filesystem::path here = + std::filesystem::path(info.dli_fname).parent_path(); + std::error_code ec; + if (std::filesystem::exists(here / CPPINTEROP_LIBRARY, ec)) + anchor = here; + } +#endif + InterOpPaths Paths; + Paths.Library = (anchor / CPPINTEROP_LIBRARY).string(); + // The include coordinate may carry several ':'-separated directories (an + // external CppInterOp build tree splits source and generated headers). + std::istringstream includeSpec{CPPINTEROP_INCLUDE_DIR}; + for (std::string dir; std::getline(includeSpec, dir, ':');) + if (!dir.empty()) + Paths.IncludeDirs.push_back((anchor / dir).string()); + // A bundled install ships the build clang's builtin headers (see the + // CMake install rule); an empty coordinate or a missing directory falls + // back to resource-dir detection. + const std::string clangSpec = CPPJIT_CLANG_INCLUDE_DIR; + if (!clangSpec.empty()) { + const std::filesystem::path bundled = anchor / clangSpec; + std::error_code ec; + if (std::filesystem::exists(bundled / "include", ec)) + Paths.ClangIncludeDir = bundled.string(); + } + return Paths; +} + +// The one place libclangCppInterOp is dlopen'd. +static bool loadDispatchAPI(const InterOpPaths& Paths) { + if (!Cpp::LoadDispatchAPI(Paths.Library.c_str())) { + 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); +} + +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; +} + +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; + + 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; + } + return true; + } + + 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 + + // The trampoline declares its variable in the global scope, so a name + // written relative to a parent (e.g. "vector" looked up in std) + // won't resolve. Try the name as given, then qualified by the parent. + std::vector candidates = {resolved_name}; + if (parent && parent != Cpp::GetGlobalScope() && + (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. + if (!Cpp::Declare(("__cppjit_interop_AppendTypesSlow<" + candidate + + "> __attribute__((nodebug)) " + var + ";\n") + .c_str(), + /*silent=*/true)) { + TCppType_t varN = + Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); + TCppScope_t instance_class = Cpp::GetScopeFromType(varN); + 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; + + 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. + + 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); + return Cpp::GetCanonicalType(lookup_ty); + } + 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::lock_guard Lock(InterOpMutex); + 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; + + // 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; + InterOpMutex.unlock(); // unlock to allow AppendTypesSlow + bool added_new_type = + !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope); + std::lock_guard Lock(InterOpMutex); + 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); + return Cpp::GetNamed(name, parent_scope); +} + +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 (!Cpp::IsClassPolymorphic(klass)) + return klass; + + const std::type_info* typ = &typeid(*(AutoCastRTTI*)obj.data); + + std::string mangled_name = typ->name(); + std::string demangled_name = Cpp::Demangle(mangled_name); + + if (TCppScope_t scope = interop::GetScope(demangled_name)) + 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); +} + +interop::TCppObject_t interop::Construct(TCppScope_t scope, + void* arena /*=nullptr*/) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + return Cpp::Construct(scope, arena, /*count=*/1); +} + +void interop::Destruct(TCppScope_t scope, TCppObject_t instance) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + Cpp::Destruct(instance, scope, true, /*count=*/0); +} + +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) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); +} + +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); +} + +// class reflection information ---------------------------------------------- +std::string interop::GetFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)); +} + +std::string interop::GetScopedFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return 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); + return Cpp::IsSubclass(derived, base); +} + +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; + + 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); + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); + + if (offset == -1) // Cling error, treat silently + return rerror ? (ptrdiff_t)offset : 0; + + 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 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); + return Cpp::GetTypeAsString( + Cpp::GetCanonicalType(Cpp::GetFunctionReturnType(method))); +} + +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 << 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 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" || n1 == "std::basic_string") { + if (n2 == "std::basic_string") + return "std::basic_string&"; // match like for like + return "std::basic_string&"; // probably best bet + } else if (n1 == "std::basic_string") { + 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/cppjit/src/interop/precommondefs.h b/bindings/pyroot/cppjit/src/interop/precommondefs.h new file mode 100644 index 0000000000000..3cb57df58e87c --- /dev/null +++ b/bindings/pyroot/cppjit/src/interop/precommondefs.h @@ -0,0 +1,74 @@ +/***** Start of precommondefs.h *****/ + +/* This is extracted from pyconfig.h from CPython. It sets the macros + that affect the features we get from system include files. + It must not #include anything. */ + +#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. */ +#define _FILE_OFFSET_BITS 64 +/* Define on Linux to activate all library features */ +#define _GNU_SOURCE 1 +/* This must be defined on some systems to enable large file support. */ +#define _LARGEFILE_SOURCE 1 +/* Define on NetBSD to activate all library features */ +#define _NETBSD_SOURCE 1 +/* Define to activate features from IEEE Stds 1003.1-2001 */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif +/* Define on FreeBSD to activate all library features */ +#define __BSD_VISIBLE 1 +#define __XSI_VISIBLE 700 +/* Windows: winsock/winsock2 mess */ +#define WIN32_LEAN_AND_MEAN +#ifdef _WIN64 +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 +#endif + +#if !defined(RPY_ASSERT) && !defined(RPY_LL_ASSERT) && !defined(NDEBUG) +#define NDEBUG +#endif + +/* All functions and global variables declared anywhere should use + one of the following attributes: + + RPY_EXPORTED: the symbol is exported out of libpypy-c.so. + + RPY_EXTERN: the symbol is not exported out of libpypy-c.so, but + otherwise works like 'extern' by being available to + other C sources. + + static: as usual, this means the symbol is local to this C file. + + Don't use _RPY_HIDDEN directly. For tests involving building a custom + .so, translator/tool/cbuild.py overrides RPY_EXTERN so that it becomes + equal to RPY_EXPORTED. + + Any function or global variable declared with no attribute at all is + a bug; please report or fix it. +*/ +#ifdef __GNUC__ +#define RPY_EXPORTED extern __attribute__((visibility("default"))) +#define _RPY_HIDDEN __attribute__((visibility("hidden"))) +#else +#define RPY_EXPORTED extern __declspec(dllexport) +#define _RPY_HIDDEN /* nothing */ +#endif +#ifndef RPY_EXTERN +#define RPY_EXTERN extern +#endif + +#endif /* __PYPY_PRECOMMONDEFS_H */ + +/***** End of precommondefs.h *****/ 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/cppjit/test/cpp/advancedcpp.cxx b/bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx new file mode 100644 index 0000000000000..b15eabaeb3d94 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/advancedcpp.cxx @@ -0,0 +1,173 @@ +#include "advancedcpp.h" + +#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; \ + } +IMPLEMENT_DEFAULTERS(short, short) +IMPLEMENT_DEFAULTERS(unsigned short, ushort) +IMPLEMENT_DEFAULTERS(int, int) +IMPLEMENT_DEFAULTERS(unsigned, uint) +IMPLEMENT_DEFAULTERS(long, long) +IMPLEMENT_DEFAULTERS(unsigned long, ulong) +IMPLEMENT_DEFAULTERS(long long, llong) +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"; +} + +// 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; } + +// 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::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 T3; +template class T3, T2>>; +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; } +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 +} + +// a couple of globals for access testing +double my_global_double = 12.; +double my_global_array[500]; +static double sd = 1234.; +double* my_global_ptr = &sd; +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_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); +} + +void* new_overloader::operator new(std::size_t, void* p) throw() { + // 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); +} + +// overload order testing +int overload_one_way::gime() const { return 1; } +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_exception() { + throw std::runtime_error("C++ function failed"); +} + +// operator to __str__ mapping +std::ostream& Cpp2PyPrinting::Printable1::operator<<(std::ostream& os) { + os << "Printable1::operator<<"; + return os; +} + +std::ostream& Cpp2PyPrinting::operator<<(std::ostream& os, const Printable2&) { + os << "Cpp2PyPrinting::operator<<"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&) { + os << "::operator<<(3)"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Printable4&) { + os << "::operator<<(4)"; + return os; +} + +Printable6& Printable6::operator<<(int) { return *this; } + +std::ostream& operator<<(std::ostream& os, const Printable6& y) { + return os << "Printable6"; +} + +// for using directives testing +int UsedSpace1::foo1() { return 13; } + +int UsedSpace2::bar() { return 42; } + +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/cppjit/test/cpp/conversions.h b/bindings/pyroot/cppjit/test/cpp/conversions.h new file mode 100644 index 0000000000000..c0f8f7950e383 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/conversions.h @@ -0,0 +1,27 @@ +#include + +namespace CNS { + +//=========================================================================== +double sumit(const std::vector&); +double sumit(const std::vector&, const std::vector&); + +class Counter { +public: + Counter(); + Counter(const Counter&); + Counter& operator=(const Counter&); + ~Counter(); + + static int s_count; +}; + +double myhowmany(const std::vector&); +double myhowmany(const std::vector&, const std::vector&); + +int sumints(const std::vector&); +int sumints(const std::vector&, const std::vector&); + +double notallowed(std::vector&); + +} // namespace CNS 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/cppjit/test/cpp/overloads.cxx b/bindings/pyroot/cppjit/test/cpp/overloads.cxx new file mode 100644 index 0000000000000..dd89b3fe2cb58 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/overloads.cxx @@ -0,0 +1,77 @@ +#include "overloads.h" + +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; +} + +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(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; } + +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(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; } + +class bb_ol {}; +bb_ol* get_bb_ol() { return new bb_ol{}; } +class dd_ol {}; +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 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::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"; } + +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); } + +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/cppjit/test/cpp/std_streams.h b/bindings/pyroot/cppjit/test/cpp/std_streams.h new file mode 100644 index 0000000000000..db5cac8a3d3f7 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/std_streams.h @@ -0,0 +1,15 @@ +#ifndef STD_STREAMS_H +#define STD_STREAMS_H 1 + +#ifndef __CLING__ +#include +#endif +#include + +#ifndef __CLING__ +#ifndef _WIN32 +extern template class std::basic_ios>; +#endif +#endif + +#endif // STD_STREAMS_H 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/cppjit/test/cpp/templates.cxx b/bindings/pyroot/cppjit/test/cpp/templates.cxx new file mode 100644 index 0000000000000..b748e09349114 --- /dev/null +++ b/bindings/pyroot/cppjit/test/cpp/templates.cxx @@ -0,0 +1,50 @@ +#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_double_size() { return (long)sizeof(double); } +long MyTemplatedMethodClass::get_self_size() { + return (long)sizeof(MyTemplatedMethodClass); +} + +// variadic templates +#ifdef WIN32 +__declspec(dllexport) +#endif +std::string some_variadic::gTypeName = ""; + +// template with empty body +namespace T_WithEmptyBody { + +#ifdef WIN32 +__declspec(dllexport) +#endif +std::string side_effect = "not set"; + +template void some_empty() { side_effect = "side effect"; } + +template void some_empty(); + +} // 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 +// the compiled instantation or from the interpreter. + +namespace FailedTypeDeducer { + +template class A { +public: + T result() { return T{42}; } +}; + +template class A; + +template class B; + +} // namespace FailedTypeDeducer 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..371a0d456ddf6 --- /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 = str(currpath.join("cpp/example01Dict")) + + +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..39f6ab70a7d83 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_advancedcpp.py @@ -0,0 +1,1032 @@ +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 = str(currpath.join("cpp/advancedcppDict")) + + +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(str(currpath.join("cpp/advancedcpp2Dict"))) + + 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) + + @mark.xfail(reason="templated free function returns a string proxy, not str") + 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", + ) + 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 + + @mark.xfail(reason="using-declared overloads expose the base class signature") + 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 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..2618e2a9fbed8 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_basic_api.py @@ -0,0 +1,93 @@ +import os +import shutil +import tempfile + +import py +from pytest import raises +from support import needs_dictionary, setup_make, soext + +# reuse the example01 +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/example01Dict")) + + +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="Could not 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) + + 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/cppjit/test/test_concurrent.py b/bindings/pyroot/cppjit/test/test_concurrent.py new file mode 100644 index 0000000000000..026fdcc0abbfa --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_concurrent.py @@ -0,0 +1,333 @@ +from pytest import mark, skip +from support import IS_LINUX_ARM, IS_MAC_ARM + + +class TestCONCURRENT: + def setup_class(cls): + import cppjit + + cls.data = [3.1415, 2.7183, 1.4142, 1.3807, -9.2848] + + cppjit.cppdef("""\ + namespace Workers { + double calc(double d) { return d*42.; } + }""") + + cppjit.gbl.Workers.calc.__release_gil__ = True + + def test01_simple_threads(self): + """Run basic Python threads""" + + import threading + + import cppjit + + cppjit.gbl.Workers.calc.__release_gil__ = True + + threads = [] + for d in self.data: + threads.append(threading.Thread(target=cppjit.gbl.Workers.calc, args=(d,))) + + for t in threads: + t.start() + + for t in threads: + t.join() + + def test02_futures(self): + """Run with Python futures""" + + import cppjit + + try: + import concurrent.futures + except ImportError: + skip("module concurrent is not installed") + + 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 + + def test03_timeout(self): + """Time-out with threads""" + + return + import threading + import time + + import cppjit + + cppjit.cppdef("""\ + namespace test12_timeout { + bool _islive = false; volatile bool* islive = &_islive; + bool _stopit = false; volatile bool* stopit = &_stopit; + }""") + + cppjit.gbl.cling.runtime.gCling.process.__release_gil__ = True + cmd = r"""\ + *test12_timeout::islive = true; + while (!*test12_timeout::stopit); + """ + + 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 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. + + if t.is_alive(): # was timed-out + cppjit.gbl.test12_timeout.stopit[0] = True + + 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 cppjit + + cppjit.include("cpyrt/PyException.h") + + cppjit.cppdef(""" + #include + namespace thread_test { + + struct consumer { + virtual ~consumer() {} + virtual void process(int) = 0; + }; + + struct worker { + worker(consumer* c) : cons(c) { } + ~worker() { wait(); } + + void start() { + t = std::thread([this] { + int counter = 0; + while (counter++ < 10) + try { + cons->process(counter); + } catch (cppjit::cpyrt::PyException& e) { + err_msg = e.what(); + return; + } + }); + } + + void wait() { + if (t.joinable()) + t.join(); + } + + std::thread t; + consumer* cons = nullptr; + std::string err_msg; + }; }""") + + 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 + + c = C() + assert c.count == 0 + + w = worker(c) + w.start() + w.wait() + + assert c.count == 10 + + class C(consumer): + count = 0 + + def process(self, c): + raise RuntimeError("all wrong") + + c = C() + + w = worker(c) + w.start() + w.wait() + + assert "RuntimeError" 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 cppjit + + cppjit.cppdef("""\ + #include + namespace FloatDim2 { + + struct Buffer { + Buffer() = default; + + void setData(float** newData) { + data = newData; + } + + void setSample(int newChannel, int newSample, float value) { + data[newChannel][newSample] = value; + } + + float** data = nullptr; + }; + + struct Processor { + virtual ~Processor() = default; + virtual void process(float** data, int channels, int samples) = 0; + }; + + void callback(Processor& p) { + std::thread t([&p] { + int channels = 2; + int samples = 32; + + float** data = new float*[channels]; + for (int i = 0; i < channels; ++i) + data[i] = new float[samples]; + + p.process(data, channels, samples); + + for (int i = 0; i < channels; ++i) + delete[] data[i]; + + delete[] data; + }); + + t.join(); + } }""") + + cppjit.gbl.FloatDim2.callback.__release_gil__ = True + + class Processor(cppjit.gbl.FloatDim2.Processor): + buffer = cppjit.gbl.FloatDim2.Buffer() + + def process(self, data, channels, samples): + self.buffer.setData(data) + + try: + for c in range(channels): + for s in range(samples): + self.buffer.setSample(c, s, 0.0) # < used to crash here + except Exception as e: + print(e) + + p = Processor() + cppjit.gbl.FloatDim2.callback(p) + + def test06_overload_reuse_in_threads(self): + """Threads reuse overload objects; check for clashes""" + + import threading + + import cppjit + + cppjit.cppdef("""\ + namespace CPPOverloadReuse { + class Simulation1 { + public: + virtual void set_something(std::map, std::string) {} + }; }""") + + def test(): + 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)] + + for t in threads: + t.start() + + for t in threads: + t.join() + + 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 + skip("JIT exceptions can not be caught in JITed code on Mac ARM") + + import threading + + import cppjit + + cppjit.cppdef("""\ + namespace CPPOverloadReuse { + class Simulation2 { + int fCount; + public: + Simulation2(int count) : fCount(count) {} + virtual int do_something() { return fCount; } + }; }""") + + 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 + + simulation = cppjit.gbl.CPPOverloadReuse.Simulation2(i) + res = simulation.do_something() + + with state.lock: + state.c3 += state.c1 + state.c2 += res + state.c1 += 1 + + threads = [threading.Thread(target=test, args=[i]) for i in range(0, 1000)] + + for t in threads: + t.start() + + for t in threads: + t.join() + + 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 + + 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..65b1600a3d8ec --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_conversions.py @@ -0,0 +1,156 @@ +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 = str(currpath.join("cpp/conversionsDict")) + + +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 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) diff --git a/bindings/pyroot/cppjit/test/test_cpp11features.py b/bindings/pyroot/cppjit/test/test_cpp11features.py new file mode 100644 index 0000000000000..a221480a8758c --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_cpp11features.py @@ -0,0 +1,697 @@ +import sys + +import py +from pytest import mark, raises +from support import ( + IS_LINUX, + IS_LINUX_ARM, + IS_MAC, + IS_VALGRIND, + ispypy, + setup_make, +) + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/cpp11featuresDict")) + + +def setup_module(mod): + setup_make("cpp11features") + + +class TestCPP11FEATURES: + def setup_class(cls): + cls.test_dct = 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<>""" + + 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 + + ptr1 = cf() + assert ptr1 + assert not not ptr1 + assert TestSmartPtr.s_counter == 1 + + ptr2 = cf() + assert ptr2 + assert not not ptr2 + assert TestSmartPtr.s_counter == 2 + + del ptr2 + gc.collect() + assert TestSmartPtr.s_counter == 1 + + del ptr1 + 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""" + + import gc + + from cppjit.gbl import TestSmartPtr, std + + class C(TestSmartPtr): + pass + + for cls in [std.shared_ptr, std.unique_ptr]: + assert TestSmartPtr.s_counter == 0 + + c = C() + assert TestSmartPtr.s_counter == 1 + c.__python_owns__ = False + cc = cls[TestSmartPtr](c) + assert cc.__python_owns__ + + del c + + assert cc + assert TestSmartPtr.s_counter == 1 + + del cc + 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""" + + import gc + + from cppjit.gbl import TestSmartPtr, std + + class C(TestSmartPtr): + pass + + for cls in [std.shared_ptr, std.unique_ptr]: + assert TestSmartPtr.s_counter == 0 + + t = TestSmartPtr() + assert TestSmartPtr.s_counter == 1 + assert t.__python_owns__ + + tt = std.shared_ptr[TestSmartPtr](t) + assert not t.__python_owns__ + + c = C() + assert TestSmartPtr.s_counter == 2 + assert c.__python_owns__ + + cc = std.shared_ptr[TestSmartPtr](c) + assert not c.__python_owns__ + + del cc, tt + 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""" + + 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 + + dd = std.make_shared[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) + assert TestSmartPtr.s_counter == 1 + assert ff(mv(dd)) == 100 + + del dd + gc.collect() + assert TestSmartPtr.s_counter == 0 + + # ability to take over by-value python-owned objects + tsp = create_TestSmartPtr_by_value() + assert TestSmartPtr.s_counter == 1 + assert tsp.__python_owns__ + + shared_stp = std.make_shared[TestSmartPtr](tsp) + assert TestSmartPtr.s_counter == 1 + assert not tsp.__python_owns__ + + del shared_stp + gc.collect() + assert TestSmartPtr.s_counter == 0 + + # alternative make_shared with type taken from pointer + tsp = create_TestSmartPtr_by_value() + shared_stp = std.make_shared(tsp) + assert TestSmartPtr.s_counter == 1 + del shared_stp + gc.collect() + assert TestSmartPtr.s_counter == 0 + + def test05_unique_ptr_passing(self): + """Ability to pass unique_ptr through unique_ptr""" + + 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 + dd = std.make_unique[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) + assert TestSmartPtr.s_counter == 1 + assert move_unique_ptr_derived(std.move(dd)) == 100 + assert dd.__python_owns__ + + del dd + gc.collect() + assert TestSmartPtr.s_counter == 0 + + # 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?? + # assert move_unique_ptr(std.move(dd)) == 100 + assert dd.__python_owns__ + + del dd + gc.collect() + assert TestSmartPtr.s_counter == 0 + + # ability to take over by-value python-owned objects + tsp = create_TestSmartPtr_by_value() + assert TestSmartPtr.s_counter == 1 + assert tsp.__python_owns__ + + unique_stp = std.make_unique[TestSmartPtr](tsp) + assert TestSmartPtr.s_counter == 1 + assert not tsp.__python_owns__ + + del unique_stp + gc.collect() + assert TestSmartPtr.s_counter == 0 + + # alternative make_unique with type taken from pointer + tsp = create_TestSmartPtr_by_value() + unique_stp = std.make_unique(tsp) + assert TestSmartPtr.s_counter == 1 + + del unique_stp + gc.collect() + assert TestSmartPtr.s_counter == 0 + + def test06_nullptr(self): + """Allow the programmer to pass NULL in certain cases""" + + import cppjit + + # test existence + nullptr = cppjit.nullptr + # assert not hasattr(cppjit.gbl, '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 + + def test07_move(self): + """Move construction, assignment, and methods""" + + import gc + + import cppjit + + def moveit(T): + assert T.s_instance_counter == 0 + + from cppjit.gbl import std + + # move constructor + i1 = T() + assert T.s_move_counter == 0 + + i2 = T(i1) # cctor + assert T.s_move_counter == 0 + + if ispypy or 0x3000000 <= sys.hexversion: + i3 = T(std.move(T())) # can't check ref-count + else: + 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 + assert T.s_move_counter == 2 + + i4 = T(std.move(i1)) + assert T.s_move_counter == 3 + + # 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 + else: + i4.__assign__(T()) + assert T.s_move_counter == 4 + + i4.__assign__(std.move(i2)) + assert T.s_move_counter == 5 + + del i1, i2, i3, i4 + gc.collect() + assert T.s_instance_counter == 0 + + # 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 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 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 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.0, 2.0, 3.0, 4.0] + + 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]: + l = list() + for i in range(10): + l.append(cls_arg(i)) + + v = cls[cls_arg](l) + assert len(v) == len(l) + for i in range(len(l)): + assert v[i].m_int == l[i].m_int + + import cppjit + + cppjit.cppdef(r""" + namespace InitializerListTest { + std::vector foo(const std::initializer_list& vals) { + return std::vector{vals}; + } }""") + + ns = cppjit.gbl.InitializerListTest + + for l in (["x"], ["x", "y", "z"]): + assert ns.foo(l) == std.vector["std::string"](l) + + def test09_lambda_calls(self): + """Call (global) lambdas""" + + import cppjit + + cppjit.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") + + assert cppjit.gbl.gMyLambda + assert cppjit.gbl.gMyLambda(2) == 42 + assert cppjit.gbl.gMyLambda(40) == 80 + + cppjit.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") + l1 = cppjit.gbl.gime_a_lambda1() + assert l1 + assert l1() == 42 + + 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 + + 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 cppjit + + assert cppjit.gbl.std.optional + assert cppjit.gbl.std.nullopt + + cppjit.cppdef(""" + enum Enum { A = -1 }; + bool callopt(std::optional) { return true; } + """) + + a = cppjit.gbl.std.optional[cppjit.gbl.Enum]() + assert cppjit.gbl.callopt(a) + + c = cppjit.gbl.std.nullopt + assert cppjit.gbl.callopt(c) + + def test11_chrono(self): + """Use of chrono and overloaded operator+""" + + 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) + + def test12_stdfunction(self): + """Use of std::function with arguments in a namespace""" + + import cppjit + from cppjit.gbl import FNCreateTestStructFunc, FNTestStruct, FunctionNS + + t = FNTestStruct(42) + f = FNCreateTestStructFunc() + assert f(t) == 42 + + t = FunctionNS.FNTestStruct(13) + f = FunctionNS.FNCreateTestStructFunc() + assert f(t) == 13 + + # 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 cppjit.gbl import FunctionNS2 # noqa: F401 + + t = FunctionNS.FNTestStruct(27) + f = FunctionNS.FNCreateTestStructFunc() + assert f(t) == 27 + + def test13_stdhash(self): + """Use of std::hash""" + + from cppjit.gbl import StructWithHash, StructWithoutHash + + 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 + + @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""" + + import gc + + 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() + + assert TestSmartPtr.s_counter == 1 + assert not obj.__smartptr__() + assert pass_shared_ptr(obj) == val + assert obj.__smartptr__() + assert obj.__python_owns__ + assert TestSmartPtr.s_counter == 1 + + 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 cppjit + + cppjit.cppdef("""namespace UniqueTempl { + template + std::unique_ptr returnptr(std::unique_ptr&& a) { + return std::move(a); + } }""") + + 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 = cppjit.gbl.std.make_unique["int"]() + with raises(ValueError): # not an RValue + 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 cppjit + + cppjit.cppdef("""namespace unique_ptr_moves { + template + std::unique_ptr returnptr_value(std::unique_ptr a) { + return std::move(a); + } + template + std::unique_ptr returnptr_move(std::unique_ptr&& a) { + return std::move(a); + } }""") + + up = cppjit.gbl.std.make_unique[int](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) + + def test17_unique_ptr_data(self): + """std::unique_ptr as data means implicitly no copy ctor""" + + import cppjit + + cppjit.cppdef("""namespace unique_ptr_data{ + class Example { + private: + std::unique_ptr x; + public: + Example() {} + virtual ~Example() = default; + double y = 66.; + }; }""") + + class Inherit(cppjit.gbl.unique_ptr_data.Example): + pass + + a = Inherit() + # Test whether this attribute was inherited + assert a.y == 66.0 + + def test18_unique_ptr_identity(self): + """std::unique_ptr identity preservation""" + + import cppjit + + cppjit.cppdef("""\ + namespace UniqueIdentity { + struct A { + A(int _a) : a(_a) {} + int a; + }; + + std::unique_ptr create() { return std::make_unique(37); } + + struct Consumer { + public: + Consumer(std::unique_ptr & ptr) : fPtr{std::move(ptr)} { + ptr.reset(); + } + + const A& get() const { return *fPtr; } + const std::unique_ptr& pget() const { return fPtr; } + + private: + std::unique_ptr fPtr; + }; }""") + + ns = cppjit.gbl.UniqueIdentity + + x = ns.create() + assert x.a == 37 + + c = ns.Consumer(x) + x = c.get() + assert x.a == 37 + + p1 = c.pget() + p2 = c.pget() + assert p1 is p2 + + @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 cppjit + + cppjit.cppdef(r"""\ + namespace SmartPtrCallback { + struct Dummy { + virtual ~Dummy() = default; + }; + + std::shared_ptr dummy_create() { + return std::make_shared(); + } + + typedef std::shared_ptr (*fff)(); + + std::shared_ptr call_creator(fff func) { + return func(); + }}""") + + std = cppjit.gbl.std + ns = cppjit.gbl.SmartPtrCallback + + def pyfunc() -> std.shared_ptr[ns.Dummy]: + return ns.dummy_create() + + assert ns.call_creator(pyfunc) + + def test21_smart_ptr_downcast(self): + """Object returned through a smart pointer is auto-downcast""" + + import cppjit + + gbl = cppjit.gbl + + # unique_ptr holding a Derived comes back as Derived, with the + # derived-only method callable, just like a raw pointer return + for cf in [gbl.create_unique_ptr_to_derived, gbl.create_shared_ptr_to_derived]: + obj = cf() + assert type(obj) == gbl.PubDerivedTestSmartPtr + assert obj.only_in_derived() == 27 + 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() + assert type(obj) == gbl.TestSmartPtr + + # the most derived type sits at a non-zero offset from the declared + # interface, which the dereferencer can not apply: stay the declared + # type and keep behaving correctly + obj = gbl.create_unique_ptr_to_offset_derived() + assert type(obj) == gbl.TestSmartPtrIface + assert obj.only_in_iface() == 37 + + # the auto-down-cast must not enable C++-invalid conversions: the proxy + # 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(), + ) + + # passing it where the matching base smart pointer is expected still works + assert gbl.pass_shared_ptr(gbl.create_shared_ptr_to_derived()) == 17 + + # calling function with overloads for both the base class and the + # 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" + ) diff --git a/bindings/pyroot/cppjit/test/test_cpp23features.py b/bindings/pyroot/cppjit/test/test_cpp23features.py new file mode 100644 index 0000000000000..16b5423688de1 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_cpp23features.py @@ -0,0 +1,428 @@ +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/ + # ------------------------------------------------------------------ # + + 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/cppjit/test/test_crossinheritance.py b/bindings/pyroot/cppjit/test/test_crossinheritance.py new file mode 100644 index 0000000000000..2e537df968eca --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_crossinheritance.py @@ -0,0 +1,1920 @@ +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, + setup_make, +) + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/crossinheritanceDict")) + + +def setup_module(mod): + setup_make("crossinheritance") + + +class TestCROSSINHERITANCE: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) + + def test01_override_function(self): + """Test ability to override a simple function""" + + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 + + assert Base1().get_value() == 42 + + class Derived(Base1): + def get_value(self): + return 13 + + assert Derived().get_value() == 13 + + assert "Derived" in str(Derived()) + assert "Derived" in repr(Derived()) + + 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 cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 + + assert Base1(27).get_value() == 27 + + class Derived1(Base1): + def __init__(self, pyval): + Base1.__init__(self) + self.m_pyint = pyval + + def get_value(self): + 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 Base1.call_get_value(d) == 44 + + class Derived2(Base1): + def __init__(self, pyval, cppval): + Base1.__init__(self, cppval) + self.m_pyint = pyval + + def get_value(self): + 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 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 cppjit + + CX = cppjit.gbl.CrossInheritance + + class C1PyBase2(CX.IBase2): + def __init__(self): + super(C1PyBase2, self).__init__() + + def get_value(self): + return 99 + + class C2PyBase2(CX.IBase2): + def __init__(self): + CX.IBase2.__init__(self) + + def get_value(self): + return 91 + + class C3PyBase2(CX.CBase2): + def __init__(self): + super(C3PyBase2, self).__init__() + + class C4PyBase2(CX.CBase2): + def __init__(self): + super(C4PyBase2, self).__init__() + + def get_value(self): + return 13 + + try: + 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 "abstract" in str(e) + + c1, c3, c4 = C1PyBase2(), C3PyBase2(), C4PyBase2() + + assert CX.IBase2.call_get_value(c1) == 99 + assert CX.IBase2.call_get_value(c3) == 42 + assert CX.IBase2.call_get_value(c4) == 13 + + # now with abstract constructor that takes an argument + class C4PyBase2(CX.IBase3): + def __init__(self, intval): + super(C4PyBase2, self).__init__(intval) + + def get_value(self): + return 77 + + c4 = C4PyBase2(88) + 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 cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 + + assert Base1(27).sum_value(-7) == 20 + + class Derived1(Base1): + def sum_value(self, val): + return val + 13 + + d1 = Derived1() + 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 + + class Derived2(Base1): + def pass_value1(self, a): + return a * 2 + + def pass_value2(self, a): + return a.value * 2 + + def pass_value3(self, a): + return a.value * 2 + + def pass_value4(self, b): + return b.m_int * 2 + + def pass_value5(self, b): + return b.m_int * 2 + + d2 = Derived2() + 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 cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 + + assert Base1(27).sum_all(-7) == 20 + assert Base1(27).sum_all(-3, -4) == 20 + + class Derived(Base1): + def sum_all(self, *args): + return sum(args) + 13 + + d = Derived() + 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 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 cppjit + + CX = cppjit.gbl.CrossInheritance + + class C1PyBase4(CX.IBase4): + def __init__(self): + super(C1PyBase4, self).__init__() + + def get_value(self): + return 17 + + class C2PyBase4(CX.CBase4): + def __init__(self): + super(C2PyBase4, self).__init__() + + c1, c2 = C1PyBase4(), C2PyBase4() + + assert CX.IBase4.call_get_value(c1) == 17 + assert CX.IBase4.call_get_value(c2) == 27 + + def test07_templated_base(self): + """Derive from a base class that is instantiated from a template""" + + import cppjit + + CX = cppjit.gbl.CrossInheritance + TBase1, TBase1_I, TDerived1 = CX.TBase1, CX.TBase1_I, CX.TDerived1 + + class TPyDerived1(TBase1_I): + def __init__(self): + super(TBase1_I, self).__init__() + + def get_value(self): + return 13 + + b1, b2 = TBase1[int](), TBase1_I() + assert b1.get_value() == 42 + assert b2.get_value() == 42 + + d1 = TDerived1() + assert d1.get_value() == 27 + + p1 = TPyDerived1() + assert p1.get_value() == 13 + + @mark.xfail(condition=IS_MAC_ARM, run=False, reason="Fails on macOS arm") + def test08_error_handling(self): + """Python errors should propagate through wrapper""" + + 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) + + d = Derived() + raises(ValueError, Base1.call_sum_value, d, -7) + + if IS_MAC_ARM: + skip("JIT exceptions from signals not supported on Mac ARM") + + try: + Base1.call_sum_value(d, -7) + assert not "should not get here" + except ValueError as e: + assert errmsg in str(e) + + cppjit.cppdef("""\ + namespace CrossInheritance { + std::string call_base1(Base1* b) { + try { + b->sum_value(-7); + } catch (cppjit::cpyrt::PyException& e) { + e.clear(); + return e.what(); + } + return ""; + } }""") + + res = cppjit.gbl.CrossInheritance.call_base1(d) + + 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 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 + + d = Derived(4) + + assert raises(TypeError, Base1.call_get_value, d) + + def test10_python_in_templates(self): + """Usage of Python derived objects in std::vector""" + + import gc + + import cppjit + + CB = cppjit.gbl.CrossInheritance.CountableBase + + class PyCountable(CB): + def call(self): + try: + return self.extra + 42 + except AttributeError: + return 42 + + start_count = CB.s_count + + 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 + + p = PyCountable() + assert p.call() == 42 + p.extra = 42 + assert p.call() == 84 + v.emplace_back(p) + assert len(v) == 2 + assert v[1].call() == 84 # copy ctor copies python state + p.extra = 13 + assert p.call() == 55 + assert v[1].call() == 84 + del p + gc.collect() + assert CB.s_count == 2 + start_count + + v.push_back(PyCountable()) # uses copy ctor + assert len(v) == 3 + gc.collect() + assert CB.s_count == 3 + start_count + + del v + 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 cppjit + + cppjit.cppdef("""namespace MakeSharedTest { + class Abstract { + public: + virtual ~Abstract() = 0; + virtual int some_imp() = 0; + }; + + Abstract::~Abstract() {} + + int call_shared(std::shared_ptr& ptr) { + return ptr->some_imp(); + } }""") + + 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 + + v = std.make_shared[PyDerived](42) + + assert call_shared(v) == 42 + assert v.some_imp() == 42 + + p = PyDerived(13) + v = std.make_shared[PyDerived](p) + assert call_shared(v) == 13 + assert v.some_imp() == 13 + + def test12a_counter_test(self): + """Test countable base counting""" + + import gc + + import cppjit + + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase + + class PyCountable(CB): + def call(self): + try: + return self.extra + 42 + except AttributeError: + return 42 + + start_count = CB.s_count + + # test counter + pyc = PyCountable() + assert CB.s_count == 1 + start_count + + del pyc + 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 gc + + import cppjit + + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase + + class PyCountable(CB): + def call(self): + try: + return self.extra + 42 + except AttributeError: + return 42 + + start_count = CB.s_count + + v = std.vector[std.shared_ptr[PyCountable]]() + v.push_back(std.make_shared[PyCountable]()) + + gc.collect() + assert CB.s_count == 1 + start_count + + del v + gc.collect() + assert CB.s_count == 0 + start_count + + def test13_virtual_dtors_and_del(self): + """Usage of virtual destructors and Python-side del.""" + + import warnings + + import cppjit + + cppjit.cppdef("""namespace VirtualDtor { + class MyClass1 {}; // no virtual dtor ... + + class MyClass2 { + public: + virtual ~MyClass2() {} + }; + + class MyClass3 : public MyClass2 {}; + + template + class MyClass4 { + public: + virtual ~MyClass4() {} + }; }""") + + 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 + 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 + # catch the exception is that the error would be changed into a + # more uninformative "TypeError: no python-side overrides supported ()" + warnings.simplefilter("default") + + class MyPyDerived1(VD.MyClass1): + 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 + + class MyPyDerived2(VD.MyClass2): + pass + + d = MyPyDerived2() + del d # used to crash + + # check a few more derivations that should not fail + class MyPyDerived3(VD.MyClass3): + pass + + class MyPyDerived4(VD.MyClass4[int]): + pass + + def test14_protected_access(self): + """Derived classes should have access to protected members""" + + import cppjit + + ns = cppjit.gbl.AccessProtected + + 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 MyPyDerived().my_data == 101 + + class MyPyDerived(ns.MyBase): + def __init__(self): + super(MyPyDerived, self).__init__() + assert self.my_data == 101 + self.py_data = 13 + self.my_data = 42 + + m = MyPyDerived() + 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 cppjit + + # Part 1: return of a new C++ object + cppjit.cppdef("""namespace object_returns { + class Base { + public: + virtual Base* foo() { return new Base(); } + virtual ~Base() {} + virtual std::string whoami() { return "Base"; } + }; + + class CppDerived : public Base { + CppDerived* foo() { return new CppDerived(); } + ~CppDerived() {} + virtual std::string whoami() { return "CppDerived"; } + }; + + Base* call_foo(Base& obj) { return obj.foo(); } }""") + + ns = cppjit.gbl.object_returns + + class PyDerived1(ns.Base): + def foo(self): + return ns.CppDerived() + + obj = PyDerived1() + assert not ns.call_foo(obj) + + class PyDerived2(ns.Base): + def foo(self): + x = ns.CppDerived() + x.__python_owns__ = False + return x + + obj = PyDerived2() + assert not not ns.call_foo(obj) + + # Part 2: return of a new Python derived object + class PyDerived3(ns.Base): + def foo(self): + return PyDerived3() + + def whoami(self): + return "PyDerived3" + + obj = PyDerived3() + newobj = ns.call_foo(obj) + assert not newobj + + class PyDerived4(ns.Base): + def foo(self): + d = PyDerived4() + d.__python_owns__ = False + d.alpha = 2 + return d + + def whoami(self): + return "PyDerived4" + + obj = PyDerived4() + new_obj = ns.call_foo(obj) + assert not not new_obj + assert new_obj.whoami() == "PyDerived4" + + def test16_cctor_access_controlled(self): + """Python derived class of C++ class with access controlled cctor""" + + import cppjit + + cppjit.cppdef("""namespace cctor_access_controlled { + class CommonBase { + public: + virtual ~CommonBase() {} + virtual std::string whoami() = 0; + }; + + class Base1 : public CommonBase { + Base1(const Base1&) {} + public: + Base1() {} + virtual ~Base1() {} + virtual std::string whoami() { return "Base1"; } + }; + + class Base2 : public CommonBase { + protected: + Base2(const Base2&) {} + public: + Base2() {} + virtual ~Base2() {} + virtual std::string whoami() { return "Base2"; } + }; + + std::string callit(CommonBase& obj) { return obj.whoami(); } }""") + + ns = cppjit.gbl.cctor_access_controlled + + for base in (ns.Base1, ns.Base2): + + class PyDerived(base): + def whoami(self): + return "PyDerived" + + obj = PyDerived() + assert ns.callit(obj) == "PyDerived" + + def test17_deep_hierarchy(self): + """Test a deep Python hierarchy with pure virtual functions""" + + import cppjit + + cppjit.cppdef("""namespace deep_hierarchy { + class Base { + public: + virtual ~Base() {} + virtual std::string whoami() = 0; + }; + + std::string callit(Base& obj) { return obj.whoami(); } }""") + + ns = cppjit.gbl.deep_hierarchy + + class PyDerived1(ns.Base): + def whoami(self): + return "PyDerived1" + + obj = PyDerived1() + assert ns.callit(obj) == "PyDerived1" + + class PyDerived2(PyDerived1): + pass + + obj = PyDerived2() + assert obj.whoami() == "PyDerived1" + assert ns.callit(obj) == "PyDerived1" + + class PyDerived3(PyDerived1): + def whoami(self): + return "PyDerived3" + + obj = PyDerived3() + assert obj.whoami() == "PyDerived3" + assert ns.callit(obj) == "PyDerived3" + + class PyDerived4(PyDerived2): + def whoami(self): + return "PyDerived4" + + obj = PyDerived4() + assert obj.whoami() == "PyDerived4" + assert ns.callit(obj) == "PyDerived4" + + def test18_abstract_hierarchy(self): + """Hierarchy with abstract classes""" + + import cppjit + + cppjit.cppdef("""namespace abstract_classes { + class Base { + public: + virtual ~Base() {} + virtual std::string whoami() = 0; + virtual std::string message() = 0; + }; + + std::string whois(Base& obj) { return obj.whoami(); } + std::string saywot(Base& obj) { return obj.message(); } }""") + + ns = cppjit.gbl.abstract_classes + + class PyDerived1(ns.Base): + def __init__(self): + super(PyDerived1, self).__init__() + self._name = "PyDerived1" + + def whoami(self): + return self._name + + class PyDerived2(PyDerived1): + def __init__(self): + super(PyDerived2, self).__init__() + self._message = "Hello, World!" + + def message(self): + return self._message + + obj = PyDerived2() + assert obj.whoami() == "PyDerived1" + assert ns.whois(obj) == "PyDerived1" + + 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 cppjit + + cppjit.cppdef(""" namespace cpp_side_multiple_inheritance { + struct Result { + Result() : result(1337) {} + Result(int r) : result(r) {} + int result; + }; + + class Base1 { + public: + virtual ~Base1() {} + virtual Result abstract1() = 0; + }; + + class Base2 { + public: + virtual ~Base2() {} + virtual Result abstract2() = 0; + }; + + class Base : public Base1, public Base2 { + public: + Result abstract2() override { return Result(999); } + }; } """) + + 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 cppjit + + cppjit.cppdef("""namespace basic_multiple_inheritance { + class MyClass1 { + public: + MyClass1() : m_1(13) {} + virtual ~MyClass1() {} + virtual int x() = 0; + + public: + int m_1; + }; + int callx(MyClass1& m) { return m.x(); } + + class MyClass2 { + public: + MyClass2() : m_2(42) {} + virtual ~MyClass2() {} + virtual int y() = 0; + + public: + int m_2; + }; + int cally(MyClass2& m) { return m.y(); } + + class MyClass3 { + public: + MyClass3() : m_3(67) {} + virtual ~MyClass3() {} + virtual int z() = 0; + + public: + int m_3; + }; + int callz(MyClass3& m) { return m.z(); } }""") + + ns = cppjit.gbl.basic_multiple_inheritance + + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): + def x(self): + return 16 + + def y(self): + return 32 + + assert len(MyPyDerived.__bases__) == 2 + + a = MyPyDerived() + assert a.x() == ns.callx(a) + assert a.y() == ns.cally(a) + + assert a.m_1 == 13 + assert a.m_2 == 42 + + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + def x(self): + return 16 + + def y(self): + return 32 + + def z(self): + return 64 + + assert len(MyPyDerived2.__bases__) == 3 + + a = MyPyDerived2() + assert a.x() == ns.callx(a) + assert a.y() == ns.cally(a) + assert a.z() == ns.callz(a) + + assert a.m_1 == 13 + 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 cppjit + + cppjit.cppdef("""namespace multiple_inheritance_with_constructors { + class MyClass1 { + public: + MyClass1() : m_1(13) {} + MyClass1(int i) : m_1(i) {} + virtual ~MyClass1() {} + virtual int x() = 0; + + public: + int m_1; + }; + int callx(MyClass1& m) { return m.x(); } + + class MyClass2 { + public: + MyClass2() : m_2(42) {} + MyClass2(int i) : m_2(i) {} + virtual ~MyClass2() {} + virtual int y() = 0; + + public: + int m_2; + }; + int cally(MyClass2& m) { return m.y(); } + + class MyClass3 { + public: + MyClass3() : m_3(67) {} + MyClass3(int i) : m_3(i) {} + virtual ~MyClass3() {} + virtual int z() = 0; + + public: + int m_3; + }; + int callz(MyClass3& m) { return m.z(); } }""") + + ns = cppjit.gbl.multiple_inheritance_with_constructors + + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): + def __init__(self, val1, val2): + super(MyPyDerived, self).__init__((val1,), (val2,)) + + def x(self): + return 16 + + def y(self): + return 32 + + assert len(MyPyDerived.__bases__) == 2 + + a = MyPyDerived(27, 88) + assert a.x() == ns.callx(a) + assert a.y() == ns.cally(a) + + assert a.m_1 == 27 + assert a.m_2 == 88 + + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + def __init__(self, val1, val2, val3): + super(MyPyDerived2, self).__init__((val1,), (val2,), (val3,)) + + def x(self): + return 16 + + def y(self): + return 32 + + def z(self): + return 64 + + assert len(MyPyDerived2.__bases__) == 3 + + a = MyPyDerived2(27, 88, -11) + assert a.x() == ns.callx(a) + 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_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 cppjit + + cppjit.cppdef("""namespace multiple_inheritance_with_defaults { + class MyClass1 { + public: + MyClass1(int i=13) : m_1(i) {} + virtual ~MyClass1() {} + virtual int x() = 0; + + public: + int m_1; + }; + int callx(MyClass1& m) { return m.x(); } + + class MyClass2 { + public: + MyClass2(int i=42) : m_2(i) {} + virtual ~MyClass2() {} + virtual int y() = 0; + + public: + int m_2; + }; + int cally(MyClass2& m) { return m.y(); } + + class MyClass3 { + public: + MyClass3(int i=67) : m_3(i) {} + virtual ~MyClass3() {} + virtual int z() = 0; + + public: + int m_3; + }; + int callz(MyClass3& m) { return m.z(); } }""") + + ns = cppjit.gbl.multiple_inheritance_with_defaults + + 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 () + a3 = val3 is not None and (val3,) or () + if nArgs == 3: + super(MyPyDerived, self).__init__(a1, a2, a3) + elif nArgs == 0: + super(MyPyDerived, self).__init__() + elif nArgs == 1: + super(MyPyDerived, self).__init__(a1) + elif nArgs == 2: + super(MyPyDerived, self).__init__(a1, a2) + + def x(self): + return 16 + + def y(self): + return 32 + + def z(self): + return 64 + + assert len(MyPyDerived.__bases__) == 3 + + def verify(a, n1, n2, n3): + assert a.m_1 == n1 + assert a.m_2 == n2 + assert a.m_3 == n3 + + a = MyPyDerived(27, 88, -11) + assert a.x() == ns.callx(a) + assert a.y() == ns.cally(a) + assert a.z() == ns.callz(a) + + verify(a, 27, 88, -11) + + a = MyPyDerived(val2=27) + verify(a, 13, 27, 67) + + a = MyPyDerived(nArgs=0) + verify(a, 13, 42, 67) + + a = MyPyDerived(27, nArgs=1) + verify(a, 27, 42, 67) + + 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 cppjit + + cppjit.cppdef("""namespace const_byvalue_return { + struct Const { + Const() = default; + explicit Const(const std::string& s) { m_value = s; } + std::string m_value; + }; + + struct Abstract { + virtual ~Abstract() {} + virtual const Const return_const() = 0; + }; + + const Const callit(Abstract* a) { return a->return_const(); } }""") + + ns = cppjit.gbl.const_byvalue_return + + class ReturnConstByValue(ns.Abstract): + def return_const(self): + return ns.Const("abcdef") + + a = ReturnConstByValue() + assert a.return_const().m_value == "abcdef" + assert ns.callit(a).m_value == "abcdef" + + def test24_non_copyable(self): + """Inheriting from a non-copyable base class""" + + import cppjit + + cppjit.cppdef("""\ + namespace non_copyable { + struct Copyable { + Copyable() = default; + virtual ~Copyable() {} + + Copyable(const Copyable&) = default; + Copyable& operator=(const Copyable&) = default; + }; + + struct Movable { + Movable() = default; + virtual ~Movable() {} + + Movable(const Movable&) = delete; + Movable& operator=(const Movable&) = delete; + Movable(Movable&&) = default; + Movable& operator=(Movable&&) = default; + }; + + class NoCopyNoMove { + public: + NoCopyNoMove() = delete; + NoCopyNoMove(const NoCopyNoMove&) = delete; + NoCopyNoMove(NoCopyNoMove&&) = delete; + NoCopyNoMove& operator=(const NoCopyNoMove&) = delete; + NoCopyNoMove& operator=(NoCopyNoMove&&) = delete; + virtual ~NoCopyNoMove() = default; + + template + explicit NoCopyNoMove(DerivedType* ptr) : fActual(ptr) {} + + std::string callme() { + if (!fActual) return "failed!"; + return fActual->callme_imp(); + } + + private: + virtual std::string callme_imp() = 0; + + protected: + NoCopyNoMove* fActual; + }; }""") + + ns = cppjit.gbl.non_copyable + + Copyable = ns.Copyable + Movable = ns.Movable + NoCopyNoMove = ns.NoCopyNoMove + + class DerivedCopyable(Copyable): + pass + + # used to fail with compilation error + class DerivedMovable(Movable): + pass + + # used to fail with compilation error + class DerivedMulti(cppjit.multi(Movable, Copyable)): + pass + + # 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 + self.fActual = self + + def callme_imp(self): + return "Hello, World!" + + assert DerivedNoCopyNoMove().callme() == "Hello, World!" + + def test25_default_ctor_and_multiple_inheritance(self): + """Regression test: default ctor did not get added""" + + import cppjit + + cppjit.cppdef("""namespace default_ctor_and_multiple { + struct Copyable { + Copyable() = default; + virtual ~Copyable() {} + + Copyable(const Copyable&) = default; + Copyable& operator=(const Copyable&) = default; + }; + + struct Movable { + Movable() = default; + virtual ~Movable() {} + + Movable(const Movable&) = delete; + Movable& operator=(const Movable&) = delete; + Movable(Movable&&) = default; + Movable& operator=(Movable&&) = default; + }; + + struct SomeClass { + virtual ~SomeClass() {} + }; }""") + + ns = cppjit.gbl.default_ctor_and_multiple + Copyable = ns.Copyable + Movable = ns.Movable + SomeClass = ns.SomeClass + + class DerivedMulti(cppjit.multi(Movable, Copyable, SomeClass)): + def __init__(self): + super(DerivedMulti, self).__init__() + + d = DerivedMulti() + assert d + + def test26_no_default_ctor(self): + """Make sure no default ctor is created if not viable""" + + import cppjit + + cppjit.cppdef("""namespace no_default_ctor { + struct NoDefCtor1 { + NoDefCtor1(int) {} + virtual ~NoDefCtor1() {} + }; + + struct NoDefCtor2 { + NoDefCtor2() = delete; + virtual ~NoDefCtor2() {} + }; + + class NoDefCtor3 { + NoDefCtor3() = default; + public: + virtual ~NoDefCtor3() {} + }; + + class Simple { + public: + virtual ~Simple() {} + }; + }""") + + 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__() + + with raises(TypeError): + PyDerived() + + class PyDerived(cppjit.multi(kls, ns.Simple)): + def __init__(self): + super(PyDerived, self).__init__() + + with raises(TypeError): + PyDerived() + + class PyDerived(cppjit.multi(ns.Simple, kls)): + def __init__(self): + super(PyDerived, self).__init__() + + with raises(TypeError): + PyDerived() + + def test27_interfaces(self): + """Inherit from base with non-standard offset""" + + import cppjit + + cppjit.cppdef("""\ + namespace NonStandardOffset { + struct Calc1 { + virtual int calc1() = 0; + virtual ~Calc1() = default; + }; + + struct Calc2 { + virtual int calc2() = 0; + virtual ~Calc2() = default; + }; + + struct Base : virtual public Calc1, virtual public Calc2 { + Base() {} + }; + + struct Derived : Base, virtual public Calc2 { + int calc1() override { return 1; } + int calc2() override { return 2; } + }; + + int callback1(Calc1* c1) { return c1->calc1(); } + int callback2(Calc2* c2) { return c2->calc2(); } + }""") + + ns = cppjit.gbl.NonStandardOffset + + class MyPyDerived(ns.Derived): + pass + + obj = MyPyDerived() + + assert obj.calc1() == 1 + assert ns.callback1(obj) == 1 + + 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 cppjit + + cppjit.cppdef("""\ + namespace CrossDeep { + + class A { + public: + A(const std::string& /* name */) {} + virtual ~A() {} + virtual int fun1() const { return 0; } + virtual int fun2() const { return fun1(); } + }; }""") + + A = cppjit.gbl.CrossDeep.A + + class B(A): + def __init__(self, name="b"): + super(B, self).__init__(name) + + def fun1(self): + return 1 + + class C(B): + def fun1(self): + return -1 + + class D(B): + pass + + 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 cppjit + + cppjit.cppdef("""\ + namespace CrossMultiDeep { + + class A { + public: + virtual ~A() {} + virtual int calc_a() { return 17; } + }; + + int calc_a(A* a) { return a->calc_a(); } + + class B { + public: + virtual ~B() {} + virtual int calc_b() { return 42; } + }; + + int calc_b(B* b) { return b->calc_b(); } }""") + + ns = cppjit.gbl.CrossMultiDeep + + class C(cppjit.multi(ns.A, ns.B)): + def calc_a(self): + return 18 + + def calc_b(self): + return 43 + + c = C() + assert ns.calc_a(c) == 18 + assert ns.calc_b(c) == 43 + + class D(ns.B): + def calc_b(self): + return 44 + + d = D() + assert ns.calc_b(d) == 44 + + class E(cppjit.multi(ns.A, D)): + def calc_a(self): + return 19 + + e = E() + assert ns.calc_a(e) == 19 + assert ns.calc_b(e) == 44 + + class F(ns.A): + def calc_a(self): + return 20 + + f = F() + assert ns.calc_a(f) == 20 + + class G(cppjit.multi(F, ns.B)): + def calc_b(self): + return 45 + + g = G() + assert ns.calc_a(g) == 20 + assert ns.calc_b(g) == 45 + + class H(object): + def calc_a(self): + return 66 + + class I(cppjit.multi(ns.A, H)): + def calc_a(self): + return 77 + + i = I() + assert ns.calc_a(i) == 77 + + 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 cppjit + + cppjit.cppdef("""\ + namespace AccessAndOverload { + class Base { + public: + virtual ~Base() {} + + protected: + virtual int call1(int i) { return i; } + virtual int call1(int i, int j) { return i+j; } + + virtual void call2(int) { return; } + virtual void call2(int, int) { return; } + + int call3(int i) { return i; } + + private: + int call3(int i, int j) { return i+j; } + }; }""") + + ns = cppjit.gbl.AccessAndOverload + + # used to produce uncompilable code + class PyDerived(ns.Base): + pass + + def test31_object_rebind(self): + """Usage of bind_object to cast with Python derived objects""" + + import gc + + import cppjit + + ns = cppjit.gbl.CrossInheritance + ns.build_component.__creates__ = True + + assert ns.Component.get_count() == 0 + + cmp1 = ns.build_component(42) + assert cmp1.__python_owns__ + assert type(cmp1) == ns.Component + with raises(AttributeError): + cmp1.getValue() + + 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 + cppjit.cppdef("""namespace CrossInheritance { + class ComponentWithValue : public Component { + public: + ComponentWithValue(int value) : m_value(value) {} + int getValue() { return m_value; } + + protected: + int m_value; + }; }""") + + # 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 + + del act_cmp1, cmp1 + gc.collect() + assert ns.Component.get_count() == 0 + + # 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 + pycmp2a = PyComponentWithValue(27) + assert not pycmp2a.__python_owns__ + pycmp2a.__python_owns__ = True + assert ns.Component.get_count() == 1 + + pycmp2b = ns.cycle_component(pycmp2a) + assert ns.Component.get_count() == 1 + assert pycmp2b is pycmp2a + + del pycmp2b, pycmp2a + gc.collect() + assert ns.Component.get_count() == 0 + + 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 + assert ns.Component.get_count() == 1 + # assert type(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 + + del cmp2, act_cmp2 + gc.collect() + assert ns.Component.get_count() == 0 + + # 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) + self.m_pyvalue = 11 + + def getValue(self): + return self.m_value + self.m_pyvalue + + cmp3 = cppjit.bind_object(PyComponentWithInit(77), PyComponentWithInit) + assert type(cmp3) == PyComponentWithInit + assert ns.Component.get_count() == 1 + + assert cmp3.getValue() == 77 + 11 + + del cmp3 + gc.collect() + assert ns.Component.get_count() == 0 + + pyc = PyComponentWithInit(88) + 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 = 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 = 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 = cppjit.bind_object(cmp5, PyComponentWithInit) + assert not cmp5.__python_owns__ + assert act_cmp5.__python_owns__ + 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 cppjit + + cppjit.cppdef("""\ + namespace CrossCallWithValue { + struct Data { + int value; + }; + + struct CppBase { + virtual ~CppBase() {} + + int func(Data d) { + return d.value + extra_func(d); + } + + virtual int extra_func(Data d) = 0; + }; }""") + + ns = cppjit.gbl.CrossCallWithValue + + class PyDerived(ns.CppBase): + def extra_func(self, d): + return 42 + d.value + + d = ns.Data(13) + p = PyDerived() + + assert p.func(d) == 42 + 2 * d.value + + def test33_direct_base_methods(self): + """Call base class methods directly""" + + import cppjit + + cppjit.cppdef("""\ + namespace DirectCalls { + struct A { + virtual ~A() {} + virtual int func() { + return 1; + } + }; + + struct B : public A { + virtual int func() { + return 2; + } + }; }""") + + ns = cppjit.gbl.DirectCalls + + a = ns.A() + assert a.func() == 1 + assert ns.A.func(a) == 1 + + b = ns.B() + assert b.func() == 2 + assert ns.B.func(b) == 2 + assert ns.A.func(b) == 1 + + with raises(TypeError): + ns.B.func(a) + + class C(ns.A): + def func(self): + from_a = ns.A.func(self) + return from_a + 2 + + c = C() + assert c.func() == 3 + + def test34_no_ctors_in_base(self): + """Base classes with no constructors""" + + import cppjit + + cppjit.cppdef("""\ + namespace BlockedCtors { + class Z { + public: + virtual ~Z() {} + }; + + class X : Z { + X(); + X(const X&&) = delete; + }; + + class Y: Z { + protected: + Y() {} + Y(const Y&&) = delete; + }; }""") + + ns = cppjit.gbl.BlockedCtors + + with raises(TypeError): + ns.X() + + with raises(TypeError): + ns.Y() + + class PyY1(ns.Y): + pass + + with raises(TypeError): + PyY1() + + class PyY2(ns.Y): + def __init__(self): + super(ns.Y, self).__init__() + + assert PyY2() + + def test35_deletion(self): + """C++ side deletion should propagate to the Python side""" + + import cppjit + + cppjit.cppdef("""\ + namespace DeletionTest1 { + class Base { + public: + virtual ~Base() {} + void do_work() {} + }; + + void delete_it(Base *p) { delete p; } + }""") + + ns = cppjit.gbl.DeletionTest1 + + class Derived(ns.Base): + was_deleted = False + + def __del__(self): + Derived.was_deleted = True + + o1 = Derived() + o1.do_work() + ns.delete_it(o1) + + with raises(ReferenceError): + o1.do_work() + + assert Derived.was_deleted == False + del o1 + assert Derived.was_deleted == True + + def test36_custom_destruct(self): + """C++ side deletion calls __destruct__""" + + import cppjit + + cppjit.cppdef("""\ + namespace DeletionTest2 { + class Base { + public: + virtual ~Base() {} + void do_work() {} + }; + + void delete_it(Base *p) { delete p; } + }""") + + ns = cppjit.gbl.DeletionTest2 + + class Derived(ns.Base): + was_cpp_deleted = False + was_py_deleted = False + + def __destruct__(self): + Derived.was_cpp_deleted = True + + def __del__(self): + Derived.was_py_deleted = True + + o1 = Derived() + o1.do_work() + ns.delete_it(o1) + + with raises(ReferenceError): + o1.do_work() + + assert Derived.was_cpp_deleted == True + assert Derived.was_py_deleted == False + del o1 + assert Derived.was_py_deleted == True + + def test37_deep_tree(self): + """Find overridable methods deep in the tree""" + + import cppjit + + cppjit.cppdef("""\ + namespace DeepTree { + + class Base { + public: + virtual ~Base() {} + + virtual std::string f1() { return "C++: Base::f1()"; } + virtual std::string f2() { return "C++: Base::f2()"; } + virtual std::string f3() { return "C++: Base::f3()"; } + }; + + class Intermediate: public Base { + public: + virtual ~Intermediate() {} + + using Base::f2; + }; + + class Sub: public Intermediate { + public: + virtual ~Sub() {} + + using Intermediate::f3; // `using Base::f3;` would also work + }; + + class CppSub: public Sub { + public: + virtual ~CppSub() {} + + std::string f1() { return "C++: CppSub::f1()"; } + std::string f2() { return "C++: CppSub::f2()"; } + std::string f3() { return "C++: CppSub::f3()"; } + }; + + std::string call_fs(Base *b) { + std::string res; + res += b->f1(); + res += b->f2(); + res += b->f3(); + return res; + } }""") + + ns = cppjit.gbl.DeepTree + + cppsub = ns.CppSub() + assert cppsub.f1() == "C++: CppSub::f1()" + assert cppsub.f2() == "C++: CppSub::f2()" + assert cppsub.f3() == "C++: CppSub::f3()" + assert ns.call_fs(cppsub) == cppsub.f1() + cppsub.f2() + cppsub.f3() + + class PySub(ns.Sub): + def f1(self): + return "Python: PySub::f1()" + + def f2(self): + return "Python: PySub::f2()" + + def f3(self): + return "Python: PySub::f3()" + + pysub = PySub() + assert pysub.f1() == "Python: PySub::f1()" + assert pysub.f2() == "Python: PySub::f2()" + assert pysub.f3() == "Python: PySub::f3()" + assert ns.call_fs(pysub) == pysub.f1() + pysub.f2() + pysub.f3() + + def test38_protected_data(self): + """Multiple cross inheritance with protected data""" + + import cppjit + + cppjit.cppdef(""" + namespace multiple_inheritance_with_protected_data { + class MyBaseClass { + public: + virtual ~MyBaseClass() {} + + protected: + int x = 0; + std::string s = "Hello"; + int y = 0; + std::string t = "World"; + int z = 0; + public: + MyBaseClass(int x, int y, int z) : x(x), y(y), z(z) {} + int get_x() { return x; } + int get_y() { return y; } + int get_z() { return z; } + }; }""") + + ns = cppjit.gbl.multiple_inheritance_with_protected_data + + class MyDerivedClass(ns.MyBaseClass): + def __init__(self, x, y, z): + super(MyDerivedClass, self).__init__(x, y, z) + + derived = MyDerivedClass(5, 7, 9) + assert derived.get_x() == derived.x + assert derived.get_y() == derived.y + assert derived.get_z() == derived.z + assert derived.s == "Hello" + assert derived.t == "World" + + def test39_returning_multi_keyword_types(self): + """Supporting dispatcher for functions that return multi-keyword types like `unsigned int`""" + + import cppjit + + cppjit.cppdef(""" + class MyUIntBaseClass { + public: + virtual ~MyUIntBaseClass() = default; + virtual unsigned int give_unsigned_int() const = 0; + }; + + """) + + # Compiling the dispatcher for the overridden member function used to + # fail because of the `unsigned int` type, whose name has two keywords. + class MyUIntDerivedClass(cppjit.gbl.MyUIntBaseClass): + def give_unsigned_int(self): + return 1 diff --git a/bindings/pyroot/cppjit/test/test_datatypes.py b/bindings/pyroot/cppjit/test/test_datatypes.py new file mode 100644 index 0000000000000..96d6307087703 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_datatypes.py @@ -0,0 +1,2802 @@ +import sys + +import py +from pytest import mark, raises, skip +from support import IS_CLANG_REPL, IS_CPP23, IS_MAC, pylong, pyunicode, setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/datatypesDict")) + + +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 + + @mark.xfail( + condition=IS_CPP23 and not IS_MAC, + reason="since C++23 (P1467) the narrowing std::complex to " + "complex constructor is explicit, breaking the implicit " + "conversion of Python complex arguments; libc++ does not implement " + "P1467 yet, so the conversion still succeeds on macOS", + ) + 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
into Cling.""" - with _stderr_capture() as err: + 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)) @@ -301,7 +317,7 @@ def include(header): def c_include(header): """Load (and JIT) header file
into Cling.""" - with _stderr_capture() as err: + with _stderr_capture() as err, _interpreter_lock(): errcode = gbl.Cpp.Declare( """extern "C" { #include "%s" @@ -318,7 +334,8 @@ 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.Cpp.AddIncludePath(path) + with _interpreter_lock(): + gbl.Cpp.AddIncludePath(path) def add_library_path(path): diff --git a/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py index 50a32f374cf5a..69f42845243b2 100644 --- a/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py +++ b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py @@ -15,6 +15,8 @@ "_backend", "_begin_capture_stderr", "_end_capture_stderr", + "_lock_interpreter", + "_unlock_interpreter", ] @@ -206,6 +208,10 @@ def load_reflection_info(name): return True +_lock_interpreter = _backend._lock_interpreter +_unlock_interpreter = _backend._unlock_interpreter + + def _begin_capture_stderr(): _backend._begin_capture_stderr() diff --git a/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx index 6293389ad3eb9..d716fd1d77982 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx @@ -821,6 +821,22 @@ static PyObject* EndCaptureStderr(PyObject*, PyObject*) { } // 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"}, @@ -874,6 +890,10 @@ static PyMethodDef gcpyrtMethods[] = { 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 { diff --git a/bindings/pyroot/cppjit/src/interop/cppjit_interop.h b/bindings/pyroot/cppjit/src/interop/cppjit_interop.h index 9eab697351852..db2ccf0b51dc4 100644 --- a/bindings/pyroot/cppjit/src/interop/cppjit_interop.h +++ b/bindings/pyroot/cppjit/src/interop/cppjit_interop.h @@ -48,6 +48,12 @@ 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); diff --git a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx index bd04fb9f1c377..f4fd20470af47 100644 --- a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx +++ b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx @@ -15,6 +15,7 @@ using namespace cppjit; // ROOT #include "TClass.h" +#include "TInterpreter.h" #include "TROOT.h" #include "TSystem.h" @@ -45,7 +46,44 @@ static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } #include #include -std::recursive_mutex InterOpMutex; +// 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", @@ -256,7 +294,7 @@ extern "C" int LoadCppInterOp() { static std::once_flag Once; static int Loaded = 0; std::call_once(Once, [] { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); const InterOpPaths Paths = cppinterop_paths(); if (!loadDispatchAPI(Paths)) return; @@ -281,13 +319,17 @@ static inline char* cppstring_to_cstring(const std::string& cppstr) { // 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); + 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); + std::lock_guard Lock(InterOpMutex); if (klass && obj && !Cpp::IsNamespace(klass)) return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), obj.data); @@ -307,7 +349,7 @@ std::string interop::ResolveName(const std::string& name) { } interop::TCppType_t interop::ResolveEnumReferenceType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) return type; @@ -321,7 +363,7 @@ interop::TCppType_t interop::ResolveEnumReferenceType(TCppType_t type) { } interop::TCppType_t interop::ResolveEnumPointerType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); if (!Cpp::IsPointerType(type)) return type; @@ -351,7 +393,7 @@ interop::TCppType_t interop::ResolveType(TCppType_t type) { if (!type) return type; - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); TCppType_t check_int_typedefs = int_like_type(type); if (check_int_typedefs) @@ -371,7 +413,7 @@ interop::TCppType_t interop::ResolveType(TCppType_t type) { } interop::TCppType_t interop::GetRealType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); TCppType_t check_int_typedefs = int_like_type(type); if (check_int_typedefs) return check_int_typedefs; @@ -379,12 +421,12 @@ interop::TCppType_t interop::GetRealType(TCppType_t type) { } interop::TCppType_t interop::GetPointerType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetPointerType(type); } interop::TCppType_t interop::GetReferencedType(TCppType_t type, bool rvalue) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetReferencedType(type, rvalue); } @@ -548,7 +590,7 @@ bool interop::AppendTypesSlow(const std::string& name, // ROOT::RVecF); the trampoline below applies real unqualified lookup. } - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); // We might have an entire expression such as int, double. static unsigned long long struct_count = 0; @@ -663,7 +705,7 @@ interop::TCppType_t interop::GetType(const std::string& name, // The ast printer gave us garbage. if (name == "") return nullptr; - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); if (auto type = Cpp::GetType(name)) return type; @@ -707,12 +749,12 @@ interop::TCppType_t interop::GetType(const std::string& name, } interop::TCppType_t interop::GetComplexType(const std::string& name) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetComplexType(Cpp::GetType(name)); } std::string interop::ResolveEnum(TCppScope_t handle) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::string type = Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(handle)); if (type == "signed char") @@ -721,13 +763,13 @@ std::string interop::ResolveEnum(TCppScope_t handle) { } interop::TCppScope_t interop::GetUnderlyingScope(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetUnderlyingScope(scope); } interop::TCppScope_t interop::GetScope(const std::string& name, TCppScope_t parent_scope) { - std::lock_guard Lock(InterOpMutex); + 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 @@ -762,10 +804,10 @@ interop::TCppScope_t interop::GetScope(const std::string& name, // Splitting off the argument list and resolving it directly cannot // represent non-type arguments such as the `3` in std::array. std::vector types; - InterOpMutex.unlock(); // unlock to allow AppendTypesSlow + Lock.unlock(); // unlock to allow AppendTypesSlow bool added_new_type = !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope); - std::lock_guard Lock(InterOpMutex); + 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 @@ -792,33 +834,33 @@ interop::TCppScope_t interop::GetFullScope(const std::string& name) { } interop::TCppScope_t interop::GetTypeScope(TCppScope_t var) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetNamed(name, parent_scope); } interop::TCppScope_t interop::GetParentScope(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetParentScope(scope); } interop::TCppScope_t interop::GetScopeFromType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetScopeFromType(type); } interop::TCppType_t interop::GetTypeFromScope(TCppScope_t klass) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeFromScope(klass); } interop::TCppScope_t interop::GetGlobalScope() { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetGlobalScope(); } @@ -841,7 +883,7 @@ class AutoCastRTTI { interop::TCppScope_t interop::GetActualClass(TCppScope_t klass, TCppObject_t obj) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); if (!obj || !Cpp::IsClassPolymorphic(klass)) return klass; @@ -910,14 +952,14 @@ interop::TCppScope_t interop::GetActualClass(TCppScope_t klass, } size_t interop::SizeOf(TCppScope_t klass) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetSizeOfType(klass); } @@ -951,33 +993,49 @@ bool interop::IsBuiltin(const std::string& type_name) { bool interop::IsBuiltin(TCppType_t type) { return Cpp::IsBuiltin(type); } bool interop::IsComplete(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::IsComplete(scope); } // // memory management // --------------------------------------------------------- interop::TCppObject_t interop::Allocate(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + 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); + 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::lock_guard Lock( - InterOpMutex); // TODO: this shouldn't locks the JIT call - return Cpp::Construct(scope, arena, /*count=*/1); + 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) { - std::lock_guard Lock( - InterOpMutex); // TODO: this shouldn't locks the JIT call - Cpp::Destruct(instance, scope, true, /*count=*/0); + 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) { @@ -1115,9 +1173,8 @@ interop::TCppObject_t interop::CallConstructor(TCppMethod_t method, } void interop::CallDestructor(TCppScope_t scope, TCppObject_t self) { - std::lock_guard Lock( - InterOpMutex); // TODO: this shouldn't locks the JIT call - Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); + 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, @@ -1135,7 +1192,7 @@ interop::TCppObject_t interop::CallO(TCppMethod_t method, TCppObject_t self, interop::TCppFuncAddr_t interop::GetFunctionAddress(TCppMethod_t method, bool /*check_enabled*/) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionAddress(method); } @@ -1158,7 +1215,7 @@ bool interop::IsNamespace(TCppScope_t scope) { return false; // Test if this scope represents a namespace. - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; } @@ -1186,7 +1243,7 @@ bool interop::IsAggregate(TCppScope_t type) { } bool interop::IsDefaultConstructable(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); // Test if this type has a default constructor or is a "plain old data" type return Cpp::HasDefaultConstructor(scope); } @@ -1198,14 +1255,14 @@ void interop::GetAllCppNames(TCppScope_t scope, // 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); + std::lock_guard Lock(InterOpMutex); Cpp::GetAllCppNames(scope, cppnames); } // class reflection information ---------------------------------------------- std::vector interop::GetUsingNamespaces(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetUsingNamespaces(scope); } @@ -1231,25 +1288,25 @@ static std::string cppyy_normalize_name(std::string name) { // class reflection information ---------------------------------------------- std::string interop::GetFinalName(TCppScope_t klass) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return cppyy_normalize_name(Cpp::GetQualifiedCompleteName(klass)); } bool interop::HasVirtualDestructor(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetNumBases(klass); } @@ -1285,18 +1342,18 @@ interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t klass) { } std::string interop::GetBaseName(TCppScope_t klass, TCppIndex_t ibase) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetBaseClass(klass, ibase); } bool interop::IsSubclass(TCppScope_t derived, TCppScope_t base) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::IsSubclass(derived, base); } @@ -1326,7 +1383,7 @@ bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw, std::vector ops; { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, /*kind=*/Cpp::OperatorArity::kBoth); if (ops.size() != 1) @@ -1354,7 +1411,7 @@ bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw, ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t /*address*/, int direction, bool rerror) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); // Either base or derived class is incomplete, treat silently if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base)) return rerror ? (ptrdiff_t)-1 : 0; @@ -1381,14 +1438,14 @@ remove_deleted_methods(std::vector& methods) { void interop::GetClassMethods(TCppScope_t scope, std::vector& methods) { - std::lock_guard Lock(InterOpMutex); + 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::lock_guard Lock(InterOpMutex); std::vector methods = Cpp::GetFunctionsUsingName(scope, name); remove_deleted_methods(methods); @@ -1396,22 +1453,22 @@ interop::GetMethodsFromName(TCppScope_t scope, const std::string& name) { } std::string interop::GetName(TCppScope_t method) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetName(method); } std::string interop::GetFullName(TCppScope_t method) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionReturnType(method); } std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) { - std::lock_guard Lock(InterOpMutex); + 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 @@ -1425,12 +1482,12 @@ std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) { } interop::TCppIndex_t interop::GetMethodNumArgs(TCppMethod_t method) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionNumArgs(method); } interop::TCppIndex_t interop::GetMethodReqArgs(TCppMethod_t method) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionRequiredArgs(method); } @@ -1438,26 +1495,26 @@ std::string interop::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { if (!method) return ""; - std::lock_guard Lock(InterOpMutex); + 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); + 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); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString( Cpp::GetCanonicalType(Cpp::GetFunctionArgType(method, iarg))); } @@ -1467,7 +1524,7 @@ std::string interop::GetMethodArgDefault(TCppMethod_t method, if (!method) return ""; - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionArgDefault(method, iarg); } @@ -1573,26 +1630,26 @@ bool interop::IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2) { } std::string interop::GetDoxygenComment(TCppScope_t scope, bool strip_markers) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::IsConstMethod(method); } void interop::GetTemplatedMethods(TCppScope_t scope, std::vector& methods) { - std::lock_guard Lock(InterOpMutex); + 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::lock_guard Lock(InterOpMutex); std::vector mc; Cpp::GetFunctionTemplatedDecls(scope, mc); return mc.size(); @@ -1600,7 +1657,7 @@ interop::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) { std::string interop::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::vector mc; Cpp::GetFunctionTemplatedDecls(scope, mc); @@ -1611,7 +1668,7 @@ std::string interop::GetTemplatedMethodName(TCppScope_t scope, } bool interop::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::ExistsFunctionTemplate(name, scope); } @@ -1647,7 +1704,7 @@ interop::TCppMethod_t interop::GetMethodTemplate(TCppScope_t scope, pureName = name; } - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::vector unresolved_candidate_methods; Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); @@ -1723,7 +1780,7 @@ static inline std::string type_remap(const std::string& n1, void interop::GetClassOperators(interop::TCppScope_t klass, const std::string& opname, std::vector& operators) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::string op = opname.substr(8); Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, /*kind=*/Cpp::OperatorArity::kBoth); @@ -1804,14 +1861,14 @@ bool interop::IsExplicit(TCppMethod_t method) { // data member reflection information ---------------------------------------- void interop::GetDatamembers(TCppScope_t scope, std::vector& datamembers) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return (bool)Cpp::LookupDatamember(name, scope); } @@ -1820,7 +1877,7 @@ bool interop::IsLambdaClass(TCppType_t type) { } interop::TCppScope_t interop::WrapLambdaFromVariable(TCppScope_t var) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::ostringstream code; std::string name = interop::GetFinalName(var); code << "namespace __cppjit_internal_wrap_g {\n" @@ -1839,7 +1896,7 @@ interop::TCppScope_t interop::WrapLambdaFromVariable(TCppScope_t var) { interop::TCppMethod_t interop::AdaptFunctionForLambdaReturn(interop::TCppMethod_t fn) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); std::string signature = interop::GetMethodSignature(fn, true); @@ -1870,23 +1927,23 @@ interop::AdaptFunctionForLambdaReturn(interop::TCppMethod_t fn) { } interop::TCppType_t interop::GetDatamemberType(TCppScope_t var) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); } std::string interop::GetDatamemberTypeAsString(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString(type); } intptr_t interop::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); } @@ -1911,7 +1968,7 @@ 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::lock_guard Lock(InterOpMutex); std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); std::string signature = interop::GetMethodSignature(fn, true); @@ -1943,35 +2000,35 @@ interop::TCppMethod_t interop::ReduceReturnType(TCppMethod_t fn, } std::vector interop::GetDimensions(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetDimensions(type); } // enum properties ----------------------------------------------------------- std::vector interop::GetEnumConstants(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetEnumConstants(scope); } interop::TCppType_t interop::GetEnumConstantType(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); } long long interop::GetEnumDataValue(TCppScope_t scope) { - std::lock_guard Lock(InterOpMutex); + 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); + 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); + std::lock_guard Lock(InterOpMutex); Cpp::DumpScope(scope); } diff --git a/bindings/pyroot/cppjit/test/test_root_interpreter_lock.py b/bindings/pyroot/cppjit/test/test_root_interpreter_lock.py new file mode 100644 index 0000000000000..44e7d98d180f2 --- /dev/null +++ b/bindings/pyroot/cppjit/test/test_root_interpreter_lock.py @@ -0,0 +1,49 @@ +class TestROOTINTERPRETERLOCK: + def test01_declare_holds_the_interpreter_lock(self): + """cppdef runs under gInterpreterMutex once ROOT thread safety is on""" + + import cppjit + + cppjit.gbl.ROOT.EnableThreadSafety() + cppjit.cppdef(r""" + #include "TInterpreter.h" + #include "TVirtualMutex.h" + #include + #include + #include + + namespace lock_probe { + std::atomic in_decl{false}, done{false}, saw_in_decl{false}; + + // Waits until a declaration is being processed, then takes the + // interpreter lock: when the declaring thread holds it, this + // only succeeds once the declaration has finished. + void contend() { + while (!in_decl) + std::this_thread::yield(); + R__LOCKGUARD(gInterpreterMutex); + saw_in_decl = in_decl.load(); + done = true; + } + + // Static initializer of the probe declaration: gives the + // contender up to 300 ms to acquire the lock meanwhile. + int spin() { + in_decl = true; + auto t0 = std::chrono::steady_clock::now(); + while (!done && std::chrono::steady_clock::now() - t0 < + std::chrono::milliseconds(300)) + std::this_thread::yield(); + in_decl = false; + return 0; + } + + std::thread* start() { return new std::thread(contend); } + bool contender_saw_declaration() { return saw_in_decl; } + }""") + + thread = cppjit.gbl.lock_probe.start() + cppjit.cppdef("namespace lock_probe { int marker = spin(); }") + thread.join() + + assert not cppjit.gbl.lock_probe.contender_saw_declaration() From 39b519ca47766dcf9db0c8491e223f96ccb07ca7 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 18:11:03 +0200 Subject: [PATCH 42/53] [cppjit] Resolve cppjit.gbl.gROOT to its underlying variable [ROOT-patch] gROOT is a macro expanding to ROOT::GetROOT(), and the TGlobalMappedFunction ROOT registers for it is not consulted by this backend. Resolve it to ROOT::Internal::gROOTLocal instead, initializing it first. --- .../cppjit/src/interop/interop_wrapper.cxx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx index f4fd20470af47..22f8ffb675122 100644 --- a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx +++ b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx @@ -841,7 +841,22 @@ interop::TCppScope_t interop::GetTypeScope(TCppScope_t var) { interop::TCppScope_t interop::GetNamed(const std::string& name, TCppScope_t parent_scope) { std::lock_guard Lock(InterOpMutex); - return Cpp::GetNamed(name, parent_scope); + 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) { From 6574b0d1593bfbe5f7d4f68700f01ee0636df596 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 18:12:56 +0200 Subject: [PATCH 43/53] [cppjit] Apply ROOT-specific patches [ROOT-patch] Frontend adaptations for ROOT: - Expose gbl.gInterpreter via TInterpreter::Instance() - Route load_library, add_library_path and load_reflection_info through gSystem so library resolution follows ROOT's dynamic path (with the Windows winmode=0 special case for absolute paths) - Check cppexec's captured diagnostics besides the Process return code, which covers parse errors only: on Windows a runtime lookup failure's exception cannot unwind through the JITed wrapper frame, so nothing is raised and the code stays 0 --- .../pyroot/cppjit/python/cppjit/__init__.py | 55 ++++++++----------- .../cppjit/python/cppjit/_cpython_cppjit.py | 26 +++++---- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/bindings/pyroot/cppjit/python/cppjit/__init__.py b/bindings/pyroot/cppjit/python/cppjit/__init__.py index ce30ffbaaea62..3d0282af458fa 100644 --- a/bindings/pyroot/cppjit/python/cppjit/__init__.py +++ b/bindings/pyroot/cppjit/python/cppjit/__init__.py @@ -52,7 +52,6 @@ import contextlib import ctypes -import importlib.util import os import sys import sysconfig @@ -206,6 +205,8 @@ def __getitem__(self, cls): _install_smartptr_makers() +gbl.gInterpreter = gbl.TInterpreter.Instance() + # --- interface to Cling ------------------------------------------------------ class _stderr_capture(object): @@ -265,7 +266,13 @@ def cppexec(stmt): if not errcode.value: errcode.value = 1 - if not errcode == 0: + # 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:]) @@ -298,10 +305,20 @@ def macro(cppm): def load_library(name): """Explicitly load a shared library.""" - reason = gbl.std.string() - result = gbl.Cpp.LoadLibrary(name, True, reason) - if result == False: # noqa: E712 - raise RuntimeError('Could not load library "%s": %s' % (name, reason)) + 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 @@ -342,7 +359,7 @@ 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.Cpp.AddSearchPath(path, True, False) + gbl.gSystem.AddDynamicPath(path) def _setup_include_paths(): @@ -360,30 +377,6 @@ def _setup_include_paths(): ): add_include_path(apipath) - # add access to the cpyrt dispatcher API headers, which install next to the - # extension module; anchoring on the extension resolves editable and regular - # installs alike. CPPJIT_API_PATH overrides ("none" disables the lookup). - if not _ispypy: - apipath_extra = os.environ.get("CPPJIT_API_PATH") - if apipath_extra: - if os.path.basename(apipath_extra) == "cpyrt": - apipath_extra = os.path.dirname(apipath_extra) - else: - spec = importlib.util.find_spec("cppjit.libcppjit") - if spec is not None and spec.origin: - apipath_extra = os.path.join( - os.path.dirname(spec.origin), "interop", "include" - ) - - if apipath_extra and apipath_extra.lower() != "none": - if os.path.isdir(os.path.join(apipath_extra, "cpyrt")): - add_include_path(apipath_extra) - else: - warnings.warn( - "cpyrt API not found (tried: %s); set CPPJIT_API_PATH envar to the 'cpyrt' API directory to fix" - % apipath_extra - ) - if os.getenv("CONDA_PREFIX"): # MacOS, Linux include_path = os.path.join(os.getenv("CONDA_PREFIX"), "include") diff --git a/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py index 69f42845243b2..cb4e4ec52525f 100644 --- a/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py +++ b/bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py @@ -2,9 +2,17 @@ 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", @@ -27,6 +35,11 @@ def _preload_backend_library(): 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 @@ -194,16 +207,9 @@ def _add_default_paths(): def load_reflection_info(name): - # with _stderr_capture() as err: - # FIXME: Remove the .so and add logic in libcppinterop - name = name + ".so" - result = Cpp.LoadLibrary(name, True) - if name.endswith("Dict.so"): - header = name[:-7] + ".h" - Cpp.Declare('#include "' + header + '"', False) - - if result == False: # noqa: E712 - raise RuntimeError('Could not load library "%s"' % (name)) + sc = gbl.gSystem.Load(name) + if sc == -1: + raise RuntimeError("Unable to load reflection library " + name) return True From d93852140bb43a59c33c20145c40b872084b2a59 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 8 Sep 2026 18:49:02 +0200 Subject: [PATCH 44/53] [cppjit] Adjust cppjit test expectations for the interop migration [ROOT-patch] Includes loading the test dictionaries by their ACLiC names. --- .../pyroot/cppjit/test/test_aclassloader.py | 2 +- bindings/pyroot/cppjit/test/test_advancedcpp.py | 10 +++++++--- bindings/pyroot/cppjit/test/test_basic_api.py | 7 ++++--- bindings/pyroot/cppjit/test/test_concurrent.py | 3 ++- bindings/pyroot/cppjit/test/test_conversions.py | 2 +- .../pyroot/cppjit/test/test_cpp11features.py | 2 +- .../pyroot/cppjit/test/test_cpp23features.py | 5 +++++ .../pyroot/cppjit/test/test_crossinheritance.py | 5 ++++- bindings/pyroot/cppjit/test/test_datatypes.py | 12 +++--------- .../pyroot/cppjit/test/test_doc_features.py | 4 +++- bindings/pyroot/cppjit/test/test_fragile.py | 17 +++++++++++++++-- bindings/pyroot/cppjit/test/test_lowlevel.py | 8 +++++++- bindings/pyroot/cppjit/test/test_operators.py | 2 +- bindings/pyroot/cppjit/test/test_overloads.py | 2 +- bindings/pyroot/cppjit/test/test_pythonify.py | 2 +- .../pyroot/cppjit/test/test_pythonization.py | 2 +- bindings/pyroot/cppjit/test/test_regression.py | 3 +++ bindings/pyroot/cppjit/test/test_stltypes.py | 14 ++++++++------ bindings/pyroot/cppjit/test/test_streams.py | 2 +- bindings/pyroot/cppjit/test/test_templates.py | 4 ++-- 20 files changed, 71 insertions(+), 37 deletions(-) diff --git a/bindings/pyroot/cppjit/test/test_aclassloader.py b/bindings/pyroot/cppjit/test/test_aclassloader.py index 371a0d456ddf6..a8f4da6fa2054 100644 --- a/bindings/pyroot/cppjit/test/test_aclassloader.py +++ b/bindings/pyroot/cppjit/test/test_aclassloader.py @@ -2,7 +2,7 @@ from support import setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/example01Dict")) +test_dct = "example01_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_advancedcpp.py b/bindings/pyroot/cppjit/test/test_advancedcpp.py index 87aa9ab9679b4..b59ddc59e1af4 100644 --- a/bindings/pyroot/cppjit/test/test_advancedcpp.py +++ b/bindings/pyroot/cppjit/test/test_advancedcpp.py @@ -11,7 +11,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/advancedcppDict")) +test_dct = "advancedcpp_cxx" def setup_module(mod): @@ -174,7 +174,7 @@ def test03a_namespace_lookup_on_update(self): gbl = cppjit.gbl - lib2 = cppjit.load_reflection_info(str(currpath.join("cpp/advancedcpp2Dict"))) + 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 @@ -784,6 +784,11 @@ def test21a_autocast_pointer_variables(self): 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""" @@ -807,7 +812,6 @@ def test22_exceptions(self): caught = True assert caught == True - @mark.xfail(reason="using-declared overloads expose the base class signature") def test23_using(self): """Accessibility of using declarations""" diff --git a/bindings/pyroot/cppjit/test/test_basic_api.py b/bindings/pyroot/cppjit/test/test_basic_api.py index 2618e2a9fbed8..c6f9402679014 100644 --- a/bindings/pyroot/cppjit/test/test_basic_api.py +++ b/bindings/pyroot/cppjit/test/test_basic_api.py @@ -3,12 +3,12 @@ import tempfile import py -from pytest import raises +from pytest import mark, raises from support import needs_dictionary, setup_make, soext # reuse the example01 currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/example01Dict")) +test_dct = "example01_cxx" def setup_module(mod): @@ -55,13 +55,14 @@ def test03_add_library_path(self): # now we should actually see if load library can follow this... # first, try to load without moving to directory... - with raises(RuntimeError, match="Could not load library"): + 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""" diff --git a/bindings/pyroot/cppjit/test/test_concurrent.py b/bindings/pyroot/cppjit/test/test_concurrent.py index 026fdcc0abbfa..cae84025be112 100644 --- a/bindings/pyroot/cppjit/test/test_concurrent.py +++ b/bindings/pyroot/cppjit/test/test_concurrent.py @@ -1,5 +1,5 @@ from pytest import mark, skip -from support import IS_LINUX_ARM, IS_MAC_ARM +from support import IS_LINUX_ARM, IS_MAC_ARM, IS_WINDOWS class TestCONCURRENT: @@ -89,6 +89,7 @@ def test03_timeout(self): if t.is_alive(): # was timed-out cppjit.gbl.test12_timeout.stopit[0] = True + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test04_cpp_threading_with_exceptions(self): """Threads and Python exceptions""" diff --git a/bindings/pyroot/cppjit/test/test_conversions.py b/bindings/pyroot/cppjit/test/test_conversions.py index d668f29548d2d..803d48b5cfc0b 100644 --- a/bindings/pyroot/cppjit/test/test_conversions.py +++ b/bindings/pyroot/cppjit/test/test_conversions.py @@ -3,7 +3,7 @@ from support import IS_CLANG_REPL, IS_CLING, IS_MAC, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/conversionsDict")) +test_dct = "conversions_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_cpp11features.py b/bindings/pyroot/cppjit/test/test_cpp11features.py index a221480a8758c..0a475572bae85 100644 --- a/bindings/pyroot/cppjit/test/test_cpp11features.py +++ b/bindings/pyroot/cppjit/test/test_cpp11features.py @@ -12,7 +12,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/cpp11featuresDict")) +test_dct = "cpp11features_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_cpp23features.py b/bindings/pyroot/cppjit/test/test_cpp23features.py index 16b5423688de1..683345ed48909 100644 --- a/bindings/pyroot/cppjit/test/test_cpp23features.py +++ b/bindings/pyroot/cppjit/test/test_cpp23features.py @@ -295,6 +295,11 @@ def test15_deducing_this_inheritance(self): # 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 diff --git a/bindings/pyroot/cppjit/test/test_crossinheritance.py b/bindings/pyroot/cppjit/test/test_crossinheritance.py index 2e537df968eca..05ffb20a998eb 100644 --- a/bindings/pyroot/cppjit/test/test_crossinheritance.py +++ b/bindings/pyroot/cppjit/test/test_crossinheritance.py @@ -8,11 +8,12 @@ IS_MAC, IS_MAC_ARM, IS_VALGRIND, + IS_WINDOWS, setup_make, ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/crossinheritanceDict")) +test_dct = "crossinheritance_cxx" def setup_module(mod): @@ -260,6 +261,7 @@ def get_value(self): assert p1.get_value() == 13 @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""" @@ -1630,6 +1632,7 @@ def extra_func(self, d): assert p.func(d) == 42 + 2 * d.value + @mark.xfail(strict=True) def test33_direct_base_methods(self): """Call base class methods directly""" diff --git a/bindings/pyroot/cppjit/test/test_datatypes.py b/bindings/pyroot/cppjit/test/test_datatypes.py index 96d6307087703..5e508d32dcee6 100644 --- a/bindings/pyroot/cppjit/test/test_datatypes.py +++ b/bindings/pyroot/cppjit/test/test_datatypes.py @@ -2,10 +2,10 @@ import py from pytest import mark, raises, skip -from support import IS_CLANG_REPL, IS_CPP23, IS_MAC, pylong, pyunicode, setup_make +from support import IS_CLANG_REPL, IS_MAC, IS_WINDOWS, pylong, pyunicode, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/datatypesDict")) +test_dct = "datatypes_cxx" def setup_module(mod): @@ -20,13 +20,6 @@ def setup_class(cls): cls.datatypes = cppjit.load_reflection_info(cls.test_dct) cls.N = 5 # cppjit.gbl.N - @mark.xfail( - condition=IS_CPP23 and not IS_MAC, - reason="since C++23 (P1467) the narrowing std::complex to " - "complex constructor is explicit, breaking the implicit " - "conversion of Python complex arguments; libc++ does not implement " - "P1467 yet, so the conversion still succeeds on macOS", - ) def test01_instance_data_read_access(self): """Read access to instance public data and verify values""" @@ -2704,6 +2697,7 @@ def test53_basic_nanoseconds_goodness(self): condition=IS_MAC, reason="std::optional::value_or rvalue conversion fails on OS X clang-repl (InstanceMoveConverter)", ) + @mark.xfail(condition=IS_WINDOWS, run=False, reason="Crashes assigning to a global std::optional") def test54_optional_use(self): import cppjit diff --git a/bindings/pyroot/cppjit/test/test_doc_features.py b/bindings/pyroot/cppjit/test/test_doc_features.py index 52bb06720821e..82b6a8c58ba29 100644 --- a/bindings/pyroot/cppjit/test/test_doc_features.py +++ b/bindings/pyroot/cppjit/test/test_doc_features.py @@ -14,7 +14,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/doc_helperDict")) +test_dct = "doc_helper_cxx" def setup_module(mod): @@ -455,6 +455,7 @@ def abstract_method2(self): run=False, reason="Crashes with exception not being caught on Apple Silicon", ) + @mark.xfail(condition=IS_WINDOWS == 64, run=False, reason="Crashes on Windows 64 bit") def test_exceptions(self): """Exception throwing and catching""" @@ -1314,6 +1315,7 @@ class MyException : public std::exception { with raises(CC.MyException): CC.throw_error() + @mark.xfail(strict=True) def test_unicode(self): """Unicode non-UTF-8 example""" diff --git a/bindings/pyroot/cppjit/test/test_fragile.py b/bindings/pyroot/cppjit/test/test_fragile.py index b72a5b3b05ba1..4c348344030d9 100644 --- a/bindings/pyroot/cppjit/test/test_fragile.py +++ b/bindings/pyroot/cppjit/test/test_fragile.py @@ -14,7 +14,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/fragileDict")) +test_dct = "fragile_cxx" def setup_module(mod): @@ -247,7 +247,7 @@ def test10_documentation(self): except TypeError as e: assert "cannot instantiate abstract class 'fragile::O'" in str(e) - @mark.xfail(condition=IS_MAC, reason="Fails on OSX") + @mark.xfail(condition=IS_MAC or IS_WINDOWS, reason="Fails on OSX and Windows") def test11_dir(self): """Test __dir__ method""" @@ -489,6 +489,7 @@ def test18_overload(self): ]: assert cppjit.gbl.Variable.__init__.__overload__(sig) + @mark.xfail(run=False) def test19_gbl_contents(self): """Assure cppjit.gbl is mostly devoid of ROOT thingies""" @@ -606,6 +607,11 @@ def test24_asan(self): cppjit.include("sanitizer/asan_interface.h") @mark.xfail(reason="cppdef of invalid code does not raise SyntaxError") + @mark.xfail( + run=False, + reason="rolling back a failed declaration asserts in ROOT's unload " + "callbacks (debug builds); reproduces with plain cppyy on master", + ) def test25_cppdef_error_reporting(self): """Check error reporting of cppjit.cppdef""" @@ -647,6 +653,11 @@ def test25_cppdef_error_reporting(self): }""") @mark.xfail(condition=IS_CLANG_REPL, reason="Fails on ClangRepl") + @mark.xfail( + run=False, + reason="rolling back a failed declaration asserts in ROOT's unload " + "callbacks (debug builds); reproduces with plain cppyy on master", + ) def test26_macro(self): """Test access to C++ pre-processor macro's""" @@ -846,6 +857,7 @@ class TestSTDNOTINGLOBAL: def setup_class(cls): pass + @mark.xfail(strict=True) def test01_stl_in_std(self): """STL classes should live in std:: only""" @@ -891,6 +903,7 @@ def test03_clashing_using_in_global(self): for name in ["int", "uint", "ushort", "uchar", "byte"]: getattr(cppjit.gbl, name) + @mark.xfail(strict=True) def test04_no_legacy(self): """Test some functions that previously crashed""" diff --git a/bindings/pyroot/cppjit/test/test_lowlevel.py b/bindings/pyroot/cppjit/test/test_lowlevel.py index 0d8408e63fa8c..a5789ff48482d 100644 --- a/bindings/pyroot/cppjit/test/test_lowlevel.py +++ b/bindings/pyroot/cppjit/test/test_lowlevel.py @@ -12,7 +12,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/datatypesDict")) +test_dct = "datatypes_cxx" def setup_module(mod): @@ -538,6 +538,11 @@ def py2c(pyargs): with raises(TypeError): cppjit.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) + @mark.xfail( + condition=IS_WINDOWS == 64, + run=False, + reason="Windows fatal exception: access violation", + ) def test11_array_of_const_char_ref(self): """Test passting of const char**&""" @@ -1103,6 +1108,7 @@ def test07_3D_custom_struct(self): for k in range(gbl.S + 7): assert gbl.consume_klass(gbl.klasses[i][j][k], i, j, k) + @mark.xfail(condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") def test08_reshape_sets_unknown_dimensions_only(self): """Reshaping fills in the dimensions the type leaves open""" diff --git a/bindings/pyroot/cppjit/test/test_operators.py b/bindings/pyroot/cppjit/test/test_operators.py index e1124b0cde74a..eda1b1e2fb787 100644 --- a/bindings/pyroot/cppjit/test/test_operators.py +++ b/bindings/pyroot/cppjit/test/test_operators.py @@ -8,7 +8,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/operatorsDict")) +test_dct = "operators_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_overloads.py b/bindings/pyroot/cppjit/test/test_overloads.py index e7fe51356db98..9c774a8fe54c9 100644 --- a/bindings/pyroot/cppjit/test/test_overloads.py +++ b/bindings/pyroot/cppjit/test/test_overloads.py @@ -3,7 +3,7 @@ from support import IS_MAC, IS_MAC_ARM, IS_WINDOWS, ispypy, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/overloadsDict")) +test_dct = "overloads_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_pythonify.py b/bindings/pyroot/cppjit/test/test_pythonify.py index 47c982553087d..0355b0adb469a 100644 --- a/bindings/pyroot/cppjit/test/test_pythonify.py +++ b/bindings/pyroot/cppjit/test/test_pythonify.py @@ -8,7 +8,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/example01Dict")) +test_dct = "example01_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_pythonization.py b/bindings/pyroot/cppjit/test/test_pythonization.py index f8235460975b2..191f062a8863f 100644 --- a/bindings/pyroot/cppjit/test/test_pythonization.py +++ b/bindings/pyroot/cppjit/test/test_pythonization.py @@ -11,7 +11,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/pythonizablesDict")) +test_dct = "pythonizables_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_regression.py b/bindings/pyroot/cppjit/test/test_regression.py index af1a12be3e84a..c879ec1b8d2af 100644 --- a/bindings/pyroot/cppjit/test/test_regression.py +++ b/bindings/pyroot/cppjit/test/test_regression.py @@ -132,6 +132,7 @@ def test03_pyfunc_doc(self): assert 1 == cppjit.gbl.py2long(1) + @mark.skip(reason="For ROOT, we don't enable AVX by default ('-mavx' is not passed to Cling)") def test04_avx(self): """Test usability of AVX by default.""" @@ -1439,6 +1440,7 @@ def test45_typedef_resolution(self): run=False, reason="Crashes with exception not being caught on Apple Silicon", ) + @mark.xfail(condition=IS_WINDOWS == 64, run=False, reason="Crashes on Windows 64 bit") def test46_exception_narrowing(self): """Exception narrowing to C++ exception of all overloads""" @@ -1573,6 +1575,7 @@ def test50_using_decl_base_this_offset(self): # destruction must not crash (heap integrity preserved) del d + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test51_nontype_enum_template_arg(self): """Regression test for a class template with a non-type enum parameter diff --git a/bindings/pyroot/cppjit/test/test_stltypes.py b/bindings/pyroot/cppjit/test/test_stltypes.py index ad318c4f13ba4..b9aec12cce73c 100644 --- a/bindings/pyroot/cppjit/test/test_stltypes.py +++ b/bindings/pyroot/cppjit/test/test_stltypes.py @@ -11,6 +11,7 @@ IS_MAC_ARM, IS_MAC_X86, IS_VALGRIND, + IS_WINDOWS, ispypy, maxvalue, pylong, @@ -19,7 +20,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/stltypesDict")) +test_dct = "stltypes_cxx" def setup_module(mod): @@ -1006,8 +1007,6 @@ def test04_array_of_strings(self): assert tuple(cppjit.gbl.str_array_1) == ("a", "b", "c") str_array_2 = cppjit.gbl.str_array_2 - # fix up the size - str_array_2.size = 4 assert tuple(str_array_2) == ("d", "e", "f", "g") assert tuple(str_array_2) == ("d", "e", "f", "g") @@ -1907,6 +1906,7 @@ def test02_string_view_from_unicode(self): assert "Lorem ipsum dolor sit amet" in str(text) @mark.xfail(condition=IS_MAC, run=False, reason="Crashes on OSX") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test03_string_view_pythonize(self): """Pythonization of std::string_view""" @@ -2345,6 +2345,7 @@ def run_raiseit(t1, t2): assert cppjit.gbl.GetMyErrorCount() == 0 @mark.xfail(condition=IS_MAC_ARM, run=False, reason="Seg Faults on OSX-ARM") + @mark.xfail(condition=IS_WINDOWS == 64, run=False, reason="Crashes on Windows 64 bit") def test04_from_cpp(self): """Catch C++ exceptiosn from C++""" @@ -2495,6 +2496,7 @@ def test02_span_argument_conversions(self): class TestSTLANY: + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test01_make_any(self): """ Test that std::make_any can be used for class types. @@ -2522,9 +2524,9 @@ def test01_python_complex_template_arg(self): import cppjit - assert ( - cppjit.gbl.std.vector[complex].__cpp_name__ - == "std::vector>" + # ROOT's backend prints names with expanded default template arguments + assert cppjit.gbl.std.vector[complex].__cpp_name__ == ( + "std::vector,std::allocator > >" ) cppjit.cppdef("""\ diff --git a/bindings/pyroot/cppjit/test/test_streams.py b/bindings/pyroot/cppjit/test/test_streams.py index 804538264bec5..d21556a73e608 100644 --- a/bindings/pyroot/cppjit/test/test_streams.py +++ b/bindings/pyroot/cppjit/test/test_streams.py @@ -2,7 +2,7 @@ from support import setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/std_streamsDict")) +test_dct = "std_streams_cxx" def setup_module(mod): diff --git a/bindings/pyroot/cppjit/test/test_templates.py b/bindings/pyroot/cppjit/test/test_templates.py index 6fc9cb497d1ac..584c60c0e0c97 100644 --- a/bindings/pyroot/cppjit/test/test_templates.py +++ b/bindings/pyroot/cppjit/test/test_templates.py @@ -11,7 +11,7 @@ ) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp/templatesDict")) +test_dct = "templates_cxx" def setup_module(mod): @@ -527,7 +527,6 @@ def test18_greedy_overloads(self): assert g3.get_size(ns.SomeClass()) == cppjit.sizeof(ns.SomeClass) assert g3.get_size(cppjit.nullptr, True) == -1 - @mark.xfail(condition=IS_CLING, reason="Fails on Cling") def test19_templated_operator_add(self): """Templated operator+ is ambiguous: either __pos__ or __add__""" @@ -1574,6 +1573,7 @@ def test04_type_deduction(self): three = w.whatis(3) assert three == 3 + @mark.xfail(strict=True) def test05_type_deduction_and_extern(self): """Usage of type reducer with extern template""" From a1f3053d73531d19ed875e80c597b68d4808f62a Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 16:38:47 +0200 Subject: [PATCH 45/53] [cppjit] Build the vendored cppjit as PyROOT's cpyrt [ROOT-patch] ROOT-owned CMake files inside the vendored tree (src/, python/, test/; upstream's scikit-build/ExternalProject CMakeLists.txt stays vendored but unreferenced, keeping future vendor bumps pure): cppjit_backend STATIC on the etc/cppinterop staging + CppInterOpEtc, cpyrt SHARED with unresolved-symbol link flags and CPPJIT_USE_CLING (the interpreter is cling), a thin libcppjit python module, and the pytest suite wired through ctest with ACLiC-compiled dictionaries. Includes the PyInit_libcppjit -> Init module split enabling ROOT's three-target wiring. The build enters these files in the next commit. The interop layer locates the staged CppInterOp headers through TROOT::GetEtcDir() instead of a build-directory prefix compiled into libcpyrt, so an installed ROOT does not depend on its build tree (the dispatch API itself comes from libCling). The cppjit extension module installs into the cppjit package directory on every platform, which is where its loader looks for it. --- bindings/pyroot/cppjit/python/CMakeLists.txt | 19 ++ bindings/pyroot/cppjit/src/CMakeLists.txt | 196 ++++++++++++++++++ .../pyroot/cppjit/src/cpyrt/cpyrtModule.cxx | 2 +- .../pyroot/cppjit/src/cpyrt/cpyrtModule.h | 12 ++ .../pyroot/cppjit/src/cpyrt/cpyrtPyModule.cxx | 4 + .../cppjit/src/interop/interop_wrapper.cxx | 47 +---- bindings/pyroot/cppjit/test/CMakeLists.txt | 127 ++++++++++++ 7 files changed, 369 insertions(+), 38 deletions(-) create mode 100644 bindings/pyroot/cppjit/python/CMakeLists.txt create mode 100644 bindings/pyroot/cppjit/src/CMakeLists.txt create mode 100644 bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.h create mode 100644 bindings/pyroot/cppjit/src/cpyrt/cpyrtPyModule.cxx create mode 100644 bindings/pyroot/cppjit/test/CMakeLists.txt 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/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/cpyrtModule.cxx b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx index d716fd1d77982..ac88c10c270de 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx +++ b/bindings/pyroot/cppjit/src/cpyrt/cpyrtModule.cxx @@ -920,7 +920,7 @@ static struct PyModuleDef moduledef = { namespace cppjit::cpyrt { //---------------------------------------------------------------------------- -extern "C" PyObject* PyInit_libcppjit() { +PyObject* Init() { // Initialization of extension module libcppjit. // load commonly used python strings 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/interop_wrapper.cxx b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx index 22f8ffb675122..dbd9e0c54250e 100644 --- a/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx +++ b/bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx @@ -23,10 +23,6 @@ typedef cppjit::cpyrt::Parameter Parameter; static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } -#ifndef _WIN32 -#include -#endif - // Standard #include // for std::count, std::remove #include @@ -34,7 +30,6 @@ static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } #include #include // for getenv #include -#include #include #include #include @@ -129,40 +124,18 @@ struct InterOpPaths { std::string ClangIncludeDir; // empty when no usable resource dir is known }; -// One set of coordinates, two anchors: prefer CppInterOp next to our own -// load location so wheels relocate; fall back to the build-time install -// prefix. An absolute coordinate (an external CppInterOp) replaces the -// anchor in the join ([fs.path.append]). +// 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() { - std::filesystem::path anchor = CPPINTEROP_INSTALL_PREFIX; -#ifndef _WIN32 - Dl_info info; - if (dladdr((void*)&cppinterop_paths, &info) && info.dli_fname) { - const std::filesystem::path here = - std::filesystem::path(info.dli_fname).parent_path(); - std::error_code ec; - if (std::filesystem::exists(here / CPPINTEROP_LIBRARY, ec)) - anchor = here; - } -#endif InterOpPaths Paths; - Paths.Library = (anchor / CPPINTEROP_LIBRARY).string(); - // The include coordinate may carry several ':'-separated directories (an - // external CppInterOp build tree splits source and generated headers). - std::istringstream includeSpec{CPPINTEROP_INCLUDE_DIR}; - for (std::string dir; std::getline(includeSpec, dir, ':');) - if (!dir.empty()) - Paths.IncludeDirs.push_back((anchor / dir).string()); - // A bundled install ships the build clang's builtin headers (see the - // CMake install rule); an empty coordinate or a missing directory falls - // back to resource-dir detection. - const std::string clangSpec = CPPJIT_CLANG_INCLUDE_DIR; - if (!clangSpec.empty()) { - const std::filesystem::path bundled = anchor / clangSpec; - std::error_code ec; - if (std::filesystem::exists(bundled / "include", ec)) - Paths.ClangIncludeDir = bundled.string(); - } + 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; } 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) From 8a2c1cbb8d05f5ff61140a333a5363c47e600be0 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 29 Sep 2026 16:38:47 +0200 Subject: [PATCH 46/53] [cppjit] Rewire PyROOT from the cppyy stack to vendored cppjit [ROOT-patch] Switch commit: the tree moves from bindings/pyroot/cppyy to the vendored cppjit in one step so it never holds two live binding stacks. - Enter the vendored tree's ROOT-owned CMake files (previous commit) from bindings/pyroot/CMakeLists.txt - Install a cppyy compatibility alias package: import cppyy resolves to cppjit via sys.modules registration plus a meta-path finder, so cppyy.gbl call sites (ROOT facade, roottest, tutorials) keep working; the alias loader keeps the module's own __spec__, which importlib overwrites on the way - Retarget pythonizations and TPython to the cpyrt API (includes, namespaces, link targets) and the type-repr checks to cppjit spellings, including the canonical std::basic_string forms in the std::string exclusion list and gInterpreter cached on the facade; CPPYY_API_PATH=none and CPPYY_NO_ROOT_FILTER are dropped (cppjit has neither the header check nor the filter) - Pythonize template instantiations that cppjit caches in the namespace (they carry __cpp_template__ too), and test membership against ns_vars.values(): the previous `instance not in ns_vars` looked at the keys, a master bug - Adapt the roottest atlas-datavector template-instantiation test: with cppjit, instantiating foo> succeeds, since resolving the name autoloads the dictionary and the default second template argument becomes visible, so the test asserts the new behaviour and checks the demangled typeid name of the instantiation - Retarget the PyException regression macro (ScottCppyy.C and the regression test) from CPyCppyy::PyException to cppjit::cpyrt::PyException and the cpyrt/PyException.h header - Export CppGetProcAddress from libCling's linker script for the dispatch mechanism - Drop the superseded bindings/pyroot/cppyy tree, including master's patches/ machinery --- bindings/pyroot/CMakeLists.txt | 8 +- bindings/pyroot/cppyy/CMakeLists.txt | 9 - bindings/pyroot/cppyy/CPyCppyy/.gitignore | 17 - bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt | 133 - bindings/pyroot/cppyy/CPyCppyy/LICENSE.txt | 58 - bindings/pyroot/cppyy/CPyCppyy/MANIFEST.in | 10 - bindings/pyroot/cppyy/CPyCppyy/README.rst | 31 - .../cppyy/CPyCppyy/include/CPyCppyy/API.h | 231 - .../CPyCppyy/include/CPyCppyy/CommonDefs.h | 34 - .../CPyCppyy/include/CPyCppyy/DispatchPtr.h | 76 - .../CPyCppyy/include/CPyCppyy/PyException.h | 56 - .../cppyy/CPyCppyy/include/CPyCppyy/Reflex.h | 30 - bindings/pyroot/cppyy/CPyCppyy/pyproject.toml | 2 - bindings/pyroot/cppyy/CPyCppyy/setup.cfg | 5 - bindings/pyroot/cppyy/CPyCppyy/setup.py | 133 - bindings/pyroot/cppyy/CPyCppyy/src/API.cxx | 470 -- .../cppyy/CPyCppyy/src/CPPClassMethod.cxx | 58 - .../cppyy/CPyCppyy/src/CPPClassMethod.h | 25 - .../cppyy/CPyCppyy/src/CPPConstructor.cxx | 385 -- .../cppyy/CPyCppyy/src/CPPConstructor.h | 89 - .../cppyy/CPyCppyy/src/CPPDataMember.cxx | 435 -- .../pyroot/cppyy/CPyCppyy/src/CPPDataMember.h | 79 - .../pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx | 238 - bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h | 16 - .../cppyy/CPyCppyy/src/CPPExcInstance.cxx | 291 -- .../cppyy/CPyCppyy/src/CPPExcInstance.h | 39 - .../pyroot/cppyy/CPyCppyy/src/CPPFunction.cxx | 157 - .../pyroot/cppyy/CPyCppyy/src/CPPFunction.h | 45 - .../cppyy/CPyCppyy/src/CPPGetSetItem.cxx | 119 - .../pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.h | 35 - .../pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx | 1142 ----- .../pyroot/cppyy/CPyCppyy/src/CPPInstance.h | 164 - .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 1154 ----- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.h | 129 - .../pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx | 64 - .../pyroot/cppyy/CPyCppyy/src/CPPOperator.h | 28 - .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 1287 ------ .../pyroot/cppyy/CPyCppyy/src/CPPOverload.h | 125 - .../pyroot/cppyy/CPyCppyy/src/CPPScope.cxx | 711 --- bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h | 115 - bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyy.h | 418 -- .../cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 1189 ----- .../cppyy/CPyCppyy/src/CPyCppyyModule.h | 12 - .../cppyy/CPyCppyy/src/CPyCppyyPyModule.cxx | 13 - .../pyroot/cppyy/CPyCppyy/src/CallContext.cxx | 47 - .../pyroot/cppyy/CPyCppyy/src/CallContext.h | 174 - .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 3844 ----------------- .../pyroot/cppyy/CPyCppyy/src/Converters.h | 87 - bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 293 -- .../cppyy/CPyCppyy/src/CustomPyTypes.cxx | 463 -- .../pyroot/cppyy/CPyCppyy/src/CustomPyTypes.h | 108 - .../cppyy/CPyCppyy/src/DeclareConverters.h | 488 --- .../cppyy/CPyCppyy/src/DeclareExecutors.h | 199 - .../pyroot/cppyy/CPyCppyy/src/Dimensions.h | 87 - .../pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx | 111 - .../pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx | 512 --- .../pyroot/cppyy/CPyCppyy/src/Dispatcher.h | 16 - .../pyroot/cppyy/CPyCppyy/src/Executors.cxx | 1114 ----- .../pyroot/cppyy/CPyCppyy/src/Executors.h | 47 - .../cppyy/CPyCppyy/src/LowLevelViews.cxx | 1234 ------ .../pyroot/cppyy/CPyCppyy/src/LowLevelViews.h | 104 - .../cppyy/CPyCppyy/src/MemoryRegulator.cxx | 274 -- .../cppyy/CPyCppyy/src/MemoryRegulator.h | 40 - .../cppyy/CPyCppyy/src/ProxyWrappers.cxx | 985 ----- .../pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h | 33 - .../pyroot/cppyy/CPyCppyy/src/PyCallable.h | 54 - .../pyroot/cppyy/CPyCppyy/src/PyException.cxx | 141 - .../cppyy/CPyCppyy/src/PyObjectDir27.inc | 237 - .../pyroot/cppyy/CPyCppyy/src/PyStrings.cxx | 245 -- .../pyroot/cppyy/CPyCppyy/src/PyStrings.h | 91 - .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 2233 ---------- .../pyroot/cppyy/CPyCppyy/src/Pythonize.h | 15 - .../cppyy/CPyCppyy/src/SignalTryCatch.h | 81 - .../cppyy/CPyCppyy/src/TemplateProxy.cxx | 984 ----- .../pyroot/cppyy/CPyCppyy/src/TemplateProxy.h | 105 - .../cppyy/CPyCppyy/src/TupleOfInstances.cxx | 255 -- .../cppyy/CPyCppyy/src/TupleOfInstances.h | 37 - .../pyroot/cppyy/CPyCppyy/src/TypeManip.cxx | 274 -- .../pyroot/cppyy/CPyCppyy/src/TypeManip.h | 29 - .../pyroot/cppyy/CPyCppyy/src/Utility.cxx | 1326 ------ bindings/pyroot/cppyy/CPyCppyy/src/Utility.h | 129 - .../pyroot/cppyy/cppyy-backend/CMakeLists.txt | 11 - .../cppyy-backend/clingwrapper/LICENSE.txt | 55 - .../clingwrapper/src/callcontext.h | 43 - .../clingwrapper/src/clingwrapper.cxx | 2668 ------------ .../clingwrapper/src/cpp_cppyy.h | 292 -- .../clingwrapper/src/precommondefs.h | 77 - bindings/pyroot/cppyy/cppyy/.gitignore | 46 - bindings/pyroot/cppyy/cppyy/.readthedocs.yaml | 22 - bindings/pyroot/cppyy/cppyy/CMakeLists.txt | 4 - bindings/pyroot/cppyy/cppyy/LICENSE.txt | 59 - bindings/pyroot/cppyy/cppyy/MANIFEST.in | 9 - bindings/pyroot/cppyy/cppyy/README.rst | 33 - bindings/pyroot/cppyy/cppyy/bench/Makefile | 55 - .../pyroot/cppyy/cppyy/bench/basic_micro.py | 38 - .../cppyy/cppyy/bench/bench_functioncalls.py | 168 - .../cppyy/cppyy/bench/bench_runvector.py | 54 - .../cppyy/cppyy/bench/functioncalls.cxx | 66 - .../pyroot/cppyy/cppyy/bench/functioncalls.h | 54 - .../pyroot/cppyy/cppyy/bench/functioncalls.i | 6 - .../cppyy/cppyy/bench/functioncalls.xml | 19 - .../cppyy/cppyy/bench/functioncalls_main.cxx | 75 - .../cppyy/cppyy/bench/py11_functioncalls.cxx | 55 - .../cppyy/cppyy/bench/py_functioncalls.py | 56 - .../pyroot/cppyy/cppyy/bench/py_runvector.py | 10 - .../pyroot/cppyy/cppyy/bench/runvector.cxx | 15 - bindings/pyroot/cppyy/cppyy/bench/runvector.h | 5 - bindings/pyroot/cppyy/cppyy/bench/runvector.i | 12 - .../pyroot/cppyy/cppyy/bench/runvector.xml | 6 - .../cppyy/cppyy/bench/runvector_main.cxx | 42 - bindings/pyroot/cppyy/cppyy/bench/support.py | 14 - bindings/pyroot/cppyy/cppyy/doc/.gitignore | 1 - bindings/pyroot/cppyy/cppyy/doc/Makefile | 192 - bindings/pyroot/cppyy/cppyy/doc/make.bat | 263 -- .../pyroot/cppyy/cppyy/doc/requirements.txt | 4 - .../cppyy/doc/source/_static/css/custom.css | 7 - .../cppyy/cppyy/doc/source/basic_types.rst | 194 - .../pyroot/cppyy/cppyy/doc/source/bugs.rst | 11 - .../cppyy/cppyy/doc/source/changelog.rst | 638 --- .../pyroot/cppyy/cppyy/doc/source/classes.rst | 417 -- .../cppyy/doc/source/cmake_interface.rst | 248 -- .../pyroot/cppyy/cppyy/doc/source/conf.py | 361 -- .../doc/source/cppyy_features_header.rst | 162 - .../pyroot/cppyy/cppyy/doc/source/cuda.rst | 42 - .../cppyy/cppyy/doc/source/debugging.rst | 113 - .../cppyy/cppyy/doc/source/examples.rst | 51 - .../cppyy/cppyy/doc/source/exceptions.rst | 101 - .../cppyy/cppyy/doc/source/functions.rst | 468 -- .../pyroot/cppyy/cppyy/doc/source/history.rst | 52 - .../pyroot/cppyy/cppyy/doc/source/index.rst | 197 - .../cppyy/cppyy/doc/source/installation.rst | 236 - .../pyroot/cppyy/cppyy/doc/source/license.rst | 85 - .../cppyy/cppyy/doc/source/lowlevel.rst | 327 -- .../pyroot/cppyy/cppyy/doc/source/misc.rst | 186 - .../pyroot/cppyy/cppyy/doc/source/numba.rst | 363 -- .../cppyy/cppyy/doc/source/packages.rst | 73 - .../cppyy/cppyy/doc/source/philosophy.rst | 236 - .../pyroot/cppyy/cppyy/doc/source/python.rst | 71 - .../cppyy/cppyy/doc/source/pythonizations.rst | 172 - .../cppyy/cppyy/doc/source/repositories.rst | 161 - .../cppyy/cppyy/doc/source/starting.rst | 65 - .../pyroot/cppyy/cppyy/doc/source/stl.rst | 282 -- .../pyroot/cppyy/cppyy/doc/source/strings.rst | 115 - .../pyroot/cppyy/cppyy/doc/source/testing.rst | 40 - .../cppyy/cppyy/doc/source/toplevel.rst | 144 - .../cppyy/doc/source/type_conversions.rst | 89 - .../cppyy/cppyy/doc/source/utilities.rst | 283 -- .../cppyy/doc/tutorial/CppyyTutorial.ipynb | 496 --- .../tutorial/GSLPythonizationTutorial.ipynb | 202 - .../cppyy/cppyy/etc/valgrind-cppyy-cling.supp | 758 ---- .../cppyy/installer/cppyy_monkey_patch.py | 39 - bindings/pyroot/cppyy/cppyy/pyproject.toml | 4 - .../pyroot/cppyy/cppyy/python/CMakeLists.txt | 21 - .../cppyy/cppyy/python/cppyy/__init__.py | 449 -- .../python/cppyy/__pyinstaller/__init__.py | 6 - .../python/cppyy/__pyinstaller/hook-cppyy.py | 34 - .../cppyy/python/cppyy/_cpython_cppyy.py | 215 - .../cppyy/cppyy/python/cppyy/_pypy_cppyy.py | 74 - .../cppyy/python/cppyy/_pythonization.py | 341 -- .../cppyy/cppyy/python/cppyy/_stdcpp_fix.py | 13 - .../cppyy/cppyy/python/cppyy/_typemap.py | 118 - .../cppyy/cppyy/python/cppyy/_version.py | 1 - .../cppyy/cppyy/python/cppyy/interactive.py | 36 - .../pyroot/cppyy/cppyy/python/cppyy/ll.py | 162 - .../cppyy/cppyy/python/cppyy/numba_ext.py | 655 --- .../pyroot/cppyy/cppyy/python/cppyy/reflex.py | 28 - .../pyroot/cppyy/cppyy/python/cppyy/types.py | 35 - .../cppyy/python/cppyy_compat/__init__.py | 69 - bindings/pyroot/cppyy/cppyy/setup.cfg | 9 - bindings/pyroot/cppyy/cppyy/setup.py | 160 - .../pyroot/cppyy/cppyy/test/CMakeLists.txt | 66 - bindings/pyroot/cppyy/cppyy/test/__init__.py | 0 .../pyroot/cppyy/cppyy/test/advancedcpp.cxx | 186 - .../pyroot/cppyy/cppyy/test/advancedcpp.h | 533 --- .../pyroot/cppyy/cppyy/test/advancedcpp.xml | 82 - .../pyroot/cppyy/cppyy/test/advancedcpp2.cxx | 13 - .../pyroot/cppyy/cppyy/test/advancedcpp2.h | 36 - .../pyroot/cppyy/cppyy/test/advancedcpp2.xml | 11 - .../cppyy/cppyy/test/assert_interactive.py | 17 - .../pyroot/cppyy/cppyy/test/bindexplib.py | 32 - bindings/pyroot/cppyy/cppyy/test/conftest.py | 1 - .../pyroot/cppyy/cppyy/test/conversions.cxx | 51 - .../pyroot/cppyy/cppyy/test/conversions.h | 28 - .../pyroot/cppyy/cppyy/test/conversions.xml | 8 - .../pyroot/cppyy/cppyy/test/cpp11features.cxx | 69 - .../pyroot/cppyy/cppyy/test/cpp11features.h | 150 - .../pyroot/cppyy/cppyy/test/cpp11features.xml | 25 - .../cppyy/cppyy/test/crossinheritance.cxx | 130 - .../cppyy/cppyy/test/crossinheritance.h | 135 - .../cppyy/cppyy/test/crossinheritance.xml | 10 - .../pyroot/cppyy/cppyy/test/datatypes.cxx | 1012 ----- bindings/pyroot/cppyy/cppyy/test/datatypes.h | 833 ---- .../pyroot/cppyy/cppyy/test/datatypes.xml | 70 - .../pyroot/cppyy/cppyy/test/doc_args_funcs.py | 5 - .../pyroot/cppyy/cppyy/test/doc_helper.cxx | 6 - bindings/pyroot/cppyy/cppyy/test/doc_helper.h | 31 - .../pyroot/cppyy/cppyy/test/doc_helper.xml | 6 - .../pyroot/cppyy/cppyy/test/example01.cxx | 214 - bindings/pyroot/cppyy/cppyy/test/example01.h | 115 - .../pyroot/cppyy/cppyy/test/example01.xml | 19 - bindings/pyroot/cppyy/cppyy/test/fragile.cxx | 69 - bindings/pyroot/cppyy/cppyy/test/fragile.h | 151 - bindings/pyroot/cppyy/cppyy/test/fragile.xml | 44 - .../cppyy/cppyy/test/make_dict_win32.py | 108 - .../pyroot/cppyy/cppyy/test/operators.cxx | 194 - bindings/pyroot/cppyy/cppyy/test/operators.h | 332 -- .../pyroot/cppyy/cppyy/test/operators.xml | 35 - .../pyroot/cppyy/cppyy/test/overloads.cxx | 67 - bindings/pyroot/cppyy/cppyy/test/overloads.h | 119 - .../pyroot/cppyy/cppyy/test/overloads.xml | 22 - .../pyroot/cppyy/cppyy/test/pythonizables.cxx | 53 - .../pyroot/cppyy/cppyy/test/pythonizables.h | 165 - .../pyroot/cppyy/cppyy/test/pythonizables.xml | 7 - .../pyroot/cppyy/cppyy/test/std_streams.cxx | 5 - .../pyroot/cppyy/cppyy/test/std_streams.h | 15 - .../pyroot/cppyy/cppyy/test/std_streams.xml | 12 - bindings/pyroot/cppyy/cppyy/test/stltypes.cxx | 98 - bindings/pyroot/cppyy/cppyy/test/stltypes.h | 252 -- bindings/pyroot/cppyy/cppyy/test/stltypes.xml | 37 - bindings/pyroot/cppyy/cppyy/test/support.py | 59 - .../cppyy/cppyy/test/templ_args_funcs.py | 9 - .../pyroot/cppyy/cppyy/test/templates.cxx | 56 - bindings/pyroot/cppyy/cppyy/test/templates.h | 538 --- .../pyroot/cppyy/cppyy/test/templates.xml | 44 - .../cppyy/cppyy/test/test_aclassloader.py | 25 - .../cppyy/cppyy/test/test_advancedcpp.py | 964 ----- bindings/pyroot/cppyy/cppyy/test/test_api.py | 228 - .../pyroot/cppyy/cppyy/test/test_boost.py | 172 - .../cppyy/cppyy/test/test_concurrent.py | 315 -- .../cppyy/cppyy/test/test_conversions.py | 128 - .../cppyy/cppyy/test/test_cpp11features.py | 643 --- .../cppyy/cppyy/test/test_crossinheritance.py | 1841 -------- .../pyroot/cppyy/cppyy/test/test_datatypes.py | 2467 ----------- .../cppyy/cppyy/test/test_doc_features.py | 1285 ------ .../pyroot/cppyy/cppyy/test/test_eigen.py | 171 - .../pyroot/cppyy/cppyy/test/test_fragile.py | 893 ---- .../pyroot/cppyy/cppyy/test/test_leakcheck.py | 285 -- .../pyroot/cppyy/cppyy/test/test_lowlevel.py | 803 ---- .../pyroot/cppyy/cppyy/test/test_numba.py | 842 ---- .../pyroot/cppyy/cppyy/test/test_operators.py | 424 -- .../pyroot/cppyy/cppyy/test/test_overloads.py | 471 -- .../pyroot/cppyy/cppyy/test/test_pythonify.py | 594 --- .../cppyy/cppyy/test/test_pythonization.py | 402 -- .../cppyy/cppyy/test/test_regression.py | 1447 ------- .../pyroot/cppyy/cppyy/test/test_stltypes.py | 2306 ---------- .../pyroot/cppyy/cppyy/test/test_streams.py | 77 - .../pyroot/cppyy/cppyy/test/test_templates.py | 1434 ------ .../CPyCppyy-Adapt-to-no-std-in-ROOT.patch | 163 - ...level-views-for-fixed-width-integers.patch | 250 -- ...y-Always-convert-returned-std-string.patch | 96 - ...check-for-temporaries-in-Python-3.14.patch | 111 - ...std-vector-numpy-array-pythonization.patch | 65 - ...e-of-Python-GIL-to-support-threading.patch | 170 - ...and-types-of-all-overloads-signature.patch | 239 - ...t-construction-of-agg-init-for-tuple.patch | 27 - ...-Support-conversion-from-str-to-char.patch | 124 - ...etOptionalItemString-where-necessary.patch | 44 - ...reMethodArgType-to-give-arg-compat-s.patch | 76 - ...cppyy-Don-t-enable-cling-autoloading.patch | 28 - .../patches/cppyy-Enable-testsuite.patch | 1045 ----- ...-from-template-proxy-using-tmpl-args.patch | 70 - .../cppyy-No-CppyyLegacy-namespace.patch | 48 - .../cppyy-Remove-Windows-workaround.patch | 27 - .../cppyy/patches/using-data-members.patch | 147 - bindings/pyroot/cppyy/sync-upstream | 111 - bindings/pyroot/pythonizations/CMakeLists.txt | 4 +- .../pythonizations/python/CMakeLists.txt | 3 + .../pythonizations/python/ROOT/__init__.py | 6 - .../pythonizations/python/ROOT/_facade.py | 2 +- .../python/ROOT/_pythonization/__init__.py | 6 +- .../python/ROOT/_pythonization/_generic.py | 11 +- .../_pythonization/_roofit/_rooglobalfunc.py | 2 +- .../pythonizations/python/cppyy/__init__.py | 50 + .../pythonizations/src/CPPInstancePyz.cxx | 14 +- .../pyroot/pythonizations/src/GenericPyz.cxx | 12 +- .../pythonizations/src/PyROOTModule.cxx | 10 +- .../pyroot/pythonizations/src/TClassPyz.cxx | 26 +- .../pyroot/pythonizations/src/TTreePyz.cxx | 46 +- .../pythonizations/test/import_load_libs.py | 6 +- bindings/tpython/CMakeLists.txt | 2 +- bindings/tpython/src/TPyReturn.cxx | 8 +- bindings/tpython/src/TPython.cxx | 30 +- core/metacling/src/libCling.script | 1 + .../regression/PyROOT_regressiontests.py | 8 +- roottest/python/regression/ScottCppyy.C | 8 +- .../template_instantiation.py | 43 +- test/PostInstall/check-headers.sh | 2 +- 287 files changed, 191 insertions(+), 68357 deletions(-) delete mode 100644 bindings/pyroot/cppyy/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/.gitignore delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/LICENSE.txt delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/MANIFEST.in delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/README.rst delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/Reflex.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/pyproject.toml delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/setup.cfg delete mode 100755 bindings/pyroot/cppyy/CPyCppyy/setup.py delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/API.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyy.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyPyModule.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Converters.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/CustomPyTypes.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Dimensions.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Executors.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.h delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx delete mode 100644 bindings/pyroot/cppyy/CPyCppyy/src/Utility.h delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/LICENSE.txt delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h delete mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h delete mode 100644 bindings/pyroot/cppyy/cppyy/.gitignore delete mode 100644 bindings/pyroot/cppyy/cppyy/.readthedocs.yaml delete mode 100644 bindings/pyroot/cppyy/cppyy/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/cppyy/LICENSE.txt delete mode 100644 bindings/pyroot/cppyy/cppyy/MANIFEST.in delete mode 100644 bindings/pyroot/cppyy/cppyy/README.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/Makefile delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/basic_micro.py delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/bench_functioncalls.py delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/bench_runvector.py delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/functioncalls.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/functioncalls.h delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/functioncalls.i delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/functioncalls.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/functioncalls_main.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/py11_functioncalls.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/py_functioncalls.py delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/py_runvector.py delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/runvector.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/runvector.h delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/runvector.i delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/runvector.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/runvector_main.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/bench/support.py delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/.gitignore delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/Makefile delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/make.bat delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/requirements.txt delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/_static/css/custom.css delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/basic_types.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/bugs.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/changelog.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/classes.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/cmake_interface.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/conf.py delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/cppyy_features_header.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/cuda.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/debugging.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/examples.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/exceptions.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/functions.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/history.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/index.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/installation.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/license.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/lowlevel.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/misc.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/numba.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/packages.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/philosophy.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/python.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/pythonizations.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/repositories.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/starting.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/stl.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/strings.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/testing.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/toplevel.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/type_conversions.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/source/utilities.rst delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/tutorial/CppyyTutorial.ipynb delete mode 100644 bindings/pyroot/cppyy/cppyy/doc/tutorial/GSLPythonizationTutorial.ipynb delete mode 100644 bindings/pyroot/cppyy/cppyy/etc/valgrind-cppyy-cling.supp delete mode 100644 bindings/pyroot/cppyy/cppyy/installer/cppyy_monkey_patch.py delete mode 100644 bindings/pyroot/cppyy/cppyy/pyproject.toml delete mode 100644 bindings/pyroot/cppyy/cppyy/python/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/__init__.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/__pyinstaller/hook-cppyy.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/reflex.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy/types.py delete mode 100644 bindings/pyroot/cppyy/cppyy/python/cppyy_compat/__init__.py delete mode 100644 bindings/pyroot/cppyy/cppyy/setup.cfg delete mode 100755 bindings/pyroot/cppyy/cppyy/setup.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/CMakeLists.txt delete mode 100644 bindings/pyroot/cppyy/cppyy/test/__init__.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp2.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp2.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/advancedcpp2.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/assert_interactive.py delete mode 100755 bindings/pyroot/cppyy/cppyy/test/bindexplib.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/conftest.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/conversions.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/conversions.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/conversions.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/cpp11features.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/cpp11features.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/cpp11features.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/crossinheritance.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/crossinheritance.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/crossinheritance.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/datatypes.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/datatypes.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/datatypes.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/doc_args_funcs.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/doc_helper.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/doc_helper.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/doc_helper.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/example01.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/example01.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/example01.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/fragile.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/fragile.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/fragile.xml delete mode 100755 bindings/pyroot/cppyy/cppyy/test/make_dict_win32.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/operators.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/operators.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/operators.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/overloads.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/overloads.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/overloads.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/pythonizables.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/pythonizables.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/pythonizables.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/std_streams.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/std_streams.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/std_streams.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/stltypes.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/stltypes.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/stltypes.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/support.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/templ_args_funcs.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/templates.cxx delete mode 100644 bindings/pyroot/cppyy/cppyy/test/templates.h delete mode 100644 bindings/pyroot/cppyy/cppyy/test/templates.xml delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_api.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_boost.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_concurrent.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_conversions.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_datatypes.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_doc_features.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_eigen.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_fragile.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_numba.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_operators.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_overloads.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_pythonify.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_pythonization.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_regression.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_stltypes.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_streams.py delete mode 100644 bindings/pyroot/cppyy/cppyy/test/test_templates.py delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Adapt-to-no-std-in-ROOT.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Add-converters-and-low-level-views-for-fixed-width-integers.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Always-convert-returned-std-string.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Correct-check-for-temporaries-in-Python-3.14.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Disable_std-vector-numpy-array-pythonization.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Fix-usage-of-Python-GIL-to-support-threading.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Get-names-and-types-of-all-overloads-signature.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Prevent-construction-of-agg-init-for-tuple.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Support-conversion-from-str-to-char.patch delete mode 100644 bindings/pyroot/cppyy/patches/CPyCppyy-Use-PyMapping_GetOptionalItemString-where-necessary.patch delete mode 100644 bindings/pyroot/cppyy/patches/NumbaExt-Add-CompareMethodArgType-to-give-arg-compat-s.patch delete mode 100644 bindings/pyroot/cppyy/patches/cppyy-Don-t-enable-cling-autoloading.patch delete mode 100644 bindings/pyroot/cppyy/patches/cppyy-Enable-testsuite.patch delete mode 100644 bindings/pyroot/cppyy/patches/cppyy-Get-overload-from-template-proxy-using-tmpl-args.patch delete mode 100644 bindings/pyroot/cppyy/patches/cppyy-No-CppyyLegacy-namespace.patch delete mode 100644 bindings/pyroot/cppyy/patches/cppyy-Remove-Windows-workaround.patch delete mode 100644 bindings/pyroot/cppyy/patches/using-data-members.patch delete mode 100755 bindings/pyroot/cppyy/sync-upstream create mode 100644 bindings/pyroot/pythonizations/python/cppyy/__init__.py 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/cppyy/CMakeLists.txt b/bindings/pyroot/cppyy/CMakeLists.txt deleted file mode 100644 index a1cfb02dc3fd6..0000000000000 --- a/bindings/pyroot/cppyy/CMakeLists.txt +++ /dev/null @@ -1,9 +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_subdirectory(cppyy-backend) -add_subdirectory(CPyCppyy) -add_subdirectory(cppyy) diff --git a/bindings/pyroot/cppyy/CPyCppyy/.gitignore b/bindings/pyroot/cppyy/CPyCppyy/.gitignore deleted file mode 100644 index 6562f33442ca3..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# python compiled files -*.pyc -*.pyo - -# dictionary products -*.so -*.pcm -*.rootmap - -# build products -dist -*.egg-info -__pycache__ -.cache -.pytest_cache -.vscode -build diff --git a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt b/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt deleted file mode 100644 index 6e25dbe92a4a3..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt +++ /dev/null @@ -1,133 +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. - -set(headers - include/CPyCppyy/API.h - include/CPyCppyy/Reflex.h - include/CPyCppyy/CommonDefs.h - include/CPyCppyy/PyException.h - include/CPyCppyy/DispatchPtr.h -) - -set(sources - src/API.cxx - src/CPPClassMethod.cxx - src/CPPConstructor.cxx - src/CPPDataMember.cxx - src/CPPEnum.cxx - src/CPPExcInstance.cxx - src/CPPFunction.cxx - src/CPPGetSetItem.cxx - src/CPPInstance.cxx - src/CPPMethod.cxx - src/CPPOperator.cxx - src/CPPOverload.cxx - src/CPPScope.cxx - src/CPyCppyyModule.cxx - src/CallContext.cxx - src/Converters.cxx - src/CustomPyTypes.cxx - src/DispatchPtr.cxx - src/Dispatcher.cxx - src/Executors.cxx - src/LowLevelViews.cxx - src/MemoryRegulator.cxx - src/ProxyWrappers.cxx - src/PyException.cxx - src/PyStrings.cxx - src/Pythonize.cxx - src/TemplateProxy.cxx - src/TupleOfInstances.cxx - src/TypeManip.cxx - src/Utility.cxx -) - - -add_library(CPyCppyy SHARED ${headers} ${sources}) -# Set the suffix to '.so' and the prefix to 'lib' -set_target_properties(CPyCppyy PROPERTIES ${ROOT_LIBRARY_PROPERTIES}) -if(MSVC) - target_link_libraries(CPyCppyy PRIVATE cppyy_backend) - target_link_libraries(CPyCppyy PUBLIC Python3::Python) - set_target_properties(CPyCppyy PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) - target_link_options(CPyCppyy PRIVATE "-ignore:4197") -elseif(APPLE) - target_link_libraries(CPyCppyy PRIVATE -Wl,-bind_at_load -Wl,-w -Wl,-undefined -Wl,dynamic_lookup cppyy_backend) -else() - target_link_libraries(CPyCppyy PRIVATE -Wl,--unresolved-symbols=ignore-all cppyy_backend) -endif() - -if(NOT MSVC) - target_compile_options(CPyCppyy PRIVATE -Wno-strict-aliasing) -endif() -if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND NOT MSVC) - target_compile_options(CPyCppyy PRIVATE - -Wno-unused-but-set-parameter) -endif() - - -add_library(cppyy SHARED src/CPyCppyyPyModule.cxx) - -# Set the suffix to '.so' and the prefix to 'lib' -set_target_properties(cppyy PROPERTIES ${ROOT_LIBRARY_PROPERTIES_NO_VERSION} - LIBRARY_OUTPUT_DIRECTORY ${localruntimedir}/cppyy) -if(MSVC) - target_link_libraries(cppyy PRIVATE CPyCppyy) - set_target_properties(cppyy PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) - set_target_properties(cppyy PROPERTIES PREFIX "lib") - set_target_properties(cppyy PROPERTIES SUFFIX ".pyd") -elseif(APPLE) - target_link_libraries(cppyy PRIVATE -Wl,-bind_at_load -Wl,-w -Wl,-undefined -Wl,dynamic_lookup CPyCppyy) -else() - target_link_libraries(cppyy PRIVATE -Wl,--unresolved-symbols=ignore-all CPyCppyy) -endif() - -# Avoid warnings due to invalid function casts from C++ functions in CPyCppyy -# 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(CPyCppyy PRIVATE -Wno-cast-function-type) -endif() - -target_compile_definitions(CPyCppyy PRIVATE NO_CPPYY_LEGACY_NAMESPACE) - -target_include_directories(CPyCppyy SYSTEM PUBLIC ${Python3_INCLUDE_DIRS}) - -target_include_directories(CPyCppyy - PUBLIC - $ -) - -set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS CPyCppyy) -set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS cppyy) - -if(NOT MSVC) - # Make sure that relative RUNPATH to main ROOT libraries is always correct. - ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(cppyy ${CMAKE_INSTALL_PYTHONDIR}/cppyy) - ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(CPyCppyy ${CMAKE_INSTALL_LIBDIR}) - set(CPPYY_INSTALL_DIR ${CMAKE_INSTALL_PYTHONDIR}/cppyy) -else() - set(CPPYY_INSTALL_DIR ${CMAKE_INSTALL_PYTHONDIR}) -endif() - -# Install library -install(TARGETS CPyCppyy 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 cppyy EXPORT ${CMAKE_PROJECT_NAME}Exports - RUNTIME DESTINATION ${CPPYY_INSTALL_DIR} COMPONENT libraries # Windows - LIBRARY DESTINATION ${CPPYY_INSTALL_DIR} COMPONENT libraries - ARCHIVE DESTINATION ${CPPYY_INSTALL_DIR} COMPONENT libraries) - -file(COPY ${headers} DESTINATION ${CMAKE_BINARY_DIR}/include/CPyCppyy) - -install(FILES ${headers} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CPyCppyy - COMPONENT headers) diff --git a/bindings/pyroot/cppyy/CPyCppyy/LICENSE.txt b/bindings/pyroot/cppyy/CPyCppyy/LICENSE.txt deleted file mode 100644 index 383f5d3644d7e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/LICENSE.txt +++ /dev/null @@ -1,58 +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): - - CERN - Lucio Asnaghi - Torok Attila - Simone Bacchio - Niko Fink - Aaron Jomy - Mac Kolin - Baidyanath Kundu - Toby StClere-Smithe diff --git a/bindings/pyroot/cppyy/CPyCppyy/MANIFEST.in b/bindings/pyroot/cppyy/CPyCppyy/MANIFEST.in deleted file mode 100644 index eff5a253b5ea7..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -# Include the license file -include LICENSE.txt - -# Include all headers -include include/CPyCppyy/*.h -include src/*.h - -# Include source files -include src/*.cxx -include src/*.inc diff --git a/bindings/pyroot/cppyy/CPyCppyy/README.rst b/bindings/pyroot/cppyy/CPyCppyy/README.rst deleted file mode 100644 index 2b931e7d3518c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/README.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. -*- mode: rst -*- - -CPyCppyy: Python-C++ bindings interface based on Cling/LLVM -=========================================================== - -CPyCppyy is the CPython equivalent of _cppyy in PyPy. -It provides dynamic Python-C++ bindings by leveraging the Cling C++ -interpreter and LLVM. -Details and performance are described in -`this paper `_. - -CPyCppyy is a CPython extension module built on top of the same backend API -as PyPy/_cppyy. -It thus requires the installation of the -`cppyy backend `_ -for use, which will pull in Cling. -CPython/cppyy and PyPy/cppyy are designed to be compatible, although there -are differences due to the former being reference counted and the latter -being garbage collected, as well as temporary differences due to different -release cycles of the respective projects. - ----- - -Find the cppyy documentation here: - http://cppyy.readthedocs.io - -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/CPyCppyy/include/CPyCppyy/API.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h deleted file mode 100644 index e619348e11c0d..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h +++ /dev/null @@ -1,231 +0,0 @@ -#ifndef CPYCPPYY_API_H -#define CPYCPPYY_API_H - -// -// Access to the python interpreter and API onto CPyCppyy. -// - -// 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 CPYCPPYY_VERSION_HEX 0x010c10 - -// Cppyy types -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; -} // namespace Cppyy - -// Bindings -#include "CPyCppyy/CommonDefs.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -//- type conversion --------------------------------------------------------- - -#ifndef CPYCPPYY_PARAMETER -#define CPYCPPYY_PARAMETER -// generic function argument type -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; -}; -#endif // CPYCPPYY_PARAMETER - -// CallContext is not currently exposed -struct CallContext; - -// Dimensions class not currently exposed -#ifndef CPYCPPYY_DIMENSIONS_H -#define CPYCPPYY_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 // !CPYCPPYY_DIMENSIONS_H - -// type converter base class -class CPYCPPYY_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 -CPYCPPYY_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0); - -// delete a previously created converter -CPYCPPYY_EXTERN void DestroyConverter(Converter* p); - -// register a custom converter -typedef Converter* (*ConverterFactory_t)(cdims_t); -CPYCPPYY_EXTERN bool RegisterConverter(const std::string& name, ConverterFactory_t); - -// register a custom converter that is a reference to an existing converter -CPYCPPYY_EXTERN bool RegisterConverterAlias(const std::string& name, const std::string& target); - -// remove a custom converter -CPYCPPYY_EXTERN bool UnregisterConverter(const std::string& name); - - -// function executor base class -class CPYCPPYY_CLASS_EXTERN Executor { -public: - virtual ~Executor(); - -// callback when executing a function from Python - virtual PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::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 -CPYCPPYY_EXTERN Executor* CreateExecutor(const std::string& name, cdims_t = 0); - -// delete a previously created executor -CPYCPPYY_EXTERN void DestroyConverter(Converter* p); - -// register a custom executor -typedef Executor* (*ExecutorFactory_t)(cdims_t); -CPYCPPYY_EXTERN bool RegisterExecutor(const std::string& name, ExecutorFactory_t); - -// register a custom executor that is a reference to an existing converter -CPYCPPYY_EXTERN bool RegisterExecutorAlias(const std::string& name, const std::string& target); - -// remove a custom executor -CPYCPPYY_EXTERN bool UnregisterExecutor(const std::string& name); - -// helper for calling into C++ from a custom executor -CPYCPPYY_EXTERN void* CallVoidP(Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*); - - -//- C++ access to cppyy objects --------------------------------------------- - -// Get C++ Instance (python object proxy) name. -// Sets a TypeError and returns an empty string if the pyobject is not a CPPInstance. -CPYCPPYY_EXTERN std::string Instance_GetScopedFinalName(PyObject* pyobject); - -// C++ Instance (python object proxy) to void* conversion -CPYCPPYY_EXTERN void* Instance_AsVoidPtr(PyObject* pyobject); - -// void* to C++ Instance (python object proxy) conversion, returns a new reference -CPYCPPYY_EXTERN PyObject* Instance_FromVoidPtr( - void* addr, const std::string& classname, bool python_owns = false); - -// type verifiers for C++ Scope -CPYCPPYY_EXTERN bool Scope_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Scope_CheckExact(PyObject* pyobject); - -// type verifiers for C++ Instance -CPYCPPYY_EXTERN bool Instance_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Instance_CheckExact(PyObject* pyobject); - -// memory management: ownership of the underlying C++ object -CPYCPPYY_EXTERN void Instance_SetPythonOwns(PyObject* pyobject); -CPYCPPYY_EXTERN void Instance_SetCppOwns(PyObject* pyobject); - -// type verifier for sequences -CPYCPPYY_EXTERN bool Sequence_Check(PyObject* pyobject); - -// helper to verify expected safety of moving an instance into C++ -CPYCPPYY_EXTERN bool Instance_IsLively(PyObject* pyobject); - -// type verifiers for C++ Overload -CPYCPPYY_EXTERN bool Overload_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Overload_CheckExact(PyObject* pyobject); - -// Sets the __reduce__ method for the CPPInstance class, which is by default not -// implemented by cppyy but might make sense to implement by frameworks that -// support IO of arbitrary C++ objects, like ROOT. -CPYCPPYY_EXTERN void Instance_SetReduceMethod(PyCFunction reduceMethod); - - -//- access to the python interpreter ---------------------------------------- - -// import a python module, making its classes available to Cling -CPYCPPYY_EXTERN bool Import(const std::string& name); - -// execute a python statement (e.g. "import sys") -CPYCPPYY_EXTERN bool Exec(const std::string& cmd); - -// execute a python stand-alone script, with argv CLI arguments -CPYCPPYY_EXTERN void ExecScript(const std::string& name, const std::vector& args); - -// enter an interactive python session (exit with ^D) -CPYCPPYY_EXTERN void Prompt(); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_API_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h deleted file mode 100644 index e309c6a0f9b3b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef CPYCPPYY_COMMONDEFS_H -#define CPYCPPYY_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 CPYCPPYY_EXPORT extern __declspec(dllexport) -#define CPYCPPYY_CLASS_EXPORT __declspec(dllexport) - -// CPYCPPYY_EXTERN is dual use in the public API -#ifndef CPYCPPYY_INTERNAL -#define CPYCPPYY_EXTERN extern __declspec(dllexport) -#define CPYCPPYY_CLASS_EXTERN __declspec(dllexport) -#else -#define CPYCPPYY_EXTERN extern -#define CPYCPPYY_CLASS_EXTERN -#endif - -#define CPYCPPYY_STATIC - -#else -// Linux, Mac, etc. -#define CPYCPPYY_EXPORT extern -#define CPYCPPYY_CLASS_EXPORT -#define CPYCPPYY_EXTERN extern -#define CPYCPPYY_CLASS_EXTERN -#define CPYCPPYY_STATIC static - -#endif - -#define CPYCPPYY_IMPORT extern - -#endif // !CPYCPPYY_COMMONDEFS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h deleted file mode 100644 index bd098f6917fa0..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef CPYCPPYY_DISPATCHPTR_H -#define CPYCPPYY_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 "CPyCppyy/CommonDefs.h" - - -namespace CPyCppyy { - -class CPYCPPYY_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 CPyCppyy - -#endif // !CPYCPPYY_DISPATCHPTR_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h deleted file mode 100644 index 5c8133d89ca7b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/PyException.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef CPYCPPYY_PYEXCEPTION_H -#define CPYCPPYY_PYEXCEPTION_H - -////////////////////////////////////////////////////////////////////////////// -// // -// PyException // -// // -// 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. // -// // -// The situation is: // -// - We're calling C++ code from python. // -// - 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++. // -// To do that we throw a TPyException. // -// We can then catch this exception when we do the C++ call. // -// // -// Note that we don't need to save any state in the exception -- it's // -// already in the python error info variables. // -// (??? Actually, if the program is multithreaded, this is dangerous // -// if the code has released and reacquired the lock along the call chain. // -// Punt on this for now, though.) // -// // -////////////////////////////////////////////////////////////////////////////// - -// Standard -#include -#include - -// Bindings -#include "CPyCppyy/CommonDefs.h" - - -namespace CPyCppyy { - -class CPYCPPYY_CLASS_EXTERN PyException : public std::exception { -public: - PyException(); - ~PyException() 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; - -private: - std::string fMsg; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYEXCEPTION_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/Reflex.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/Reflex.h deleted file mode 100644 index b37b9eed9621e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/Reflex.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef CPYCPPYY_REFLEX_H -#define CPYCPPYY_REFLEX_H - -// -// Access to the C++ reflection information -// - -namespace Cppyy { - -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 Cppyy - -#endif // !CPYCPPYY_REFLEX_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/pyproject.toml b/bindings/pyroot/cppyy/CPyCppyy/pyproject.toml deleted file mode 100644 index e7181f393b641..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/pyproject.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build-system] -requires = ["cppyy-cling==6.32.8", "cppyy-backend==1.15.3", "setuptools", "wheel"] diff --git a/bindings/pyroot/cppyy/CPyCppyy/setup.cfg b/bindings/pyroot/cppyy/CPyCppyy/setup.cfg deleted file mode 100644 index 9ae562903000d..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -universal=0 - -[metadata] -license_files = LICENSE.txt diff --git a/bindings/pyroot/cppyy/CPyCppyy/setup.py b/bindings/pyroot/cppyy/CPyCppyy/setup.py deleted file mode 100755 index 0bab79eb338d5..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/setup.py +++ /dev/null @@ -1,133 +0,0 @@ -import codecs, glob, os, sys, subprocess -from setuptools import setup, find_packages, Extension -from distutils import log - -from distutils.command.build_ext import build_ext as _build_ext -try: - from wheel.bdist_wheel import bdist_wheel as _bdist_wheel - has_wheel = True -except ImportError: - has_wheel = False - -requirements = ['cppyy-cling==6.32.8', 'cppyy-backend==1.15.3'] -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() - - -# -# platform-dependent helpers -# -def _get_link_libraries(): - if 'win32' in sys.platform: - return ['libcppyy_backend', 'libCoreLegacy'] - return [] - -def _get_link_dirs(): - if 'win32' in sys.platform: - try: - import cppyy_backend - return [os.path.join(os.path.dirname(cppyy_backend.__file__), 'lib')] - except ImportError: # happens during egg_info and other non-build/install commands - pass - return [] - -def _get_config_exec(): - return [sys.executable, '-m', 'cppyy_backend._cling_config'] - -def get_cflags(): - config_exec_args = _get_config_exec() - config_exec_args.append('--auxcflags') - cli_arg = subprocess.check_output(config_exec_args) - return cli_arg.decode("utf-8").strip() - - -# -# customized commands -# -class my_build_extension(_build_ext): - def build_extension(self, ext): - ext.extra_compile_args += ['-O2']+get_cflags().split() - if ('linux' in sys.platform) or ('darwin' in sys.platform): - if 'clang' in self.compiler.compiler_cxx[0]: - ext.extra_compile_args += \ - ['-Wno-bad-function-cast'] # clang for same - elif 'g++' in self.compiler.compiler_cxx[0]: - ext.extra_compile_args += \ - ['-Wno-cast-function-type', # g++ >8.2, complaint of CPyFunction cast - '-Wno-unknown-warning'] # since clang/g++ don't have the same options - ext.extra_compile_args += \ - ['-Wno-register', # C++17, Python headers - '-Wno-strict-aliasing'] # not all Pythons provide this - if 'linux' in sys.platform: - ext.extra_link_args += ['-Wl,-Bsymbolic-functions'] - elif 'win32' in sys.platform: - # /EHsc and sometimes /MT are hardwired in distutils, but the compiler/linker will - # let the last argument take precedence - ext.extra_compile_args += ['/GR', '/EHsc-', '/MD'] - ext.extra_link_args += ['/EXPORT:_Init_thread_abort', '/EXPORT:_Init_thread_epoch', - '/EXPORT:_Init_thread_footer', '/EXPORT:_Init_thread_header', '/EXPORT:_tls_index'] - return _build_ext.build_extension(self, ext) - - -cmdclass = { - 'build_ext': my_build_extension } - - -setup( - name='CPyCppyy', - version='1.13.0', - description='Cling-based Python-C++ bindings for CPython', - long_description=long_description, - - url='http://cppyy.readthedocs.io/', - - # 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 :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: C', - 'Programming Language :: C++', - - 'Natural Language :: English' - ], - - setup_requires=setup_requirements, - install_requires=requirements, - - keywords='C++ bindings data science', - - ext_modules=[Extension('libcppyy', - sources=glob.glob(os.path.join('src', '*.cxx')), - include_dirs=['include'], - libraries=_get_link_libraries(), - library_dirs=_get_link_dirs())], - - headers=glob.glob(os.path.join('include', 'CPyCppyy', '*.h')), - - cmdclass=cmdclass, - - zip_safe=False, -) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx deleted file mode 100644 index 63e698163524b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx +++ /dev/null @@ -1,470 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/API.h" -#undef CPYCPPYY_INTERNAL - -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CPPScope.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// Standard -#include -#include -#include - -//______________________________________________________________________________ -// CPyCppyy API: Interpreter and Proxy Access -// ========================================== -// -// Access to cppyy 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 CPyCppyy { - extern PyObject* gThisModule; -} - - -//- private helpers ---------------------------------------------------------- -namespace { - -static bool Initialize() -{ -// Private initialization method: setup the python interpreter and load the -// cppyy module. - static bool isInitialized = false; - if (isInitialized) - return true; - - if (!Py_IsInitialized()) { - // this happens if Cling comes in first -#if PY_VERSION_HEX < 0x03020000 - PyEval_InitThreads(); -#endif -#if PY_VERSION_HEX < 0x03080000 - Py_Initialize(); -#else - PyConfig config; - PyConfig_InitPythonConfig(&config); - PyConfig_SetString(&config, &config.program_name, L"cppyy"); - Py_InitializeFromConfig(&config); -#endif -#if PY_VERSION_HEX >= 0x03020000 -#if PY_VERSION_HEX < 0x03090000 - PyEval_InitThreads(); -#endif -#endif - - // 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; - } - - // set the command line arguments on python's sys.argv -#if PY_VERSION_HEX < 0x03000000 - char* argv[] = {const_cast("cppyy")}; -#elif PY_VERSION_HEX < 0x03080000 - wchar_t* argv[] = {const_cast(L"cppyy")}; -#endif -#if PY_VERSION_HEX < 0x03080000 - PySys_SetArgv(sizeof(argv)/sizeof(argv[0]), argv); -#endif - } - -// Make sure the cppyy extension module is imported, as this is what runs the -// CPyCppyy module initialization that sets up internals such as gThisModule. - if (!CPyCppyy::gThisModule) { - PyObject* cppyymod = PyImport_ImportModule("cppyy"); - if (!cppyymod) - return false; - Py_DECREF(cppyymod); - } - - if (!gMainDict) { - // 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 cppyy objects --------------------------------------------- -std::string CPyCppyy::Instance_GetScopedFinalName(PyObject* pyobject) -{ - if (!Instance_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "Instance_GetScopedFinalName : object is not a C++ instance"); - return ""; - } - - Cppyy::TCppType_t pyobjectClass = ((CPPInstance *)pyobject)->ObjectIsA(); - return Cppyy::GetScopedFinalName(pyobjectClass); -} - -//----------------------------------------------------------------------------- -void* CPyCppyy::Instance_AsVoidPtr(PyObject* pyobject) -{ -// Extract the object pointer held by the CPPInstance pyobject. - if (!Initialize()) - return nullptr; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return nullptr; - -// get held object (may be null) - return ((CPPInstance*)pyobject)->GetObject(); -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::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; - -// perform cast (the call will check TClass and addr, and set python errors) - PyObject* pyobject = BindCppObjectNoCast(addr, Cppyy::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; -} - -namespace CPyCppyy { -// 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 CPyCppyy - -//----------------------------------------------------------------------------- -bool CPyCppyy::Scope_Check(PyObject* pyobject) -{ -// Test if the given object is of a CPPScope derived type. - if (!Initialize()) - return false; - - return CPPScope_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Scope_CheckExact(PyObject* pyobject) -{ -// Test if the given object is of a CPPScope type. - if (!Initialize()) - return false; - - return CPPScope_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Instance_Check(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPInstance derived type. - if (!Initialize()) - return false; - -// detailed walk through inheritance hierarchy - return CPPInstance_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Instance_CheckExact(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPInstance type. - if (!Initialize()) - return false; - -// direct pointer comparison of type member - return CPPInstance_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetPythonOwns(PyObject* pyobject) -{ - if (!Initialize()) - return; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return; - - ((CPPInstance *)pyobject)->PythonOwns(); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetCppOwns(PyObject* pyobject) -{ - if (!Initialize()) - return; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return; - - ((CPPInstance *)pyobject)->CppOwns(); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Sequence_Check(PyObject* pyobject) -{ -// 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 CPyCppyy::Instance_IsLively(PyObject* pyobject) -{ -// 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 CPyCppyy::Overload_Check(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPOverload derived type. - if (!Initialize()) - return false; - -// detailed walk through inheritance hierarchy - return CPPOverload_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Overload_CheckExact(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPOverload type. - if (!Initialize()) - return false; - -// direct pointer comparison of type member - return CPPOverload_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetReduceMethod(PyCFunction reduceMethod) -{ - CPPInstance::ReduceMethod() = reduceMethod; -} - -//- access to the python interpreter ---------------------------------------- -bool CPyCppyy::Import(const std::string& mod_name) -{ -// Import the named python module and create Cling equivalents for its classes. - if (!Initialize()) - return false; - - 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 Cppyy.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 += CPyCppyy_PyText_AsString(pyClName); - - Py_XDECREF(pyClName); - } - - Py_DECREF(value); - } - - Py_DECREF(values); - -// TODO: mod "leaks" here - if (PyErr_Occurred()) - return false; - return true; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::ExecScript(const std::string& name, const std::vector& args) -{ -// Execute a python stand-alone script, with argv CLI arguments. -// -// example of use: -// CPyCppyy::ExecScript("test.py", {"1", "2", "3"}); - - if (!Initialize()) - return; - -// 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 CPyCppyy::Exec(const std::string& cmd) -{ -// Execute a python statement (e.g. "import noddy"). - if (!Initialize()) - return false; - -// 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 CPyCppyy::Prompt() { -// Enter an interactive python session (exit with ^D). State is preserved -// between successive calls. - if (!Initialize()) - return; - -// enter i/o interactive mode - PyRun_InteractiveLoop(stdin, const_cast("\0")); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx deleted file mode 100644 index f55e50f7a3e1a..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx +++ /dev/null @@ -1,58 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPClassMethod.h" -#include "CPPInstance.h" - - -//- public members -------------------------------------------------------------- -PyObject* CPyCppyy::CPPClassMethod::Call(CPPInstance*& -#if PY_VERSION_HEX >= 0x03080000 - self -#endif - , CPyCppyy_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 -#if PY_VERSION_HEX >= 0x03080000 -// 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)CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - if ((!self || (PyObject*)self == Py_None) && nargs) { - PyObject* arg0 = CPyCppyy_PyArgs_GET_ITEM(args, 0); - if (CPPInstance_Check(arg0) && fArgsRequired <= nargs - 1 && - Cppyy::IsSubtype(reinterpret_cast(arg0)->ObjectIsA(), GetScope())) { - args += 1; // drops first argument - nargsf -= 1; - } - } -#endif - - if (!this->ConvertAndSetArgs(args, nargsf, ctxt)) - return nullptr; - -// execute function - return this->Execute(nullptr, 0, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPClassMethod::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.h deleted file mode 100644 index c8c3e8513e7d9..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CPYCPPYY_CPPCLASSMETHOD_H -#define CPYCPPYY_CPPCLASSMETHOD_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -class CPPClassMethod : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyObject* GetTypeName() override; - -public: - PyCallable* Clone() override { return new CPPClassMethod(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPCLASSMETHOD_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx deleted file mode 100644 index f935d3d5371cb..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx +++ /dev/null @@ -1,385 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPConstructor.h" -#include "CPPInstance.h" -#include "Executors.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// Standard -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - extern PyObject* gNullPtrObject; -} - - -//- protected members -------------------------------------------------------- -bool CPyCppyy::CPPConstructor::InitExecutor_(Executor*& executor, CallContext*) -{ -// pick up special case new object executor - executor = CreateExecutor("__init__"); - return true; -} - -//- public members ----------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::GetDocString() -{ -// GetMethod() may return an empty function if this is just a special case place holder - const std::string& clName = Cppyy::GetFinalName(this->GetScope()); - return CPyCppyy_PyText_FromFormat("%s::%s%s", - clName.c_str(), clName.c_str(), this->GetMethod() ? this->GetSignatureString().c_str() : "()"); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::Reflex( - Cppyy::Reflex::RequestId_t request, Cppyy::Reflex::FormatId_t format) -{ -// C++ reflection tooling for constructors. - - if (request == Cppyy::Reflex::RETURN_TYPE) { - std::string fn = Cppyy::GetScopedFinalName(this->GetScope()); - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_TYPE) - return CreateScopeProxy(fn); - else if (format == Cppyy::Reflex::AS_STRING) - return CPyCppyy_PyText_FromString(fn.c_str()); - } - - return PyCallable::Reflex(request, format); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, - CPyCppyy_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. We recognize this by checking the CPPScope::kNoImplicit -// flag, as further implicit conversions are disabled to prevent infinite -// recursion. See also the ConvertImplicit() helper in Converters.cxx. - if((cppScopeFlags & CPPScope::kNoImplicit) && Cppyy::IsExplicit(GetMethod())) - 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 - Cppyy::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 = CPyCppyy::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 " + Cppyy::GetScopedFinalName(disp) + " is not a dispatcher").c_str())); - return nullptr; - } - - PyObject* pyobj = CPyCppyy_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::kNoImplicit) - 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, (Cppyy::TCppObject_t)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( - (Cppyy::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; -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor::CPPMultiConstructor(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : - CPPConstructor(scope, method) -{ - fNumBases = Cppyy::GetNumBases(scope); -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor::CPPMultiConstructor(const CPPMultiConstructor& s) : - CPPConstructor(s), fNumBases(s.fNumBases) -{ -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor& CPyCppyy::CPPMultiConstructor::operator=(const CPPMultiConstructor& s) -{ - if (this != &s) { - CPPConstructor::operator=(s); - fNumBases = s.fNumBases; - } - return *this; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMultiConstructor::Call(CPPInstance*& self, - CPyCppyy_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 - -#if PY_VERSION_HEX >= 0x03080000 -// 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 = CPyCppyy_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; - -#else - PyObject* args = argsin; - Py_INCREF(args); -#endif - - 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; - } - } - -#if PY_VERSION_HEX < 0x03080000 - Py_ssize_t -#endif - nargs = PyTuple_GET_SIZE(args); - -#if PY_VERSION_HEX >= 0x03080000 -// 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); - CPyCppyy_PyArgs_t _args = argsu.get(); -#else - CPyCppyy_PyArgs_t _args = args; -#endif - - PyObject* result = CPPConstructor::Call(self, _args, nargs, kwds, ctxt); - Py_DECREF(args); - - return result; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPAbstractClassConstructor::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// do not allow instantiation of abstract classes - if ((self && GetScope() != self->ObjectIsA() -#if PY_VERSION_HEX >= 0x03080000 - ) || (!self && !(ctxt->fFlags & CallContext::kFromDescr) && \ - CPyCppyy_PyArgs_GET_SIZE(args, nargsf) && CPPInstance_Check(args[0]) && \ - GetScope() != ((CPPInstance*)args[0])->ObjectIsA() -#endif - )) { - // 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)", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPNamespaceConstructor::Call( - CPPInstance*&, CPyCppyy_PyArgs_t, size_t, PyObject*, CallContext*) -{ -// do not allow instantiation of namespaces - PyErr_Format(PyExc_TypeError, "cannot instantiate namespace \'%s\'", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPIncompleteClassConstructor::Call( - CPPInstance*&, CPyCppyy_PyArgs_t, size_t, PyObject*, CallContext*) -{ -// do not allow instantiation of incomplete (forward declared) classes) - PyErr_Format(PyExc_TypeError, "cannot instantiate incomplete class \'%s\'", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPAllPrivateClassConstructor::Call( - CPPInstance*&, CPyCppyy_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", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.h deleted file mode 100644 index 753bed9843f43..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef CPYCPPYY_CPPCONSTRUCTOR_H -#define CPYCPPYY_CPPCONSTRUCTOR_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -class CPPConstructor : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyObject* GetDocString() override; - PyObject* Reflex(Cppyy::Reflex::RequestId_t, - Cppyy::Reflex::FormatId_t = Cppyy::Reflex::OPTIMAL) override; - - PyCallable* Clone() override { return new CPPConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_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(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method); - CPPMultiConstructor(const CPPMultiConstructor&); - CPPMultiConstructor& operator=(const CPPMultiConstructor&); - -public: - PyCallable* Clone() override { return new CPPMultiConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_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, - CPyCppyy_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, - CPyCppyy_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, - CPyCppyy_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, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPCONSTRUCTOR_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx deleted file mode 100644 index 4246ba487543b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx +++ /dev/null @@ -1,435 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPyCppyy/Reflex.h" -#include "PyStrings.h" -#include "CPPDataMember.h" -#include "CPPInstance.h" -#include "Dimensions.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include - -namespace CPyCppyy { - -enum ETypeDetails { - kNone = 0x0000, - kIsStaticData = 0x0001, - kIsConstData = 0x0002, - kIsArrayType = 0x0004, - kIsEnumPrep = 0x0008, - kIsEnumType = 0x0010, - kIsCachable = 0x0020 -}; - -//= CPyCppyy 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) { - CPyCppyy::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; - } - } - } - -// 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->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 std::string& lookup = CPyCppyy_PyText_AsString(dm->fDescription); - const std::string& enum_type = TypeManip::extract_namespace(lookup); - const std::string& enum_scope = TypeManip::extract_namespace(enum_type); - - PyObject* pyscope = nullptr; - if (enum_scope.empty()) pyscope = GetScopeProxy(Cppyy::gGlobalScope); - else pyscope = CreateScopeProxy(enum_scope); - if (pyscope) { - PyObject* pyEnumType = PyObject_GetAttrString(pyscope, - enum_type.substr(enum_scope.size() ? enum_scope.size()+2 : 0, std::string::npos).c_str()); - if (pyEnumType) { - PyObject* pyval = PyObject_GetAttrString(pyEnumType, - lookup.substr(enum_type.size()+2, std::string::npos).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 (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 (!Cppyy::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) { - CPyCppyy::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; -} - -//= CPyCppyy 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 = 0; - 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 */ -}; - -//= CPyCppyy datamember proxy access to internals ============================ -static PyObject* dm_reflex(CPPDataMember* dm, PyObject* args) -{ -// Provide the requested reflection information. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL; - if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, &format)) - return nullptr; - - if (request == Cppyy::Reflex::TYPE) { - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_STRING) - return CPyCppyy_PyText_FromString(dm->fFullType.c_str()); - } else if (request == Cppyy::Reflex::OFFSET) { - if (format == Cppyy::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 } -}; - - -//= CPyCppyy data member type ================================================ -PyTypeObject CPPDataMember_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.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*)"cppyy 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 -#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 - - -//- public members ----------------------------------------------------------- -void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) -{ - fEnclosingScope = scope; - fOffset = Cppyy::GetDatamemberOffset(scope, idata); // TODO: make lazy - fFlags = Cppyy::IsStaticData(scope, idata) ? kIsStaticData : 0; - - std::vector dims; - int ndim = 0; Py_ssize_t size = 0; - while (0 < (size = Cppyy::GetDimensionSize(scope, idata, ndim))) { - ndim += 1; - if (size == INT_MAX) // meaning: incomplete array type - size = UNKNOWN_SIZE; - if (ndim == 1) dims.reserve(4); - dims.push_back((dim_t)size); - } - if (!dims.empty()) - fFlags |= kIsArrayType; - - const std::string name = Cppyy::GetDatamemberName(scope, idata); - fFullType = Cppyy::GetDatamemberType(scope, idata); - if (Cppyy::IsEnumData(scope, idata)) { - if (fFullType.find("(anonymous)") == std::string::npos && - fFullType.find("(unnamed)") == std::string::npos) { - // repurpose fDescription for lazy lookup of the enum later - fDescription = CPyCppyy_PyText_FromString((fFullType + "::" + name).c_str()); - fFlags |= kIsEnumPrep; - } - fFullType = Cppyy::ResolveEnum(fFullType); - fFlags |= kIsConstData; - } else if (Cppyy::IsConstData(scope, idata)) { - fFlags |= kIsConstData; - } - -// if this data member is an array, the conversion needs to be pointer to object for instances, -// to prevent the need for copying in the conversion; furthermore, fixed arrays' full type for -// builtins are not declared as such if more than 1-dim (TODO: fix in clingwrapper) - if (!dims.empty() && fFullType.back() != '*') { - if (Cppyy::GetScope(fFullType)) fFullType += '*'; - else if (fFullType.back() != ']') { - for (auto d: dims) fFullType += d == UNKNOWN_SIZE ? "*" : "[]"; - } - } - - if (dims.empty()) - fConverter = CreateConverter(fFullType); - else - fConverter = CreateConverter(fFullType, {(dim_t)dims.size(), dims.data()}); - - if (!(fFlags & kIsEnumPrep)) - fDescription = CPyCppyy_PyText_FromString(name.c_str()); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, const std::string& name, void* address) -{ - fEnclosingScope = scope; - fDescription = CPyCppyy_PyText_FromString(name.c_str()); - fOffset = (intptr_t)address; - fFlags = kIsStaticData | kIsConstData; - fConverter = CreateConverter("internal_enum_type_t"); - fFullType = "unsigned int"; -} - - -//----------------------------------------------------------------------------- -void* CPyCppyy::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; - Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - if (oisa != fEnclosingScope) - offset = Cppyy::GetBaseOffset(oisa, fEnclosingScope, obj, 1 /* up-cast */); - - return (void*)((intptr_t)obj + offset + fOffset); -} - - -//----------------------------------------------------------------------------- -std::string CPyCppyy::CPPDataMember::GetName() -{ - if (fFlags & kIsEnumType) { - PyObject* repr = PyObject_Repr(fDescription); - if (repr) { - std::string res = CPyCppyy_PyText_AsString(repr); - Py_DECREF(repr); - return res; - } - PyErr_Clear(); - return ""; - } else if (fFlags & kIsEnumPrep) { - std::string fullName = CPyCppyy_PyText_AsString(fDescription); - return fullName.substr(fullName.rfind("::")+2, std::string::npos); - } - - return CPyCppyy_PyText_AsString(fDescription); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h deleted file mode 100644 index 9fe32cfd93e72..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef CPYCPPYY_CPPDATAMEMBER_H -#define CPYCPPYY_CPPDATAMEMBER_H - -// Bindings -#include "Converters.h" - -// Standard -#include - - -namespace CPyCppyy { - -class CPPInstance; - -class CPPDataMember { -public: - void Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata); - void Set(Cppyy::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; - Cppyy::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( - Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) -{ -// 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, idata); - return pyprop; -} - -inline CPPDataMember* CPPDataMember_NewConstant( - Cppyy::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 CPyCppyy - -#endif // !CPYCPPYY_CPPDATAMEMBER_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx deleted file mode 100644 index dbb2f0fe4da29..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx +++ /dev/null @@ -1,238 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPEnum.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - - -//- private helpers ---------------------------------------------------------- -static PyObject* pytype_from_enum_type(const std::string& enum_type) -{ - if (enum_type == "char") - return (PyObject*)&CPyCppyy_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 -} - -//---------------------------------------------------------------------------- -static PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, - PyObject* btype, Cppyy::TCppEnum_t etype, Cppyy::TCppIndex_t idata) { - long long llval = Cppyy::GetEnumDataValue(etype, idata); - - if (enum_type == "bool") { - PyObject* result = (bool)llval ? Py_True : Py_False; - Py_INCREF(result); - return result; // <- immediate return; - } - - PyObject* bval; - if (enum_type == "char") { - char val = (char)llval; -#if PY_VERSION_HEX < 0x03000000 - bval = CPyCppyy_PyText_FromStringAndSize(&val, 1); -#else - bval = PyUnicode_FromOrdinal((int)val); -#endif - } 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 - - PyObject* args = PyTuple_New(1); - PyTuple_SET_ITEM(args, 0, bval); - PyObject* result = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); - Py_DECREF(args); - return result; -} - - -//- 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 CPyCppyy; - - 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 = Cppyy::ResolveEnum(CPyCppyy_PyText_AsString(kls_cppname)); - repr = CPyCppyy_PyText_FromFormat("(%s::%s) : (%s) %s", - CPyCppyy_PyText_AsString(kls_cppname), CPyCppyy_PyText_AsString(obj_cppname), - resolved.c_str(), CPyCppyy_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::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, CPyCppyy::PyStrings::gUnderlying); - if (!pyres) PyErr_Clear(); - - std::string underlying = pyres ? CPyCppyy_PyText_AsString(pyres) : "int"; - PyTypeObject* ct = GetCTypesType(underlying); - if (!ct) - return nullptr; - - return PyType_Type.tp_call((PyObject*)ct, args, kwds); -} - - -//- creation ----------------------------------------------------------------- -CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::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; - - const std::string& ename = scope == Cppyy::gGlobalScope ? name : Cppyy::GetScopedFinalName(scope)+"::"+name; - Cppyy::TCppEnum_t etype = Cppyy::GetEnum(scope, name); - 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 = Cppyy::ResolveEnum(ename); - 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 = CPyCppyy_PyText_FromString(ename.c_str()); - PyDict_SetItem(dct, PyStrings::gCppName, pycppname); - Py_DECREF(pycppname); - PyObject* pyresolved = CPyCppyy_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 = CPyCppyy_PyText_FromString(("cppyy.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 - Cppyy::TCppIndex_t ndata = Cppyy::GetNumEnumData(etype); - bool values_ok = true; - for (Cppyy::TCppIndex_t idata = 0; idata < ndata; ++idata) { - PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, etype, idata); - if (!val) { - values_ok = false; - break; - } - const std::string& dname = Cppyy::GetEnumDataName(etype, idata); - PyObject* pydname = CPyCppyy_PyText_FromString(dname.c_str()); - PyObject_SetAttr(pyenum, pydname, val); - Py_DECREF(pydname); - PyObject* pydcppname = CPyCppyy_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/cppyy/CPyCppyy/src/CPPEnum.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h deleted file mode 100644 index 730baf4ba75d9..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CPYCPPYY_CPPENUM_H -#define CPYCPPYY_CPPENUM_H - - -namespace CPyCppyy { - -// 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, Cppyy::TCppScope_t scope); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPENUM_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.cxx deleted file mode 100644 index d89ddeef14ab4..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.cxx +++ /dev/null @@ -1,291 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#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 CPyCppyy { - -//= CPyCppyy 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 (CPyCppyy_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; - CPyCppyy_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 -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_divide -#endif - 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 -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_coerce -#endif - 0, // nb_int - 0, // nb_long (nb_reserved in p3) - 0, // nb_float -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_oct - 0, // nb_hex -#endif - 0, // nb_inplace_add - 0, // nb_inplace_subtract - 0, // nb_inplace_multiply -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_inplace_divide -#endif - 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 -#if PY_VERSION_HEX >= 0x02020000 - , 0 // nb_floor_divide -#if PY_VERSION_HEX < 0x03000000 - , 0 // nb_true_divide -#else - , 0 // nb_true_divide -#endif - , 0 // nb_inplace_floor_divide - , 0 // nb_inplace_true_divide -#endif -#if PY_VERSION_HEX >= 0x02050000 - , 0 // nb_index -#endif -#if PY_VERSION_HEX >= 0x03050000 - , 0 // nb_matrix_multiply - , 0 // nb_inplace_matrix_multiply -#endif -}; - -//= CPyCppyy exception object proxy type ====================================== -PyTypeObject CPPExcInstance_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.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*)"cppyy 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 -#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/CPPExcInstance.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.h deleted file mode 100644 index 8dd71754d1d31..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPExcInstance.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CPYCPPYY_CPPEXCINSTANCE_H -#define CPYCPPYY_CPPEXCINSTANCE_H - -////////////////////////////////////////////////////////////////////////////// -// // -// CpyCppyy::CPPExceptionInstance // -// // -// Python-side proxy, encapsulaties a C++ exception object. // -// // -////////////////////////////////////////////////////////////////////////////// - -namespace CPyCppyy { - -class CPPExcInstance { -public: - PyBaseExceptionObject fBase; - PyObject* fCppInstance; - PyObject* fTopMessage; -}; - - -//- object proxy type and type verification ---------------------------------- -CPYCPPYY_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 CPyCppyy - -#endif // !CPYCPPYY_CPPEXCINSTANCE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.cxx deleted file mode 100644 index 89e65c4282f0a..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.cxx +++ /dev/null @@ -1,157 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPFunction.h" -#include "CPPInstance.h" - -// Standard -#include - - -//- CFunction helpers ----------------------------------------------------------- -bool CPyCppyy::AdjustSelf(PyCallArgs& cargs) -{ -#if PY_VERSION_HEX >= 0x03080000 - 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; - } -#else - Py_ssize_t sz = PyTuple_GET_SIZE(cargs.fArgs); - CPyCppyy_PyArgs_t newArgs = PyTuple_New(sz+1); - for (int i = 0; i < sz; ++i) { - PyObject* item = PyTuple_GET_ITEM(cargs.fArgs, i); - Py_INCREF(item); - PyTuple_SET_ITEM(newArgs, i+1, item); - } - Py_INCREF(cargs.fSelf); - PyTuple_SET_ITEM(newArgs, 0, (PyObject*)cargs.fSelf); - - cargs.fArgs = newArgs; - cargs.fFlags |= PyCallArgs::kDoDecref; - cargs.fNArgsf += 1; -#endif - return true; -} - -bool CPyCppyy::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* CPyCppyy::CPPFunction::Call(CPPInstance*& self, - CPyCppyy_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; - } - -#if PY_VERSION_HEX >= 0x03080000 -// 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 && \ - CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf)) { - ctxt->fPyContext = cargs.fArgs[0]; - } -#endif - -// translate the arguments as normal - if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) - return nullptr; - -// execute function - PyObject* result = this->Execute(nullptr, 0, ctxt); - -#if PY_VERSION_HEX >= 0x03080000 -// 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 && \ - CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) && CPPInstance_Check(cargs.fArgs[0])) { - self = (CPPInstance*)cargs.fArgs[0]; - Py_INCREF(self); - } -#endif - - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPFunction::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} - - -//- CPPReverseBinary private helper --------------------------------------------- -bool CPyCppyy::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 -#if PY_VERSION_HEX >= 0x03080000 - std::swap(((PyObject**)cargs.fArgs)[0], ((PyObject**)cargs.fArgs)[1]); -#else - std::swap(PyTuple_GET_ITEM(cargs.fArgs, 0), PyTuple_GET_ITEM(cargs.fArgs, 1)); -#endif - cargs.fFlags |= PyCallArgs::kArgsSwap; - - return true; -} - -//- CPPReverseBinary public members --------------------------------------------- -PyObject* CPyCppyy::CPPReverseBinary::Call(CPPInstance*& self, - CPyCppyy_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/cppyy/CPyCppyy/src/CPPFunction.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.h deleted file mode 100644 index f126f19c6262c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPFunction.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CPYCPPYY_CPPFUNCTION_H -#define CPYCPPYY_CPPFUNCTION_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -// 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, - CPyCppyy_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, - CPyCppyy_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 CPyCppyy - -#endif // !CPYCPPYY_CPPFUNCTION_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.cxx deleted file mode 100644 index 2ed852ff9d02c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.cxx +++ /dev/null @@ -1,119 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPGetSetItem.h" -#include "Executors.h" - - -//- private helpers ----------------------------------------------------------- -static inline -void unroll(CPyCppyy_PyArgs_t packed_args, CPyCppyy_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 = CPyCppyy_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); - CPyCppyy_PyArgs_SET_ITEM(unrolled, iur, subitem); - } - } else { - Py_INCREF(item); - CPyCppyy_PyArgs_SET_ITEM(unrolled, iur, item); - } - } -} - -//- protected members --------------------------------------------------------- -bool CPyCppyy::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 CPyCppyy::CPPSetItem::ProcessArgs(PyCallArgs& cargs) -{ -// Prepare executor with a buffer for the return value. - Py_ssize_t nArgs = CPyCppyy_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(CPyCppyy_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 = CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, i); - realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; - } - -// unroll any tuples, if present in the arguments -#if PY_VERSION_HEX >= 0x03080000 - if (realsize != nArgs-1) { - CPyCppyy_PyArgs_t unrolled = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); - unroll(cargs.fArgs, unrolled, nArgs-1); - cargs.fArgs = unrolled; - cargs.fFlags |= PyCallArgs::kDoFree; - } -#else - if (realsize != nArgs-1) { - CPyCppyy_PyArgs_t unrolled = PyTuple_New(realsize); - unroll(cargs.fArgs, unrolled, nArgs-1); - cargs.fArgs = unrolled; - } else - cargs.fArgs = PyTuple_GetSlice(cargs.fArgs, 0, nArgs-1); - cargs.fFlags |= PyCallArgs::kDoDecref; -#endif - cargs.fNArgsf = realsize; - -// continue normal method processing - return CPPMethod::ProcessArgs(cargs); -} - - -//----------------------------------------------------------------------------- -bool CPyCppyy::CPPGetItem::ProcessArgs(PyCallArgs& cargs) -{ -// Unroll tuples for call, otherwise just like regular CPPMethod of __getitem__. - Py_ssize_t nArgs = CPyCppyy_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 = CPyCppyy_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) { - CPyCppyy_PyArgs_t packed_args = cargs.fArgs; -#if PY_VERSION_HEX >= 0x03080000 - cargs.fArgs = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); - cargs.fFlags |= PyCallArgs::kDoFree; -#else - cargs.fArgs = PyTuple_New(realsize); - cargs.fFlags |= PyCallArgs::kDoDecref; -#endif - cargs.fNArgsf = realsize; - unroll(packed_args, cargs.fArgs, nArgs); - } - -// continue normal method processing - return CPPMethod::ProcessArgs(cargs); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.h deleted file mode 100644 index 3b6f42e3aeb6d..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPGetSetItem.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CPYCPPYY_CPPGETSETITEM_H -#define CPYCPPYY_CPPGETSETITEM_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -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 CPyCppyy - -#endif // !CPYCPPYY_CPPGETSETITEM_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx deleted file mode 100644 index ac702c25769f7..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx +++ /dev/null @@ -1,1142 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "CPPScope.h" -#include "CPPOverload.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -#include "CPyCppyy/DispatchPtr.h" - -// Standard -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - 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 - CPyCppyy::CI_DatamemberCache_t fDatamemberCache; - -// for smart pointer types - CPyCppyy::CPPSmartClass* fSmartClass; - -// for back-referencing from Python-derived instances - CPyCppyy::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 CPyCppyy::CPPInstance::CreateExtension() { - if (fFlags & kIsExtended) - return; - void* obj = fObject; - fObject = (void*)new ExtendedData{}; - EXT_OBJECT(this) = obj; - fFlags |= kIsExtended; -} - -void* CPyCppyy::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 Cppyy::CallR(SMART_CLS(this)->fDereferencer, EXT_OBJECT(this), 0, nullptr); - } - return EXT_OBJECT(this); -} - - -//- public methods ----------------------------------------------------------- -CPyCppyy::CPPInstance* CPyCppyy::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 CPyCppyy::CPPInstance::PythonOwns() -{ - fFlags |= kIsOwner; - if ((fFlags & kIsExtended) && DISPATCHPTR(this)) - DISPATCHPTR(this)->PythonOwns(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::CppOwns() -{ - fFlags &= ~kIsOwner; - if ((fFlags & kIsExtended) && DISPATCHPTR(this)) - DISPATCHPTR(this)->CppOwns(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::SetSmart(PyObject* smart_type) -{ - CreateExtension(); - Py_INCREF(smart_type); - SMART_CLS(this) = (CPPSmartClass*)smart_type; - fFlags |= kIsSmartPtr; -} - -//---------------------------------------------------------------------------- -Cppyy::TCppType_t CPyCppyy::CPPInstance::GetSmartIsA() const -{ - if (!IsSmart()) return (Cppyy::TCppType_t)0; - return SMART_TYPE(this); -} - -//---------------------------------------------------------------------------- -Cppyy::TCppType_t CPyCppyy::CPPInstance::GetSmartUnderlyingType() const -{ -// The declared underlying type of the embedded smart pointer (e.g. 'Base' for -// a std::unique_ptr). This is independent of any auto-down-cast applied -// to the dereferenced object, and so is what must be used to decide whether the -// smart pointer can be passed to a function expecting a particular smart type. - if (!IsSmart()) return (Cppyy::TCppType_t)0; - return SMART_CLS(this)->fUnderlyingType; -} - -//---------------------------------------------------------------------------- -CPyCppyy::CI_DatamemberCache_t& CPyCppyy::CPPInstance::GetDatamemberCache() -{ -// Return the cache for expensive data objects (and make extended as necessary) - CreateExtension(); - return DATA_CACHE(this); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::SetDispatchPtr(void* ptr) -{ -// Set up the dispatch pointer for memory management - CreateExtension(); - DISPATCHPTR(this) = (DispatchPtr*)ptr; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::op_dealloc_nofree(CPPInstance* pyobj) { -// Destroy the held C++ object, if owned; does not deallocate the proxy. - - Cppyy::TCppType_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) { - Cppyy::CallDestructor(klass, cppobj); - Cppyy::Deallocate(klass, cppobj); - } else - Cppyy::Destruct(klass, cppobj); - } - cppobj = nullptr; - - if (pyobj->IsExtended()) delete (ExtendedData*)pyobj->fObject; - pyobj->fFlags = CPPInstance::kNoWrapConv; -} - - -namespace CPyCppyy { - -//---------------------------------------------------------------------------- -static int op_traverse(CPPInstance* /*pyobj*/, visitproc /*visit*/, void* /*arg*/) -{ - return 0; -} - - -//= CPyCppyy 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; -} - -//= CPyCppyy 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; -} - -//= CPyCppyy 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__"), - &CPyCppyy_PyText_Type, &mname, &CPyCppyy_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; -} - -//= CPyCppyy smart pointer support =========================================== -static PyObject* op_get_smartptr(CPPInstance* self) -{ - if (!self->IsSmart()) { - // TODO: more likely should raise - Py_RETURN_NONE; - } - - return CPyCppyy::BindCppObjectNoCast(self->GetSmartObject(), SMART_TYPE(self), CPPInstance::kNoWrapConv); -} - -//= pointer-as-array support for legacy C code =============================== -void CPyCppyy::CPPInstance::CastToArray(Py_ssize_t sz) -{ - CreateExtension(); - fFlags |= kIsArray; - ARRAY_SIZE(this) = sz; -} - -Py_ssize_t CPyCppyy::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 = Cppyy::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} -}; - - -//= CPyCppyy 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) { - 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 == Py_None) { // can try !== or !!= as alternatives - binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; - if (binop && binop != Py_None) flipit = true; - } - - 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; - - Cppyy::TCppType_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, address); - if (clActual && clActual != klass) { - intptr_t offset = Cppyy::GetBaseOffset( - clActual, klass, address, -1 /* down-cast */, true /* report errors */); - if (offset != -1) address = (void*)((intptr_t)address + offset); - } - - return address; -} - - -#define CPYCPPYY_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: - CPYCPPYY_ORDERED_OPERATOR_STUB(<, klass->fOperators->fLt, __lt__) - break; - case Py_LE: - CPYCPPYY_ORDERED_OPERATOR_STUB(<=, klass->fOperators->fLe, __le__) - break; - case Py_GT: - CPYCPPYY_ORDERED_OPERATOR_STUB(>, klass->fOperators->fGt, __gt__) - break; - case Py_GE: - CPYCPPYY_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); - - Cppyy::TCppType_t klass = self->ObjectIsA(); - std::string clName = klass ? Cppyy::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 = Cppyy::GetScopedFinalName(SMART_TYPE(self)); - repr = CPyCppyy_PyText_FromFormat( - const_cast("<%s.%s object at %p held by %s at %p>"), - CPyCppyy_PyText_AsString(modname), clName.c_str(), - self->GetObject(), smartPtrName.c_str(), self->GetObjectRaw()); - } else { - repr = CPyCppyy_PyText_FromFormat(const_cast("<%s.%s object at %p>"), - CPyCppyy_PyText_AsString(modname), clName.c_str(), self->GetObject()); - } - - Py_DECREF(modname); - return repr; -} - -//---------------------------------------------------------------------------- -static inline Py_hash_t CPyCppyy_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 = CPyCppyy_PyLong_AsHash_t(hashval); - Py_DECREF(hashval); - } - return h; - } - - Cppyy::TCppScope_t stdhash = Cppyy::GetScope("std::hash<"+Cppyy::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 = CPyCppyy_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 Cppyy::TCppScope_t sOStringStreamID = Cppyy::GetScope("std::ostringstream"); - std::ostringstream s; - PyObject* pys = BindCppObjectNoCast(&s, sOStringStreamID); - Py_INCREF(pys); -#if PY_VERSION_HEX >= 0x03000000 -// 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); -#endif - - PyObject* res; - if (isBound) res = PyObject_CallFunctionObjArgs(lshift, pys, NULL); - else res = PyObject_CallFunctionObjArgs(lshift, pys, pyobj, NULL); - - Py_DECREF(pys); -#if PY_VERSION_HEX >= 0x03000000 - Py_DECREF(pys); -#endif - - if (res) { - Py_DECREF(res); - return CPyCppyy_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); - Cppyy::TCppScope_t rnsID = Cppyy::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 = Cppyy::ToString(self->ObjectIsA(), self->GetObject()); - if (!pretty.empty()) - return CPyCppyy_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)) { - static PyObject* printValue = nullptr; - if (!printValue) { - PyObject* gbl = PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppyy.gbl"); - PyObject* cl = PyObject_GetAttrString(gbl, (char*)"cling"); - printValue = PyObject_GetAttrString(cl, (char*)"printValue"); - Py_DECREF(cl); - // gbl is borrowed - if (printValue) { - Py_DECREF(printValue); // make borrowed - if (!PyCallable_Check(printValue)) - printValue = nullptr; // unusable ... - } - if (!printValue) // unlikely - ScopeFlagSet(self, CPPScope::kNoPrettyPrint); - } - - 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 - Cppyy::TCppObject_t cppobj = self->GetObjectRaw(); - PyObject* byref = (PyObject*)self; - if (!(self->fFlags & CPPInstance::kIsReference)) { - byref = BindCppObjectNoCast((Cppyy::TCppObject_t)&cppobj, - self->ObjectIsA(), CPPInstance::kIsReference | CPPInstance::kNoMemReg); - } else { - Py_INCREF(byref); - } - - // explicit template lookup - PyObject* clName = CPyCppyy_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) { - const std::string& pv = *(std::string*)((CPPInstance*)pretty)->GetObject(); - if (!pv.empty() && pv.find("@0x") == std::string::npos) - result = CPyCppyy_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} -}; - - -//= CPyCppyy type number stubs to allow dynamic overrides ===================== -#define CPYCPPYY_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 CPYCPPYY_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; \ - CPYCPPYY_STUB_BODY(name, op) \ -} - -#define CPYCPPYY_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; \ - CPYCPPYY_STUB_BODY(name, op) \ -} - -#define CPYCPPYY_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; \ -} - -CPYCPPYY_ASSOCIATIVE_OPERATOR_STUB(add, +, klass->fOperators->fLAdd, klass->fOperators->fRAdd) -CPYCPPYY_OPERATOR_STUB( sub, -, klass->fOperators->fSub) -CPYCPPYY_ASSOCIATIVE_OPERATOR_STUB(mul, *, klass->fOperators->fLMul, klass->fOperators->fRMul) -CPYCPPYY_OPERATOR_STUB( div, /, klass->fOperators->fDiv) -CPYCPPYY_UNARY_OPERATOR(neg, -, __neg__) -CPYCPPYY_UNARY_OPERATOR(pos, +, __pos__) -CPYCPPYY_UNARY_OPERATOR(invert, ~, __invert__) - -//----------------------------------------------------------------------------- -static PyNumberMethods op_as_number = { - (binaryfunc)op_add_stub, // nb_add - (binaryfunc)op_sub_stub, // nb_subtract - (binaryfunc)op_mul_stub, // nb_multiply -#if PY_VERSION_HEX < 0x03000000 - (binaryfunc)op_div_stub, // nb_divide -#endif - 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 -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_coerce -#endif - 0, // nb_int - 0, // nb_long (nb_reserved in p3) - 0, // nb_float -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_oct - 0, // nb_hex -#endif - 0, // nb_inplace_add - 0, // nb_inplace_subtract - 0, // nb_inplace_multiply -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_inplace_divide -#endif - 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 -#if PY_VERSION_HEX >= 0x02020000 - , 0 // nb_floor_divide -#if PY_VERSION_HEX < 0x03000000 - , 0 // nb_true_divide -#else - , (binaryfunc)op_div_stub // nb_true_divide -#endif - , 0 // nb_inplace_floor_divide - , 0 // nb_inplace_true_divide -#endif -#if PY_VERSION_HEX >= 0x02050000 - , 0 // nb_index -#endif -#if PY_VERSION_HEX >= 0x03050000 - , 0 // nb_matrix_multiply - , 0 // nb_inplace_matrix_multiply -#endif -}; - - -//= CPyCppyy object proxy type =============================================== -PyTypeObject CPPInstance_Type = { - PyVarObject_HEAD_INIT(&CPPScope_Type, 0) - (char*)"cppyy.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*)"cppyy 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 -#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/CPPInstance.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h deleted file mode 100644 index d6b7adcbe350b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef CPYCPPYY_CPPINSTANCE_H -#define CPYCPPYY_CPPINSTANCE_H - -////////////////////////////////////////////////////////////////////////////// -// // -// CpyCppyy::CPPInstance // -// // -// Python-side proxy, encapsulaties a C++ object. // -// // -////////////////////////////////////////////////////////////////////////////// - -// Bindings -#ifndef Py_LIMITED_API -#include "CPPScope.h" -#endif -#include "Cppyy.h" -#include "CallContext.h" // for Parameter - -// Standard -#include -#include - - -namespace CPyCppyy { - -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; } - Cppyy::TCppType_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(); } - Cppyy::TCppType_t GetSmartIsA() const; - Cppyy::TCppType_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 Cppyy::TCppType_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 ---------------------------------- -// Needs to be extern because the libROOTPythonizations is secretly using it -#ifdef _MSC_VER -extern __declspec(dllimport) PyTypeObject CPPInstance_Type; -#else -extern PyTypeObject CPPInstance_Type; -#endif - -#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 CPyCppyy - -#endif // !CPYCPPYY_CPPINSTANCE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx deleted file mode 100644 index 1ce92c7e36995..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ /dev/null @@ -1,1154 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPMethod.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "Converters.h" -#include "Executors.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "SignalTryCatch.h" -#include "Utility.h" - -#include "CPyCppyy/PyException.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -//- data and local helpers --------------------------------------------------- -namespace CPyCppyy { - extern PyObject* gThisModule; - extern PyObject* gBusException; - extern PyObject* gSegvException; - extern PyObject* gIllException; - extern PyObject* gAbrtException; -} - - -//- public helper ------------------------------------------------------------ -CPyCppyy::PyCallArgs::~PyCallArgs() { - if (fFlags & kSelfSwap) // if self swap, fArgs has been offset by -1 - std::swap((PyObject*&)fSelf, ((PyObject**)fArgs)[0]); - -#if PY_VERSION_HEX >= 0x03080000 - if (fFlags & kIsOffset) fArgs -= 1; - - if (fFlags & kDoItemDecref) { - for (Py_ssize_t iarg = 0; iarg < CPyCppyy_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]); - } -#else - if (fFlags & kDoDecref) - Py_DECREF((PyObject*)fArgs); - else if (fFlags & kArgsSwap) - std::swap(PyTuple_GET_ITEM(fArgs, 0), PyTuple_GET_ITEM(fArgs, 1)); -#endif -} - - -//- private helpers ---------------------------------------------------------- -inline bool CPyCppyy::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_(CPyCppyy_PyText_FromFormat( - "takes at least %d arguments (%zd given)", fArgsRequired, actual)); - return false; - } else if (maxargs < actual) { - SetPyError_(CPyCppyy_PyText_FromFormat( - "takes at most %zd arguments (%zd given)", maxargs, actual)); - return false; - } - } - return true; -} - -//---------------------------------------------------------------------------- -inline void CPyCppyy::CPPMethod::Copy_(const CPPMethod& /* other */) -{ -// fScope and fMethod handled separately - -// do not copy caches - fExecutor = nullptr; - fArgIndices = nullptr; - fArgsRequired = -1; -} - -//---------------------------------------------------------------------------- -inline void CPyCppyy::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* CPyCppyy::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, (Cppyy::TCppObject_t)((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 Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetScope("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): - Cppyy::TCppType_t actual = Cppyy::GetActualClass(exc_type, &e); - const std::string& finalname = Cppyy::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 = CPyCppyy::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()) { - // only drop a reference if there is one to drop: ConstructorExecutor hands - // back the address of the new object cast to PyObject*, and decref'ing that - // corrupts the heap. Letting it go leaks the object, but this is the error - // path of a call that is about to be reported as failed anyway. - if (ResultIsPyObject()) - Py_XDECREF(result); - result = nullptr; - } -#endif - - return result; -} - -//---------------------------------------------------------------------------- -inline PyObject* CPyCppyy::CPPMethod::ExecuteProtected( - void* self, ptrdiff_t offset, CallContext* ctxt) -{ -// helper code to prevent some code duplication; this code embeds a "try/catch" -// block that saves the call environment for restoration in case of an otherwise -// fatal signal - PyObject* result = 0; - - CLING_EXCEPTION_TRY { // copy call environment to be able to jump back on signal - result = ExecuteFast(self, offset, ctxt); - } CLING_EXCEPTION_CATCH(excode) { - // report any outstanding Python exceptions first - if (PyErr_Occurred()) { - std::cerr << "Python exception outstanding during C++ longjmp:" << std::endl; - PyErr_Print(); - std::cerr << std::endl; - } - - // unfortunately, the excodes are not the ones from signal.h, but enums from TSysEvtHandler.h - if (excode == 0) - PyErr_SetString(gBusException, "bus error in C++; program state was reset"); - else if (excode == 1) - PyErr_SetString(gSegvException, "segfault in C++; program state was reset"); - else if (excode == 4) - PyErr_SetString(gIllException, "illegal instruction in C++; program state was reset"); - else if (excode == 5) - PyErr_SetString(gAbrtException, "abort from C++; program state was reset"); - else if (excode == 12) - PyErr_SetString(PyExc_FloatingPointError, "floating point exception in C++; program state was reset"); - else - PyErr_SetString(PyExc_SystemError, "problem in C++; program state was reset"); - result = 0; - } CLING_EXCEPTION_ENDTRY; - - return result; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::InitConverters_() -{ -// build buffers for argument dispatching - const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod); - fConverters.resize(nArgs); - -// setup the dispatch cache - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string& fullType = Cppyy::GetMethodArgType(fMethod, iarg); - Converter* conv = CreateConverter(fullType); - if (!conv) { - PyErr_Format(PyExc_TypeError, "argument type %s not handled", fullType.c_str()); - return false; - } - - fConverters[iarg] = conv; - } - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::InitExecutor_(Executor*& executor, CallContext* /* ctxt */) -{ -// install executor conform to the return type - executor = CreateExecutor( - (bool)fMethod == true ? Cppyy::GetMethodResultType(fMethod) \ - : Cppyy::GetScopedFinalName(fScope)); - - if (!executor) - return false; - - return true; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::CPPMethod::GetSignatureString(bool fa) -{ -// built a signature representation (used for doc strings) - return Cppyy::GetMethodSignature(fMethod, fa); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) -{ -// Helper to report errors in a consistent format (derefs msg). -// -// Handles three cases: -// 1. No Python error occurred 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 occurred: -// 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 = CPyCppyy_PyText_AsString(descr); - Py_DECREF(descr); - } - } - - PyObject* doc = GetDocString(); - const char* cdoc = CPyCppyy_PyText_AsString(doc); - const char* cmsg = msg ? CPyCppyy_PyText_AsString(msg) : nullptr; - PyObject* errtype = etype ? etype : PyExc_TypeError; - PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); - const char* cname = pyname ? CPyCppyy_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 = CPyCppyy_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); - } else { - topMessage = CPyCppyy_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); -} - -//- constructors and destructor ---------------------------------------------- -CPyCppyy::CPPMethod::CPPMethod( - Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : - fMethod(method), fScope(scope), fExecutor(nullptr), fArgIndices(nullptr), - fArgsRequired(-1) -{ - // empty -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMethod::CPPMethod(const CPPMethod& other) : - PyCallable(other), fMethod(other.fMethod), fScope(other.fScope) -{ - Copy_(other); -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMethod& CPyCppyy::CPPMethod::operator=(const CPPMethod& other) -{ - if (this != &other) { - Destroy_(); - Copy_(other); - fScope = other.fScope; - fMethod = other.fMethod; - } - - return *this; -} - -//---------------------------------------------------------------------------- -CPyCppyy::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* CPyCppyy::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 = Cppyy::GetScopedFinalName(fScope); - return CPyCppyy_PyText_FromFormat("%s%s %s%s%s%s", - (Cppyy::IsStaticMethod(fMethod) ? "static " : ""), - Cppyy::GetMethodResultType(fMethod).c_str(), - finalscope.c_str(), - (finalscope.empty() ? "" : "::"), // Add final set of '::' if the method is scoped in namespace(s) - Cppyy::GetMethodName(fMethod).c_str(), - GetSignatureString(fa).c_str()); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString( - (GetReturnTypeName() + \ - " (" + (fScope ? Cppyy::GetScopedFinalName(fScope) + "::*)" : "*)")).c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Reflex(Cppyy::Reflex::RequestId_t request, Cppyy::Reflex::FormatId_t format) -{ -// C++ reflection tooling for methods. - - if (request == Cppyy::Reflex::RETURN_TYPE) { - std::string rtn = GetReturnTypeName(); - Cppyy::TCppScope_t scope = 0; - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_TYPE) - scope = Cppyy::GetScope(rtn); - - if (format == Cppyy::Reflex::AS_STRING || (format == Cppyy::Reflex::OPTIMAL && !scope)) - return CPyCppyy_PyText_FromString(rtn.c_str()); - else if (format == Cppyy::Reflex::AS_TYPE || format == Cppyy::Reflex::OPTIMAL) { - if (scope) return CreateScopeProxy(scope); - /* TODO: builtins as type */ - } - } - - return PyCallable::Reflex(request, format); -} - -//---------------------------------------------------------------------------- -int CPyCppyy::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 = Cppyy::GetMethodNumArgs(fMethod); - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgType(fMethod, iarg); - - if (Cppyy::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 - - // oddball - else if (strstr(aname.c_str(), "void*")) - priority -= 1000; // void*/void** shouldn't be too greedy - - } 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); - Cppyy::TCppScope_t scope = Cppyy::GetScope(clean_name); - if (scope) - priority += static_cast(Cppyy::GetNumBasesLongestBranch(scope)); - - if (Cppyy::IsEnum(clean_name)) - 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 && !Cppyy::IsComplete(clean_name)) { - // 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)Cppyy::GetMethodReqArgs(fMethod) - (int)nArgs); - -// add a small penalty to prefer non-const methods over const ones for get/setitem - if (Cppyy::IsConstMethod(fMethod) && Cppyy::GetMethodName(fMethod) == "operator[]") - priority += -10; - - return priority; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::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 = Cppyy::GetMethodReqArgs(fMethod); - if (!nArgs) return false; - - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgType(fMethod, iarg); - if (aname.find("void*") != 0) - return false; - } - return true; -} - - -//---------------------------------------------------------------------------- -int CPyCppyy::CPPMethod::GetMaxArgs() -{ - return (int)Cppyy::GetMethodNumArgs(fMethod); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::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, CPyCppyy_PyText_FromString("self")); - for (int iarg = 0; iarg < co_argcount; ++iarg) { - std::string argrep = Cppyy::GetMethodArgType(fMethod, iarg); - const std::string& parname = Cppyy::GetMethodArgName(fMethod, iarg); - if (!parname.empty()) { - argrep += " "; - argrep += parname; - } - - PyObject* pyspec = CPyCppyy_PyText_FromString(argrep.c_str()); - PyTuple_SET_ITEM(co_varnames, iarg+1, pyspec); - } - - return co_varnames; -} - -PyObject* CPyCppyy::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 cppyy.gbl module to use its dictionary to eval in - static PyObject* gbl = PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppyy.gbl"); - - std::string defvalue = Cppyy::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 (!Cppyy::IsBuiltin(possible_scope)) { - std::string cand_scope = Cppyy::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(), "cppyy_default_compiler", Py_eval_input); - if (pycode) { - pyval = PyEval_EvalCode( -#if PY_VERSION_HEX < 0x03000000 - (PyCodeObject*) -#endif - pycode, gdct, gdct); - Py_DECREF(pycode); - } - - if (!pyval && PyErr_Occurred() && silent) { - PyErr_Clear(); - pyval = CPyCppyy_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", Cppyy::GetMethodArgName(fMethod, iarg).c_str()); - return nullptr; -} - - -bool CPyCppyy::CPPMethod::IsConst() { - return Cppyy::IsConstMethod(GetMethod()); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetScopeProxy() -{ -// Get or build the scope of this method. - return CreateScopeProxy(fScope); -} - - -//---------------------------------------------------------------------------- -Cppyy::TCppFuncAddr_t CPyCppyy::CPPMethod::GetFunctionAddress() -{ -// Return the C++ pointer of this function - return Cppyy::GetFunctionAddress(fMethod, false /* don't check fast path envar */); -} - -//---------------------------------------------------------------------------- -int CPyCppyy::CPPMethod::GetArgMatchScore(PyObject* args_tuple) -{ - Py_ssize_t n = PyTuple_Size(args_tuple); - - int req_args = Cppyy::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(!CPyCppyy_PyText_Check(pItem)) { - PyErr_SetString(PyExc_TypeError, "argument types should be in string format"); - return INT_MAX; - } - std::string req_type(CPyCppyy_PyText_AsString(pItem)); - - size_t arg_score = Cppyy::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 CPyCppyy::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 ? Cppyy::GetMethodReqArgs(fMethod) : 0); - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::ProcessKwds(PyObject* self_in, PyCallArgs& cargs) -{ -#if PY_VERSION_HEX >= 0x03080000 - if (!PyTuple_CheckExact(cargs.fKwds)) { - SetPyError_(CPyCppyy_PyText_FromString("received unknown keyword names object")); - return false; - } - Py_ssize_t nKeys = PyTuple_GET_SIZE(cargs.fKwds); -#else - if (!PyDict_CheckExact(cargs.fKwds)) { - SetPyError_(CPyCppyy_PyText_FromString("received unknown keyword arguments object")); - return false; - } - Py_ssize_t nKeys = PyDict_Size(cargs.fKwds); -#endif - - if (nKeys == 0 && !self_in) - return true; - - if (!fArgIndices) { - fArgIndices = new std::map{}; - for (int iarg = 0; iarg < (int)Cppyy::GetMethodNumArgs(fMethod); ++iarg) - (*fArgIndices)[Cppyy::GetMethodArgName(fMethod, iarg)] = iarg; - } - - Py_ssize_t nArgs = CPyCppyy_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; - -#if PY_VERSION_HEX >= 0x03080000 - Py_ssize_t npos_args = CPyCppyy_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]; -#else - Py_ssize_t pos = 0; - while (PyDict_Next(cargs.fKwds, &pos, &key, &value)) { -#endif - const char* ckey = CPyCppyy_PyText_AsStringChecked(key); - if (!ckey) - return false; - - auto p = fArgIndices->find(ckey); - if (p == fArgIndices->end()) { - SetPyError_(CPyCppyy_PyText_FromFormat("%s::%s got an unexpected keyword argument \'%s\'", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetMethodName(fMethod).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; - CPyCppyy_PyArgs_t newArgs = CPyCppyy_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) - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, nullptr); - -// fill out the positional arguments - Py_ssize_t start = 0; - if (self_in) { - Py_INCREF(self_in); - CPyCppyy_PyArgs_SET_ITEM(newArgs, 0, self_in); - start = 1; - } - - for (Py_ssize_t i = start; i < nArgs; ++i) { - if (vArgs[i]) { - SetPyError_(CPyCppyy_PyText_FromFormat("%s::%s got multiple values for argument %d", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetMethodName(fMethod).c_str(), (int)i+1)); - CPyCppyy_PyArgs_DEL(newArgs); - return false; - } - - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, i); - Py_INCREF(item); - CPyCppyy_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); - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, item); - } else { - // try retrieving the default - item = GetArgDefault((int)i, false /* i.e. not silent */); - if (!item) { - CPyCppyy_PyArgs_DEL(newArgs); - return false; - } - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, item); - } - } - -#if PY_VERSION_HEX >= 0x03080000 - if (cargs.fFlags & PyCallArgs::kDoFree) { - if (cargs.fFlags & PyCallArgs::kIsOffset) - cargs.fArgs -= 1; -#else - if (cargs.fFlags & PyCallArgs::kDoDecref) { -#endif - CPyCppyy_PyArgs_DEL(cargs.fArgs); - } - - cargs.fArgs = newArgs; - cargs.fNArgsf = maxargs; -#if PY_VERSION_HEX >= 0x03080000 - cargs.fFlags = PyCallArgs::kDoFree | PyCallArgs::kDoItemDecref; -#else - cargs.fFlags = PyCallArgs::kDoDecref; -#endif - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::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 (CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) != 0) { - CPPInstance* pyobj = (CPPInstance*)CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, 0); - - // demand CPyCppyy object, and an argument that may match down the road - if (CPPInstance_Check(pyobj)) { - Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - if (fScope == Cppyy::gGlobalScope || // free global - oisa == 0 || // null pointer or ctor call - oisa == fScope || // matching types - Cppyy::IsSubtype(oisa, fScope)) { // id. - - // reset self - Py_INCREF(pyobj); // corresponding Py_DECREF is in CPPOverload - cargs.fSelf = pyobj; - - // offset args by 1 -#if PY_VERSION_HEX >= 0x03080000 - cargs.fArgs += 1; - cargs.fFlags |= PyCallArgs::kIsOffset; -#else - if (cargs.fFlags & PyCallArgs::kDoDecref) - Py_DECREF((PyObject*)cargs.fArgs); - cargs.fArgs = PyTuple_GetSlice(cargs.fArgs, 1, PyTuple_GET_SIZE(cargs.fArgs)); - cargs.fFlags |= PyCallArgs::kDoDecref; -#endif - 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_(CPyCppyy_PyText_FromFormat( - "unbound method %s::%s must be called with a %s instance as first argument", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetMethodName(fMethod).c_str(), - Cppyy::GetFinalName(fScope).c_str())); - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::ConvertAndSetArgs(CPyCppyy_PyArgs_t args, size_t nargsf, CallContext* ctxt) -{ - Py_ssize_t argc = CPyCppyy_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(CPyCppyy_PyArgs_GET_ITEM(args, i), cppArgs[i], ctxt)) { - SetPyError_(CPyCppyy_PyText_FromFormat("could not convert argument %d", i+1)); - isOK = false; - break; - } - } - - return isOK; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext* ctxt) -{ -// call the interface method - PyObject* result = 0; - - if (!(CallContext::GlobalPolicyFlags() & CallContext::kProtected) && !(ctxt->fFlags & CallContext::kProtected)) { - // bypasses try block (i.e. segfaults will abort) - result = ExecuteFast(self, offset, ctxt); - } else { - // at the cost of ~10% performance, don't abort the interpreter on any signal - result = ExecuteProtected(self, offset, ctxt); - } - - if (!result && PyErr_Occurred()) - SetPyError_(0); - - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Call(CPPInstance*& self, - CPyCppyy_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 || CPyCppyy_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, "attempt to access a null-pointer"); - return nullptr; - } - -// get its class - Cppyy::TCppType_t derived = self->ObjectIsA(); - -// calculate offset (the method expects 'this' to be an object of fScope) - ptrdiff_t offset = 0; - if (derived && derived != fScope) - offset = Cppyy::GetBaseOffset(derived, fScope, 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* CPyCppyy::CPPMethod::GetSignature(bool fa) -{ -// construct python string from the method's signature - return CPyCppyy_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 *CPyCppyy::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 = Cppyy::GetMethodArgName(fMethod, iarg); - PyObject *argname_py = CPyCppyy_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 *CPyCppyy::CPPMethod::GetSignatureTypes() -{ - - PyObject *signature_types_dict = PyDict_New(); - - // Insert the return type first - std::string return_type = GetReturnTypeName(); - PyObject *return_type_py = CPyCppyy_PyText_FromString(return_type.c_str()); - PyDict_SetItem(signature_types_dict, CPyCppyy_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 = Cppyy::GetMethodArgType(fMethod, iarg); - PyObject *argtype_py = CPyCppyy_PyText_FromString(argtype_cpp.c_str()); - PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); - } - - PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("input_types"), parameter_types); - - return signature_types_dict; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::CPPMethod::GetReturnTypeName() -{ - return Cppyy::GetMethodResultType(fMethod); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h deleted file mode 100644 index 05b5b237adb1e..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef CPYCPPYY_CPPMETHOD_H -#define CPYCPPYY_CPPMETHOD_H - -// Bindings -#include "PyCallable.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -class Executor; -class Converter; - -class PyCallArgs { -public: - PyCallArgs(CPPInstance*& self, CPyCppyy_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 -#if PY_VERSION_HEX >= 0x03080000 - kDoFree = 0x0008, // args need to be free'd (vector call only) - kDoItemDecref = 0x0010 // items in args need a decref (vector call only) -#else - kDoDecref = 0x0020 // args need a decref -#endif - }; - -public: - CPPInstance*& fSelf; - CPyCppyy_PyArgs_t fArgs; - size_t fNArgsf; - PyObject* fKwds; - int fFlags; -}; - -class CPPMethod : public PyCallable { -public: - CPPMethod(Cppyy::TCppScope_t scope, Cppyy::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(Cppyy::Reflex::RequestId_t request, - Cppyy::Reflex::FormatId_t = Cppyy::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; - Cppyy::TCppFuncAddr_t GetFunctionAddress() override; - - PyCallable* Clone() override { return new CPPMethod(*this); } - - int GetArgMatchScore(PyObject* args_tuple) override; - -public: - PyObject* Call(CPPInstance*& self, - CPyCppyy_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(CPyCppyy_PyArgs_t, size_t nargsf, CallContext* ctxt = nullptr); - PyObject* Execute(void* self, ptrdiff_t offset, CallContext* ctxt = nullptr); - - Cppyy::TCppMethod_t GetMethod() { return fMethod; } - Cppyy::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 hands back a real PyObject*; ConstructorExecutor does not, -// it returns the address of the newly constructed object - virtual bool ResultIsPyObject() const { return true; } - -private: - void Copy_(const CPPMethod&); - void Destroy_(); - bool VerifyArgCount_(Py_ssize_t); - - PyObject* ExecuteFast(void*, ptrdiff_t, CallContext*); - PyObject* ExecuteProtected(void*, ptrdiff_t, CallContext*); - - bool InitConverters_(); - - void SetPyError_(PyObject* msg); - -private: -// representation - Cppyy::TCppMethod_t fMethod; - Cppyy::TCppScope_t fScope; - Executor* fExecutor; - -// call dispatch buffers - std::vector fConverters; - std::map* fArgIndices; - -protected: -// cached value that doubles as initialized flag (uninitialized if -1) - int fArgsRequired; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPMETHOD_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx deleted file mode 100644 index 4832136d74cd2..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx +++ /dev/null @@ -1,64 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPOperator.h" -#include "CPPInstance.h" -#include "Utility.h" - - -//- constructor -------------------------------------------------------------- -CPyCppyy::CPPOperator::CPPOperator( - Cppyy::TCppScope_t scope, Cppyy::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 == CPPYY__div__) -#if PY_VERSION_HEX < 0x03000000 - fStub = CPPInstance_Type.tp_as_number->nb_divide; -#else - fStub = CPPInstance_Type.tp_as_number->nb_true_divide; -#endif - 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* CPyCppyy::CPPOperator::Call(CPPInstance*& self, - CPyCppyy_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 (CPyCppyy_PyArgs_GET_SIZE(args, nargsf) != 1) { -#if PY_VERSION_HEX >= 0x03080000 - if ((CPyCppyy_PyArgs_GET_SIZE(args, nargsf) == 2 && CPyCppyy_PyArgs_GET_ITEM(args, 0) == (PyObject*)self)) - idx_other = 1; - else -#endif - return result; - } - -// fetch the current error, resetting the error buffer - auto error = CPyCppyy::Utility::FetchPyError(); - - result = fStub((PyObject*)self, CPyCppyy_PyArgs_GET_ITEM(args, idx_other)); - -// if there was still a problem, restore the Python error buffer - if (!result) { - CPyCppyy::Utility::RestorePyError(error); - } - - return result; -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.h deleted file mode 100644 index e26026785f43c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CPYCPPYY_CPPOPERATOR_H -#define CPYCPPYY_CPPOPERATOR_H - -// Bindings -#include "CPPMethod.h" - -// Standard -#include - - -namespace CPyCppyy { - -class CPPOperator : public CPPMethod { -public: - CPPOperator(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method, const std::string& name); - -public: - PyCallable* Clone() override { return new CPPOperator(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -private: - binaryfunc fStub; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPOPERATOR_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx deleted file mode 100644 index 6d2a2edfb5aee..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ /dev/null @@ -1,1287 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPyCppyy/Reflex.h" -#include "structmember.h" // from Python -#if PY_VERSION_HEX >= 0x02050000 -#if PY_VERSION_HEX < 0x030b0000 -#include "code.h" // from Python -#endif -#else -#include "compile.h" // from Python -#endif -#ifndef CO_NOFREE -// python2.2 does not have CO_NOFREE defined -#define CO_NOFREE 0x0040 -#endif -#include "CPPOverload.h" -#include "CPPInstance.h" -#include "CallContext.h" -#include "PyStrings.h" -#include "Utility.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -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 CPyCppyy_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 CPyCppyy_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; - } - - Cppyy::TCppFuncAddr_t GetFunctionAddress() override { - return (Cppyy::TCppFuncAddr_t)nullptr; - } - - PyCallable* Clone() override { return new TPythonCallback(*this); } - - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* /* ctxt = 0 */) override { - -#if PY_VERSION_HEX >= 0x03080000 - 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 = CPyCppyy_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); - } -#else - PyObject* newArgs = nullptr; - if (self) { - Py_ssize_t nargs = PyTuple_Size(args); - newArgs = PyTuple_New(nargs+1); - Py_INCREF(self); - PyTuple_SET_ITEM(newArgs, 0, (PyObject*)self); - for (Py_ssize_t iarg = 0; iarg < nargs; ++iarg) { - PyObject* pyarg = PyTuple_GET_ITEM(args, iarg); - Py_INCREF(pyarg); - PyTuple_SET_ITEM(newArgs, iarg+1, pyarg); - } - } else { - Py_INCREF(args); - newArgs = args; - } - PyObject* result = PyObject_Call(fCallable, newArgs, kwds); - Py_DECREF(newArgs); -#endif - 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)Cppyy::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; -} - - -//= CPyCppyy method proxy object behaviour =================================== -static PyObject* mp_name(CPPOverload* pymeth, void*) -{ - return CPyCppyy_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 = CPyCppyy_PyText_FromString("\n"); - for (CPPOverload::Methods_t::size_type i = 1; i < nMethods; ++i) { - CPyCppyy_PyText_Append(&doc, separator); - CPyCppyy_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'}} - */ -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; -} - -// To declare a variable as unused only when compiling for Python 3. -#if PY_VERSION_HEX < 0x03000000 -#define CPyCppyy_Py3_UNUSED(name) name -#else -#define CPyCppyy_Py3_UNUSED(name) -#endif - -//---------------------------------------------------------------------------- -static PyObject* mp_func_code(CPPOverload* CPyCppyy_Py3_UNUSED(pymeth), void*) -{ -// Code details are used in module inspect to fill out interactive help() -#if PY_VERSION_HEX < 0x03000000 - CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; - -// collect arguments only if there is just 1 overload, otherwise put in a -// fake *args (see below for co_varnames) - PyObject* co_varnames = methods.size() == 1 ? methods[0]->GetCoVarNames() : nullptr; - if (!co_varnames) { - // TODO: static methods need no 'self' (but is harmless otherwise) - co_varnames = PyTuple_New(1 /* self */ + 1 /* fake */); - PyTuple_SET_ITEM(co_varnames, 0, CPyCppyy_PyText_FromString("self")); - PyTuple_SET_ITEM(co_varnames, 1, CPyCppyy_PyText_FromString("*args")); - } - - int co_argcount = (int)PyTuple_Size(co_varnames); - -// for now, code object representing the statement 'pass' - PyObject* co_code = PyString_FromStringAndSize("d\x00\x00S", 4); - -// tuples with all the const literals used in the function - PyObject* co_consts = PyTuple_New(0); - PyObject* co_names = PyTuple_New(0); - -// names, freevars, and cellvars go unused - PyObject* co_unused = PyTuple_New(0); - -// filename is made-up - PyObject* co_filename = PyString_FromString("cppyy.py"); - -// name is the function name, also through __name__ on the function itself - PyObject* co_name = PyString_FromString(pymeth->GetName().c_str()); - -// firstlineno is the line number of first function code in the containing scope - -// lnotab is a packed table that maps instruction count and line number - PyObject* co_lnotab = PyString_FromString("\x00\x01\x0c\x01"); - - PyObject* code = (PyObject*)PyCode_New( - co_argcount, // argcount - co_argcount+1, // nlocals - 2, // stacksize - CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE, // flags - co_code, // code - co_consts, // consts - co_names, // names - co_varnames, // varnames - co_unused, // freevars - co_unused, // cellvars - co_filename, // filename - co_name, // name - 1, // firstlineno - co_lnotab); // lnotab - - Py_DECREF(co_lnotab); - Py_DECREF(co_name); - Py_DECREF(co_unused); - Py_DECREF(co_filename); - Py_DECREF(co_varnames); - Py_DECREF(co_names); - Py_DECREF(co_consts); - Py_DECREF(co_code); - - return code; -#else -// not important for functioning of most code, so not implemented for p3 for now (TODO) - Py_RETURN_NONE; -#endif -} - -//---------------------------------------------------------------------------- -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 cppyy module); used -// for lookup of names from co_code indexing into co_names. - PyObject* pyglobal = PyModule_GetDict(PyImport_AddModule((char*)"cppyy")); - 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 CPPYY_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); \ -} - -CPPYY_BOOLEAN_PROPERTY(lifeline, CallContext::kSetLifeLine, "__set_lifeline__") -CPPYY_BOOLEAN_PROPERTY(threaded, CallContext::kReleaseGIL, "__release_gil__") -CPPYY_BOOLEAN_PROPERTY(useffi, CallContext::kUseFFI, "__useffi__") -CPPYY_BOOLEAN_PROPERTY(sig2exc, CallContext::kProtected, "__sig2exc__") - -static PyObject* mp_getcppname(CPPOverload* pymeth, void*) -{ - if ((void*)pymeth == (void*)&CPPOverload_Type) - return CPyCppyy_PyText_FromString("CPPOverload_Type"); - - auto& methods = pymeth->fMethodInfo->fMethods; - if (methods.empty()) - return CPyCppyy_PyText_FromString("void (*)()"); // debatable - - if (methods.size() == 1) - return methods[0]->GetTypeName(); - - return CPyCppyy_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} -}; - -//= CPyCppyy method proxy function behavior ================================== -#if PY_VERSION_HEX >= 0x03080000 -static PyObject* mp_vectorcall( - CPPOverload* pymeth, PyObject* const *args, size_t nargsf, PyObject* kwds) -#else -static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) -#endif -{ -#if PY_VERSION_HEX < 0x03080000 - size_t nargsf = PyTuple_GET_SIZE(args); -#endif - -// 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; - 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; - } - - std::vector errors; - std::vector implicit_possible(methods.size()); - for (int stage = 0; stage < 2; ++stage) { - bool bHaveImplicit = false; - 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", - CPyCppyy_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; - - ctxt.fFlags |= CallContext::kAllowImplicit; - } - -// first summarize, then add details - PyObject* topmsg = CPyCppyy_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 CPyCppyy_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 - -#if PY_VERSION_HEX < 0x03080000 - if (!pyobj || (PyObject*)pyobj == Py_None /* from unbound TemplateProxy */) { - Py_XDECREF(pymeth->fSelf); pymeth->fSelf = nullptr; - pymeth->fFlags |= CallContext::kCallDirect | CallContext::kFromDescr; - Py_INCREF(pymeth); - return pymeth; // unbound, e.g. free functions - } -#endif - -// 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; - -#if PY_VERSION_HEX >= 0x03080000 - 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; - -#else -// new method is to be bound to current object - Py_INCREF((PyObject*)pyobj); - newPyMeth->fSelf = pyobj; - -// reset flags of the new method, as there is a self (which may or may not have -// come in through direct call syntax, but that's now impossible to know, so this -// is the safer choice) - newPyMeth->fFlags = CallContext::kNone; -#endif - - if (gc_track) - PyObject_GC_Track(newPyMeth); - - return newPyMeth; -} - - -//= CPyCppyy 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 = (CPyCppyy::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; -} - - -//= CPyCppyy 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. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::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 - - -//= CPyCppyy method proxy type =============================================== -PyTypeObject CPPOverload_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPOverload", // tp_name - sizeof(CPPOverload), // tp_basicsize - 0, // tp_itemsize - (destructor)mp_dealloc, // tp_dealloc -#if PY_VERSION_HEX >= 0x03080000 - offsetof(CPPOverload, 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 - 0, // tp_as_mapping - (hashfunc)mp_hash, // tp_hash -#if PY_VERSION_HEX >= 0x03080000 - (ternaryfunc)PyVectorcall_Call, // tp_call -#else - (ternaryfunc)mp_call, // tp_call -#endif - (reprfunc)mp_str, // 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 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 -#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 - - -//- public members ----------------------------------------------------------- -void CPyCppyy::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; - } - } - -#if PY_VERSION_HEX >= 0x03080000 - fVectorCall = (vectorcallfunc)mp_vectorcall; -#endif -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPOverload::AdoptMethod(PyCallable* pc) -{ -// Fill in the data of a freshly created method proxy. - fMethodInfo->fMethods.push_back(pc); - fMethodInfo->fFlags &= ~CallContext::kIsSorted; -} - -//---------------------------------------------------------------------------- -void CPyCppyy::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* CPyCppyy::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(CPyCppyy_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(CPyCppyy_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* CPyCppyy::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 Cppyy::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(!CPyCppyy_PyText_Check(pItem)) { - PyErr_Format(PyExc_LookupError, "argument types should be in string format"); - return (PyObject*) nullptr; - } - std::string arg_type(CPyCppyy_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; -} - -//---------------------------------------------------------------------------- -CPyCppyy::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/cppyy/CPyCppyy/src/CPPOverload.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h deleted file mode 100644 index 8bd3b2de234a9..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef CPYCPPYY_CPPOVERLOAD_H -#define CPYCPPYY_CPPOVERLOAD_H - -// Bindings -#include "PyCallable.h" - -// Standard -#include -#include -#include -#include - - -namespace CPyCppyy { - -// signature hashes are also used by TemplateProxy -inline uint64_t HashSignature(CPyCppyy_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 = CPyCppyy_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 = CPyCppyy_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; -#if PY_VERSION_HEX >= 0x03080000 - vectorcallfunc fVectorCall; -#endif - -private: - CPPOverload() = delete; -}; - - -//- method proxy type and type verification ---------------------------------- -CPYCPPYY_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 CPyCppyy - -#endif // !CPYCPPYY_CPPOVERLOAD_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx deleted file mode 100644 index 21f512593370b..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx +++ /dev/null @@ -1,711 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPScope.h" -#include "CPPDataMember.h" -#include "CPPEnum.h" -#include "CPPFunction.h" -#include "CPPOverload.h" -#include "CustomPyTypes.h" -#include "Dispatcher.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TemplateProxy.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include // for for_each -#include -#include -#include - - -namespace CPyCppyy { - -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 = CPyCppyy_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 -} - - -//= CPyCppyy 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 CPyCppyy_PyText_FromString("CPPInstance_Type"); - return CPyCppyy_PyText_FromString(Cppyy::GetScopedFinalName(scope->fCppType).c_str()); -} - -//----------------------------------------------------------------------------- -static PyObject* meta_getmodule(CPPScope* scope, void*) -{ - if ((void*)scope == (void*)&CPPInstance_Type) - return CPyCppyy_PyText_FromString("cppyy.gbl"); - - if (scope->fModuleName) - return CPyCppyy_PyText_FromString(scope->fModuleName); - -// get C++ representation of outer scope - std::string modname = - TypeManip::extract_namespace(Cppyy::GetScopedFinalName(scope->fCppType)); - if (modname.empty()) - return CPyCppyy_PyText_FromString(const_cast("cppyy.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 = CPyCppyy::GetScopeProxy(Cppyy::GetScope(modname)); - 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) { - CPyCppyy_PyText_AppendAndDel(&pymodule, CPyCppyy_PyText_FromString(".")); - CPyCppyy_PyText_AppendAndDel(&pymodule, pymodname); - } - } - Py_DECREF(pyscope); - } - - if (pymodule) - return pymodule; - PyErr_Clear(); - -// lookup through python failed, so simply cook up a '::' -> '.' replacement - TypeManip::cppscope_to_pyscope(modname); - return CPyCppyy_PyText_FromString(("cppyy.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 'cppyy.CPPScope\' objects is not writable"); - return -1; - } - - const char* newname = CPyCppyy_PyText_AsStringChecked(value); - if (!value) - return -1; - - free(scope->fModuleName); - Py_ssize_t sz = CPyCppyy_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 CPyCppyy_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 = Cppyy::GetFinalName(scope->fCppType); - const char* kind = (scope->fFlags & CPPScope::kIsNamespace) ? "namespace" : "class"; - - PyObject* repr = CPyCppyy_PyText_FromFormat("<%s %s.%s at %p>", - kind, CPyCppyy_PyText_AsString(modname), clName.c_str(), scope); - - Py_DECREF(modname); - return repr; -} - - -//= CPyCppyy 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 -// cppyy 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 - Cppyy::TCppType_t raw{0}; Cppyy::TCppMethod_t deref{0}; - if (CPPScope_CheckExact(subtype)) { - if (Cppyy::GetSmartPtrInfo(Cppyy::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 = Cppyy::GetScope( - CPyCppyy_PyText_AsString(PyTuple_GET_ITEM(args, 0))); - } else { - // coming here from cppyy 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 && !Cppyy::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 = CPyCppyy_PyText_FromString(Cppyy::GetBaseName(result->fCppType, 0).c_str()); - PyErr_Clear(); - 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 (!Cppyy::IsNamespace(result->fCppType)) { - static Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetScope("std::exception"); - if (Cppyy::IsSubtype(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 - return attr; - } - - if (!CPyCppyy_PyText_CheckExact(pyname) || !CPPScope_Check(pyclass)) - return possibly_shadowed; - -// filter for python specials - std::string name = CPyCppyy_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); - Cppyy::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) { - // tickle lazy lookup of functions - const std::vector methods = - Cppyy::GetMethodIndicesFromName(scope, name); - if (!methods.empty()) { - // function exists, now collect overloads - std::vector overloads; - for (auto idx : methods) { - overloads.push_back( - new CPPFunction(scope, Cppyy::GetMethod(scope, idx))); - } - - // 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 (Cppyy::ExistsMethodTemplate(scope, name)) - attr = add_template(pyclass, name, &overloads); - else - attr = (PyObject*)CPPOverload_New(name, overloads); - templated_functions_checked = true; - } - - // tickle lazy lookup of data members - if (!attr) { - Cppyy::TCppIndex_t dmi = Cppyy::GetDatamemberIndex(scope, name); - if (dmi != (Cppyy::TCppIndex_t)-1) attr = (PyObject*)CPPDataMember_New(scope, dmi); - } - } - - // this may be a typedef that resolves to a sugared type - if (!attr) { - const std::string& lookup = Cppyy::GetScopedFinalName(klass->fCppType) + "::" + name; - const std::string& resolved = Cppyy::ResolveName(lookup); - if (resolved != lookup) { - const std::string& cpd = TypeManip::compound(resolved); - if (cpd == "*") { - const std::string& clean = TypeManip::clean_type(resolved, false, true); - Cppyy::TCppType_t tcl = Cppyy::GetScope(clean); - 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 (Cppyy::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) { - // TODO: IsEnum should deal with the scope, using klass->GetListOfEnums()->FindObject() - const std::string& ename = scope == Cppyy::gGlobalScope ? name : Cppyy::GetScopedFinalName(scope)+"::"+name; - if (Cppyy::IsEnum(ename)) { - // enum types (incl. named and class enums) - attr = (PyObject*)CPPEnum_New(name, scope); - } 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); - 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 = Cppyy::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 = Cppyy::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 = CPyCppyy_PyText_FromString(("__lifeline_"+uname).c_str()); - PyType_Type.tp_setattro(pyclass, llname, pyuscope); - Py_DECREF(llname); - Py_DECREF(pyuscope); - } - } - } - - // 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 = CPyCppyy_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* sklass = PyObject_Str(pyclass); - if (sklass) { - topmsg = CPyCppyy_PyText_FromFormat("%s has no attribute \'%s\'. Full details:", - CPyCppyy_PyText_AsString(sklass), CPyCppyy_PyText_AsString(pyname)); - Py_DECREF(sklass); - } else { - topmsg = CPyCppyy_PyText_FromFormat("no such attribute \'%s\'. Full details:", - CPyCppyy_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. - if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace) { - // skip if the given pyval is a descriptor already, or an unassignable class - if (!CPyCppyy::CPPDataMember_Check(pyval) && !CPyCppyy::CPPScope_Check(pyval)) { - std::string name = CPyCppyy_PyText_AsString(pyname); - Cppyy::TCppIndex_t dmi = Cppyy::GetDatamemberIndex(((CPPScope*)pyclass)->fCppType, name); - if (dmi != (Cppyy::TCppIndex_t)-1) - meta_getattro(pyclass, pyname); // triggers creation - } - } - PyErr_Clear(); // creation might have failed - return PyType_Type.tp_setattro(pyclass, pyname, pyval); -} - - -//---------------------------------------------------------------------------- -static PyObject* meta_reflex(CPPScope* klass, PyObject* args) -{ -// Provide the requested reflection information. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL; - if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, &format)) - return nullptr; - - switch (request) { - case Cppyy::Reflex::IS_NAMESPACE: - if (klass->fFlags & CPPScope::kIsNamespace) - Py_RETURN_TRUE; - Py_RETURN_FALSE; - break; - case Cppyy::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 (Cppyy::IsAggregate(klass->fCppType) || !Cppyy::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 Cppyy; - - if ((void*)klass == (void*)&CPPInstance_Type) - return PyList_New(0); - - if (!CPyCppyy::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; - Cppyy::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(CPyCppyy_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++, CPyCppyy_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} -}; - - -//= CPyCppyy object proxy type type ========================================== -PyTypeObject CPPScope_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPScope", // tp_name - sizeof(CPyCppyy::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 -#if PY_VERSION_HEX >= 0x03040000 - | Py_TPFLAGS_TYPE_SUBCLASS -#endif - , // tp_flags - (char*)"CPyCppyy 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 -#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/CPPScope.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h deleted file mode 100644 index 7a6959fa260e6..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef CPYCPPYY_CPPSCOPE_H -#define CPYCPPYY_CPPSCOPE_H - -#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 - - -namespace CPyCppyy { - -/** 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::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, - kNoImplicit = 0x0080, - kNoOSInsertion = 0x0100, - kGblOSInsertion = 0x0200, - kNoPrettyPrint = 0x0400 }; - -public: - PyHeapTypeObject fType; - Cppyy::TCppType_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: - Cppyy::TCppType_t fUnderlyingType; - Cppyy::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(Cppyy::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 CPyCppyy - -#endif // !CPYCPPYY_CPPSCOPE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyy.h deleted file mode 100644 index 7a4298d9d1a22..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyy.h +++ /dev/null @@ -1,418 +0,0 @@ -#ifndef CPYCPPYY_CPYCPPYY_H -#define CPYCPPYY_CPYCPPYY_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 CPyCppyy { - typedef Py_ssize_t dim_t; -} // namespace CPyCppyy - -// for 3.3 support -#if PY_VERSION_HEX < 0x03030000 - typedef PyDictEntry* (*dict_lookup_func)(PyDictObject*, PyObject*, long); -#else -#if PY_VERSION_HEX >= 0x030b0000 - typedef Py_ssize_t (*dict_lookup_func)( - PyDictObject*, PyObject*, Py_hash_t, PyObject**); -#elif PY_VERSION_HEX >= 0x03060000 - typedef Py_ssize_t (*dict_lookup_func)( - PyDictObject*, PyObject*, Py_hash_t, PyObject***, Py_ssize_t*); -#else - struct PyDictKeyEntry; - typedef PyDictKeyEntry* (*dict_lookup_func)(PyDictObject*, PyObject*, Py_hash_t, PyObject***); -#define PyDictEntry PyDictKeyEntry -#endif -#endif - -// for 3.0 support (backwards compatibility, really) -#if PY_VERSION_HEX < 0x03000000 -#define PyBytes_Check PyString_Check -#define PyBytes_CheckExact PyString_CheckExact -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyBytes_AsString PyString_AsString -#define PyBytes_AsStringAndSize PyString_AsStringAndSize -#define PyBytes_GET_SIZE PyString_GET_SIZE -#define PyBytes_Size PyString_Size -#define PyBytes_FromFormat PyString_FromFormat -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromStringAndSize PyString_FromStringAndSize - -#define PyBytes_Type PyString_Type - -#define CPyCppyy_PyText_Check PyString_Check -#define CPyCppyy_PyText_CheckExact PyString_CheckExact -#define CPyCppyy_PyText_AsString PyString_AS_STRING -#define CPyCppyy_PyText_AsStringChecked PyString_AsString -#define CPyCppyy_PyText_GET_SIZE PyString_GET_SIZE -#define CPyCppyy_PyText_GetSize PyString_Size -#define CPyCppyy_PyText_FromFormat PyString_FromFormat -#define CPyCppyy_PyText_FromString PyString_FromString -#define CPyCppyy_PyText_InternFromString PyString_InternFromString -#define CPyCppyy_PyText_Append PyString_Concat -#define CPyCppyy_PyText_AppendAndDel PyString_ConcatAndDel -#define CPyCppyy_PyText_FromStringAndSize PyString_FromStringAndSize - -static inline const char* CPyCppyy_PyText_AsStringAndSize(PyObject* pystr, Py_ssize_t* size) -{ - const char* cstr = CPyCppyy_PyText_AsStringChecked(pystr); - if (cstr) *size = CPyCppyy_PyText_GetSize(pystr); - return cstr; -} - -#define CPyCppyy_PyText_Type PyString_Type - -#define CPyCppyy_PyUnicode_GET_SIZE PyUnicode_GET_SIZE - -static inline PyObject* CPyCppyy_PyCapsule_New( - void* cobj, const char* /* name */, void (*destr)(void*)) -{ - return PyCObject_FromVoidPtr(cobj, destr); -} -#define CPyCppyy_PyCapsule_CheckExact PyCObject_Check -static inline void* CPyCppyy_PyCapsule_GetPointer(PyObject* capsule, const char* /* name */) -{ - return (void*)PyCObject_AsVoidPtr(capsule); -} - -#define CPPYY__long__ "__long__" -#define CPPYY__idiv__ "__idiv__" -#define CPPYY__div__ "__div__" -#define CPPYY__next__ "next" - -typedef long Py_hash_t; - -#endif // ! 3.0 - -// for 3.0 support (backwards compatibility, really) -#if PY_VERSION_HEX >= 0x03000000 -#define CPyCppyy_PyText_Check PyUnicode_Check -#define CPyCppyy_PyText_CheckExact PyUnicode_CheckExact -#define CPyCppyy_PyText_AsString PyUnicode_AsUTF8 -#define CPyCppyy_PyText_AsStringChecked PyUnicode_AsUTF8 -#define CPyCppyy_PyText_GetSize PyUnicode_GetSize -#if PY_VERSION_HEX < 0x03090000 -#define CPyCppyy_PyText_GET_SIZE PyUnicode_GET_SIZE -#define CPyCppyy_PyUnicode_GET_SIZE PyUnicode_GET_LENGTH -#else -#define CPyCppyy_PyText_GET_SIZE PyUnicode_GET_LENGTH -#define CPyCppyy_PyUnicode_GET_SIZE PyUnicode_GET_LENGTH -#endif -#define CPyCppyy_PyText_FromFormat PyUnicode_FromFormat -#define CPyCppyy_PyText_FromString PyUnicode_FromString -#define CPyCppyy_PyText_InternFromString PyUnicode_InternFromString -#define CPyCppyy_PyText_Append PyUnicode_Append -#define CPyCppyy_PyText_AppendAndDel PyUnicode_AppendAndDel -#define CPyCppyy_PyText_FromStringAndSize PyUnicode_FromStringAndSize - -#if PY_VERSION_HEX >= 0x03030000 -#define _CPyCppyy_PyText_AsStringAndSize PyUnicode_AsUTF8AndSize -#else -#define _CPyCppyy_PyText_AsStringAndSize PyUnicode_AsStringAndSize -#endif // >= 3.3 - -static inline const char* CPyCppyy_PyText_AsStringAndSize(PyObject* pystr, Py_ssize_t* size) -{ - const char* cstr = _CPyCppyy_PyText_AsStringAndSize(pystr, size); - if (!cstr && PyBytes_CheckExact(pystr)) { - PyErr_Clear(); - PyBytes_AsStringAndSize(pystr, (char**)&cstr, size); - } - return cstr; -} - -#define CPyCppyy_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 CPyCppyy_PyCapsule_New PyCapsule_New -#define CPyCppyy_PyCapsule_CheckExact PyCapsule_CheckExact -#define CPyCppyy_PyCapsule_GetPointer PyCapsule_GetPointer - -#define CPPYY__long__ "__int__" -#define CPPYY__idiv__ "__itruediv__" -#define CPPYY__div__ "__truediv__" -#define CPPYY__next__ "__next__" - -#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 -#define Py_TPFLAGS_CHECKTYPES 0 - -#define PyClass_Check PyType_Check - -#define PyBuffer_Type PyMemoryView_Type -#endif // ! 3.0 - -#if PY_VERSION_HEX >= 0x03020000 -#define CPyCppyy_PySliceCast PyObject* -#define PyUnicode_GetSize PyUnicode_GetLength -#else -#define CPyCppyy_PySliceCast PySliceObject* -#endif // >= 3.2 - -// feature of 3.0 not in 2.5 and earlier -#if PY_VERSION_HEX < 0x02060000 -#define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, -#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) -#endif - -// API changes in 2.5 (int -> Py_ssize_t) and 3.2 (PyUnicodeObject -> PyObject) -#if PY_VERSION_HEX < 0x03020000 -static inline Py_ssize_t CPyCppyy_PyUnicode_AsWideChar(PyObject* pyobj, wchar_t* w, Py_ssize_t size) -{ -#if PY_VERSION_HEX < 0x02050000 - return (Py_ssize_t)PyUnicode_AsWideChar((PyUnicodeObject*)pyobj, w, (int)size); -#else - return PyUnicode_AsWideChar((PyUnicodeObject*)pyobj, w, size); -#endif -} -#else -#define CPyCppyy_PyUnicode_AsWideChar PyUnicode_AsWideChar -#endif - -// backwards compatibility, pre python 2.5 -#if PY_VERSION_HEX < 0x02050000 -typedef int Py_ssize_t; -#define PyInt_AsSsize_t PyInt_AsLong -#define PyInt_FromSsize_t PyInt_FromLong -# define PY_SSIZE_T_FORMAT "%d" -# if !defined(PY_SSIZE_T_MIN) -# define PY_SSIZE_T_MAX INT_MAX -# define PY_SSIZE_T_MIN INT_MIN -# endif -#define ssizeobjargproc intobjargproc -#define lenfunc inquiry -#define ssizeargfunc intargfunc - -#define PyIndex_Check(obj) \ - (PyInt_Check(obj) || PyLong_Check(obj)) - -inline Py_ssize_t PyNumber_AsSsize_t(PyObject* obj, PyObject*) { - return (Py_ssize_t)PyLong_AsLong(obj); -} - -#else -# 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 -#endif - -#if PY_VERSION_HEX < 0x02020000 -#define PyBool_FromLong PyInt_FromLong -#endif - -#if PY_VERSION_HEX < 0x03000000 -// the following should quiet Solaris -#ifdef Py_False -#undef Py_False -#define Py_False ((PyObject*)(void*)&_Py_ZeroStruct) -#endif - -#ifdef Py_True -#undef Py_True -#define Py_True ((PyObject*)(void*)&_Py_TrueStruct) -#endif -#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 -#if PY_VERSION_HEX >= 0x03090000 -#define CPyCppyy_PyCFunction_Call PyObject_Call -#else -#define CPyCppyy_PyCFunction_Call PyCFunction_Call -#endif - -// vector call support -#if PY_VERSION_HEX >= 0x03080000 -typedef PyObject* const* CPyCppyy_PyArgs_t; -static inline PyObject* CPyCppyy_PyArgs_GET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i) { - return args[i]; -} -static inline PyObject* CPyCppyy_PyArgs_SET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i, PyObject* item) { - return ((PyObject**)args)[i] = item; -} -static inline Py_ssize_t CPyCppyy_PyArgs_GET_SIZE(CPyCppyy_PyArgs_t, size_t nargsf) { - return PyVectorcall_NARGS(nargsf); -} -static inline CPyCppyy_PyArgs_t CPyCppyy_PyArgs_New(Py_ssize_t N) { - return (CPyCppyy_PyArgs_t)PyMem_Malloc(N*sizeof(PyObject*)); -} -static inline void CPyCppyy_PyArgs_DEL(CPyCppyy_PyArgs_t args) { - PyMem_Free((void*)args); -} -#if PY_VERSION_HEX >= 0x03090000 -#define CPyCppyy_PyObject_Call PyObject_Vectorcall -#else -#define CPyCppyy_PyObject_Call _PyObject_Vectorcall -#endif -inline PyObject* CPyCppyy_tp_call( - PyObject* cb, CPyCppyy_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 - -#else - -typedef PyObject* CPyCppyy_PyArgs_t; -static inline PyObject* CPyCppyy_PyArgs_GET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i) { - return PyTuple_GET_ITEM(args, i); -} -static inline PyObject* CPyCppyy_PyArgs_SET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i, PyObject* item) { - return PyTuple_SET_ITEM(args, i, item); -} -static inline Py_ssize_t CPyCppyy_PyArgs_GET_SIZE(CPyCppyy_PyArgs_t args, size_t) { - return PyTuple_GET_SIZE(args); -} -static inline CPyCppyy_PyArgs_t CPyCppyy_PyArgs_New(Py_ssize_t N) { - return PyTuple_New(N); -} -static inline void CPyCppyy_PyArgs_DEL(CPyCppyy_PyArgs_t args) { - Py_DECREF(args); -} -inline PyObject* CPyCppyy_PyObject_Call(PyObject* cb, PyObject* args, size_t, PyObject* kwds) { - return PyObject_Call(cb, args, kwds); -} -inline PyObject* CPyCppyy_tp_call(PyObject* cb, PyObject* args, size_t, PyObject* kwds) { - return Py_TYPE(cb)->tp_call(cb, args, kwds); -} -#endif - -// weakref forced strong reference -#if PY_VERSION_HEX < 0x30d0000 -static inline PyObject* CPyCppyy_GetWeakRef(PyObject* ref) { - PyObject* pyobject = PyWeakref_GetObject(ref); - if (!pyobject || pyobject == Py_None) - return nullptr; - Py_INCREF(pyobject); - return pyobject; -} -#else -static inline PyObject* CPyCppyy_GetWeakRef(PyObject* ref) { - PyObject* pyobject = nullptr; - if (PyWeakref_GetRef(ref, &pyobject) != -1) - return pyobject; - return nullptr; -} -#endif - -// Py_TYPE as inline function -#if PY_VERSION_HEX < 0x03090000 && !defined(Py_SET_TYPE) -static inline -void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { ob->ob_type = type; } -#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type) -#endif - -// py39 gained several faster (through vector call) equivalent method call API -#if PY_VERSION_HEX < 0x03090000 -static inline PyObject* PyObject_CallMethodNoArgs(PyObject* obj, PyObject* name) { - return PyObject_CallMethodObjArgs(obj, name, nullptr); -} - -static inline PyObject* PyObject_CallMethodOneArg(PyObject* obj, PyObject* name, PyObject* arg) { - return PyObject_CallMethodObjArgs(obj, name, arg, nullptr); -} -#endif - -// C++ version of the cppyy API -#include "Cppyy.h" - -// export macros for our own API -#include "CPyCppyy/CommonDefs.h" - -// --- reusable PyTypeObject initializer tail ------------------------------- -// Members appended to PyTypeObject in newer CPython releases. Every CPyCppyy -// 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 -// CPYCPPYY_PYTYPE_TAIL. - -#if PY_VERSION_HEX >= 0x030c0000 -#define CPYCPPYY_TP_WATCHED , 0 /* tp_watched (>= 3.12) */ -#else -#define CPYCPPYY_TP_WATCHED -#endif - -#if PY_VERSION_HEX >= 0x030d0000 -#define CPYCPPYY_TP_VERSIONS_USED , 0 /* tp_versions_used(>= 3.13) */ -#else -#define CPYCPPYY_TP_VERSIONS_USED -#endif - -#if PY_VERSION_HEX >= 0x030f0000 -#define CPYCPPYY_TP_ITERITEM , nullptr /* _tp_iteritem (>= 3.15) */ -#else -#define CPYCPPYY_TP_ITERITEM -#endif - -#define CPYCPPYY_PYTYPE_TAIL \ - CPYCPPYY_TP_WATCHED \ - CPYCPPYY_TP_VERSIONS_USED \ - CPYCPPYY_TP_ITERITEM -// -------------------------------------------------------------------------- - -#endif // !CPYCPPYY_CPYCPPYY_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx deleted file mode 100644 index 8d12ff5dc6740..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ /dev/null @@ -1,1189 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CallContext.h" -#include "Converters.h" -#include "CPPDataMember.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CPPScope.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" - -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/DispatchPtr.h" -namespace CPyCppyy { -void* Instance_AsVoidPtr(PyObject* pyobject); -PyObject* Instance_FromVoidPtr( - void* addr, const std::string& classname, bool python_owns = false); -} // namespace CPyCppyy -#undef CPYCPPYY_INTERNAL - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include - -#if PY_VERSION_HEX < 0x030b0000 -namespace CPyCppyy { -extern dict_lookup_func gDictLookupOrg; -dict_lookup_func gDictLookupOrg = nullptr; -} // namespace CPyCppyy -#endif - -// Note: as of py3.11, dictionary objects no longer carry a function pointer for -// the lookup, so it can no longer be shimmed and "from cppyy.interactive import *" -// thus no longer works. -#if PY_VERSION_HEX < 0x030b0000 - -//- from Python's dictobject.c ------------------------------------------------- -#if PY_VERSION_HEX >= 0x03030000 - typedef struct PyDictKeyEntry { - /* Cached hash code of me_key. */ - Py_hash_t me_hash; - PyObject *me_key; - PyObject *me_value; /* This field is only meaningful for combined tables */ - } PyDictEntry; - - typedef struct _dictkeysobject { - Py_ssize_t dk_refcnt; - Py_ssize_t dk_size; - dict_lookup_func dk_lookup; - Py_ssize_t dk_usable; -#if PY_VERSION_HEX >= 0x03060000 - Py_ssize_t dk_nentries; - union { - int8_t as_1[8]; - int16_t as_2[4]; - int32_t as_4[2]; -#if SIZEOF_VOID_P > 4 - int64_t as_8[1]; -#endif - } dk_indices; -#else - PyDictKeyEntry dk_entries[1]; -#endif - } PyDictKeysObject; - -#define CPYCPPYY_GET_DICT_LOOKUP(mp) \ - ((dict_lookup_func&)mp->ma_keys->dk_lookup) - -#else - -#define CPYCPPYY_GET_DICT_LOOKUP(mp) \ - ((dict_lookup_func&)mp->ma_lookup) - -#endif - -#endif // PY_VERSION_HEX < 0x030b0000 - -//- data ----------------------------------------------------------------------- -static PyObject* nullptr_repr(PyObject*) -{ - return CPyCppyy_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, -#if PY_VERSION_HEX < 0x03000000 - 0, -#endif - 0, 0, 0, 0, 0, 0, - (inquiry)nullptr_nonzero, // tp_nonzero (nb_bool in p3) - 0, 0, 0, 0, 0, 0, -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_coerce -#endif - 0, 0, 0, -#if PY_VERSION_HEX < 0x03000000 - 0, 0, -#endif - 0, 0, 0, -#if PY_VERSION_HEX < 0x03000000 - 0, // nb_inplace_divide -#endif - 0, 0, 0, 0, 0, 0, 0 -#if PY_VERSION_HEX >= 0x02020000 - , 0 // nb_floor_divide -#if PY_VERSION_HEX < 0x03000000 - , 0 // nb_true_divide -#else - , 0 // nb_true_divide -#endif - , 0, 0 -#endif -#if PY_VERSION_HEX >= 0x02050000 - , 0 // nb_index -#endif -#if PY_VERSION_HEX >= 0x03050000 - , 0 // nb_matrix_multiply - , 0 // nb_inplace_matrix_multiply -#endif -}; - -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 -#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 PyObject* default_repr(PyObject*) -{ - return CPyCppyy_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 -#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 { - -struct { - PyObject_HEAD -} _CPyCppyy_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; - -struct { - PyObject_HEAD -} _CPyCppyy_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; - -// TODO: refactor with Converters.cxx -struct CPyCppyy_tagCDataObject { // non-public (but stable) - PyObject_HEAD - char* b_ptr; - int b_needsfree; -}; - -} // unnamed namespace - -namespace CPyCppyy { - 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::set gPinnedTypes; - std::ostringstream gCapturedError; - std::streambuf* gOldErrorBuffer = nullptr; - - std::map> &pythonizations() - { - static std::map> pyzMap; - return pyzMap; - } -} - - -//- private helpers ------------------------------------------------------------ -namespace { - -using namespace CPyCppyy; - - -//---------------------------------------------------------------------------- -#if PY_VERSION_HEX < 0x030b0000 -namespace { - -class GblGetter { -public: - GblGetter() { - PyObject* cppyy = PyImport_AddModule((char*)"cppyy"); - fGbl = PyObject_GetAttrString(cppyy, (char*)"gbl"); - } - ~GblGetter() { Py_DECREF(fGbl); } - - PyObject* operator*() { return fGbl; } - -private: - PyObject* fGbl; -}; - -} // unnamed namespace - -#if PY_VERSION_HEX >= 0x03060000 -inline Py_ssize_t OrgDictLookup(PyDictObject* mp, PyObject* key, - Py_hash_t hash, PyObject*** value_addr, Py_ssize_t* hashpos) -{ - return (*gDictLookupOrg)(mp, key, hash, value_addr, hashpos); -} -#define CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos) \ - OrgDictLookup(mp, key, hash, value_addr, hashpos) - -Py_ssize_t CPyCppyyLookDictString(PyDictObject* mp, PyObject* key, - Py_hash_t hash, PyObject*** value_addr, Py_ssize_t* hashpos) - -#elif PY_VERSION_HEX >= 0x03030000 -inline PyDictKeyEntry* OrgDictLookup( - PyDictObject* mp, PyObject* key, Py_hash_t hash, PyObject*** value_addr) -{ - return (*gDictLookupOrg)(mp, key, hash, value_addr); -} - -#define CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos) \ - OrgDictLookup(mp, key, hash, value_addr) - -PyDictKeyEntry* CPyCppyyLookDictString( - PyDictObject* mp, PyObject* key, Py_hash_t hash, PyObject*** value_addr) - -#else /* < 3.3 */ - -inline PyDictEntry* OrgDictLookup(PyDictObject* mp, PyObject* key, long hash) -{ - return (*gDictLookupOrg)(mp, key, hash); -} - -#define CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos) \ - OrgDictLookup(mp, key, hash) - -PyDictEntry* CPyCppyyLookDictString(PyDictObject* mp, PyObject* key, long hash) -#endif -{ - static GblGetter gbl; -#if PY_VERSION_HEX >= 0x03060000 - Py_ssize_t ep; -#else - PyDictEntry* ep; -#endif - -// first search dictionary itself - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - if (gDictLookupActive) - return ep; - -#if PY_VERSION_HEX >= 0x03060000 - if (ep >= 0) -#else - if (!ep || (ep->me_key && ep->me_value)) -#endif - return ep; - -// filter for builtins - if (PyDict_GetItem(PyEval_GetBuiltins(), key) != 0) - return ep; - -// normal lookup failed, attempt to get C++ enum/global/class from top-level - gDictLookupActive = true; - -// attempt to get C++ enum/global/class from top-level - PyObject* val = PyObject_GetAttr(*gbl, key); - - if (val) { - // success ... - - if (CPPDataMember_CheckExact(val)) { - // don't want to add to dictionary (the proper place would be the - // dictionary of the (meta)class), but modifying ep will be noticed no - // matter what; just return the actual value and live with the copy in - // the dictionary (mostly, this is correct) - PyObject* actual_val = Py_TYPE(val)->tp_descr_get(val, nullptr, nullptr); - Py_DECREF(val); - val = actual_val; - } - - // add reference to C++ entity in the given dictionary - CPYCPPYY_GET_DICT_LOOKUP(mp) = gDictLookupOrg; // prevent recursion - if (PyDict_SetItem((PyObject*)mp, key, val) == 0) { - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - } else { -#if PY_VERSION_HEX >= 0x03060000 - ep = -1; -#else - ep->me_key = nullptr; - ep->me_value = nullptr; -#endif - } - CPYCPPYY_GET_DICT_LOOKUP(mp) = CPyCppyyLookDictString; // restore - - // done with val - Py_DECREF(val); - } else - PyErr_Clear(); - -#if PY_VERSION_HEX >= 0x03030000 - if (mp->ma_keys->dk_usable <= 0) { - // big risk that this lookup will result in a resize, so force it here - // to be able to reset the lookup function; of course, this is nowhere - // near fool-proof, but should cover interactive usage ... - CPYCPPYY_GET_DICT_LOOKUP(mp) = gDictLookupOrg; - const int maxinsert = 5; - PyObject* buf[maxinsert]; - for (int varmax = 1; varmax <= maxinsert; ++varmax) { - for (int ivar = 0; ivar < varmax; ++ivar) { - buf[ivar] = CPyCppyy_PyText_FromFormat("__CPYCPPYY_FORCE_RESIZE_%d", ivar); - PyDict_SetItem((PyObject*)mp, buf[ivar], Py_None); - } - for (int ivar = 0; ivar < varmax; ++ivar) { - PyDict_DelItem((PyObject*)mp, buf[ivar]); - Py_DECREF(buf[ivar]); - } - if (0 < mp->ma_keys->dk_usable) - break; - } - - // make sure the entry pointer is still valid by re-doing the lookup - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - - // full reset of all lookup functions - gDictLookupOrg = CPYCPPYY_GET_DICT_LOOKUP(mp); - CPYCPPYY_GET_DICT_LOOKUP(mp) = CPyCppyyLookDictString; // restore - } -#endif - -// stopped calling into the reflection system - gDictLookupActive = false; - return ep; -} - -#endif // PY_VERSION_HEX < 0x030b0000 - -//---------------------------------------------------------------------------- -static PyObject* SetCppLazyLookup(PyObject*, PyObject* args) -{ -#if PY_VERSION_HEX < 0x030b0000 -// Modify the given dictionary to install the lookup function that also -// tries the global C++ namespace before failing. Called on a module's dictionary, -// this allows for lazy lookups. This works fine for p3.2 and earlier, but should -// not be used beyond interactive code for p3.3 and later b/c resizing causes the -// lookup function to revert to the default (lookdict_unicode_nodummy). - PyDictObject* dict = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!"), &PyDict_Type, &dict)) - return nullptr; - - CPYCPPYY_GET_DICT_LOOKUP(dict) = CPyCppyyLookDictString; -#else -// As of py3.11, there is no longer a lookup function pointer in the dict object -// to replace. Since this feature is not widely advertised, it's simply dropped - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"lazy lookup is no longer supported", 1) < 0) - return nullptr; - (void)args; // avoid warning about unused parameter -#endif - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* MakeCppTemplateClass(PyObject*, 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; - } - -// build "< type, type, ... >" part of class name (modifies pyname) - const std::string& tmpl_name = - Utility::ConstructTemplateArgs(PyTuple_GET_ITEM(args, 0), args, nullptr, Utility::kNone, 1); - if (!tmpl_name.size()) - return nullptr; - - return CreateScopeProxy(tmpl_name); -} - -//---------------------------------------------------------------------------- -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, &CPyCppyy_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", CPyCppyy_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 (CPyCppyy_PyText_Check(pyobj)) { - // special cases for access to the CPyCppyy API - std::string req = CPyCppyy_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 (cppyy-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; - } - - Cppyy::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); - } - - // 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 && CPyCppyy_PyText_Check(str)) - PyErr_Format(PyExc_TypeError, "unknown object %s", CPyCppyy_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 CPyCppyy_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) -#if PY_VERSION_HEX < 0x02060000 - return PyCObject_FromVoidPtr(addr, nullptr); -#else - return PyCapsule_New(addr, nullptr, nullptr); -#endif - 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**)((CPyCppyy_tagCDataObject*)ref)->b_ptr = addr; - ((CPyCppyy_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; - Cppyy::TCppType_t klass = ((CPPClass*)Py_TYPE(pyobject))->fCppType; - - Py_ssize_t array_len = pyobj->ArrayLength(); - - if (array_len < 0 || !Cppyy::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 = Cppyy::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) - Cppyy::TCppType_t cast_type = 0; - PyObject* arg1 = PyTuple_GET_ITEM(args, 1); - if (!CPyCppyy_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 = (Cppyy::TCppType_t)Cppyy::GetScope(CPyCppyy_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; - Cppyy::TCppType_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; - Cppyy::TCppType_t base = 0, derived = 0; - if (Cppyy::IsSubtype(cast_type, cur_type)) { - derived = cast_type; - base = cur_type; - direction = -1; // down-cast - } else if (Cppyy::IsSubtype(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; - } - - Cppyy::TCppObject_t address = (Cppyy::TCppObject_t)arg0_pyobj->GetObject(); - ptrdiff_t offset = Cppyy::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( - (void*)((intptr_t)address + 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 + offset); - PyObject* pyobj = ((PyTypeObject*)arg1)->tp_new((PyTypeObject*)arg1, nullptr, nullptr); - ((CPPInstance*)pyobj)->GetObjectRaw() = cast_address; - - PyObject* dispproxy = CPyCppyy::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 = CPyCppyy_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", CPyCppyy_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; - - Cppyy::AddTypeReducer(reducable, reduced); - - 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; - - Cppyy::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 PyMethodDef gCPyCppyyMethods[] = { - {(char*) "CreateScopeProxy", (PyCFunction)CPyCppyy::CreateScopeProxy, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "MakeCppTemplateClass", (PyCFunction)MakeCppTemplateClass, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "_set_cpp_lazy_lookup", (PyCFunction)SetCppLazyLookup, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "_DestroyPyStrings", (PyCFunction)CPyCppyy::DestroyPyStrings, - METH_NOARGS, (char*)"cppyy 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."}, - {nullptr, nullptr, 0, nullptr} -}; - - -#if PY_VERSION_HEX >= 0x03000000 -struct module_state { - PyObject *error; -}; - -#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) - -static int cpycppyymodule_traverse(PyObject* m, visitproc visit, void* arg) -{ - Py_VISIT(GETSTATE(m)->error); - return 0; -} - -static int cpycppyymodule_clear(PyObject* m) -{ - Py_CLEAR(GETSTATE(m)->error); - return 0; -} - - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "libcppyy", - nullptr, - sizeof(struct module_state), - gCPyCppyyMethods, - nullptr, - cpycppyymodule_traverse, - cpycppyymodule_clear, - nullptr -}; -#endif - -namespace CPyCppyy { - -//---------------------------------------------------------------------------- -PyObject* Init() -{ -// Initialization of extension module libcppyy. - -// load commonly used python strings - if (!CPyCppyy::CreatePyStrings()) - return nullptr; - -// setup interpreter -#if PY_VERSION_HEX < 0x03090000 - PyEval_InitThreads(); -#endif - -#if PY_VERSION_HEX < 0x030b0000 -// prepare for laziness (the insert is needed to capture the most generic lookup -// function, just in case ...) - PyObject* dict = PyDict_New(); - PyObject* notstring = PyInt_FromLong(5); - PyDict_SetItem(dict, notstring, notstring); - Py_DECREF(notstring); -#if PY_VERSION_HEX >= 0x03030000 - gDictLookupOrg = (dict_lookup_func)((PyDictObject*)dict)->ma_keys->dk_lookup; -#else - gDictLookupOrg = (dict_lookup_func)((PyDictObject*)dict)->ma_lookup; -#endif - Py_DECREF(dict); -#endif // PY_VERSION_HEX < 0x030b0000 - -// setup this module -#if PY_VERSION_HEX >= 0x03000000 - gThisModule = PyModule_Create(&moduledef); -#else - gThisModule = Py_InitModule(const_cast("libcppyy"), gCPyCppyyMethods); -#endif - 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; - -// inject custom data types -#if PY_VERSION_HEX < 0x03000000 - if (!Utility::InitProxy(gThisModule, &RefFloat_Type, "Double")) - return nullptr; - - if (!Utility::InitProxy(gThisModule, &RefInt_Type, "Long")) - return nullptr; -#endif - - 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*)&_CPyCppyy_NullPtrStruct; - Py_INCREF(gNullPtrObject); - PyModule_AddObject(gThisModule, (char*)"nullptr", gNullPtrObject); - - gDefaultObject = (PyObject*)&_CPyCppyy_DefaultStruct; - Py_INCREF(gDefaultObject); - PyModule_AddObject(gThisModule, (char*)"default", gDefaultObject); - -// C++-specific exceptions - PyObject* cppfatal = PyErr_NewException((char*)"cppyy.ll.FatalError", nullptr, nullptr); - PyModule_AddObject(gThisModule, (char*)"FatalError", cppfatal); - - gBusException = PyErr_NewException((char*)"cppyy.ll.BusError", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"BusError", gBusException); - gSegvException = PyErr_NewException((char*)"cppyy.ll.SegmentationViolation", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"SegmentationViolation", gSegvException); - gIllException = PyErr_NewException((char*)"cppyy.ll.IllegalInstruction", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"IllegalInstruction", gIllException); - gAbrtException = PyErr_NewException((char*)"cppyy.ll.AbortSignal", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException); - -// gbl namespace is injected in cppyy.py - -// create the memory regulator - static MemoryRegulator s_memory_regulator; - -#if PY_VERSION_HEX >= 0x03000000 - Py_INCREF(gThisModule); -#endif - return gThisModule; -} - -} // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.h deleted file mode 100644 index dec1256e15652..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef CPYCPPYY_CPYCPPYYMODULE_H -#define CPYCPPYY_CPYCPPYYMODULE_H - -#include "Python.h" - -namespace CPyCppyy { - -PyObject *Init(); - -} - -#endif // !CPYCPPYY_CPYCPPYYMODULE_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyPyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyPyModule.cxx deleted file mode 100644 index 72056aceca91c..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyPyModule.cxx +++ /dev/null @@ -1,13 +0,0 @@ -#include "CPyCppyyModule.h" - -//---------------------------------------------------------------------------- -#if PY_VERSION_HEX >= 0x03000000 -extern "C" PyObject* PyInit_libcppyy() { -#else -extern "C" void initlibcppyy() { -#endif - PyObject *thisModule = CPyCppyy::Init(); -#if PY_VERSION_HEX >= 0x03000000 - return thisModule; -#endif -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx deleted file mode 100644 index 416741d0caab1..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx +++ /dev/null @@ -1,47 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CallContext.h" - -//----------------------------------------------------------------------------- -uint32_t &CPyCppyy::CallContext::GlobalPolicyFlags() -{ - static uint32_t flags = 0; - return flags; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::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 CPyCppyy::CallContext::Cleanup() { - Temporary* tmp = fTemps; - while (tmp) { - Py_DECREF(tmp->fPyObject); - Temporary* tmp2 = tmp->fNext; - delete tmp; - tmp = tmp2; - } - fTemps = nullptr; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::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/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h deleted file mode 100644 index 4b88bfdd3a6fb..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef CPYCPPYY_CALLCONTEXT_H -#define CPYCPPYY_CALLCONTEXT_H - -// Standard -#include - -#include - - -namespace CPyCppyy { - -// 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)) - -#ifndef CPYCPPYY_PARAMETER -#define CPYCPPYY_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 // CPYCPPYY_PARAMETER - -// extra call information -struct CallContext { - CallContext() : fCurScope(0), 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 - Cppyy::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 = CPyCppyy::CallContext; - return !(CC::GlobalPolicyFlags() & CC::kUseHeuristics); -} - -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 CPyCppyy - -#endif // !CPYCPPYY_CALLCONTEXT_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx deleted file mode 100644 index d3e472350afe2..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ /dev/null @@ -1,3844 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "DeclareConverters.h" -#include "CallContext.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CustomPyTypes.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" - -// Standard -#include -#include -#include -#include // for ptrdiff_t -#include -#include -#include -#include // for wstring_convert -#include -#include -#include -#include -#include -#if __cplusplus >= 202002L -#include -#endif - -// codecvt does not exist for gcc4.8.5 and is in principle deprecated; it is -// only used in py2 for char -> wchar_t conversion for std::wstring; if not -// available, the conversion is done through Python (requires an extra copy) -#if PY_VERSION_HEX < 0x03000000 -#if defined(__GNUC__) && !defined(__APPLE__) -# if __GNUC__ > 4 && __has_include("codecvt") -# include -# define HAS_CODECVT 1 -# endif -#else -#include -#define HAS_CODECVT 1 -#endif -#endif // py2 - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - -// factories - typedef std::map ConvFactories_t; - static ConvFactories_t gConvFactories; - -// special objects - extern PyObject* gNullPtrObject; - extern PyObject* gDefaultObject; - -// regular expression for matching function pointer - static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); -} - -// 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 -#if PY_VERSION_HEX < 0x03000000 -const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; -#elif PY_VERSION_HEX < 0x03080000 -// p3 has at least 2 ref-counts, as contrary to p2, it will create a descriptor -// copy for the method holding self in the case of __init__; but there can also -// be a reference held by the frame object, which is indistinguishable from a -// local variable reference, so the cut-off has to remain 2. -const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 2; -#else -// since py3.8, vector calls behave again as expected -const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; -#endif -inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *pyobject) { - return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; -} -#endif - -//- pretend-ctypes helpers --------------------------------------------------- -struct CPyCppyy_tagCDataObject { // non-public (but stable) - PyObject_HEAD - char* b_ptr; - int b_needsfree; -}; - -struct CPyCppyy_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 - long long q; - long double D; - void *p; - } value; - PyObject* obj; -}; - -// 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" }; -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 ... -} _cppyy_ctypes_state; - -} // unnamed namespace - -static bool IsCTypesArrayOrPointer(PyObject* pyobject) -{ - static _cppyy_ctypes_state* state = nullptr; - if (!state) { - PyObject* ctmod = PyImport_AddModule("_ctypes"); // the extension module, not the Python one - if (ctmod) - state = (_cppyy_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 CPyCppyy::CPPInstance* GetCppInstance( - PyObject* pyobject, Cppyy::TCppType_t klass = (Cppyy::TCppType_t)0, bool accept_rvalue = false) -{ - using namespace CPyCppyy; - 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 ImplicitBool(PyObject* pyobject, CPyCppyy::CallContext* ctxt) -{ - using namespace CPyCppyy; - if (!AllowImplicit(ctxt) && PyBool_Check(pyobject)) { - if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; - return false; - } - return true; -} - -static inline bool StrictBool(PyObject* pyobject, CPyCppyy::CallContext* ctxt) -{ - using namespace CPyCppyy; - if (!AllowImplicit(ctxt) && !PyBool_Check(pyobject)) { - if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; - return false; - } - return true; -} - -static inline bool CPyCppyy_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 CPPYY_PYLONG_AS_TYPE(name, type, limit_low, limit_high) \ -static inline type CPyCppyy_PyLong_As##name(PyObject* pyobject) \ -{ \ - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { \ - if (pyobject == CPyCppyy::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; \ -} - -CPPYY_PYLONG_AS_TYPE(UInt8, uint8_t, 0, UCHAR_MAX) -CPPYY_PYLONG_AS_TYPE(Int8, int8_t, SCHAR_MIN, SCHAR_MAX) -CPPYY_PYLONG_AS_TYPE(UInt16, uint16_t, 0, UINT16_MAX) -CPPYY_PYLONG_AS_TYPE(Int16, int16_t, INT16_MIN, INT16_MAX) -CPPYY_PYLONG_AS_TYPE(UInt32, uint32_t, 0, UINT32_MAX) -CPPYY_PYLONG_AS_TYPE(Int32, int32_t, INT32_MIN, INT32_MAX) -CPPYY_PYLONG_AS_TYPE(UShort, unsigned short, 0, USHRT_MAX) -CPPYY_PYLONG_AS_TYPE(Short, short, SHRT_MIN, SHRT_MAX) -CPPYY_PYLONG_AS_TYPE(StrictInt, int, INT_MIN, INT_MAX) - -static inline long CPyCppyy_PyLong_AsStrictLong(PyObject* pyobject) -{ -// strict python integer to C++ long integer conversion - -// prevent float -> long (see CPyCppyy_PyLong_AsStrictInt) - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { - if (pyobject == CPyCppyy::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 CPyCppyy_PyLong_AsStrictLongLong(PyObject* pyobject) -{ -// strict python integer to C++ long long integer conversion - -// prevent float -> long (see CPyCppyy_PyLong_AsStrictInt) - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { - if (pyobject == CPyCppyy::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, CPyCppyy::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 (CPyCppyy::LowLevelView_Check(pyobject)) { - auto llview = ((CPyCppyy::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 == CPyCppyy::gNullPtrObject || pyobject == CPyCppyy::gDefaultObject) - para.fValue.fVoidp = nullptr; - else { - Py_ssize_t buflen = CPyCppyy::Utility::GetBuffer(pyobject, tc, size, para.fValue.fVoidp, check); - if (!buflen) { - // stuck here as it's the least common - if (CPyCppyy_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 CPyCppyy::CPPInstance* ConvertImplicit(Cppyy::TCppType_t klass, - PyObject* pyobject, CPyCppyy::Parameter& para, CPyCppyy::CallContext* ctxt, bool manage=true) -{ - using namespace CPyCppyy; - -// filter out copy and move constructors - if (IsConstructor(ctxt->fFlags) && klass == ctxt->fCurScope && ctxt->GetSize() == 1) - 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::kNoImplicit; - 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::kNoImplicit; - - 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 -------------------------------------------- -CPyCppyy::Converter::~Converter() -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Converter::FromMemory(void*) -{ -// could happen if no derived class override - PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted from memory"); - return nullptr; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::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 CPPYY_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 = CPyCppyy::Utility::FetchPyError(); \ - ctypes_type = GetCTypesType(ct_##ctype); \ - CPyCppyy::Utility::RestorePyError(error); \ - } \ - if (Py_TYPE(pyobject) == ctypes_type) { \ - PyErr_Clear(); \ - val = *((type*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr); \ - } else if (pyobject == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - val = (type)0; \ - } else \ - return false; \ - } \ - para.fValue.f##name = val; \ - para.fTypeCode = tc; \ - return true; - -#define CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - return F1((stype)*((type*)address)); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory( \ - PyObject* value, void* address, PyObject* /* ctxt */) \ -{ \ - type s = (type)F2(value); \ - if (s == (type)-1 && PyErr_Occurred()) { \ - if (value == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - s = (type)0; \ - } else \ - return false; \ - } \ - *((type*)address) = (type)s; \ - return true; \ -} - -#define CPPYY_IMPL_BASIC_CONVERTER_NI(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (!StrictBool(pyobject, ctxt)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) - -#define CPPYY_IMPL_BASIC_CONVERTER_IB(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (!ImplicitBool(pyobject, ctxt)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) - -#define CPPYY_IMPL_BASIC_CONVERTER_NB(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) \ -{ \ - if (PyBool_Check(pyobject)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_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 (CPyCppyy_PyText_Check(pyobject)) { - if (CPyCppyy_PyText_GET_SIZE(pyobject) == 1) - lchar = (int)(CPyCppyy_PyText_AsString(pyobject)[0]); - else - PyErr_Format(PyExc_ValueError, "%s expected, got str of size " PY_SSIZE_T_FORMAT, - tname, CPyCppyy_PyText_GET_SIZE(pyobject)); - } else if (pyobject == CPyCppyy::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 CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) \ -PyObject* CPyCppyy::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); \ - ((CPyCppyy_tagCDataObject*)ref)->b_ptr = (char*)ptr; \ - ((CPyCppyy_tagCDataObject*)ref)->b_needsfree = 0; \ - return ref; \ -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CONST_REFCONVERTER(name, type, ctype, F1) \ -bool CPyCppyy::Const##name##RefConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ - type val = (type)F1(pyobject); \ - if (val == (type)-1 && PyErr_Occurred()) { \ - if (pyobject == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - val = (type)0; \ - } else \ - return false; \ - } \ - para.fValue.f##name = val; \ - para.fRef = ¶.fValue.f##name; \ - para.fTypeCode = 'r'; \ - return true; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(name, type, ctype, low, high)\ -bool CPyCppyy::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; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) - - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CHAR_CONVERTER(name, type, low, high) \ -bool CPyCppyy::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* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - /* return char in "native" str type as that's more natural in use */ \ - return CPyCppyy_PyText_FromFormat("%c", *((type*)address)); \ -} \ - \ -bool CPyCppyy::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 = CPyCppyy_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 == CPyCppyy::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 ------------------------------------------------- -CPPYY_IMPL_BASIC_CONVERTER_IB(Long, long, long, c_long, PyLong_FromLong, CPyCppyy_PyLong_AsStrictLong, 'l') - -//---------------------------------------------------------------------------- -bool CPyCppyy::LongRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ long&, set arg for call -#if PY_VERSION_HEX < 0x03000000 - if (RefInt_CheckExact(pyobject)) { - para.fValue.fVoidp = (void*)&((PyIntObject*)pyobject)->ob_ival; - para.fTypeCode = 'V'; - return true; - } -#endif - - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_long)) { - para.fValue.fVoidp = (void*)((CPyCppyy_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; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(Char, char, c_char, CHAR_MIN, CHAR_MAX) -CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(UChar, unsigned char, c_uchar, 0, UCHAR_MAX) - -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Bool, bool, c_bool, CPyCppyy_PyLong_AsBool) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int8, int8_t, c_int8, CPyCppyy_PyLong_AsInt8) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt8, uint8_t, c_uint8, CPyCppyy_PyLong_AsUInt8) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int16, int16_t, c_int16, CPyCppyy_PyLong_AsInt16) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt16, uint16_t, c_uint16, CPyCppyy_PyLong_AsUInt16) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int32, int32_t, c_int32, CPyCppyy_PyLong_AsInt32) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt32, uint32_t, c_uint32, CPyCppyy_PyLong_AsUInt32) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Short, short, c_short, CPyCppyy_PyLong_AsShort) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UShort, unsigned short, c_ushort, CPyCppyy_PyLong_AsUShort) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int, int, c_int, CPyCppyy_PyLong_AsStrictInt) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt, unsigned int, c_uint, PyLongOrInt_AsULong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Long, long, c_long, CPyCppyy_PyLong_AsStrictLong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(ULong, unsigned long, c_ulong, PyLongOrInt_AsULong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(LLong, PY_LONG_LONG, c_longlong, CPyCppyy_PyLong_AsStrictLongLong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(ULLong, PY_ULONG_LONG, c_ulonglong, PyLongOrInt_AsULong64) - -//---------------------------------------------------------------------------- -bool CPyCppyy::IntRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ (pseudo)int&, set arg for call -#if PY_VERSION_HEX < 0x03000000 - if (RefInt_CheckExact(pyobject)) { - para.fValue.fVoidp = (void*)&((PyIntObject*)pyobject)->ob_ival; - para.fTypeCode = 'V'; - return true; - } -#endif - -#if PY_VERSION_HEX >= 0x02050000 - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_int)) { - 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, 'i', sizeof(int), para.fValue.fVoidp); - if (para.fValue.fVoidp && buflen) { - para.fTypeCode = 'V'; - return true; - }; - -#if PY_VERSION_HEX < 0x02050000 - PyErr_SetString(PyExc_TypeError, "use cppyy.Long for pass-by-ref of ints"); -#else - PyErr_SetString(PyExc_TypeError, "use ctypes.c_int for pass-by-ref of ints"); -#endif - return false; -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_REFCONVERTER(name, ctype, type, code) \ -bool CPyCppyy::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*)((CPyCppyy_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; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) - -CPPYY_IMPL_REFCONVERTER(Bool, c_bool, bool, '?'); -CPPYY_IMPL_REFCONVERTER(Char, c_char, char, 'b'); -CPPYY_IMPL_REFCONVERTER(WChar, c_wchar, wchar_t, 'u'); -CPPYY_IMPL_REFCONVERTER(Char16, c_uint16, char16_t, 'H'); -CPPYY_IMPL_REFCONVERTER(Char32, c_uint32, char32_t, 'I'); -CPPYY_IMPL_REFCONVERTER(SChar, c_byte, signed char, 'b'); -CPPYY_IMPL_REFCONVERTER(UChar, c_ubyte, unsigned char, 'B'); -CPPYY_IMPL_REFCONVERTER(Int8, c_int8, int8_t, 'b'); -CPPYY_IMPL_REFCONVERTER(UInt8, c_uint8, uint8_t, 'B'); -CPPYY_IMPL_REFCONVERTER(Int16, c_int16, int16_t, 'h'); -CPPYY_IMPL_REFCONVERTER(UInt16, c_uint16, uint16_t, 'H'); -CPPYY_IMPL_REFCONVERTER(Int32, c_int32, int32_t, 'i'); -CPPYY_IMPL_REFCONVERTER(UInt32, c_uint32, uint32_t, 'I'); -CPPYY_IMPL_REFCONVERTER(Short, c_short, short, 'h'); -CPPYY_IMPL_REFCONVERTER(UShort, c_ushort, unsigned short, 'H'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Int, c_int); -CPPYY_IMPL_REFCONVERTER(UInt, c_uint, unsigned int, 'I'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Long, c_long); -CPPYY_IMPL_REFCONVERTER(ULong, c_ulong, unsigned long, 'L'); -CPPYY_IMPL_REFCONVERTER(LLong, c_longlong, long long, 'q'); -CPPYY_IMPL_REFCONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q'); -CPPYY_IMPL_REFCONVERTER(Float, c_float, float, 'f'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Double, c_double); -CPPYY_IMPL_REFCONVERTER(LDouble, c_longdouble, PY_LONG_DOUBLE, 'g'); - - -//---------------------------------------------------------------------------- -// convert to C++ bool, allow int/long -> bool, set arg for call -CPPYY_IMPL_BASIC_CONVERTER_NI( - Bool, bool, long, c_bool, PyBool_FromLong, CPyCppyy_PyLong_AsBool, 'l') - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CHAR_CONVERTER(Char, char, CHAR_MIN, CHAR_MAX) -CPPYY_IMPL_BASIC_CHAR_CONVERTER(UChar, unsigned char, 0, UCHAR_MAX) - -PyObject* CPyCppyy::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* CPyCppyy::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 CPyCppyy::WCharConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || CPyCppyy_PyUnicode_GET_SIZE(pyobject) != 1) { - PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); - return false; - } - wchar_t val; - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(pyobject, &val, 1); - if (res == -1) - return false; - para.fValue.fLong = (long)val; - para.fTypeCode = 'U'; - return true; -} - -PyObject* CPyCppyy::WCharConverter::FromMemory(void* address) -{ - return PyUnicode_FromWideChar((const wchar_t*)address, 1); -} - -bool CPyCppyy::WCharConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || CPyCppyy_PyUnicode_GET_SIZE(value) != 1) { - PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); - return false; - } - wchar_t val; - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(value, &val, 1); - if (res == -1) - return false; - *((wchar_t*)address) = val; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Char16Converter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || CPyCppyy_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* CPyCppyy::Char16Converter::FromMemory(void* address) -{ - return PyUnicode_DecodeUTF16((const char*)address, sizeof(char16_t), nullptr, nullptr); -} - -bool CPyCppyy::Char16Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || CPyCppyy_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 CPyCppyy::Char32Converter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || 2 < CPyCppyy_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* CPyCppyy::Char32Converter::FromMemory(void* address) -{ - return PyUnicode_DecodeUTF32((const char*)address, sizeof(char32_t), nullptr, nullptr); -} - -bool CPyCppyy::Char32Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || 2 < CPyCppyy_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; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int8, int8_t, long, c_int8, PyInt_FromLong, CPyCppyy_PyLong_AsInt8, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt8, uint8_t, long, c_uint8, PyInt_FromLong, CPyCppyy_PyLong_AsUInt8, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int16, int16_t, long, c_int16, PyInt_FromLong, CPyCppyy_PyLong_AsInt16, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt16, uint16_t, long, c_uint16, PyInt_FromLong, CPyCppyy_PyLong_AsUInt16, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int32, int32_t, long, c_int32, PyInt_FromLong, CPyCppyy_PyLong_AsInt32, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt32, uint32_t, long, c_uint32, PyInt_FromLong, CPyCppyy_PyLong_AsUInt32, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Short, short, long, c_short, PyInt_FromLong, CPyCppyy_PyLong_AsShort, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UShort, unsigned short, long, c_ushort, PyInt_FromLong, CPyCppyy_PyLong_AsUShort, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int, int, long, c_uint, PyInt_FromLong, CPyCppyy_PyLong_AsStrictInt, 'l') - -//---------------------------------------------------------------------------- -bool CPyCppyy::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* CPyCppyy::ULongConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned long read at
- 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/PyObjectDir27.inc b/bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc deleted file mode 100644 index 3274a7559983a..0000000000000 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyObjectDir27.inc +++ /dev/null @@ -1,237 +0,0 @@ -/* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved - -Copied from Objects/object.c under the PSF License Version 2. - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are retained -in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - -*/ - -/* Helper for PyObject_Dir. - Merge the __dict__ of aclass into dict, and recursively also all - the __dict__s of aclass's base classes. The order of merging isn't - defined, as it's expected that only the final set of dict keys is - interesting. - Return 0 on success, -1 on error. -*/ - -static int merge_class_dict(PyObject* dict, PyObject* aclass) -{ - PyObject *classdict; - PyObject *bases; - - assert(PyDict_Check(dict)); - assert(aclass); - - /* Merge in the type's dict (if any). */ - classdict = PyObject_GetAttrString(aclass, "__dict__"); - if (classdict == NULL) - PyErr_Clear(); - else { - int status = PyDict_Update(dict, classdict); - Py_DECREF(classdict); - if (status < 0) - return -1; - } - - /* Recursively merge in the base types' (if any) dicts. */ - bases = PyObject_GetAttrString(aclass, "__bases__"); - if (bases == NULL) - PyErr_Clear(); - else { - /* We have no guarantee that bases is a real tuple */ - Py_ssize_t i, n; - n = PySequence_Size(bases); /* This better be right */ - if (n < 0) - PyErr_Clear(); - else { - for (i = 0; i < n; i++) { - int status; - PyObject *base = PySequence_GetItem(bases, i); - if (base == NULL) { - Py_DECREF(bases); - return -1; - } - status = merge_class_dict(dict, base); - Py_DECREF(base); - if (status < 0) { - Py_DECREF(bases); - return -1; - } - } - } - Py_DECREF(bases); - } - 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. -*/ -static PyObject * -_specialized_dir_type(PyObject *obj) -{ - PyObject *result = NULL; - PyObject *dict = PyDict_New(); - - if (dict != NULL && merge_class_dict(dict, obj) == 0) - result = PyDict_Keys(dict); - - Py_XDECREF(dict); - return result; -} - -static PyObject* _generic_dir(PyObject *obj) -{ - if (PyType_Check(obj) || PyClass_Check(obj)) - return _specialized_dir_type(obj); - - PyObject *result = NULL; - PyObject *dict = NULL; - PyObject *itsclass = NULL; - - /* Get __dict__ (which may or may not be a real dict...) */ - dict = PyObject_GetAttrString(obj, "__dict__"); - if (dict == NULL) { - PyErr_Clear(); - dict = PyDict_New(); - } - else if (!PyDict_Check(dict)) { - Py_DECREF(dict); - dict = PyDict_New(); - } - else { - /* Copy __dict__ to avoid mutating it. */ - PyObject *temp = PyDict_Copy(dict); - Py_DECREF(dict); - dict = temp; - } - - 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) - /* XXX(tomer): Perhaps fall back to obj->ob_type if no - __class__ exists? */ - PyErr_Clear(); - else { - if (merge_class_dict(dict, itsclass) != 0) - goto error; - } - - result = PyDict_Keys(dict); - /* fall through */ -error: - Py_XDECREF(itsclass); - Py_XDECREF(dict); - return result; -} 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-backend/clingwrapper/src/precommondefs.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h deleted file mode 100644 index 62710b6e41b3e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/precommondefs.h +++ /dev/null @@ -1,77 +0,0 @@ -/***** Start of precommondefs.h *****/ - -/* This is extracted from pyconfig.h from CPython. It sets the macros - that affect the features we get from system include files. - It must not #include anything. */ - -#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. */ -#define _FILE_OFFSET_BITS 64 -/* Define on Linux to activate all library features */ -#define _GNU_SOURCE 1 -/* This must be defined on some systems to enable large file support. */ -#define _LARGEFILE_SOURCE 1 -/* Define on NetBSD to activate all library features */ -#define _NETBSD_SOURCE 1 -/* Define to activate features from IEEE Stds 1003.1-2001 */ -#ifndef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 200112L -#endif -/* Define on FreeBSD to activate all library features */ -#define __BSD_VISIBLE 1 -#define __XSI_VISIBLE 700 -/* Windows: winsock/winsock2 mess */ -#define WIN32_LEAN_AND_MEAN -#ifdef _WIN64 - 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 -#endif - -#if !defined(RPY_ASSERT) && !defined(RPY_LL_ASSERT) && !defined(NDEBUG) -# define NDEBUG -#endif - - -/* All functions and global variables declared anywhere should use - one of the following attributes: - - RPY_EXPORTED: the symbol is exported out of libpypy-c.so. - - RPY_EXTERN: the symbol is not exported out of libpypy-c.so, but - otherwise works like 'extern' by being available to - other C sources. - - static: as usual, this means the symbol is local to this C file. - - Don't use _RPY_HIDDEN directly. For tests involving building a custom - .so, translator/tool/cbuild.py overrides RPY_EXTERN so that it becomes - equal to RPY_EXPORTED. - - Any function or global variable declared with no attribute at all is - a bug; please report or fix it. -*/ -#ifdef __GNUC__ -# define RPY_EXPORTED extern __attribute__((visibility("default"))) -# define _RPY_HIDDEN __attribute__((visibility("hidden"))) -#else -# define RPY_EXPORTED extern __declspec(dllexport) -# define _RPY_HIDDEN /* nothing */ -#endif -#ifndef RPY_EXTERN -# define RPY_EXTERN extern -#endif - - -#endif /* __PYPY_PRECOMMONDEFS_H */ - -/***** End of precommondefs.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/_pythonization.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py deleted file mode 100644 index f8b2a4d0f870f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py +++ /dev/null @@ -1,341 +0,0 @@ -""" Pythonization API. -""" - -import re - -__all__ = [ - 'add_pythonization', - 'remove_pythonization', - 'pin_type', - 'add_type_reducer', - ] - -def _set_backend(backend): - global _backend - _backend = backend - - -# user-provided, general pythonizations -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 . - """ - return _backend.remove_pythonization(pythonizor, scope) - - -# prevent auto-casting (e.g. for interfaces) -def pin_type(klass): - return _backend._pin_type(klass) - - -# mapper to reduce template expression trees -def add_type_reducer(reducable, reduced): - """Reduce to type on returns from function calls. - """ - return _backend._add_type_reducer(reducable, reduced) - - -# exception pythonizations -def add_exception_mapping(cpp_exception, py_exception): - _backend.UserExceptions[cpp_exception] = py_exception - - - -#--- 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)) - - -def set_ownership_policy(match_class, match_method, 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 -# make do with the hackier version here. -def rename_attribute(match_class, orig_attribute, new_attribute, keep_orig=False): - 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) - - def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): - self.match_class = re.compile(match_class) - self.match_attr = re.compile(orig_attribute) - self.new_attr = new_attribute - self.keep_orig = keep_orig - - def __call__(self, obj, name): - if not self.match_class.match(name): - return - 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) - 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): -# self.match_class = re.compile(match_class) -# self.match_attr = re.compile(orig_attribute) -# self.new_attr = new_attribute -# self.keep_orig = keep_orig - -# def __call__(self, obj, name): -# import sys -# if not self.match_class.match(name): -# return -# sys.stderr.write("%s %s %s %s" % ("!!!", obj, name, "\n")) -# for k in dir(obj): #obj.__dict__: -# if not self.match_attr.match(k): continue -# try: -# tmp = getattr(obj, k) -# except Exception as e: -# continue -# setattr(obj, self.new_attr, tmp) -# if not self.keep_orig: delattr(obj, k) -# return method_pythonizor(match_class, orig_attribute, new_attribute, keep_orig) - - -# Shared with PyPy: - -def add_overload(match_class, match_method, overload): - class method_pythonizor(object): - def __init__(self, match_class, match_method, overload): - self.match_class = re.compile(match_class) - self.match_method = re.compile(match_method) - self.overload = overload - - def __call__(self, obj, name): - if not self.match_class.match(name): - return - for k in dir(obj): #.__dict__: - try: - tmp = getattr(obj, k) - except AttributeError: - continue - if self.match_method.match(k): - try: - tmp.__add_overload__(overload) - except AttributeError: pass - return method_pythonizor(match_class, match_method, overload) - - -def compose_method(match_class, match_method, g): - class composition_pythonizor(object): - def __init__(self, match_class, match_method, g): - self.match_class = re.compile(match_class) - self.match_method = re.compile(match_method) - self.g = g - - def __call__(self, obj, name): - if not self.match_class.match(name): - return - g = self.g - for k in obj.__dict__: - if not self.match_method.match(k): - continue - try: - f = getattr(obj, k) - except AttributeError: - 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) - - -def set_method_property(match_class, match_method, prop, value): - class method_pythonizor(object): - def __init__(self, match_class, match_method, prop, value): - self.match_class = re.compile(match_class) - self.match_method = re.compile(match_method) - self.prop = prop - self.value = value - - def __call__(self, obj, name): - if not self.match_class.match(name): - return - for k in dir(obj): #.__dict__: - try: - tmp = getattr(obj, k) - except AttributeError: - 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): - class property_pythonizor(object): - def __init__(self, match_class, match_get, match_set, match_del, prop_name): - self.match_class = re.compile(match_class) - - self.match_get = re.compile(match_get) - match_many_getters = self.match_get.groups == 1 - - if match_set: - 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') - else: - self.match_set = None - - if match_del: - 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') - 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 self.match_many and 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") - - self.prop_name = prop_name - - def make_get_del_proxy(self, getter): - class proxy(object): - def __init__(self, getter): - self.getter = getter - - def __call__(self, obj): - return getattr(obj, self.getter)() - return proxy(getter) - - def make_set_proxy(self, setter): - class proxy(object): - def __init__(self, setter): - self.setter = setter - - def __call__(self, obj, arg): - return getattr(obj, self.setter)(arg) - return proxy(setter) - - def __call__(self, obj, name): - if not self.match_class.match(name): - return - - names = [] - named_getters = {} - named_setters = {} - named_deleters = {} - - if not self.match_many: - fget, fset, fdel = None, None, None - - for k in dir(obj): #.__dict__: - match = self.match_get.match(k) - try: - tmp = getattr(obj, k) - except AttributeError: - continue - if match and hasattr(tmp, '__call__'): - if self.match_many: - name = match.group(1) - named_getters[name] = k - else: - fget = self.make_get_del_proxy(k) - break - - if self.match_set: - for k in dir(obj): #.__dict__: - match = self.match_set.match(k) - try: - tmp = getattr(obj, k) - except AttributeError: - continue - if match and hasattr(tmp, '__call__'): - if self.match_many: - name = match.group(1) - named_setters[name] = k - else: - fset = self.make_set_proxy(k) - break - - if self.match_del: - for k in dir(obj): #.__dict__: - match = self.match_del.match(k) - try: - tmp = getattr(obj, k) - except AttributeError: - continue - if match and hasattr(tmp, '__call__'): - if self.match_many: - name = match.group(1) - named_deleters[name] = k - else: - fdel = self.make_get_del_proxy(k) - break - - if not self.match_many: - new_prop = property(fget, fset, fdel) - setattr(obj, self.prop_name, new_prop) - return - - names += list(named_getters.keys()) - names += list(named_setters.keys()) - names += list(named_deleters.keys()) - names = set(names) - - for name in names: - if name in named_getters: - fget = self.make_get_del_proxy(named_getters[name]) - else: - fget = None - - if name in named_setters: - fset = self.make_set_proxy(named_setters[name]) - else: - fset = None - - if name in named_deleters: - fdel = self.make_get_del_proxy(named_deleters[name]) - else: - fdel = None - - new_prop = property(fget, fset, fdel) - if self.prop_name: - prop_name = self.prop_name.format(name) - else: - prop_name = 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/cppyy/cppyy/python/cppyy/_stdcpp_fix.py deleted file mode 100644 index 0004c87803b72..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py +++ /dev/null @@ -1,13 +0,0 @@ -import sys - -# It may be that the interpreter (whether 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: - # TODO: check executable to see whether linking indeed didn't happen - import ctypes - try: - 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/cppyy/cppyy/python/cppyy/_typemap.py deleted file mode 100644 index fd09510c4af8f..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py +++ /dev/null @@ -1,118 +0,0 @@ -""" 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 - else: - cppname = name - modname = 'cppyy.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 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# 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 - else: - resolved_bases = bases - return meta(name, resolved_bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) -# --- end from six.py - -class _BoolMeta(type): - def __call__(cls, val = bool()): - if val: - return True - return False - -class _Bool(with_metaclass(_BoolMeta, object)): - pass - - -def initialize(backend): - 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) - - # char types - str_tm = _create_mapper(str) - for tp in ['char', 'unsigned char', 'signed char']: - tm[tp] = str_tm - if sys.hexversion < 0x3000000: - tm['wchar_t'] = _create_mapper(unicode) - else: - tm['wchar_t'] = str_tm - - # integer types - int_tm = _create_mapper(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) - else: - 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']: - tm[tp] = float_tm - - # void* - def voidp_init(self, arg=0): - import cppyy, ctypes - if arg == cppyy.nullptr: arg = 0 - ctypes.c_void_p.__init__(self, arg) - tm['void*'] = _create_mapper(ctypes.c_void_p, {'__init__' : voidp_init}) 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/numba_ext.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py deleted file mode 100644 index a83985b909094..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py +++ /dev/null @@ -1,655 +0,0 @@ -""" cppyy extensions for numba -""" - -import cppyy -import cppyy.types as cpp_types -import cppyy.reflex as cpp_refl - -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 - -from llvmlite import ir -from numba.extending import make_attribute_wrapper -import re - -# setuptools entry point for Numba -def _init_extension(): - pass - - -class Qualified: - 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) - -# special case access to unboxing/boxing APIs -cppyy_as_voidptr = cppyy.addressof('Instance_AsVoidPtr') -cppyy_from_voidptr = cppyy.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 -} - -def resolve_std_vector(val): - return re.match(r'std::vector<(.+?)>', val).group(1) - -def resolve_const_types(val): - return re.match(r'const\s+(.+)\s*\*', val).group(1) - -def cpp2numba(val): - if not isinstance(val, str): - # TODO: distinguish ptr/ref/byval - # TODO: Only metaclasses/proxies end up here since - # ref cases makes the RETURN_TYPE from reflex a string - return typeof_scope(val, nb_typing.typeof.Purpose.argument, Qualified.value) - 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'): - return nb_types.CPointer(cpp2numba(resolve_const_types(val))) - return nb_types.CPointer(_cpp2numba[val[:-1]]) - 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' - -def numba2cpp(val): - if hasattr(val, 'literal_type'): - val = val.literal_type - 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 - elif isinstance(val, numba.types.CPointer): - return _numba2cpp[val.dtype] - elif isinstance(val, numba.types.RawPointer): - return _numba2cpp[nb_types.voidptr] - elif isinstance(val, numba.types.Array): - return "std::vector<" + _numba2cpp[val.dtype] + ">" - elif isinstance(val, CppClassNumbaType): - return val._scope.__cpp_name__ - else: - try: - return _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): - # If the user explicitly passes an argument using numba CPointer, the regex match is used - # to detect the pass by reference since the dispatcher always returns typeref[val*] - match = re.search(r"typeref\[(.*?)\*\]", str(arg)) - if match: - literal_val = match.group(1) - arg_type = numba.typeof(eval(literal_val)) - args_cpp.append(to_ref(numba2cpp(arg_type))) - else: - 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 + '&') - 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(), -} - -def cpp2ir(val): - try: - return _cpp2ir[val] - except KeyError: - if val.startswith("std::vector"): - ## 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'): - return ir.PointerType(cpp2ir(resolve_const_types(val))) - type_2 = _cpp2ir[val[:-1]] - return ir.PointerType(type_2) - -# -# C++ function pointer -> Numba -# -class CppFunctionNumbaType(nb_types.Callable): - targetdescr = nb_reg.cpu_target - requires_gil = False - - def __init__(self, func, is_method=False): - super(CppFunctionNumbaType, self).__init__('CppFunction(%s)' % str(func)) - - self.sig = None - self._func = func - self._is_method = is_method - - self._signatures = list() - self._impl_keys = dict() - self._arg_set_matched = tuple() - self.ret_type = None - - def is_precise(self): - return True # by definition - - def get_call_type(self, context, args, kwds): - try: - return self._impl_keys[args].sig - except KeyError: - pass - - ol = CppFunctionNumbaType( - self._func.__overload__(numba_arg_convertor(args)), self._is_method) - - thistype = None - if self._is_method: - thistype = nb_types.voidptr - - 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) - - 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) - - 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): - ptrty = context.get_function_pointer_type(ty) - ptrval = context.add_dynamic_addr( - builder, ty.get_pointer(pyval), info=str(pyval)) - fptr = builder.bitcast(ptrval, ptrty) - return context.call_function_pointer(builder, fptr, args) - - return ol.sig - - def get_call_signatures(self): - return list(self._signatures), False - - def get_impl_key(self, sig): - return self._impl_keys[sig.args] - - # TODO: Remove the redundancy of __overload__ matching and use this function - # to only obtain the address given the matched overload - def get_pointer(self, func): - if func is None: - func = self._func - - ol = func.__overload__(numba_arg_convertor(self.sig.args)) - - address = cppyy.addressof(ol) - if not address: - raise RuntimeError("unresolved address for %s" % str(ol)) - return address - - @property - def key(self): - return self._func - - -@nb_ext.typeof_impl.register(cpp_types.Function) -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__'): - 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* - 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 - # actual overload is handled dynamically. - return - - -# -# C++ method / data member -> Numba -# -class CppDataMemberInfo(object): - __slots__ = ['f_name', 'f_offset', 'f_nbtype', 'f_irtype'] - - def __init__(self, name, offset, cpptype): - self.f_name = name - self.f_offset = offset - self.f_nbtype = cpp2numba(cpptype) - self.f_irtype = cpp2ir(cpptype) - - -# -# C++ class -> Numba -# -class CppClassNumbaType(CppFunctionNumbaType): - def __init__(self, scope, qualifier): - addr = None - cppinstance_val = None - if qualifier == Qualified.instance: - addr = cppyy.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._qualifier = qualifier - self._cppinstanceval = cppinstance_val - self._addr = addr - - def get_scope(self): - return self._scope - - def get_qualifier(self): - return self._qualifier - - def get_call_type(self, context, args, kwds): - sig = super(CppClassNumbaType, self).get_call_type(context, args, kwds) - self.sig = sig - return sig - - def is_precise(self): - return True - - @property - def key(self): - return (self._scope, self._qualifier) - - -@nb_tmpl.infer_getattr -class CppClassFieldResolver(nb_tmpl.AttributeTemplate): - key = CppClassNumbaType - - def generic_resolve(self, typ, attr): - ft = typ.__dict__.get(attr, None) - if ft is not None: - return ft - - try: - f = getattr(typ._scope, attr) - if isinstance(f, cpp_types.Function): - ft = CppFunctionNumbaType(f, is_method=True) - except AttributeError: - pass - - try: - f = typ._scope.__dict__[attr] - if isinstance(f, cpp_types.DataMember): - ct = f.__cpp_reflex__(cpp_refl.TYPE) - ft = cpp2numba(ct) - except AttributeError: - pass - - if ft is not None: - typ.__dict__[attr] = ft - - 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): - dm = typ._scope.__dict__[attr] - ct = dm.__cpp_reflex__(cpp_refl.TYPE) - offset = dm.__cpp_reflex__(cpp_refl.OFFSET) - - q = typ.get_qualifier() - if q == Qualified.default: - llval = builder.bitcast(val, ir_byteptr) - pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, offset)]) - pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) - return builder.load(pf) - - elif q == Qualified.value: - model = nb_dm.default_manager.lookup(typ) - return model.get(builder, val, attr) - - else: - assert not "unknown qualified type" - - # TODO: easier with inttoptr and ptrtoint (cgutils.pointer_add)? - llval = builder.bitcast(val, ir_byteptr) - pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, offset)]) - pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) - return builder.load(pf) - - # assume this is a method - q = typ.get_qualifier() - if q == Qualified.default: - return builder.bitcast(val, ir_voidptr) - - elif q == Qualified.value: - return None - - assert not "unknown qualified type" - return None - - -class ImplAggregateValueModel(nb_dm.models.StructModel): - def get(self, builder, val, pos): - """Get a field at the given position/field name""" - - 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. - dmi = self._data_members[pos] - - stack = nb_cgu.alloca_once(builder, self.get_data_type()) - builder.store(val, stack) - - llval = builder.bitcast(stack, ir_byteptr) - pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, dmi.f_offset)]) - pf = builder.bitcast(pfc, ir.PointerType(dmi.f_irtype)) - - 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. - def get_value_type(self): - return self.get_data_type() - - # 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. - if self._data_type is None: - 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. - 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): - is_instance = False - cppinstance_val = None - if q == Qualified.instance: - cppinstance_val = val - val = type(val) - q = Qualified.default - is_instance = True - - global scope_numbatypes - - try: - return scope_numbatypes[q][val] - except KeyError: - pass - - if val.__cpp_reflex__(cpp_refl.IS_NAMESPACE): - cnt = nb_types.Module(val) - scope_numbatypes[Qualified.default][val] = cnt - return cnt - - class ImplClassType(CppClassNumbaType): - pass - - if is_instance: - cnt = ImplClassType(cppinstance_val, Qualified.instance) - else: - cnt = ImplClassType(val, q) - - scope_numbatypes[q][val] = cnt - - # 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)) - ) - 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 ... - nb_reg.cpu_target.typing_context.refresh() - - # 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 - 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 - def traverse(self, builder): - return [] - - def traverse_models(self): - return [] - - def traverse_types(self): - return [self._fe_type] # from StructModel - - # 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. - 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 - - # 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. - def get_argument_type(self): - return self.get_value_type() - - def as_argument(self, builder, value): - return value - - def from_argument(self, builder, value): - return value - - # return: representation used for return argument. - # TODO ... - - # access to public data members - def get(self, builder, val, pos): - """Get a field at the given position/field name""" - - if isinstance(pos, str): - pos = self.get_field_position(pos) - - dmi = self._data_members[pos] - - llval = builder.bitcast(val, ir_byteptr) - pfc = builder.gep(llval, [ir.Constant(ir_intptr_t, dmi.f_offset)]) - pf = builder.bitcast(pfc, ir.PointerType(dmi.f_irtype)) - - return builder.load(pf) - - 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)): - 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 - members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] - nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) - - ImplClassModel.__init__ = init - - else: - assert not "unknown qualified type" - - # Python proxy unwrapping for arguments into the Numba trace - @nb_ext.unbox(ImplClassType) - def unbox_instance(typ, obj, c): - global cppyy_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') - fp = c.builder.bitcast(ptrval, ptrty) - - vptr = c.context.call_function_pointer(c.builder, fp, [obj]) - model = nb_dm.default_manager.lookup(typ) - pobj = c.builder.bitcast(vptr, model.get_argument_type()) - - return nb_ext.NativeValue(pobj, is_error=None, cleanup=None) - - 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 - @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 - - if isinstance(val, ir.Constant): - if val.constant == ir.Undefined: - assert not "Value passed to instance boxing is undefined" - return NULL - - implclass = make_implclass(c.context, c.builder, typ) - classobj = c.pyapi.unserialize(c.pyapi.serialize_object(cpp_types.Instance)) - - box_list = [] - - model = implclass._datamodel - 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_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: - c.pyapi.decref(i) - - return box_res - - return cnt - -# -# C++ instance -> Numba -# -@nb_ext.typeof_impl.register(cpp_types.Instance) -def typeof_instance(val, c): - global scope_numbatypes - - try: - return scope_numbatypes[Qualified.default][type(val)] - except KeyError: - pass - # Pass the val itself to obtain Cling address of the CPPInstance for reference to C++ objects - return typeof_scope(val, c, Qualified.instance) 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.cxx b/bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx deleted file mode 100644 index 23f4c041f69c2..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/advancedcpp.cxx +++ /dev/null @@ -1,186 +0,0 @@ -#include "advancedcpp.h" - -#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; \ -} -IMPLEMENT_DEFAULTERS(short, short) -IMPLEMENT_DEFAULTERS(unsigned short, ushort) -IMPLEMENT_DEFAULTERS(int, int) -IMPLEMENT_DEFAULTERS(unsigned, uint) -IMPLEMENT_DEFAULTERS(long, long) -IMPLEMENT_DEFAULTERS(unsigned long, ulong) -IMPLEMENT_DEFAULTERS(long long, llong) -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"; -} - - -// 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; } - - -// 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::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 T3; -template class T3, T2 > >; -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; } -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 -} - - -// a couple of globals for access testing -double my_global_double = 12.; -double my_global_array[500]; -static double sd = 1234.; -double* my_global_ptr = &sd; -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_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); -} - -void* new_overloader::operator new(std::size_t, void* p) throw() { - // 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); -} - - -// overload order testing -int overload_one_way::gime() const { return 1; } -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_exception() { - throw std::runtime_error("C++ function failed"); -} - - -// operator to __str__ mapping -std::ostream& Cpp2PyPrinting::Printable1::operator<<(std::ostream& os) { - os << "Printable1::operator<<"; - return os; -} - -std::ostream& Cpp2PyPrinting::operator<<(std::ostream& os, const Printable2&) { - os << "Cpp2PyPrinting::operator<<"; - return os; -} - -std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&) { - os << "::operator<<(3)"; - return os; -} - -std::ostream& operator<<(std::ostream& os, const Printable4&) { - os << "::operator<<(4)"; - return os; -} - -Printable6& Printable6::operator<<(int) { return *this; } - -std::ostream& operator<<(std::ostream& os, const Printable6& y) { return os << "Printable6"; } - - -// for using directives testing -int UsedSpace1::foo1() { - return 13; -} - -int UsedSpace2::bar() { - return 42; -} - -int UsedSpace1::inner::foo2() { - return 27; -} 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.h b/bindings/pyroot/cppyy/cppyy/test/conversions.h deleted file mode 100644 index 9f1401b50fb0e..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/conversions.h +++ /dev/null @@ -1,28 +0,0 @@ -#include - - -namespace CNS { - -//=========================================================================== -double sumit(const std::vector&); -double sumit(const std::vector&, const std::vector&); - -class Counter { -public: - Counter(); - Counter(const Counter&); - Counter& operator=(const Counter&); - ~Counter(); - - static int s_count; -}; - -double myhowmany(const std::vector&); -double myhowmany(const std::vector&, const std::vector&); - -int sumints(const std::vector&); -int sumints(const std::vector&, const std::vector&); - -double notallowed(std::vector&); - -} // namespace CNS 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.cxx b/bindings/pyroot/cppyy/cppyy/test/overloads.cxx deleted file mode 100644 index eeab7dde49719..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/overloads.cxx +++ /dev/null @@ -1,67 +0,0 @@ -#include "overloads.h" - - -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; } - -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(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; } - -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(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; } - -class bb_ol {}; -bb_ol* get_bb_ol() { return new bb_ol{}; } -class dd_ol {}; -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 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::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"; } - -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); } - - -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/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.h b/bindings/pyroot/cppyy/cppyy/test/std_streams.h deleted file mode 100644 index e67622373c7f6..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/std_streams.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef STD_STREAMS_H -#define STD_STREAMS_H 1 - -#ifndef __CLING__ -#include -#endif -#include - -#ifndef __CLING__ -#ifndef _WIN32 -extern template class std::basic_ios >; -#endif -#endif - -#endif // STD_STREAMS_H 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.cxx b/bindings/pyroot/cppyy/cppyy/test/templates.cxx deleted file mode 100644 index 90c3ec48e6da5..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/templates.cxx +++ /dev/null @@ -1,56 +0,0 @@ -#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_double_size() { return (long)sizeof(double); } -long MyTemplatedMethodClass::get_self_size() { return (long)sizeof(MyTemplatedMethodClass); } - - -// variadic templates -#ifdef WIN32 -__declspec(dllexport) -#endif -std::string some_variadic::gTypeName = ""; - - -// template with empty body -namespace T_WithEmptyBody { - -#ifdef WIN32 -__declspec(dllexport) -#endif -std::string side_effect = "not set"; - -template -void some_empty() { - side_effect = "side effect"; -} - -template void some_empty(); - -} // namespace T_WithRValue - - -// 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 -// the compiled instantation or from the interpreter. - -namespace FailedTypeDeducer { - -template -class A { -public: - T result() { return T{42}; } -}; - -template class A; - -template class B; - -} // namespace FailedTypeDeducer 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_concurrent.py b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py deleted file mode 100644 index 748c8fae4533c..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py +++ /dev/null @@ -1,315 +0,0 @@ -import pytest -from pytest import raises, skip, mark -from support import IS_MAC_ARM, IS_WINDOWS - - -class TestCONCURRENT: - - def setup_class(cls): - import cppyy - - 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" - - namespace Workers { - double calc(double d) { return d*42.; } - }""") - - cppyy.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 - - threads = [] - for d in self.data: - threads.append(threading.Thread(target=cppyy.gbl.Workers.calc, args=(d,))) - - for t in threads: - t.start() - - 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 - - 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] - for f in concurrent.futures.as_completed(futures): - total += f.result() - assert round(total+26.4642, 8) == 0.0 - - def test03_timeout(self): - """Time-out with threads""" - - return - import cppyy - import threading, time - - cppyy.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 - cmd = r"""\ - *test12_timeout::islive = true; - while (!*test12_timeout::stopit); - """ - - t = threading.Thread(target=cppyy.gbl.gInterpreter.ProcessLine, 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 - - # 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 - - @mark.xfail(strict=True, 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 - - cppyy.include("CPyCppyy/PyException.h") - - cppyy.cppdef("""namespace thread_test { - #include - - struct consumer { - virtual ~consumer() {} - virtual void process(int) = 0; - }; - - struct worker { - worker(consumer* c) : cons(c) {} - ~worker() { wait(); } - - void start() { - t = std::thread([this] { - int counter = 0; - while (counter++ < 10) - try { - cons->process(counter); - } catch (CPyCppyy::PyException& e) { - err_msg = e.what(); - return; - } - }); - } - - void wait() { - if (t.joinable()) - t.join(); - } - - std::thread t; - consumer* cons = nullptr; - std::string err_msg; - }; }""") - - ns = cppyy.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 - - c = C() - assert c.count == 0 - - w = worker(c) - w.start() - w.wait() - - assert c.count == 10 - - class C(consumer): - count = 0 - def process(self, c): - raise RuntimeError("all wrong") - - c = C() - - w = worker(c) - w.start() - w.wait() - - assert "RuntimeError" in w.err_msg - assert "all wrong" in w.err_msg - - def test05_float2d_callback(self): - """Passing of 2-dim float arguments""" - - import cppyy - - cppyy.cppdef("""\ - namespace FloatDim2 { - #include - - struct Buffer { - Buffer() = default; - - void setData(float** newData) { - data = newData; - } - - void setSample(int newChannel, int newSample, float value) { - data[newChannel][newSample] = value; - } - - float** data = nullptr; - }; - - struct Processor { - virtual ~Processor() = default; - virtual void process(float** data, int channels, int samples) = 0; - }; - - void callback(Processor& p) { - std::thread t([&p] { - int channels = 2; - int samples = 32; - - float** data = new float*[channels]; - for (int i = 0; i < channels; ++i) - data[i] = new float[samples]; - - p.process(data, channels, samples); - - for (int i = 0; i < channels; ++i) - delete[] data[i]; - - delete[] data; - }); - - t.join(); - } }""") - - cppyy.gbl.FloatDim2.callback.__release_gil__ = True - - class Processor(cppyy.gbl.FloatDim2.Processor): - buffer = cppyy.gbl.FloatDim2.Buffer() - - def process(self, data, channels, samples): - self.buffer.setData(data) - - try: - for c in range(channels): - for s in range(samples): - self.buffer.setSample(c, s, 0.0) # < used to crash here - except Exception as e: - print(e) - - p = Processor() - cppyy.gbl.FloatDim2.callback(p) - - def test06_overload_reuse_in_threads(self): - """Threads reuse overload objects; check for clashes""" - - import cppyy - import threading - - cppyy.cppdef("""\ - namespace CPPOverloadReuse { - class Simulation1 { - public: - virtual void set_something(std::map, std::string) {} - }; }""") - - def test(): - o = {"a": "b"} # causes a temporary to be created - simulation = cppyy.gbl.CPPOverloadReuse.Simulation1() - simulation.set_something(o, ".") - - threads = [threading.Thread(target=test) for i in range(0, 100)] - - for t in threads: - t.start() - - 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 - skip("JIT exceptions can not be caught in JITed code on Mac ARM") - - import cppyy - import threading - - cppyy.cppdef("""\ - namespace CPPOverloadReuse { - class Simulation2 { - int fCount; - public: - Simulation2(int count) : fCount(count) {} - virtual int do_something() { return fCount; } - }; }""") - - cppyy.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 - - simulation = cppyy.gbl.CPPOverloadReuse.Simulation2(i) - res = simulation.do_something() - - with state.lock: - state.c3 += state.c1 - state.c2 += res - state.c1 += 1 - - threads = [threading.Thread(target=test, args=[i]) for i in range(0, 1000)] - - for t in threads: - t.start() - - for t in threads: - t.join() - - assert State.c1 == 1000 - assert State.c2 == State.c3 - - -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_cpp11features.py b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py deleted file mode 100644 index f143501bb7f27..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py +++ /dev/null @@ -1,643 +0,0 @@ -import sys, pytest, os -from pytest import mark, raises -from support import setup_make, ispypy - - -test_dct = "cpp11features_cxx" - - -class TestCPP11FEATURES: - def setup_class(cls): - cls.test_dct = test_dct - import cppyy - cls.cpp11features = cppyy.load_reflection_info(cls.test_dct) - - 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 - - for cf in [create_shared_ptr_instance, create_unique_ptr_instance]: - assert TestSmartPtr.s_counter == 0 - - ptr1 = cf() - assert ptr1 - assert not not ptr1 - assert TestSmartPtr.s_counter == 1 - - ptr2 = cf() - assert ptr2 - assert not not ptr2 - assert TestSmartPtr.s_counter == 2 - - del ptr2 - gc.collect() - assert TestSmartPtr.s_counter == 1 - - del ptr1 - gc.collect() - assert TestSmartPtr.s_counter == 0 - - def test02_smart_ptr_construction(self): - """Shared/Unique pointer ctor is templated, requiring special care""" - - from cppyy.gbl import std, TestSmartPtr - import gc - - class C(TestSmartPtr): - pass - - for cls in [std.shared_ptr, std.unique_ptr]: - assert TestSmartPtr.s_counter == 0 - - c = C() - assert TestSmartPtr.s_counter == 1 - c.__python_owns__ = False - cc = cls[TestSmartPtr](c) - assert cc.__python_owns__ - - del c - - assert cc - assert TestSmartPtr.s_counter == 1 - - del cc - gc.collect() - assert TestSmartPtr.s_counter == 0 - - def test03_smart_ptr_memory_handling(self): - """Test shared/unique pointer memory ownership""" - - from cppyy.gbl import std, TestSmartPtr - import gc - - class C(TestSmartPtr): - pass - - for cls in [std.shared_ptr, std.unique_ptr]: - assert TestSmartPtr.s_counter == 0 - - t = TestSmartPtr() - assert TestSmartPtr.s_counter == 1 - assert t.__python_owns__ - - tt = std.shared_ptr[TestSmartPtr](t) - assert not t.__python_owns__ - - c = C() - assert TestSmartPtr.s_counter == 2 - assert c.__python_owns__ - - cc = std.shared_ptr[TestSmartPtr](c) - assert not c.__python_owns__ - - del cc, tt - gc.collect() - assert TestSmartPtr.s_counter == 0 - - 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 - - for ff, mv in [(pass_shared_ptr, lambda x: x), (move_shared_ptr, std.move)]: - assert TestSmartPtr.s_counter == 0 - - dd = std.make_shared[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) - assert TestSmartPtr.s_counter == 1 - assert ff(mv(dd)) == 100 - - del dd - gc.collect() - assert TestSmartPtr.s_counter == 0 - - # ability to take over by-value python-owned objects - tsp = create_TestSmartPtr_by_value() - assert TestSmartPtr.s_counter == 1 - assert tsp.__python_owns__ - - shared_stp = std.make_shared[TestSmartPtr](tsp) - assert TestSmartPtr.s_counter == 1 - assert not tsp.__python_owns__ - - del shared_stp - gc.collect() - assert TestSmartPtr.s_counter == 0 - - # alternative make_shared with type taken from pointer - tsp = create_TestSmartPtr_by_value() - shared_stp = std.make_shared(tsp) - assert TestSmartPtr.s_counter == 1 - del shared_stp - gc.collect() - assert TestSmartPtr.s_counter == 0 - - 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 - - assert TestSmartPtr.s_counter == 0 - - # 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 - assert dd.__python_owns__ - - del dd - gc.collect() - assert TestSmartPtr.s_counter == 0 - - # 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?? - # assert move_unique_ptr(std.move(dd)) == 100 - assert dd.__python_owns__ - - del dd - gc.collect() - assert TestSmartPtr.s_counter == 0 - - # ability to take over by-value python-owned objects - tsp = create_TestSmartPtr_by_value() - assert TestSmartPtr.s_counter == 1 - assert tsp.__python_owns__ - - unique_stp = std.make_unique[TestSmartPtr](tsp) - assert TestSmartPtr.s_counter == 1 - assert not tsp.__python_owns__ - - del unique_stp - gc.collect() - assert TestSmartPtr.s_counter == 0 - - # alternative make_unique with type taken from pointer - tsp = create_TestSmartPtr_by_value() - unique_stp = std.make_unique(tsp) - assert TestSmartPtr.s_counter == 1 - - del unique_stp - gc.collect() - assert TestSmartPtr.s_counter == 0 - - def test06_nullptr(self): - """Allow the programmer to pass NULL in certain cases""" - - import cppyy - - # test existence - nullptr = cppyy.nullptr - # assert not hasattr(cppyy.gbl, 'nullptr') - - assert cppyy.bind_object(cppyy.nullptr, 'std::vector') == cppyy.nullptr - assert not cppyy.bind_object(cppyy.nullptr, 'std::vector') != cppyy.nullptr - - # further usage is tested in datatypes.py:test15_nullptr_passing - - def test07_move(self): - """Move construction, assignment, and methods""" - - import cppyy, gc - - def moveit(T): - assert T.s_instance_counter == 0 - - from cppyy.gbl import std - - # move constructor - i1 = T() - assert T.s_move_counter == 0 - - i2 = T(i1) # cctor - assert T.s_move_counter == 0 - - if ispypy or 0x3000000 <= sys.hexversion: - i3 = T(std.move(T())) # can't check ref-count - else: - 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 - assert T.s_move_counter == 2 - - i4 = T(std.move(i1)) - assert T.s_move_counter == 3 - - # 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 - else: - i4.__assign__(T()) - assert T.s_move_counter == 4 - - i4.__assign__(std.move(i2)) - assert T.s_move_counter == 5 - - del i1, i2, i3, i4 - 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) - - # 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()) - gc.collect() - assert cppyy.gbl.TestMoving1.s_instance_counter == 0 - assert cppyy.gbl.TestMoving2.s_instance_counter == 0 - - def test08_initializer_list(self): - """Initializer list construction""" - - from cppyy.gbl import std, TestData, TestData2, WithInitList - - 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.] - - raises(TypeError, std.vector[int], [1., 2., 3., 4.]) - - for cls in [std.vector, WithInitList]: - for cls_arg in [TestData, TestData2]: - l = list() - for i in range(10): - l.append(cls_arg(i)) - - v = cls[cls_arg](l) - assert len(v) == len(l) - for i in range(len(l)): - assert v[i].m_int == l[i].m_int - - import cppyy - - cppyy.cppdef(r""" - namespace InitializerListTest { - std::vector foo(const std::initializer_list& vals) { - return std::vector{vals}; - } }""") - - ns = cppyy.gbl.InitializerListTest - - 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 - - cppyy.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") - - assert cppyy.gbl.gMyLambda - assert cppyy.gbl.gMyLambda(2) == 42 - assert cppyy.gbl.gMyLambda(40) == 80 - - cppyy.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") - l1 = cppyy.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() - 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) - assert l3 - assert l3(2) == 48 - - def test10_optional(self): - """Use of optional and nullopt""" - - import cppyy - - assert cppyy.gbl.std.optional - assert cppyy.gbl.std.nullopt - - cppyy.cppdef(""" - enum Enum { A = -1 }; - bool callopt(std::optional) { return true; } - """) - - a = cppyy.gbl.std.optional[cppyy.gbl.Enum]() - assert cppyy.gbl.callopt(a) - - c = cppyy.gbl.std.nullopt - assert cppyy.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 - - 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 - - t = FNTestStruct(42) - f = FNCreateTestStructFunc() - assert f(t) == 42 - - t = FunctionNS.FNTestStruct(13) - f = FunctionNS.FNCreateTestStructFunc() - assert f(t) == 13 - - # and for good measure, inline - cppyy.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 - - t = FunctionNS.FNTestStruct(27) - f = FunctionNS.FNCreateTestStructFunc() - assert f(t) == 27 - - def test13_stdhash(self): - """Use of std::hash""" - - import cppyy - - cppyy.cppdef(""" - struct StructWithHash {}; // for std::hash<> testing - struct 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 - swo = StructWithoutHash() - assert hash(swo) == object.__hash__(swo) - assert hash(swo) == object.__hash__(swo) - - sw = StructWithHash() - assert hash(sw) == 17 - assert hash(sw) == 17 - - @mark.xfail(strict=True) - 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)]: - assert TestSmartPtr.s_counter == 0 - - obj = cls() - - assert TestSmartPtr.s_counter == 1 - assert not obj.__smartptr__() - assert pass_shared_ptr(obj) == val - assert obj.__smartptr__() - assert obj.__python_owns__ - assert TestSmartPtr.s_counter == 1 - - assert not not obj # pass was by shared copy - - del obj - gc.collect() - assert TestSmartPtr.s_counter == 0 - - def test15_unique_ptr_template_deduction(self): - """Argument type deduction with std::unique_ptr""" - - import cppyy - - cppyy.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)) - assert not not uptr_out - - uptr_in = cppyy.gbl.std.make_unique['int']() - with raises(ValueError): # not an RValue - cppyy.gbl.UniqueTempl.returnptr[int](uptr_in) - - def test16_unique_ptr_moves(self): - """std::unique_ptr requires moves""" - - import cppyy - cppyy.cppdef("""namespace unique_ptr_moves { - template - std::unique_ptr returnptr_value(std::unique_ptr a) { - return std::move(a); - } - template - std::unique_ptr returnptr_move(std::unique_ptr&& a) { - return std::move(a); - } }""") - - up = cppyy.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 - - with raises(TypeError): - ns.returnptr_move(up) - - def test17_unique_ptr_data(self): - """std::unique_ptr as data means implicitly no copy ctor""" - - import cppyy - - cppyy.cppdef("""namespace unique_ptr_data{ - class Example { - private: - std::unique_ptr x; - public: - Example() {} - virtual ~Example() = default; - double y = 66.; - }; }""") - - class Inherit(cppyy.gbl.unique_ptr_data.Example): - pass - - a = Inherit() - # Test whether this attribute was inherited - assert a.y == 66. - - def test18_unique_ptr_identity(self): - """std::unique_ptr identity preservation""" - - import cppyy - - cppyy.cppdef("""\ - namespace UniqueIdentity { - struct A { - A(int _a) : a(_a) {} - int a; - }; - - std::unique_ptr create() { return std::make_unique(37); } - - struct Consumer { - public: - Consumer(std::unique_ptr & ptr) : fPtr{std::move(ptr)} { - ptr.reset(); - } - - const A& get() const { return *fPtr; } - const std::unique_ptr& pget() const { return fPtr; } - - private: - std::unique_ptr fPtr; - }; }""") - - ns = cppyy.gbl.UniqueIdentity - - x = ns.create() - assert x.a == 37 - - c = ns.Consumer(x) - x = c.get() - assert x.a == 37 - - p1 = c.pget() - p2 = c.pget() - assert p1 is p2 - - @mark.xfail(strict=True) - def test19_smartptr_from_callback(self): - """Return a smart pointer from a callback""" - - import cppyy - - cppyy.cppdef(r"""\ - namespace SmartPtrCallback { - struct Dummy { - virtual ~Dummy() = default; - }; - - std::shared_ptr dummy_create() { - return std::make_shared(); - } - - typedef std::shared_ptr (*fff)(); - - std::shared_ptr call_creator(fff func) { - return func(); - }}""") - - std = cppyy.gbl.std - ns = cppyy.gbl.SmartPtrCallback - - def pyfunc() -> std.shared_ptr[ns.Dummy]: - 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 - - gbl = cppyy.gbl - - # unique_ptr holding a Derived comes back as Derived, with the - # derived-only method callable, just like a raw pointer return - for cf in [gbl.create_unique_ptr_to_derived, gbl.create_shared_ptr_to_derived]: - obj = cf() - assert type(obj) == gbl.PubDerivedTestSmartPtr - assert obj.only_in_derived() == 27 - 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() - assert type(obj) == gbl.TestSmartPtr - - # the most derived type sits at a non-zero offset from the declared - # interface, which the dereferencer can not apply: stay the declared - # type and keep behaving correctly - obj = gbl.create_unique_ptr_to_offset_derived() - assert type(obj) == gbl.TestSmartPtrIface - assert obj.only_in_iface() == 37 - - # the auto-down-cast must not enable C++-invalid conversions: the proxy - # 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()) - - # passing it where the matching base smart pointer is expected still works - assert gbl.pass_shared_ptr(gbl.create_shared_ptr_to_derived()) == 17 - - # calling function with overloads for both the base class and the - # 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__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py deleted file mode 100644 index c9f30c0295924..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py +++ /dev/null @@ -1,1841 +0,0 @@ -import os, pytest -from pytest import raises, skip, mark -from support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS - -test_dct = "crossinheritance_cxx" - - -class TestCROSSINHERITANCE: - def setup_class(cls): - cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.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 - - assert Base1().get_value() == 42 - - class Derived(Base1): - def get_value(self): - return 13 - - assert Derived().get_value() == 13 - - assert 'Derived' in str(Derived()) - assert 'Derived' in repr(Derived()) - - assert Base1.call_get_value(Base1()) == 42 - assert Base1.call_get_value(Derived()) == 13 - - def test02_constructor(self): - """Test constructor usage for derived classes""" - - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 - - assert Base1(27).get_value() == 27 - - class Derived1(Base1): - def __init__(self, pyval): - Base1.__init__(self) - self.m_pyint = pyval - - def get_value(self): - 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 Base1.call_get_value(d) == 44 - - class Derived2(Base1): - def __init__(self, pyval, cppval): - Base1.__init__(self, cppval) - self.m_pyint = pyval - - def get_value(self): - 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 Base1.call_get_value(d) == 29 - - def test03_override_function_abstract_base(self): - """Test ability to override a simple function with an abstract base""" - - import cppyy - CX = cppyy.gbl.CrossInheritance - - class C1PyBase2(CX.IBase2): - def __init__(self): - super(C1PyBase2, self).__init__() - - def get_value(self): - return 99 - - class C2PyBase2(CX.IBase2): - def __init__(self): - CX.IBase2.__init__(self) - - def get_value(self): - return 91 - - class C3PyBase2(CX.CBase2): - def __init__(self): - super(C3PyBase2, self).__init__() - - class C4PyBase2(CX.CBase2): - def __init__(self): - super(C4PyBase2, self).__init__() - - def get_value(self): - return 13 - - try: - 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 "abstract" in str(e) - - c1, c3, c4 = C1PyBase2(), C3PyBase2(), C4PyBase2() - - assert CX.IBase2.call_get_value(c1) == 99 - assert CX.IBase2.call_get_value(c3) == 42 - assert CX.IBase2.call_get_value(c4) == 13 - - # now with abstract constructor that takes an argument - class C4PyBase2(CX.IBase3): - def __init__(self, intval): - super(C4PyBase2, self).__init__(intval) - - def get_value(self): - return 77 - - c4 = C4PyBase2(88) - assert c4.m_int == 88 - assert CX.IBase2.call_get_value(c4) == 77 - - def test04_arguments(self): - """Test ability to override functions that take arguments""" - - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 - - assert Base1(27).sum_value(-7) == 20 - - class Derived1(Base1): - def sum_value(self, val): - return val + 13 - - d1 = Derived1() - 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 - - class Derived2(Base1): - def pass_value1(self, a): - return a*2 - def pass_value2(self, a): - return a.value*2 - def pass_value3(self, a): - return a.value*2 - def pass_value4(self, b): - return b.m_int*2 - def pass_value5(self, b): - return b.m_int*2 - - d2 = Derived2() - assert Base1.sum_pass_value(d2) == 12+4*d2.m_int - - def test05_override_overloads(self): - """Test ability to override overloaded functions""" - - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 - - assert Base1(27).sum_all(-7) == 20 - assert Base1(27).sum_all(-3, -4) == 20 - - class Derived(Base1): - def sum_all(self, *args): - return sum(args) + 13 - - d = Derived() - 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 Base1.call_sum_all(d, -7, -5) == 1 - - def test06_const_methods(self): - """Declared const methods should keep that qualifier""" - - import cppyy - CX = cppyy.gbl.CrossInheritance - - class C1PyBase4(CX.IBase4): - def __init__(self): - super(C1PyBase4, self).__init__() - - def get_value(self): - return 17 - - class C2PyBase4(CX.CBase4): - def __init__(self): - super(C2PyBase4, self).__init__() - - c1, c2 = C1PyBase4(), C2PyBase4() - - assert CX.IBase4.call_get_value(c1) == 17 - assert CX.IBase4.call_get_value(c2) == 27 - - def test07_templated_base(self): - """Derive from a base class that is instantiated from a template""" - - import cppyy - - from cppyy.gbl.CrossInheritance import TBase1, TDerived1, TBase1_I - - class TPyDerived1(TBase1_I): - def __init__(self): - super(TBase1_I, self).__init__() - - def get_value(self): - return 13 - - b1, b2 = TBase1[int](), TBase1_I() - assert b1.get_value() == 42 - assert b2.get_value() == 42 - - d1 = TDerived1() - assert d1.get_value() == 27 - - 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") - def test08_error_handling(self): - """Python errors should propagate through wrapper""" - - import cppyy - Base1 = cppyy.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) - - d = Derived() - raises(ValueError, Base1.call_sum_value, d, -7) - - if IS_MAC_ARM: - skip("JIT exceptions from signals not supported on Mac ARM") - - try: - Base1.call_sum_value(d, -7) - assert not "should not get here" - except ValueError as e: - assert errmsg in str(e) - - cppyy.cppdef("""\ - namespace CrossInheritance { - std::string call_base1(Base1* b) { - try { - b->sum_value(-7); - } catch (CPyCppyy::PyException& e) { - e.clear(); - return e.what(); - } - return ""; - } }""") - - res = cppyy.gbl.CrossInheritance.call_base1(d) - - assert 'ValueError' in res - assert os.path.basename(__file__) in res - - def test09_interface_checking(self): - """Conversion errors should be Python exceptions""" - - import cppyy - Base1 = cppyy.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 - - d = Derived(4) - - assert raises(TypeError, Base1.call_get_value, d) - - def test10_python_in_templates(self): - """Usage of Python derived objects in std::vector""" - - import cppyy, gc - - CB = cppyy.gbl.CrossInheritance.CountableBase - - class PyCountable(CB): - def call(self): - try: - return self.extra + 42 - except AttributeError: - return 42 - - start_count = CB.s_count - - v = cppyy.gbl.std.vector[PyCountable]() - v.emplace_back(PyCountable()) # uses copy ctor - assert len(v) == 1 - gc.collect() - assert CB.s_count == 1 + start_count - - p = PyCountable() - assert p.call() == 42 - p.extra = 42 - assert p.call() == 84 - v.emplace_back(p) - assert len(v) == 2 - assert v[1].call() == 84 # copy ctor copies python state - p.extra = 13 - assert p.call() == 55 - assert v[1].call() == 84 - del p - gc.collect() - assert CB.s_count == 2 + start_count - - v.push_back(PyCountable()) # uses copy ctor - assert len(v) == 3 - gc.collect() - assert CB.s_count == 3 + start_count - - del v - gc.collect() - assert CB.s_count == 0 + start_count - - def test11_python_in_make_shared(self): - """Usage of Python derived objects with std::make_shared""" - - import cppyy - - cppyy.cppdef("""namespace MakeSharedTest { - class Abstract { - public: - virtual ~Abstract() = 0; - virtual int some_imp() = 0; - }; - - Abstract::~Abstract() {} - - int call_shared(std::shared_ptr& ptr) { - return ptr->some_imp(); - } }""") - - from cppyy.gbl import std, MakeSharedTest - from cppyy.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 - - v = std.make_shared[PyDerived](42) - - assert call_shared(v) == 42 - assert v.some_imp() == 42 - - p = PyDerived(13) - v = std.make_shared[PyDerived](p) - assert call_shared(v) == 13 - assert v.some_imp() == 13 - - def test12a_counter_test(self): - """Test countable base counting""" - - import cppyy, gc - - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase - - class PyCountable(CB): - def call(self): - try: - return self.extra + 42 - except AttributeError: - return 42 - - start_count = CB.s_count - - # test counter - pyc = PyCountable() - assert CB.s_count == 1 + start_count - - del pyc - gc.collect() - assert CB.s_count == 0 + start_count - - def test12_python_shared_ptr_memory(self): - """Usage of Python derived objects with std::shared_ptr""" - - import cppyy, gc - - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase - - class PyCountable(CB): - def call(self): - try: - return self.extra + 42 - except AttributeError: - return 42 - - start_count = CB.s_count - - v = std.vector[std.shared_ptr[PyCountable]]() - v.push_back(std.make_shared[PyCountable]()) - - gc.collect() - assert CB.s_count == 1 + start_count - - del v - gc.collect() - assert CB.s_count == 0 + start_count - - def test13_virtual_dtors_and_del(self): - """Usage of virtual destructors and Python-side del.""" - - import cppyy, warnings - - cppyy.cppdef("""namespace VirtualDtor { - class MyClass1 {}; // no virtual dtor ... - - class MyClass2 { - public: - virtual ~MyClass2() {} - }; - - class MyClass3 : public MyClass2 {}; - - template - class MyClass4 { - public: - virtual ~MyClass4() {} - }; }""") - - VD = cppyy.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 - 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 - # catch the exception is that the error would be changed into a - # more uninformative "TypeError: no python-side overrides supported ()" - warnings.simplefilter("default") - - class MyPyDerived1(VD.MyClass1): - 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 - - class MyPyDerived2(VD.MyClass2): - pass - - d = MyPyDerived2() - del d # used to crash - - # 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 - - ns = cppyy.gbl.AccessProtected - - assert not 'my_data' in ns.MyBase.__dict__ - assert not hasattr(ns.MyBase(), 'my_data') - - class MyPyDerived(ns.MyBase): - pass - - assert 'my_data' in MyPyDerived.__dict__ - assert MyPyDerived().my_data == 101 - - class MyPyDerived(ns.MyBase): - def __init__(self): - super(MyPyDerived, self).__init__() - assert self.my_data == 101 - self.py_data = 13 - self.my_data = 42 - - m = MyPyDerived() - assert m.py_data == 13 - assert m.my_data == 42 - assert m.get_data() == 42 - assert m.get_data_v() == 42 - - def test15_object_returns(self): - """Return of C++ objects from overridden functions""" - - import cppyy - - # Part 1: return of a new C++ object - cppyy.cppdef("""namespace object_returns { - class Base { - public: - virtual Base* foo() { return new Base(); } - virtual ~Base() {} - virtual std::string whoami() { return "Base"; } - }; - - class CppDerived : public Base { - CppDerived* foo() { return new CppDerived(); } - ~CppDerived() {} - virtual std::string whoami() { return "CppDerived"; } - }; - - Base* call_foo(Base& obj) { return obj.foo(); } }""") - - ns = cppyy.gbl.object_returns - - class PyDerived1(ns.Base): - def foo(self): - return ns.CppDerived() - - obj = PyDerived1() - assert not ns.call_foo(obj) - - class PyDerived2(ns.Base): - def foo(self): - x = ns.CppDerived() - x.__python_owns__ = False - return x - - obj = PyDerived2() - assert not not ns.call_foo(obj) - - # Part 2: return of a new Python derived object - class PyDerived3(ns.Base): - def foo(self): - return PyDerived3() - - def whoami(self): - return "PyDerived3" - - obj = PyDerived3() - newobj = ns.call_foo(obj) - assert not newobj - - class PyDerived4(ns.Base): - def foo(self): - d = PyDerived4() - d.__python_owns__ = False - d.alpha = 2 - return d - - def whoami(self): - return "PyDerived4" - - obj = PyDerived4() - new_obj = ns.call_foo(obj) - assert not not new_obj - assert new_obj.whoami() == "PyDerived4" - - def test16_cctor_access_controlled(self): - """Python derived class of C++ class with access controlled cctor""" - - import cppyy - - cppyy.cppdef("""namespace cctor_access_controlled { - class CommonBase { - public: - virtual ~CommonBase() {} - virtual std::string whoami() = 0; - }; - - class Base1 : public CommonBase { - Base1(const Base1&) {} - public: - Base1() {} - virtual ~Base1() {} - virtual std::string whoami() { return "Base1"; } - }; - - class Base2 : public CommonBase { - protected: - Base2(const Base2&) {} - public: - Base2() {} - virtual ~Base2() {} - virtual std::string whoami() { return "Base2"; } - }; - - std::string callit(CommonBase& obj) { return obj.whoami(); } }""") - - ns = cppyy.gbl.cctor_access_controlled - - for base in (ns.Base1, ns.Base2): - class PyDerived(base): - def whoami(self): - return "PyDerived" - - obj = PyDerived() - assert ns.callit(obj) == "PyDerived" - - def test17_deep_hierarchy(self): - """Test a deep Python hierarchy with pure virtual functions""" - - import cppyy - - cppyy.cppdef("""namespace deep_hierarchy { - class Base { - public: - virtual ~Base() {} - virtual std::string whoami() = 0; - }; - - std::string callit(Base& obj) { return obj.whoami(); } }""") - - ns = cppyy.gbl.deep_hierarchy - - class PyDerived1(ns.Base): - def whoami(self): - return "PyDerived1" - - obj = PyDerived1() - assert ns.callit(obj) == "PyDerived1" - - class PyDerived2(PyDerived1): - pass - - obj = PyDerived2() - assert obj.whoami() == "PyDerived1" - assert ns.callit(obj) == "PyDerived1" - - class PyDerived3(PyDerived1): - def whoami(self): - return "PyDerived3" - - obj = PyDerived3() - assert obj.whoami() == "PyDerived3" - assert ns.callit(obj) == "PyDerived3" - - class PyDerived4(PyDerived2): - def whoami(self): - return "PyDerived4" - - obj = PyDerived4() - assert obj.whoami() == "PyDerived4" - assert ns.callit(obj) == "PyDerived4" - - def test18_abstract_hierarchy(self): - """Hierarchy with abstract classes""" - - - import cppyy - - cppyy.cppdef("""namespace abstract_classes { - class Base { - public: - virtual ~Base() {} - virtual std::string whoami() = 0; - virtual std::string message() = 0; - }; - - std::string whois(Base& obj) { return obj.whoami(); } - std::string saywot(Base& obj) { return obj.message(); } }""") - - ns = cppyy.gbl.abstract_classes - - class PyDerived1(ns.Base): - def __init__(self): - super(PyDerived1, self).__init__() - self._name = "PyDerived1" - - def whoami(self): - return self._name - - class PyDerived2(PyDerived1): - def __init__(self): - super(PyDerived2, self).__init__() - self._message = "Hello, World!" - - def message(self): - return self._message - - obj = PyDerived2() - assert obj.whoami() == "PyDerived1" - assert ns.whois(obj) == "PyDerived1" - - 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 - - cppyy.cppdef(""" namespace cpp_side_multiple_inheritance { - struct Result { - Result() : result(1337) {} - Result(int r) : result(r) {} - int result; - }; - - class Base1 { - public: - virtual ~Base1() {} - virtual Result abstract1() = 0; - }; - - class Base2 { - public: - virtual ~Base2() {} - virtual Result abstract2() = 0; - }; - - class Base : public Base1, public Base2 { - public: - Result abstract2() override { return Result(999); } - }; } """) - - ns = cppyy.gbl.cpp_side_multiple_inheritance - - class Derived(ns.Base): - def abstract1(self): - return ns.Result(1) - - def test20_basic_multiple_inheritance(self): - """Basic multiple inheritance""" - - import cppyy - - cppyy.cppdef("""namespace basic_multiple_inheritance { - class MyClass1 { - public: - MyClass1() : m_1(13) {} - virtual ~MyClass1() {} - virtual int x() = 0; - - public: - int m_1; - }; - int callx(MyClass1& m) { return m.x(); } - - class MyClass2 { - public: - MyClass2() : m_2(42) {} - virtual ~MyClass2() {} - virtual int y() = 0; - - public: - int m_2; - }; - int cally(MyClass2& m) { return m.y(); } - - class MyClass3 { - public: - MyClass3() : m_3(67) {} - virtual ~MyClass3() {} - virtual int z() = 0; - - public: - int m_3; - }; - int callz(MyClass3& m) { return m.z(); } }""") - - ns = cppyy.gbl.basic_multiple_inheritance - - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): - def x(self): - return 16 - - def y(self): - return 32 - - assert len(MyPyDerived.__bases__) == 2 - - a = MyPyDerived() - assert a.x() == ns.callx(a) - assert a.y() == ns.cally(a) - - assert a.m_1 == 13 - assert a.m_2 == 42 - - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): - def x(self): - return 16 - - def y(self): - return 32 - - def z(self): - return 64 - - assert len(MyPyDerived2.__bases__) == 3 - - a = MyPyDerived2() - assert a.x() == ns.callx(a) - assert a.y() == ns.cally(a) - assert a.z() == ns.callz(a) - - assert a.m_1 == 13 - assert a.m_2 == 42 - assert a.m_3 == 67 - - def test21_multiple_inheritance_with_constructors(self): - """Multiple inheritance with constructors""" - - import cppyy - - cppyy.cppdef("""namespace multiple_inheritance_with_constructors { - class MyClass1 { - public: - MyClass1() : m_1(13) {} - MyClass1(int i) : m_1(i) {} - virtual ~MyClass1() {} - virtual int x() = 0; - - public: - int m_1; - }; - int callx(MyClass1& m) { return m.x(); } - - class MyClass2 { - public: - MyClass2() : m_2(42) {} - MyClass2(int i) : m_2(i) {} - virtual ~MyClass2() {} - virtual int y() = 0; - - public: - int m_2; - }; - int cally(MyClass2& m) { return m.y(); } - - class MyClass3 { - public: - MyClass3() : m_3(67) {} - MyClass3(int i) : m_3(i) {} - virtual ~MyClass3() {} - virtual int z() = 0; - - public: - int m_3; - }; - int callz(MyClass3& m) { return m.z(); } }""") - - ns = cppyy.gbl.multiple_inheritance_with_constructors - - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): - def __init__(self, val1, val2): - super(MyPyDerived, self).__init__((val1,), (val2,)) - - def x(self): - return 16 - - def y(self): - return 32 - - assert len(MyPyDerived.__bases__) == 2 - - a = MyPyDerived(27, 88) - assert a.x() == ns.callx(a) - assert a.y() == ns.cally(a) - - assert a.m_1 == 27 - assert a.m_2 == 88 - - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): - def __init__(self, val1, val2, val3): - super(MyPyDerived2, self).__init__((val1,), (val2,), (val3,)) - - def x(self): - return 16 - - def y(self): - return 32 - - def z(self): - return 64 - - assert len(MyPyDerived2.__bases__) == 3 - - a = MyPyDerived2(27, 88, -11) - assert a.x() == ns.callx(a) - 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_3 == -11 - - def test22_multiple_inheritance_with_defaults(self): - """Multiple inheritance with defaults""" - - import cppyy - - cppyy.cppdef("""namespace multiple_inheritance_with_defaults { - class MyClass1 { - public: - MyClass1(int i=13) : m_1(i) {} - virtual ~MyClass1() {} - virtual int x() = 0; - - public: - int m_1; - }; - int callx(MyClass1& m) { return m.x(); } - - class MyClass2 { - public: - MyClass2(int i=42) : m_2(i) {} - virtual ~MyClass2() {} - virtual int y() = 0; - - public: - int m_2; - }; - int cally(MyClass2& m) { return m.y(); } - - class MyClass3 { - public: - MyClass3(int i=67) : m_3(i) {} - virtual ~MyClass3() {} - virtual int z() = 0; - - public: - int m_3; - }; - int callz(MyClass3& m) { return m.z(); } }""") - - ns = cppyy.gbl.multiple_inheritance_with_defaults - - class MyPyDerived(cppyy.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 () - a3 = val3 is not None and (val3,) or () - if nArgs == 3: - super(MyPyDerived, self).__init__(a1, a2, a3) - elif nArgs == 0: - super(MyPyDerived, self).__init__() - elif nArgs == 1: - super(MyPyDerived, self).__init__(a1) - elif nArgs == 2: - super(MyPyDerived, self).__init__(a1, a2) - - def x(self): - return 16 - - def y(self): - return 32 - - def z(self): - return 64 - - assert len(MyPyDerived.__bases__) == 3 - - def verify(a, n1, n2, n3): - assert a.m_1 == n1 - assert a.m_2 == n2 - assert a.m_3 == n3 - - a = MyPyDerived(27, 88, -11) - assert a.x() == ns.callx(a) - assert a.y() == ns.cally(a) - assert a.z() == ns.callz(a) - - verify(a, 27, 88, -11) - - a = MyPyDerived(val2=27) - verify(a, 13, 27, 67) - - a = MyPyDerived(nArgs=0) - verify(a, 13, 42, 67) - - a = MyPyDerived(27, nArgs=1) - verify(a, 27, 42, 67) - - a = MyPyDerived(27, 55, nArgs=2) - verify(a, 27, 55, 67) - - def test23_const_byvalue_return(self): - """Const by-value return in overridden method""" - - import cppyy - - cppyy.cppdef("""namespace const_byvalue_return { - struct Const { - Const() = default; - explicit Const(const std::string& s) { m_value = s; } - std::string m_value; - }; - - struct Abstract { - virtual ~Abstract() {} - virtual const Const return_const() = 0; - }; - - const Const callit(Abstract* a) { return a->return_const(); } }""") - - ns = cppyy.gbl.const_byvalue_return - - class ReturnConstByValue(ns.Abstract): - def return_const(self): - return ns.Const("abcdef") - - a = ReturnConstByValue() - assert a.return_const().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 - - cppyy.cppdef("""\ - namespace non_copyable { - struct Copyable { - Copyable() = default; - virtual ~Copyable() {} - - Copyable(const Copyable&) = default; - Copyable& operator=(const Copyable&) = default; - }; - - struct Movable { - Movable() = default; - virtual ~Movable() {} - - Movable(const Movable&) = delete; - Movable& operator=(const Movable&) = delete; - Movable(Movable&&) = default; - Movable& operator=(Movable&&) = default; - }; - - class NoCopyNoMove { - public: - NoCopyNoMove() = delete; - NoCopyNoMove(const NoCopyNoMove&) = delete; - NoCopyNoMove(NoCopyNoMove&&) = delete; - NoCopyNoMove& operator=(const NoCopyNoMove&) = delete; - NoCopyNoMove& operator=(NoCopyNoMove&&) = delete; - virtual ~NoCopyNoMove() = default; - - template - explicit NoCopyNoMove(DerivedType* ptr) : fActual(ptr) {} - - std::string callme() { - if (!fActual) return "failed!"; - return fActual->callme_imp(); - } - - private: - virtual std::string callme_imp() = 0; - - protected: - NoCopyNoMove* fActual; - }; }""") - - ns = cppyy.gbl.non_copyable - - Copyable = ns.Copyable - Movable = ns.Movable - NoCopyNoMove = ns.NoCopyNoMove - - class DerivedCopyable(Copyable): - pass - - # used to fail with compilation error - class DerivedMovable(Movable): - pass - - # used to fail with compilation error - class DerivedMulti(cppyy.multi(Movable, Copyable)): - pass - - # 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 - self.fActual = self - - def callme_imp(self): - return "Hello, World!" - - assert DerivedNoCopyNoMove().callme() == "Hello, World!" - - def test25_default_ctor_and_multiple_inheritance(self): - """Regression test: default ctor did not get added""" - - import cppyy - - cppyy.cppdef("""namespace default_ctor_and_multiple { - struct Copyable { - Copyable() = default; - virtual ~Copyable() {} - - Copyable(const Copyable&) = default; - Copyable& operator=(const Copyable&) = default; - }; - - struct Movable { - Movable() = default; - virtual ~Movable() {} - - Movable(const Movable&) = delete; - Movable& operator=(const Movable&) = delete; - Movable(Movable&&) = default; - Movable& operator=(Movable&&) = default; - }; - - struct SomeClass { - virtual ~SomeClass() {} - }; }""") - - ns = cppyy.gbl.default_ctor_and_multiple - Copyable = ns.Copyable - Movable = ns.Movable - SomeClass = ns.SomeClass - - class DerivedMulti(cppyy.multi(Movable, Copyable, SomeClass)): - def __init__(self): - super(DerivedMulti, self).__init__() - - d = DerivedMulti() - assert d - - def test26_no_default_ctor(self): - """Make sure no default ctor is created if not viable""" - - import cppyy - - cppyy.cppdef("""namespace no_default_ctor { - struct NoDefCtor1 { - NoDefCtor1(int) {} - virtual ~NoDefCtor1() {} - }; - - struct NoDefCtor2 { - NoDefCtor2() = delete; - virtual ~NoDefCtor2() {} - }; - - class NoDefCtor3 { - NoDefCtor3() = default; - public: - virtual ~NoDefCtor3() {} - }; - - class Simple { - public: - virtual ~Simple() {} - }; - }""") - - ns = cppyy.gbl.no_default_ctor - - for kls in (ns.NoDefCtor1, ns.NoDefCtor2, ns.NoDefCtor3): - class PyDerived(kls): - def __init__(self): - super(PyDerived, self).__init__() - - with raises(TypeError): - PyDerived() - - class PyDerived(cppyy.multi(kls, ns.Simple)): - def __init__(self): - super(PyDerived, self).__init__() - - with raises(TypeError): - PyDerived() - - class PyDerived(cppyy.multi(ns.Simple, kls)): - def __init__(self): - super(PyDerived, self).__init__() - - with raises(TypeError): - PyDerived() - - def test27_interfaces(self): - """Inherit from base with non-standard offset""" - - import cppyy - - cppyy.cppdef("""\ - namespace NonStandardOffset { - struct Calc1 { - virtual int calc1() = 0; - virtual ~Calc1() = default; - }; - - struct Calc2 { - virtual int calc2() = 0; - virtual ~Calc2() = default; - }; - - struct Base : virtual public Calc1, virtual public Calc2 { - Base() {} - }; - - struct Derived : Base, virtual public Calc2 { - int calc1() override { return 1; } - int calc2() override { return 2; } - }; - - int callback1(Calc1* c1) { return c1->calc1(); } - int callback2(Calc2* c2) { return c2->calc2(); } - }""") - - ns = cppyy.gbl.NonStandardOffset - - class MyPyDerived(ns.Derived): - pass - - obj = MyPyDerived() - - assert obj.calc1() == 1 - assert ns.callback1(obj) == 1 - - assert obj.calc2() == 2 - assert ns.callback2(obj) == 2 - - def test28_cross_deep(self): - """Deep inheritance hierarchy""" - - import cppyy - - cppyy.cppdef("""\ - namespace CrossDeep { - - class A { - public: - A(const std::string& /* name */) {} - virtual ~A() {} - virtual int fun1() const { return 0; } - virtual int fun2() const { return fun1(); } - }; }""") - - A = cppyy.gbl.CrossDeep.A - - class B(A): - def __init__ (self, name = 'b'): - super(B, self).__init__(name) - - def fun1(self): - return 1 - - class C(B): - def fun1(self): - return -1 - - class D(B): - pass - - 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() - - def test29_cross_deep_multi(self): - """Deep multi-inheritance hierarchy""" - - import cppyy - - cppyy.cppdef("""\ - namespace CrossMultiDeep { - - class A { - public: - virtual ~A() {} - virtual int calc_a() { return 17; } - }; - - int calc_a(A* a) { return a->calc_a(); } - - class B { - public: - virtual ~B() {} - virtual int calc_b() { return 42; } - }; - - int calc_b(B* b) { return b->calc_b(); } }""") - - ns = cppyy.gbl.CrossMultiDeep - - class C(cppyy.multi(ns.A, ns.B)): - def calc_a(self): - return 18 - - def calc_b(self): - return 43 - - c = C() - assert ns.calc_a(c) == 18 - assert ns.calc_b(c) == 43 - - class D(ns.B): - def calc_b(self): - return 44 - - d = D() - assert ns.calc_b(d) == 44 - - class E(cppyy.multi(ns.A, D)): - def calc_a(self): - return 19 - - e = E() - assert ns.calc_a(e) == 19 - assert ns.calc_b(e) == 44 - - class F(ns.A): - def calc_a(self): - return 20 - - f = F() - assert ns.calc_a(f) == 20 - - class G(cppyy.multi(F, ns.B)): - def calc_b(self): - return 45 - - g = G() - assert ns.calc_a(g) == 20 - assert ns.calc_b(g) == 45 - - class H(object): - def calc_a(self): - return 66 - - class I(cppyy.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)): - 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 - - cppyy.cppdef("""\ - namespace AccessAndOverload { - class Base { - public: - virtual ~Base() {} - - protected: - virtual int call1(int i) { return i; } - virtual int call1(int i, int j) { return i+j; } - - virtual void call2(int) { return; } - virtual void call2(int, int) { return; } - - int call3(int i) { return i; } - - private: - int call3(int i, int j) { return i+j; } - }; }""") - - ns = cppyy.gbl.AccessAndOverload - - # 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 - - ns = cppyy.gbl.CrossInheritance - ns.build_component.__creates__ = True - - assert ns.Component.get_count() == 0 - - cmp1 = ns.build_component(42) - assert cmp1.__python_owns__ - assert type(cmp1) == ns.Component - with raises(AttributeError): - cmp1.getValue() - - 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 { - class ComponentWithValue : public Component { - public: - ComponentWithValue(int value) : m_value(value) {} - int getValue() { return m_value; } - - protected: - 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 - assert act_cmp1.__python_owns__ - assert act_cmp1.getValue() == 42 - - del act_cmp1, cmp1 - gc.collect() - assert ns.Component.get_count() == 0 - - # 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 - pycmp2a = PyComponentWithValue(27) - assert not pycmp2a.__python_owns__ - pycmp2a.__python_owns__ = True - assert ns.Component.get_count() == 1 - - pycmp2b = ns.cycle_component(pycmp2a) - assert ns.Component.get_count() == 1 - assert pycmp2b is pycmp2a - - del pycmp2b, pycmp2a - gc.collect() - assert ns.Component.get_count() == 0 - - cmp2 = cppyy.bind_object(cppyy.addressof(PyComponentWithValue(13)), ns.Component) - assert ns.Component.get_count() == 1 - - cmp2 = ns.cycle_component(cmp2) # causes auto down-cast - assert ns.Component.get_count() == 1 - #assert type(cmp2) != PyComponentWithValue - - # rebind cmp2 to the python type - act_cmp2 = cppyy.bind_object(cmp2, PyComponentWithValue) - act_cmp2.__python_owns__ = True - 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 - ns.ComponentWithValue.__init__.__creates__ = True - class PyComponentWithInit(ns.ComponentWithValue): - def __init__(self, cppvalue): - super(PyComponentWithInit, self).__init__(cppvalue) - self.m_pyvalue = 11 - - def getValue(self): - return self.m_value + self.m_pyvalue - - cmp3 = cppyy.bind_object(PyComponentWithInit(77), PyComponentWithInit) - assert type(cmp3) == PyComponentWithInit - assert ns.Component.get_count() == 1 - - 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) - 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 - - 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.__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) - assert not cmp5.__python_owns__ - assert act_cmp5.__python_owns__ - assert act_cmp5.getValue() == 22+11 - - del cmp5, act_cmp5 - gc.collect() - assert ns.Component.get_count() == 0 - - def test32_by_value_arguments(self): - """Override base function taking by-value arguments""" - - import cppyy - - cppyy.cppdef("""\ - namespace CrossCallWithValue { - struct Data { - int value; - }; - - struct CppBase { - virtual ~CppBase() {} - - int func(Data d) { - return d.value + extra_func(d); - } - - virtual int extra_func(Data d) = 0; - }; }""") - - ns = cppyy.gbl.CrossCallWithValue - - class PyDerived(ns.CppBase): - def extra_func(self, d): - return 42 + d.value - - d = ns.Data(13) - p = PyDerived() - - assert p.func(d) == 42 + 2 * d.value - - @mark.xfail(strict=True) - def test33_direct_base_methods(self): - """Call base class methods directly""" - - import cppyy - - cppyy.cppdef("""\ - namespace DirectCalls { - struct A { - virtual ~A() {} - virtual int func() { - return 1; - } - }; - - struct B : public A { - virtual int func() { - return 2; - } - }; }""") - - ns = cppyy.gbl.DirectCalls - - a = ns.A() - assert a.func() == 1 - assert ns.A.func(a) == 1 - - b = ns.B() - assert b.func() == 2 - assert ns.B.func(b) == 2 - assert ns.A.func(b) == 1 - - with raises(TypeError): - ns.B.func(a) - - class C(ns.A): - def func(self): - from_a = ns.A.func(self) - return from_a + 2 - - c = C() - assert c.func() == 3 - - def test34_no_ctors_in_base(self): - """Base classes with no constructors""" - - import cppyy - - cppyy.cppdef("""\ - namespace BlockedCtors { - class Z { - public: - virtual ~Z() {} - }; - - class X : Z { - X(); - X(const X&&) = delete; - }; - - class Y: Z { - protected: - Y() {} - Y(const Y&&) = delete; - }; }""") - - ns = cppyy.gbl.BlockedCtors - - with raises(TypeError): - ns.X() - - with raises(TypeError): - ns.Y() - - class PyY1(ns.Y): - pass - - with raises(TypeError): - PyY1() - - class PyY2(ns.Y): - def __init__(self): - super(ns.Y, self).__init__() - - assert PyY2() - - def test35_deletion(self): - """C++ side deletion should propagate to the Python side""" - - import cppyy - - cppyy.cppdef("""\ - namespace DeletionTest1 { - class Base { - public: - virtual ~Base() {} - void do_work() {} - }; - - void delete_it(Base *p) { delete p; } - }""") - - ns = cppyy.gbl.DeletionTest1 - - class Derived(ns.Base): - was_deleted = False - def __del__(self): - Derived.was_deleted = True - - o1 = Derived() - o1.do_work() - ns.delete_it(o1) - - with raises(ReferenceError): - o1.do_work() - - assert Derived.was_deleted == False - del o1 - assert Derived.was_deleted == True - - def test36_custom_destruct(self): - """C++ side deletion calls __destruct__""" - - import cppyy - - cppyy.cppdef("""\ - namespace DeletionTest2 { - class Base { - public: - virtual ~Base() {} - void do_work() {} - }; - - void delete_it(Base *p) { delete p; } - }""") - - ns = cppyy.gbl.DeletionTest2 - - class Derived(ns.Base): - was_cpp_deleted = False - was_py_deleted = False - - def __destruct__(self): - Derived.was_cpp_deleted = True - - def __del__(self): - Derived.was_py_deleted = True - - o1 = Derived() - o1.do_work() - ns.delete_it(o1) - - with raises(ReferenceError): - o1.do_work() - - assert Derived.was_cpp_deleted == True - assert Derived.was_py_deleted == False - del o1 - assert Derived.was_py_deleted == True - - def test37_deep_tree(self): - """Find overridable methods deep in the tree""" - - import cppyy - - cppyy.cppdef("""\ - namespace DeepTree { - - class Base { - public: - virtual ~Base() {} - - virtual std::string f1() { return "C++: Base::f1()"; } - virtual std::string f2() { return "C++: Base::f2()"; } - virtual std::string f3() { return "C++: Base::f3()"; } - }; - - class Intermediate: public Base { - public: - virtual ~Intermediate() {} - - using Base::f2; - }; - - class Sub: public Intermediate { - public: - virtual ~Sub() {} - - using Intermediate::f3; // `using Base::f3;` would also work - }; - - class CppSub: public Sub { - public: - virtual ~CppSub() {} - - std::string f1() { return "C++: CppSub::f1()"; } - std::string f2() { return "C++: CppSub::f2()"; } - std::string f3() { return "C++: CppSub::f3()"; } - }; - - std::string call_fs(Base *b) { - std::string res; - res += b->f1(); - res += b->f2(); - res += b->f3(); - return res; - } }""") - - ns = cppyy.gbl.DeepTree - - cppsub = ns.CppSub() - assert cppsub.f1() == "C++: CppSub::f1()" - assert cppsub.f2() == "C++: CppSub::f2()" - assert cppsub.f3() == "C++: CppSub::f3()" - assert ns.call_fs(cppsub) == cppsub.f1() + cppsub.f2() + cppsub.f3() - - class PySub(ns.Sub): - def f1(self): - return "Python: PySub::f1()" - - def f2(self): - return "Python: PySub::f2()" - - def f3(self): - return "Python: PySub::f3()" - - pysub = PySub() - assert pysub.f1() == "Python: PySub::f1()" - assert pysub.f2() == "Python: PySub::f2()" - 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 - - cppyy.cppdef(""" - namespace multiple_inheritance_with_protected_data { - class MyBaseClass { - public: - virtual ~MyBaseClass() {} - - protected: - int x = 0; - std::string s = "Hello"; - int y = 0; - std::string t = "World"; - int z = 0; - public: - MyBaseClass(int x, int y, int z) : x(x), y(y), z(z) {} - int get_x() { return x; } - int get_y() { return y; } - int get_z() { return z; } - }; }""") - - ns = cppyy.gbl.multiple_inheritance_with_protected_data - - class MyDerivedClass(ns.MyBaseClass): - def __init__(self, x, y, z): - super(MyDerivedClass, self).__init__(x, y, z) - - derived = MyDerivedClass(5, 7, 9) - assert derived.get_x() == derived.x - assert derived.get_y() == derived.y - assert derived.get_z() == derived.z - assert derived.s == "Hello" - assert derived.t == "World" - - def test39_returning_multi_keyword_types(self): - """Supporting dispatcher for functions that return multi-keyword types like `unsigned int`""" - - import cppyy - - cppyy.cppdef(""" - class MyUIntBaseClass { - public: - virtual ~MyUIntBaseClass() = default; - virtual unsigned int give_unsigned_int() const = 0; - }; - - """) - - # 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 ): - return 1 - -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 a26d5892ae72a..0000000000000 --- a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py +++ /dev/null @@ -1,2467 +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, has_cpp_20 - -test_dct = "datatypes_cxx" - - -def is_modules_off(): - import cppyy - - return "runtime_cxxmodules" not in cppyy.gbl.gROOT.GetConfigFeatures() - - -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