Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2cdcc73
[cppinterop] Add `IsPackExpansionType`, `IsOperator` and `IsConversio…
Vipul-Cariappa Oct 13, 2026
ec249a7
[cppinterop] Rework `BestOverloadFunctionMatch` for methods, operator…
Vipul-Cariappa Oct 13, 2026
7831d60
[cppinterop] Refactor according to code review
Vipul-Cariappa Oct 14, 2026
3890823
[cppjit] [cpyrt] Generate the PyStrings cache from a single X-macro l…
guitargeek Oct 7, 2026
56e184f
[cppjit] [cpyrt] Remove RegisterConverterAlias() and RegisterExecutor…
guitargeek Oct 9, 2026
0b24f98
[cppjit] Check for smart pointers by class instead of by name (compil…
guitargeek Oct 9, 2026
a41f663
[cppjit] [cpyrt] Fix leaked container reference in vector_iter (compi…
guitargeek Oct 9, 2026
7158e0b
[cppjit] Adopt Clang-driven overload resolution from CppInterOp
Vipul-Cariappa Aug 27, 2026
db4e494
[cppjit] Fix crashes and overload regressions on new overload resolution
aaronj0 Sep 2, 2026
d45e4a1
[cppjit] Update tests for new overload resolution behaviour
aaronj0 Sep 2, 2026
4fadd28
[cppjit] minor refactors
Vipul-Cariappa Oct 13, 2026
b69c600
[cppjit] Implement `IsEquivalentTypes` to check if two types are equi…
Vipul-Cariappa Oct 13, 2026
6c0cdfa
[cppjit] `BestOverloadFunctionMatch` loop optimization
Vipul-Cariappa Oct 13, 2026
edfff1c
[cppjit] fix cases where `ambiguous_candidates` have duplicate entries
Vipul-Cariappa Oct 13, 2026
c53429c
[cppjit] Remove dead code
Vipul-Cariappa Oct 13, 2026
d508a1a
[cppjit] fix compiler-research/cppjit#151
Vipul-Cariappa Oct 14, 2026
142dbc5
[cppinterop] Pass ambiguous_candidates in the TemplatedOperatorArrow …
Vipul-Cariappa Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bindings/pyroot/cppjit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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)
# Temporary pin to the overload-resolution development branch so CI builds
# and tests it. Returns to a pinned commit once that branch lands.
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
Expand Down
4 changes: 4 additions & 0 deletions bindings/pyroot/cppjit/python/cppjit/_cpython_cppjit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"_end_capture_stderr",
"_lock_interpreter",
"_unlock_interpreter",
"OverloadResolutionException",
"OverloadAmbiguityException",
]


Expand Down Expand Up @@ -207,6 +209,8 @@ def _add_default_paths():
bind_object = _backend.bind_object
nullptr = _backend.nullptr
default = _backend.default
OverloadResolutionException = _backend.OverloadResolutionException
OverloadAmbiguityException = _backend.OverloadAmbiguityException


def load_reflection_info(name):
Expand Down
8 changes: 0 additions & 8 deletions bindings/pyroot/cppjit/src/cpyrt/API.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@ 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);

Expand Down Expand Up @@ -176,10 +172,6 @@ CPYRT_EXTERN void DestroyConverter(Converter* p);
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);

Expand Down
11 changes: 0 additions & 11 deletions bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -382,17 +382,6 @@ void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope,
fDescription = PyUnicode_FromString(name.c_str());
}

//-----------------------------------------------------------------------------
void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope,
const std::string& name, void* address) {
fEnclosingScope = scope;
fDescription = PyUnicode_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
Expand Down
11 changes: 0 additions & 11 deletions bindings/pyroot/cppjit/src/cpyrt/CPPDataMember.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ 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 */);
Expand Down Expand Up @@ -57,16 +56,6 @@ inline CPPDataMember* CPPDataMember_New(interop::TCppScope_t scope,
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
3 changes: 3 additions & 0 deletions bindings/pyroot/cppjit/src/cpyrt/CPPExcInstance.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef CPYRT_CPPEXCINSTANCE_H
#define CPYRT_CPPEXCINSTANCE_H

#include "Python.h"
#include "cpyrt/CommonDefs.h"

//////////////////////////////////////////////////////////////////////////////
// //
// cppjit::cpyrt::CPPExceptionInstance //
Expand Down
54 changes: 6 additions & 48 deletions bindings/pyroot/cppjit/src/cpyrt/CPPInstance.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -42,51 +42,6 @@ extern PyObject* gNullPtrObject;
// 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;
Expand Down Expand Up @@ -549,7 +504,8 @@ static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self,
const char* cppop = op == Py_EQ ? "==" : "!=";
PyCallable* pyfunc = FindBinaryOperator(self, obj, cppop);
if (pyfunc)
binop = (PyObject*)CPPOverload_New(cppop, pyfunc);
binop = (PyObject*)CPPOverload_New(
cppop, interop::GetParentScope(pyfunc->GetMethod().data), pyfunc);
else {
Py_INCREF(Py_None);
binop = Py_None;
Expand Down Expand Up @@ -613,7 +569,8 @@ static inline void* cast_actual(void* obj) {
PyCallable* pyfunc = \
Utility::FindBinaryOperator((PyObject*)self, other, #op); \
if (pyfunc) \
ometh = (PyObject*)CPPOverload_New(#label, pyfunc); \
ometh = (PyObject*)CPPOverload_New( \
#label, interop::GetParentScope(pyfunc->GetMethod().data), pyfunc); \
} \
meth = ometh;

Expand Down Expand Up @@ -1005,7 +962,8 @@ static PyGetSetDef op_getset[] = {
PyErr_Clear(); \
PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \
if (pyfunc) \
meth = (PyObject*)CPPOverload_New(#name, pyfunc); \
meth = (PyObject*)CPPOverload_New( \
#name, interop::GetParentScope(pyfunc->GetMethod().data), pyfunc); \
else { \
PyErr_SetString(PyExc_NotImplementedError, ""); \
return nullptr; \
Expand Down
47 changes: 47 additions & 0 deletions bindings/pyroot/cppjit/src/cpyrt/CPPInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include "CommonDefs.h"
#include "cppjit_interop.h"

#include "cpyrt/DispatchPtr.h"

// Standard
#include <utility>
#include <vector>
Expand Down Expand Up @@ -146,4 +148,49 @@ void op_dealloc_nofree(CPPInstance*);

} // namespace cppjit::cpyrt

//- 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
cppjit::cpyrt::CI_DatamemberCache_t fDatamemberCache;

// for smart pointer types
cppjit::cpyrt::CPPSmartClass* fSmartClass;

// for back-referencing from Python-derived instances
cppjit::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

#endif // !CPYRT_CPPINSTANCE_H
138 changes: 0 additions & 138 deletions bindings/pyroot/cppjit/src/cpyrt/CPPMethod.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -446,144 +446,6 @@ PyObject* cpyrt::CPPMethod::Reflex(interop::Reflex::RequestId_t request,
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<int>(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);
Expand Down
Loading
Loading