From ca82231c579f1cf8d799a5de4d5324ea5d8472c8 Mon Sep 17 00:00:00 2001 From: Aaron Jomy <75925957+aaronj0@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:17:59 +0200 Subject: [PATCH 1/5] Improve error reporting for method calls without a C++ object (#41) * [cpyrt] Improve error reporting for method calls without C++ object Co-Authored-By: Claude Fable 5 * [test] Add test for method calls on an instance without a C++ object Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Grigori Rybkine Co-authored-by: Claude Fable 5 --- src/cpyrt/CPPMethod.cxx | 3 ++- src/cpyrt/CPPOverload.cxx | 3 ++- test/test_fragile.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index 761d0ed..3f0d0e5 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -1058,7 +1058,8 @@ PyObject* cpyrt::CPPMethod::Call(CPPInstance*& self, cpyrt_PyArgs_t args, // validity check that should not fail if (!object) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + PyErr_SetString(PyExc_ReferenceError, "no C++ object available"); + ctxt->fFlags |= CallContext::kCppException; return nullptr; } diff --git a/src/cpyrt/CPPOverload.cxx b/src/cpyrt/CPPOverload.cxx index 8bda467..49778df 100644 --- a/src/cpyrt/CPPOverload.cxx +++ b/src/cpyrt/CPPOverload.cxx @@ -624,7 +624,8 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, return HandleReturn(pymeth, im_self, result); // fall through: python is dynamic, and so, the hashing isn't infallible - ctxt.fFlags &= ~CallContext::kAllowImplicit; + ctxt.fFlags &= ~(CallContext::kAllowImplicit | CallContext::kPyException | + CallContext::kCppException); PyErr_Clear(); ResetCallState(pymeth->fSelf, im_self); } diff --git a/test/test_fragile.py b/test/test_fragile.py index 181a38b..e4cf3bc 100644 --- a/test/test_fragile.py +++ b/test/test_fragile.py @@ -761,6 +761,24 @@ def test31_template_with_class_enum(self): for ns, val in [(cppjit.gbl, 42), (cppjit.gbl.ClassEnumNS, 37)]: assert ns.EnumTemplate[ns.ClassEnumA.A]().foo() == val + def test32_overloaded_method_error_with_null_object(self): + """Check exception type and message when method invoked on instance without C++ object""" + + import cppjit + from cppjit import gbl + + cppjit.cppdef(r"""\ + using fragile::D; + D *something = new D; + D *nothing = nullptr; + """) + + assert gbl.something.check() == gbl.something.check(0, 1) + with raises(ReferenceError, match=r"^no C\+\+ object available$"): + gbl.nothing.check() # raises error + with raises(ReferenceError, match=r"^no C\+\+ object available$"): + gbl.nothing.check(0, 1) # raises error + class TestSIGNALS: def setup_class(cls): From 323503946e59904de07f4c4ede355a9bc514a8d2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy <75925957+aaronj0@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:18:35 +0200 Subject: [PATCH 2/5] Penalize void* arguments in overload priority as intended (#40) * [cpyrt] Penalize void* arguments in overload priority as intended * [test] Add regression test for void* overload priority --------- Co-authored-by: Emery Conrad Co-authored-by: Claude Fable 5 --- src/cpyrt/CPPMethod.cxx | 13 ++++++++----- test/test_overloads.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index 3f0d0e5..3fe3e37 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -529,7 +529,14 @@ int cpyrt::CPPMethod::GetPriority() { // type: // interop::TCppType_t type = interop::GetMethodArgType(fMethod, iarg); - if (interop::IsBuiltin(aname)) { + // 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 @@ -557,10 +564,6 @@ int cpyrt::CPPMethod::GetPriority() { 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.). diff --git a/test/test_overloads.py b/test/test_overloads.py index 24b8d0a..f736c8a 100644 --- a/test/test_overloads.py +++ b/test/test_overloads.py @@ -411,3 +411,32 @@ def test15_disallow_mutable_pointer_references(self): ptr = cppjit.gbl.MyClass() raises(TypeError, cppjit.gbl.changePtr, ptr) + + def test16_voidp_does_not_outrank_conversion(self): + """Verify that a const void* overload does not shadow a converting one.""" + + import cppjit + + cppjit.cppdef(""" + namespace VoidPPriority { + struct Handle { + void* data; + Handle() : data(nullptr) {} + Handle(void* p) : data(p) {} + }; + struct ConstHandle { + const void* data; + ConstHandle() : data(nullptr) {} + ConstHandle(const void* p) : data(p) {} // declared first on purpose + ConstHandle(Handle h) : data(h.data) {} + }; + Handle make_handle() { return Handle((void*)0xABCD1234); } + bool kept_value(ConstHandle c) { return c.data == (const void*)0xABCD1234; } + }""") + + ns = cppjit.gbl.VoidPPriority + + # taking ConstHandle(const void*) would pass the proxy's address instead + h = ns.make_handle() + assert ns.kept_value(h) + assert ns.kept_value(ns.make_handle()) From 46cde5532b02c824b02b1d624504f6b009573dfa Mon Sep 17 00:00:00 2001 From: Aaron Jomy <75925957+aaronj0@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:18:07 +0200 Subject: [PATCH 3/5] Unify installed layout under cppjit, drop backend (#43) --- CMakeLists.txt | 22 +++++++++++----------- pyproject.toml | 2 +- python/cppjit/__init__.py | 4 ++-- python/cppjit/_cpython_cppjit.py | 6 +++--- python/cppjit_backend/__init__.py | 1 - python/cppjit_backend/_version.py | 1 - 6 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 python/cppjit_backend/__init__.py delete mode 100644 python/cppjit_backend/_version.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7158a15..50403dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,7 +101,7 @@ if(_python_platlib) else() set(CPPINTEROP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") endif() -set(CPPINTEROP_INSTALL_DIR "${CPPINTEROP_INSTALL_PREFIX}/cppjit_backend") +set(CPPINTEROP_INSTALL_DIR "${CPPINTEROP_INSTALL_PREFIX}/cppjit/interop") # Include cmake for CppInterOp config and build using ExternalProject. include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) @@ -121,11 +121,11 @@ add_dependencies(cppjit CppInterOp) # 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_backend/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" - CPPINTEROP_INCLUDE_DIR="cppjit_backend/include" + CPPINTEROP_INSTALL_PREFIX="${CPPINTEROP_INSTALL_PREFIX}/cppjit" + CPPINTEROP_LIBRARY="interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + CPPINTEROP_INCLUDE_DIR="interop/include" CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" - CPPJIT_CLANG_INCLUDE_DIR="cppjit_backend/lib/clang/${LLVM_VERSION_MAJOR}" + CPPJIT_CLANG_INCLUDE_DIR="interop/lib/clang/${LLVM_VERSION_MAJOR}" ) target_include_directories(cppjit PRIVATE @@ -159,21 +159,21 @@ set_target_properties(cppjit PROPERTIES PREFIX "lib" ) -# libcppjit.so is installed at the site-packages root (import libcppjit) +# the extension lives inside the package (import cppjit.libcppjit) install(TARGETS cppjit - LIBRARY DESTINATION . + LIBRARY DESTINATION cppjit ) # install CppInterOp libraries and headers install(CODE " file(GLOB _interop_libs \"${CPPINTEROP_INSTALL_DIR}/lib/libclangCppInterOp*\") foreach(_lib \${_interop_libs}) - file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit_backend/lib) + file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/lib) endforeach() ") install(CODE " - file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit_backend/include) + file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/include) ") # ship the builtin headers of the build clang, laid out as a headers-only @@ -185,7 +185,7 @@ if(NOT EXISTS "${_clang_resource_dir}/include") "${LLVM_DIR} carries no clang resource directory") endif() install(DIRECTORY "${_clang_resource_dir}/include/" - DESTINATION "cppjit_backend/lib/clang/${LLVM_VERSION_MAJOR}/include" + DESTINATION "cppjit/interop/lib/clang/${LLVM_VERSION_MAJOR}/include" ) # the public cpyrt API headers keep their installed cpyrt/ prefix @@ -195,5 +195,5 @@ install(FILES src/cpyrt/DispatchPtr.h src/cpyrt/PyException.h src/cpyrt/Reflex.h - DESTINATION cppjit_backend/include/cpyrt + DESTINATION cppjit/interop/include/cpyrt ) diff --git a/pyproject.toml b/pyproject.toml index 9878475..5308b63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ maintainers = [ [tool.scikit-build] wheel.install-dir = "." -wheel.packages = ["python/cppjit", "python/cppjit_backend"] +wheel.packages = ["python/cppjit"] cmake.build-type = "Release" [[tool.dynamic-metadata]] diff --git a/python/cppjit/__init__.py b/python/cppjit/__init__.py index ae87217..6280d6e 100644 --- a/python/cppjit/__init__.py +++ b/python/cppjit/__init__.py @@ -348,10 +348,10 @@ def _setup_include_paths(): if os.path.basename(apipath_extra) == "cpyrt": apipath_extra = os.path.dirname(apipath_extra) else: - spec = importlib.util.find_spec("libcppjit") + 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), "cppjit_backend", "include" + os.path.dirname(spec.origin), "interop", "include" ) if apipath_extra and apipath_extra.lower() != "none": diff --git a/python/cppjit/_cpython_cppjit.py b/python/cppjit/_cpython_cppjit.py index 0b11ec3..50a32f3 100644 --- a/python/cppjit/_cpython_cppjit.py +++ b/python/cppjit/_cpython_cppjit.py @@ -21,9 +21,9 @@ 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("libcppjit") + spec = importlib.util.find_spec("cppjit.libcppjit") if spec is None or not spec.origin: - raise ImportError("cannot locate the libcppjit extension module") + 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)") @@ -32,7 +32,7 @@ def _preload_backend_library(): _w = _preload_backend_library() -import libcppjit as _backend # noqa: E402 +from . import libcppjit as _backend # noqa: E402 ### template support --------------------------------------------------------- diff --git a/python/cppjit_backend/__init__.py b/python/cppjit_backend/__init__.py deleted file mode 100644 index aab79a8..0000000 --- a/python/cppjit_backend/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._version import __version__ as __version__ diff --git a/python/cppjit_backend/_version.py b/python/cppjit_backend/_version.py deleted file mode 100644 index 3dc1f76..0000000 --- a/python/cppjit_backend/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 821257bd68848e39ae44d7659bddbd710344bd83 Mon Sep 17 00:00:00 2001 From: Aaron Jomy <75925957+aaronj0@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:24:55 +0200 Subject: [PATCH 4/5] Add single header for interop API/types used by cpyrt (#42) Fixes the long-standing header duplication (previously `Cppyy.h` and `cpp_cppyy.h`) between cpyrt and interop, so that there is only a single source of definitions for the `cppjit::interop` API and types. --- src/cpyrt/CallContext.h | 36 +- src/cpyrt/cppjit_interop.h | 467 ------------------ src/interop/callcontext.h | 21 +- .../{cpp_cppjit.h => cppjit_interop.h} | 15 +- src/interop/interop_wrapper.cxx | 12 +- 5 files changed, 30 insertions(+), 521 deletions(-) delete mode 100644 src/cpyrt/cppjit_interop.h rename src/interop/{cpp_cppjit.h => cppjit_interop.h} (96%) diff --git a/src/cpyrt/CallContext.h b/src/cpyrt/CallContext.h index cf614f1..b52915b 100644 --- a/src/cpyrt/CallContext.h +++ b/src/cpyrt/CallContext.h @@ -12,40 +12,8 @@ namespace cppjit::cpyrt { -// 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 CPYRT_PARAMETER -#define CPYRT_PARAMETER -// 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; -}; -#endif // CPYRT_PARAMETER +// 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 { diff --git a/src/cpyrt/cppjit_interop.h b/src/cpyrt/cppjit_interop.h deleted file mode 100644 index f2cdb69..0000000 --- a/src/cpyrt/cppjit_interop.h +++ /dev/null @@ -1,467 +0,0 @@ -#ifndef CPYRT_CPPJIT_H -#define CPYRT_CPPJIT_H - -// Standard -#include -#include -#include -#include -#include - -// import/export (after precommondefs.h from PyPy) -#ifdef _MSC_VER -#define CPPJIT_IMPORT extern __declspec(dllimport) -#else -#define CPPJIT_IMPORT extern -#endif - -// 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 - -// FIXME: We should not duplicate these definitions here and in CppInterOp.h -// The current setup relies on finding an identical symbol definition in -// libcppjitbackend.so which is fragile and requires updating both locations -// when changing. Ideally we should have the ability to set/get the template arg -// info provided through some factory methods in CppInterOp API, so the clients -// can rely completely on opaque pointers like we do for the rest of the -// argument types. -struct TemplateArgInfo { - void* m_Type; - const char* m_IntegralValue; - TemplateArgInfo(void* type, const char* integral_value = nullptr) - : m_Type(type), m_IntegralValue(integral_value) {} -}; - -namespace Cpp { -using TemplateArgInfo = ::TemplateArgInfo; - -struct DeclRef { - void* data; - DeclRef() : data(nullptr) {} - DeclRef(void* P) : data(P) {} - DeclRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(DeclRef a, DeclRef b) { return a.data == b.data; } - friend bool operator!=(DeclRef a, DeclRef b) { return !(a == b); } -}; - -struct TypeRef { - void* data; - TypeRef() : data(nullptr) {} - TypeRef(void* P) : data(P) {} - TypeRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(TypeRef a, TypeRef b) { return a.data == b.data; } - friend bool operator!=(TypeRef a, TypeRef b) { return !(a == b); } -}; - -struct FuncRef { - void* data; - FuncRef() : data(nullptr) {} - FuncRef(void* P) : data(P) {} - FuncRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(FuncRef a, FuncRef b) { return a.data == b.data; } - friend bool operator!=(FuncRef a, FuncRef b) { return !(a == b); } -}; - -struct ObjectRef { - void* data; - ObjectRef() : data(nullptr) {} - ObjectRef(void* P) : data(P) {} - ObjectRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(ObjectRef a, ObjectRef b) { return a.data == b.data; } - friend bool operator!=(ObjectRef a, ObjectRef b) { return !(a == b); } -}; -} // namespace Cpp - -template <> struct std::hash { - std::size_t operator()(const Cpp::DeclRef& obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::TypeRef& obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::FuncRef& obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::ObjectRef& obj) const { - return std::hash{}(obj.data); - } -}; - -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; - -// direct interpreter access ------------------------------------------------- -CPPJIT_IMPORT -bool Compile(const std::string& code, bool silent = false); -CPPJIT_IMPORT -std::string ToString(TCppScope_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- -CPPJIT_IMPORT -std::string ResolveName(const std::string& cppitem_name); -CPPJIT_IMPORT -TCppType_t ResolveType(TCppType_t cppitem_name); -CPPJIT_IMPORT -TCppType_t ResolveEnumReferenceType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t ResolveEnumPointerType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t GetRealType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t GetPointerType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); -CPPJIT_IMPORT -std::string ResolveEnum(TCppScope_t enum_scope); -CPPJIT_IMPORT -bool IsLValueReferenceType(TCppType_t type); -CPPJIT_IMPORT -bool IsRValueReferenceType(TCppType_t type); -CPPJIT_IMPORT -bool IsClassType(TCppType_t type); -CPPJIT_IMPORT -bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); -CPPJIT_IMPORT -bool IsPointerType(TCppType_t type); -CPPJIT_IMPORT -bool IsFunctionPointerType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t GetType(const std::string& name, bool enable_slow_lookup = false); -CPPJIT_IMPORT -bool AppendTypesSlow(const std::string& name, - std::vector& types, - interop::TCppScope_t parent = nullptr); -CPPJIT_IMPORT -TCppType_t GetComplexType(const std::string& element_type); -CPPJIT_IMPORT -TCppScope_t GetScope(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); -CPPJIT_IMPORT -TCppScope_t GetUnderlyingScope(TCppScope_t scope); -CPPJIT_IMPORT -TCppScope_t GetFullScope(const std::string& scope_name); -CPPJIT_IMPORT -TCppScope_t GetTypeScope(TCppScope_t klass); -CPPJIT_IMPORT -TCppScope_t GetNamed(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); -CPPJIT_IMPORT -TCppScope_t GetParentScope(TCppScope_t scope); -CPPJIT_IMPORT -TCppScope_t GetScopeFromType(TCppType_t type); -CPPJIT_IMPORT -TCppType_t GetTypeFromScope(TCppScope_t klass); -CPPJIT_IMPORT -TCppScope_t GetGlobalScope(); -CPPJIT_IMPORT -TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); -CPPJIT_IMPORT -size_t SizeOf(TCppScope_t klass); -CPPJIT_IMPORT -size_t SizeOfType(TCppType_t type); - -CPPJIT_IMPORT -bool IsBuiltin(const std::string& type_name); - -CPPJIT_IMPORT -bool IsBuiltin(TCppType_t type); - -CPPJIT_IMPORT -bool IsComplete(TCppScope_t type); - -// memory management --------------------------------------------------------- -CPPJIT_IMPORT -TCppObject_t Allocate(TCppScope_t scope); -CPPJIT_IMPORT -void Deallocate(TCppScope_t scope, TCppObject_t instance); -CPPJIT_IMPORT -TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); -CPPJIT_IMPORT -void Destruct(TCppScope_t scope, TCppObject_t instance); - -// method/function dispatching ----------------------------------------------- -CPPJIT_IMPORT -void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, - void* args); -CPPJIT_IMPORT -char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, - void* args); -CPPJIT_IMPORT -float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, - void* args); - -CPPJIT_IMPORT -void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); -CPPJIT_IMPORT -char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, - size_t* length); -CPPJIT_IMPORT -TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, - size_t nargs, void* args); -CPPJIT_IMPORT -void CallDestructor(TCppScope_t type, TCppObject_t self); -CPPJIT_IMPORT -TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, - void* args, TCppType_t result_type); - -CPPJIT_IMPORT -TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, - bool check_enabled = true); - -// handling of function argument buffer -------------------------------------- -CPPJIT_IMPORT -void* AllocateFunctionArgs(size_t nargs); -CPPJIT_IMPORT -void DeallocateFunctionArgs(void* args); -CPPJIT_IMPORT -size_t GetFunctionArgSizeof(); -CPPJIT_IMPORT -size_t GetFunctionArgTypeoffset(); - -// scope reflection information ---------------------------------------------- -CPPJIT_IMPORT -bool IsNamespace(TCppScope_t scope); -CPPJIT_IMPORT -bool IsClass(TCppScope_t scope); -CPPJIT_IMPORT -bool IsTemplate(TCppScope_t scope); -CPPJIT_IMPORT -bool IsTemplateInstantiation(TCppScope_t scope); -CPPJIT_IMPORT -bool IsTypedefed(TCppScope_t scope); -CPPJIT_IMPORT -bool IsAbstract(TCppScope_t scope); -CPPJIT_IMPORT -bool IsEnumScope(TCppScope_t scope); -CPPJIT_IMPORT -bool IsEnumConstant(TCppScope_t scope); -CPPJIT_IMPORT -bool IsEnumType(TCppType_t type); -CPPJIT_IMPORT -bool IsAggregate(TCppScope_t type); -CPPJIT_IMPORT -bool IsDefaultConstructable(TCppScope_t scope); -CPPJIT_IMPORT -bool IsVariable(TCppScope_t scope); - -CPPJIT_IMPORT -void GetAllCppNames(TCppScope_t scope, std::set& cppnames); - -// namespace reflection information ------------------------------------------ -CPPJIT_IMPORT -std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- -CPPJIT_IMPORT -std::string GetFinalName(TCppScope_t type); -CPPJIT_IMPORT -std::string GetScopedFinalName(TCppScope_t type); -CPPJIT_IMPORT -bool HasVirtualDestructor(TCppScope_t type); -CPPJIT_IMPORT -TCppIndex_t GetNumBases(TCppScope_t klass); -CPPJIT_IMPORT -TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); -CPPJIT_IMPORT -std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); -CPPJIT_IMPORT -TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); -CPPJIT_IMPORT -bool IsSubclass(TCppScope_t derived, TCppScope_t base); -CPPJIT_IMPORT -bool IsSmartPtr(TCppScope_t klass); -CPPJIT_IMPORT -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 -CPPJIT_IMPORT -ptrdiff_t GetBaseOffset(TCppScope_t derived, TCppScope_t base, - TCppObject_t address, int direction, - bool rerror = false); - -// method/function reflection information ------------------------------------ -CPPJIT_IMPORT -void GetClassMethods(TCppScope_t scope, std::vector& methods); -CPPJIT_IMPORT -std::vector GetMethodsFromName(TCppScope_t scope, - const std::string& name); -CPPJIT_IMPORT -std::string GetName(TCppScope_t); -CPPJIT_IMPORT -std::string GetFullName(TCppScope_t); -CPPJIT_IMPORT -TCppType_t GetMethodReturnType(TCppMethod_t); -CPPJIT_IMPORT -std::string GetMethodReturnTypeAsString(TCppMethod_t); -CPPJIT_IMPORT -TCppIndex_t GetMethodNumArgs(TCppMethod_t); -CPPJIT_IMPORT -TCppIndex_t GetMethodReqArgs(TCppMethod_t); -CPPJIT_IMPORT -std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); -CPPJIT_IMPORT -TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); -CPPJIT_IMPORT -TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, - const std::string& req_type); -CPPJIT_IMPORT -std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); -CPPJIT_IMPORT -std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, - TCppIndex_t iarg); -CPPJIT_IMPORT -std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); -CPPJIT_IMPORT -std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, - TCppIndex_t max_args = (TCppIndex_t)-1); -// GetMethodPrototype is unused. -CPPJIT_IMPORT -std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); -CPPJIT_IMPORT -std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); -CPPJIT_IMPORT -bool IsConstMethod(TCppMethod_t); -// Templated method/function reflection information -// ------------------------------------ -CPPJIT_IMPORT -void GetTemplatedMethods(TCppScope_t scope, std::vector& methods); -CPPJIT_IMPORT -TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, - bool accept_namespace = false); -CPPJIT_IMPORT -std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); -CPPJIT_IMPORT -bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); -CPPJIT_IMPORT -bool IsTemplatedMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsStaticTemplate(TCppScope_t scope, const std::string& name); -CPPJIT_IMPORT -TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string& name, - const std::string& proto); -CPPJIT_IMPORT -void GetClassOperators(interop::TCppScope_t klass, const std::string& opname, - std::vector& operators); -CPPJIT_IMPORT -TCppMethod_t GetGlobalOperator(TCppScope_t scope, const std::string& lc, - const std::string& rc, const std::string& op); - -// method properties --------------------------------------------------------- -CPPJIT_IMPORT -bool IsDeletedMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsPublicMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsProtectedMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsPrivateMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsConstructor(TCppMethod_t method); -CPPJIT_IMPORT -bool IsDestructor(TCppMethod_t method); -CPPJIT_IMPORT -bool IsStaticMethod(TCppMethod_t method); -CPPJIT_IMPORT -bool IsExplicit(TCppMethod_t method); - -// data member reflection information ---------------------------------------- -CPPJIT_IMPORT -void GetDatamembers(TCppScope_t scope, std::vector& datamembers); -CPPJIT_IMPORT -bool IsLambdaClass(TCppType_t type); -CPPJIT_IMPORT -TCppScope_t WrapLambdaFromVariable(TCppScope_t var); -CPPJIT_IMPORT -TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); -CPPJIT_IMPORT -TCppType_t GetDatamemberType(TCppScope_t data); -CPPJIT_IMPORT -std::string GetDatamemberTypeAsString(TCppScope_t var); -CPPJIT_IMPORT -std::string GetTypeAsString(TCppType_t type); -CPPJIT_IMPORT -intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); -CPPJIT_IMPORT -bool CheckDatamember(TCppScope_t scope, const std::string& name); - -// // data member properties -// ---------------------------------------------------- -CPPJIT_IMPORT -bool IsPublicData(TCppScope_t var); -CPPJIT_IMPORT -bool IsProtectedData(TCppScope_t var); -CPPJIT_IMPORT -bool IsPrivateData(TCppScope_t var); -CPPJIT_IMPORT -bool IsStaticDatamember(TCppScope_t var); -CPPJIT_IMPORT -bool IsConstVar(TCppScope_t var); -CPPJIT_IMPORT -TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); -CPPJIT_IMPORT -std::vector GetDimensions(TCppType_t type); - -// enum properties ----------------------------------------------------------- -CPPJIT_IMPORT -std::vector GetEnumConstants(TCppScope_t scope); -CPPJIT_IMPORT -TCppType_t GetEnumConstantType(TCppScope_t scope); -CPPJIT_IMPORT -TCppIndex_t GetEnumDataValue(TCppScope_t scope); - -CPPJIT_IMPORT -TCppScope_t InstantiateTemplate(TCppScope_t tmpl, Cpp::TemplateArgInfo* args, - size_t args_size); - -CPPJIT_IMPORT -void DumpScope(TCppScope_t scope); -} // namespace cppjit::interop - -#endif // !CPYRT_CPPJIT_H diff --git a/src/interop/callcontext.h b/src/interop/callcontext.h index d0f04f9..5573dba 100644 --- a/src/interop/callcontext.h +++ b/src/interop/callcontext.h @@ -1,11 +1,23 @@ -#ifndef CPYRT_CALLCONTEXT_H -#define CPYRT_CALLCONTEXT_H +#ifndef CPPJIT_INTEROP_CALLCONTEXT_H +#define CPPJIT_INTEROP_CALLCONTEXT_H // Standard -#include +#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 { @@ -31,7 +43,8 @@ struct Parameter { void* fRef; char fTypeCode; }; +#endif // CPYRT_PARAMETER } // namespace cppjit::cpyrt -#endif // !CPYRT_CALLCONTEXT_H +#endif // !CPPJIT_INTEROP_CALLCONTEXT_H diff --git a/src/interop/cpp_cppjit.h b/src/interop/cppjit_interop.h similarity index 96% rename from src/interop/cpp_cppjit.h rename to src/interop/cppjit_interop.h index 5fb4be4..ca7c07e 100644 --- a/src/interop/cpp_cppjit.h +++ b/src/interop/cppjit_interop.h @@ -1,5 +1,5 @@ -#ifndef CPYRT_CPPJIT_H -#define CPYRT_CPPJIT_H +#ifndef CPPJIT_INTEROP_H +#define CPPJIT_INTEROP_H #include #include @@ -36,15 +36,6 @@ typedef unsigned long long PY_ULONG_LONG; typedef long double PY_LONG_DOUBLE; #endif -typedef cppjit::cpyrt::Parameter Parameter; - -// 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; } - namespace cppjit::interop { typedef Cpp::DeclRef TCppScope_t; typedef Cpp::TypeRef TCppType_t; @@ -402,4 +393,4 @@ RPY_EXPORTED void DumpScope(TCppScope_t scope); } // namespace cppjit::interop -#endif // !CPYRT_CPPJIT_H +#endif // !CPPJIT_INTEROP_H diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 0b71747..cce30fa 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -8,11 +8,15 @@ #include "precommondefs.h" // This defines several system feature macros and should be included before any system header. // Bindings -#include "cpp_cppjit.h" +#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 @@ -111,7 +115,7 @@ static InterOpPaths cppinterop_paths() { // The one place libclangCppInterOp is dlopen'd. static bool loadDispatchAPI(const InterOpPaths& Paths) { if (!Cpp::LoadDispatchAPI(Paths.Library.c_str())) { - std::cerr << "[cppjit-backend] Failed to load CppInterOp" << std::endl; + std::cerr << "[cppjit] Failed to load CppInterOp" << std::endl; return false; } return true; @@ -859,8 +863,8 @@ static inline bool WrapperCall(interop::TCppMethod_t method, size_t nargs, InterOpMutex.unlock(); bool runRelease = false; // const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; - if (nargs <= SMALL_ARGS_N) { - void* smallbuf[SMALL_ARGS_N]; + if (nargs <= cpyrt::SMALL_ARGS_N) { + void* smallbuf[cpyrt::SMALL_ARGS_N]; if (nargs) runRelease = copy_args(args, nargs, smallbuf); // CLING_CATCH_UNCAUGHT_ From 986f057d50a9a1c744bfb029dc8919ca0bb7932b Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sat, 29 Aug 2026 14:54:36 +0200 Subject: [PATCH 5/5] Support consuming an external CppInterOp via CppInterOp_DIR Providing CppInterOp_DIR selects external mode: cppjit consumes that CppInterOp install through find_package(CppInterOp) instead of building one with ExternalProject and bundling it into the wheel. In this mode nothing is bundled: the library and include paths from the package config are baked into the wrapper as absolute paths, which works because cppinterop_paths() joins with std::filesystem's operator/, where an absolute right-hand side replaces the anchor. The clang major comes from CPPINTEROP_LLVM_VERSION_MAJOR in the config, so no LLVM is needed to build the wrapper itself; the LLVM discovery and version gate only run in the default bundled mode, since compatibility was already enforced when the external CppInterOp was built. The mode is keyed to the explicitly provided CppInterOp_DIR variable, not to find_package succeeding through ambient search paths: otherwise a pip install inside e.g. a conda environment that happens to carry CppInterOp would silently produce a wheel that does not bundle its interpreter, making the wheel's composition depend on what the build environment has lying around. An external CppInterOp carries no clang builtin headers, and clang's compiled-in resource dir default resolves relative to the library location, so CPPJIT_CLANG_RESOURCE_DIR should point at the resource dir matching the CppInterOp's clang. It is optional: when unset, CMake warns and the wrapper falls back to the existing runtime DetectResourceDir("clang-") probe, which works wherever a versioned clang is on PATH (e.g. conda environments). Distributions where it is not (e.g. NixOS) pass the resource dir explicitly. The config file's CPPINTEROP_INSTALL_PREFIX is captured immediately after find_package, before the site-packages staging logic reuses that variable name. Intended for distribution packaging (e.g. Nix), where CppInterOp is a separate package and duplicating its build in every consumer is wasted work (e.g. for the matrix of different LLVM and Python versions supported by Nix). --- CMakeLists.txt | 256 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 168 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50403dc..20615a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,25 @@ set(CPPINTEROP_GIT_REPOSITORY "https://github.com/compiler-research/CppInterOp.g set(CPPINTEROP_GIT_TAG "8d624c621a4b95e36ff73ac708c85a768287478f" CACHE STRING "") set(CPPINTEROP_SOURCE_DIR "" CACHE PATH "Override default CppInterOp built by ExternalProject_Add, with a path to local CppInterOp source") +# Providing CppInterOp_DIR selects external mode: cppjit consumes that +# CppInterOp via find_package instead of building and bundling one, baking +# its absolute paths into the wrapper. The search is keyed to the explicit +# variable rather than the default search paths: find_package succeeding +# here decides whether the wheel bundles its interpreter at all, and that +# must not silently depend on the build environment (e.g. a conda env that +# happens to carry CppInterOp). Like LLVM_DIR below, an explicit dir is +# authoritative: a failed find_package resets CppInterOp_DIR to -NOTFOUND, +# so keep the requested value for the message and fail instead of falling +# back to the bundled build. +if(CppInterOp_DIR) + set(_cppinterop_dir_arg "${CppInterOp_DIR}") + find_package(CppInterOp CONFIG PATHS "${CppInterOp_DIR}" NO_DEFAULT_PATH) + if(NOT CppInterOp_FOUND) + message(FATAL_ERROR + "No CppInterOpConfig.cmake under CppInterOp_DIR (${_cppinterop_dir_arg}); " + "expected /lib/cmake/CppInterOp") + endif() +endif() # The full Development component requires libpython, which manylinux # images do not ship and extension modules do not need. @@ -28,65 +47,89 @@ 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) +if(CppInterOp_FOUND) + # The package config records the absolute library/include locations and + # the LLVM version the library embeds, so no LLVM is needed to build the + # wrapper itself; version compatibility was already enforced when that + # CppInterOp was built. + # The config computes CPPINTEROP_INSTALL_PREFIX from its own location; + # capture it before the staging logic below reuses that variable name. + set(CPPJIT_EXTERNAL_CPPINTEROP_PREFIX "${CPPINTEROP_INSTALL_PREFIX}") + set(LLVM_VERSION_MAJOR "${CPPINTEROP_LLVM_VERSION_MAJOR}") + message(STATUS "Using external CppInterOp at ${CPPJIT_EXTERNAL_CPPINTEROP_PREFIX} " + "(LLVM ${CPPINTEROP_LLVM_VERSION})") + # An external CppInterOp bundles no builtin headers, and clang's + # compiled-in default resolves relative to the library location, so the + # resource dir should be given explicitly; without it the wrapper falls + # back to probing PATH for a versioned clang at runtime, which works in + # e.g. conda environments but not everywhere. + if(NOT CPPJIT_CLANG_RESOURCE_DIR) + message(WARNING + "CPPJIT_CLANG_RESOURCE_DIR not set: the interpreter will probe " + "PATH for clang-${LLVM_VERSION_MAJOR} at runtime to locate the " + "builtin headers") + endif() +else() + # The LLVM range the pinned CppInterOp supports; update together with the tag. + set(CPPJIT_LLVM_VERSION_MIN 20) + set(CPPJIT_LLVM_VERSION_MAX 22) -set(_llvm_hints "") + set(_llvm_hints "") -if(DEFINED ENV{CONDA_PREFIX}) - list(APPEND _llvm_hints "$ENV{CONDA_PREFIX}/lib/cmake/llvm") -endif() + if(DEFINED ENV{CONDA_PREFIX}) + list(APPEND _llvm_hints "$ENV{CONDA_PREFIX}/lib/cmake/llvm") + endif() -if(DEFINED 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") + if(DEFINED 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() -else() - find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) - if(NOT LLVM_FOUND) + + 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 - "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=...)") + "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() -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(DEFINED 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") + if(DEFINED 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() -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() # CppInterOp is installed at the location cppjit ships at runtime: ask for the @@ -103,9 +146,13 @@ else() endif() set(CPPINTEROP_INSTALL_DIR "${CPPINTEROP_INSTALL_PREFIX}/cppjit/interop") -# Include cmake for CppInterOp config and build using ExternalProject. -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) -cppjit_add_cppinterop() +if(CppInterOp_FOUND) + set(CPPINTEROP_INSTALL_DIR "${CPPJIT_EXTERNAL_CPPINTEROP_PREFIX}") +else() + # Include cmake for CppInterOp config and build using ExternalProject. + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) + cppjit_add_cppinterop() +endif() # this libcppjit.so merges both cpyrt and the interop wrapper file(GLOB CPYRT_SOURCES CONFIGURE_DEPENDS src/cpyrt/*.cxx) @@ -115,18 +162,55 @@ set(INTEROP_SOURCES ) add_library(cppjit SHARED ${CPYRT_SOURCES} ${INTEROP_SOURCES}) -add_dependencies(cppjit CppInterOp) - -# The wrapper anchors these relative spellings 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}/cppjit" - CPPINTEROP_LIBRARY="interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" - CPPINTEROP_INCLUDE_DIR="interop/include" - CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" - CPPJIT_CLANG_INCLUDE_DIR="interop/lib/clang/${LLVM_VERSION_MAJOR}" -) +if(NOT CppInterOp_FOUND) + add_dependencies(cppjit CppInterOp) +endif() + +# The clang resource dir whose builtin headers the interpreter uses. In the +# default bundled mode it must exist (its include/ ships in the wheel); in +# external mode it is optional, falling back to the runtime probe. +set(CPPJIT_CLANG_RESOURCE_DIR "" CACHE PATH + "clang resource directory whose builtin headers the interpreter uses") +if(NOT CPPJIT_CLANG_RESOURCE_DIR AND NOT CppInterOp_FOUND) + set(CPPJIT_CLANG_RESOURCE_DIR "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") +endif() +if(CPPJIT_CLANG_RESOURCE_DIR AND NOT EXISTS "${CPPJIT_CLANG_RESOURCE_DIR}/include") + message(FATAL_ERROR + "No builtin headers at ${CPPJIT_CLANG_RESOURCE_DIR}/include") +endif() + +if(CppInterOp_FOUND) + if(CPPJIT_CLANG_RESOURCE_DIR) + set(_cppjit_clang_include_dir "${CPPJIT_CLANG_RESOURCE_DIR}") + else() + # Nothing is bundled and no resource dir was given: use the bundled + # relative spelling, which resolves nowhere, so the wrapper falls + # through to the runtime resource-dir probe. + set(_cppjit_clang_include_dir "interop/lib/clang/${LLVM_VERSION_MAJOR}") + endif() + # Absolute spellings from the CppInterOp package config: + # cppinterop_paths() joins with std::filesystem's /, where an absolute + # right-hand side replaces the anchor, so the external install and the + # build clang's resource dir are used in place. + target_compile_definitions(cppjit PRIVATE + CPPINTEROP_INSTALL_PREFIX="${CPPJIT_EXTERNAL_CPPINTEROP_PREFIX}" + CPPINTEROP_LIBRARY="${CPPINTEROP_LIBRARIES}" + CPPINTEROP_INCLUDE_DIR="${CPPINTEROP_INCLUDE_DIRS}" + CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" + CPPJIT_CLANG_INCLUDE_DIR="${_cppjit_clang_include_dir}" + ) +else() + # The wrapper anchors these relative spellings 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}/cppjit" + CPPINTEROP_LIBRARY="interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + CPPINTEROP_INCLUDE_DIR="interop/include" + CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" + CPPJIT_CLANG_INCLUDE_DIR="interop/lib/clang/${LLVM_VERSION_MAJOR}" + ) +endif() target_include_directories(cppjit PRIVATE # src/ itself resolves the public "cpyrt/*.h" spellings against the @@ -164,29 +248,25 @@ install(TARGETS cppjit LIBRARY DESTINATION cppjit ) -# install CppInterOp libraries and headers -install(CODE " - file(GLOB _interop_libs \"${CPPINTEROP_INSTALL_DIR}/lib/libclangCppInterOp*\") - foreach(_lib \${_interop_libs}) - file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/lib) - endforeach() -") - -install(CODE " - file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/include) -") - -# ship the builtin headers of the build clang, laid out as a headers-only -# resource dir: only include/ ships -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") +# With an external CppInterOp the wrapper references it and the clang resource +# dir at their absolute locations, so nothing needs to be bundled. +if(NOT CppInterOp_FOUND) + # install CppInterOp libraries and headers + install(CODE " + file(GLOB _interop_libs \"${CPPINTEROP_INSTALL_DIR}/lib/libclangCppInterOp*\") + foreach(_lib \${_interop_libs}) + file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/lib) + endforeach() + ") + + install(CODE " + file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/interop/include) + ") + + install(DIRECTORY "${CPPJIT_CLANG_RESOURCE_DIR}/include/" + DESTINATION "cppjit/interop/lib/clang/${LLVM_VERSION_MAJOR}/include" + ) endif() -install(DIRECTORY "${_clang_resource_dir}/include/" - DESTINATION "cppjit/interop/lib/clang/${LLVM_VERSION_MAJOR}/include" -) # the public cpyrt API headers keep their installed cpyrt/ prefix install(FILES