From 3549bb9a41bc370fd2093a35b227784b01a481ec Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 1 Apr 2026 14:19:04 +0000 Subject: [PATCH 1/2] Fix promoter function signatures and ufunc refcount leaks - Fix quad_ufunc_promoter, quad_ldexp_promoter, and comparison_ufunc_promoter signatures to match PyArrayMethod_PromoterFunction typedef: (PyObject *, PyArray_DTypeMeta *const[], PyArray_DTypeMeta *const[], PyArray_DTypeMeta *[]) - Add missing Py_DECREF(ufunc) in all create_quad_*_ufunc error/success return paths (binary_ops, comparison_ops, unary_ops, unary_props) - Add missing Py_INCREF before Py_XSETREF in comparison_ufunc_promoter --- src/csrc/umath/binary_ops.cpp | 19 +++++++++++++++++++ src/csrc/umath/comparison_ops.cpp | 15 ++++++++++++--- src/csrc/umath/unary_ops.cpp | 8 ++++++++ src/csrc/umath/unary_props.cpp | 2 ++ src/include/umath/promoters.hpp | 9 +++++---- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/csrc/umath/binary_ops.cpp b/src/csrc/umath/binary_ops.cpp index 7255d00..b92bbde 100644 --- a/src/csrc/umath/binary_ops.cpp +++ b/src/csrc/umath/binary_ops.cpp @@ -428,28 +428,33 @@ create_quad_ldexp_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } PyObject *promoter_capsule = PyCapsule_New((void *)&quad_ldexp_promoter, "numpy._ufunc_promoter", NULL); if (promoter_capsule == NULL) { + Py_DECREF(ufunc); return -1; } PyObject *DTypes = PyTuple_Pack(3, &QuadPrecDType, &PyArrayDescr_Type, &PyArrayDescr_Type); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return 0; } @@ -485,12 +490,14 @@ create_quad_binary_2out_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } PyObject *promoter_capsule = PyCapsule_New((void *)&quad_ufunc_promoter, "numpy._ufunc_promoter", NULL); if (promoter_capsule == NULL) { + Py_DECREF(ufunc); return -1; } @@ -499,12 +506,14 @@ create_quad_binary_2out_ufunc(PyObject *numpy, const char *ufunc_name) &PyArrayDescr_Type, &PyArrayDescr_Type); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(DTypes); @@ -514,16 +523,19 @@ create_quad_binary_2out_ufunc(PyObject *numpy, const char *ufunc_name) &PyArrayDescr_Type, &PyArrayDescr_Type); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return 0; } @@ -557,12 +569,14 @@ create_quad_binary_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } PyObject *promoter_capsule = PyCapsule_New((void *)&quad_ufunc_promoter, "numpy._ufunc_promoter", NULL); if (promoter_capsule == NULL) { + Py_DECREF(ufunc); return -1; } @@ -570,12 +584,14 @@ create_quad_binary_ufunc(PyObject *numpy, const char *ufunc_name) PyObject *DTypes = PyTuple_Pack(3, &QuadPrecDType, &PyArrayDescr_Type, &PyArrayDescr_Type); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(DTypes); @@ -584,16 +600,19 @@ create_quad_binary_ufunc(PyObject *numpy, const char *ufunc_name) DTypes = PyTuple_Pack(3, &PyArrayDescr_Type, &QuadPrecDType, &PyArrayDescr_Type); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return 0; } diff --git a/src/csrc/umath/comparison_ops.cpp b/src/csrc/umath/comparison_ops.cpp index a9bc6d7..dcee2e5 100644 --- a/src/csrc/umath/comparison_ops.cpp +++ b/src/csrc/umath/comparison_ops.cpp @@ -257,16 +257,17 @@ quad_reduce_comp_strided_loop_unaligned(PyArrayMethod_Context *context, char *co NPY_NO_EXPORT int -comparison_ufunc_promoter(PyUFuncObject *ufunc, PyArray_DTypeMeta *op_dtypes[], - PyArray_DTypeMeta *signature[], PyArray_DTypeMeta *new_op_dtypes[]) +comparison_ufunc_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], + PyArray_DTypeMeta *const signature[], PyArray_DTypeMeta *new_op_dtypes[]) { PyArray_DTypeMeta *new_signature[NPY_MAXARGS]; memcpy(new_signature, signature, 3 * sizeof(PyArray_DTypeMeta *)); new_signature[2] = NULL; - int res = quad_ufunc_promoter(ufunc, op_dtypes, new_signature, new_op_dtypes); + int res = quad_ufunc_promoter(ufunc_obj, op_dtypes, new_signature, new_op_dtypes); if (res < 0) { return -1; } + Py_INCREF(&PyArray_BoolDType); Py_XSETREF(new_op_dtypes[2], &PyArray_BoolDType); return 0; } @@ -301,6 +302,7 @@ create_quad_comparison_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } @@ -326,12 +328,14 @@ create_quad_comparison_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec_reduce) < 0) { + Py_DECREF(ufunc); return -1; } PyObject *promoter_capsule = PyCapsule_New((void *)&comparison_ufunc_promoter, "numpy._ufunc_promoter", NULL); if (promoter_capsule == NULL) { + Py_DECREF(ufunc); return -1; } @@ -339,12 +343,14 @@ create_quad_comparison_ufunc(PyObject *numpy, const char *ufunc_name) PyObject *DTypes = PyTuple_Pack(3, &QuadPrecDType, &PyArrayDescr_Type, &PyArray_BoolDType); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(DTypes); @@ -353,16 +359,19 @@ create_quad_comparison_ufunc(PyObject *numpy, const char *ufunc_name) DTypes = PyTuple_Pack(3, &PyArrayDescr_Type, &QuadPrecDType, &PyArray_BoolDType); if (DTypes == 0) { Py_DECREF(promoter_capsule); + Py_DECREF(ufunc); return -1; } if (PyUFunc_AddPromoter(ufunc, DTypes, promoter_capsule) < 0) { Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return -1; } Py_DECREF(promoter_capsule); Py_DECREF(DTypes); + Py_DECREF(ufunc); return 0; } diff --git a/src/csrc/umath/unary_ops.cpp b/src/csrc/umath/unary_ops.cpp index 2891176..cbfc062 100644 --- a/src/csrc/umath/unary_ops.cpp +++ b/src/csrc/umath/unary_ops.cpp @@ -138,9 +138,11 @@ create_quad_unary_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } + Py_DECREF(ufunc); return 0; } @@ -261,9 +263,11 @@ create_quad_logical_not_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } + Py_DECREF(ufunc); return 0; } @@ -402,9 +406,11 @@ create_quad_unary_2out_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } + Py_DECREF(ufunc); return 0; } @@ -561,9 +567,11 @@ create_quad_frexp_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } + Py_DECREF(ufunc); return 0; } diff --git a/src/csrc/umath/unary_props.cpp b/src/csrc/umath/unary_props.cpp index abb578e..5f06039 100644 --- a/src/csrc/umath/unary_props.cpp +++ b/src/csrc/umath/unary_props.cpp @@ -127,9 +127,11 @@ create_quad_unary_prop_ufunc(PyObject *numpy, const char *ufunc_name) }; if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + Py_DECREF(ufunc); return -1; } + Py_DECREF(ufunc); return 0; } diff --git a/src/include/umath/promoters.hpp b/src/include/umath/promoters.hpp index 576ef20..168e5d9 100644 --- a/src/include/umath/promoters.hpp +++ b/src/include/umath/promoters.hpp @@ -12,9 +12,10 @@ #include "../dtype.h" inline int -quad_ufunc_promoter(PyUFuncObject *ufunc, PyArray_DTypeMeta *op_dtypes[], - PyArray_DTypeMeta *signature[], PyArray_DTypeMeta *new_op_dtypes[]) +quad_ufunc_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], + PyArray_DTypeMeta *const signature[], PyArray_DTypeMeta *new_op_dtypes[]) { + PyUFuncObject *ufunc = (PyUFuncObject *)ufunc_obj; int nin = ufunc->nin; int nargs = ufunc->nargs; PyArray_DTypeMeta *common = NULL; @@ -88,8 +89,8 @@ quad_ufunc_promoter(PyUFuncObject *ufunc, PyArray_DTypeMeta *op_dtypes[], inline int -quad_ldexp_promoter(PyUFuncObject *ufunc, PyArray_DTypeMeta *op_dtypes[], - PyArray_DTypeMeta *signature[], PyArray_DTypeMeta *new_op_dtypes[]) +quad_ldexp_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], + PyArray_DTypeMeta *const signature[], PyArray_DTypeMeta *new_op_dtypes[]) { Py_INCREF(&QuadPrecDType); new_op_dtypes[0] = &QuadPrecDType; From d1b3c8657dabe58eebded7b92f77d60c4dd63248 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 1 Apr 2026 14:21:09 +0000 Subject: [PATCH 2/2] Simplify promoter logic: remove dead code and fix comparison reduction - quad_ufunc_promoter: Remove dead has_quad/common/PyArray_PromoteDTypeSequence machinery. Since this promoter is only registered for patterns where at least one input is QuadPrecDType, we always promote to QuadPrecDType directly. The old fallback paths were unreachable and had a refcount bug (Py_XDECREF on a borrowed pointer when has_quad was true). - comparison_ufunc_promoter: Rewrite with self-contained logic instead of delegating to quad_ufunc_promoter. Fixes reduction path which incorrectly set accumulator to QuadPrecDType instead of BoolDType (the registered reduce loop expects Bool, Quad, Bool). --- src/csrc/umath/comparison_ops.cpp | 30 +++++++++++++---- src/include/umath/promoters.hpp | 53 +++---------------------------- 2 files changed, 28 insertions(+), 55 deletions(-) diff --git a/src/csrc/umath/comparison_ops.cpp b/src/csrc/umath/comparison_ops.cpp index dcee2e5..9924bd3 100644 --- a/src/csrc/umath/comparison_ops.cpp +++ b/src/csrc/umath/comparison_ops.cpp @@ -260,15 +260,31 @@ NPY_NO_EXPORT int comparison_ufunc_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], PyArray_DTypeMeta *const signature[], PyArray_DTypeMeta *new_op_dtypes[]) { - PyArray_DTypeMeta *new_signature[NPY_MAXARGS]; - memcpy(new_signature, signature, 3 * sizeof(PyArray_DTypeMeta *)); - new_signature[2] = NULL; - int res = quad_ufunc_promoter(ufunc_obj, op_dtypes, new_signature, new_op_dtypes); - if (res < 0) { - return -1; + // Reduction: accumulator is Bool, element is QuadPrecDType, output is Bool + if (op_dtypes[0] == NULL) { + Py_INCREF(&PyArray_BoolDType); + new_op_dtypes[0] = &PyArray_BoolDType; + Py_INCREF(op_dtypes[1]); + new_op_dtypes[1] = op_dtypes[1]; + Py_INCREF(&PyArray_BoolDType); + new_op_dtypes[2] = &PyArray_BoolDType; + return 0; + } + + // Normal path: promote both inputs to QuadPrecDType, output is Bool + for (int i = 0; i < 2; i++) { + if (signature[i]) { + Py_INCREF(signature[i]); + new_op_dtypes[i] = signature[i]; + } + else { + Py_INCREF(&QuadPrecDType); + new_op_dtypes[i] = &QuadPrecDType; + } } + Py_INCREF(&PyArray_BoolDType); - Py_XSETREF(new_op_dtypes[2], &PyArray_BoolDType); + new_op_dtypes[2] = &PyArray_BoolDType; return 0; } diff --git a/src/include/umath/promoters.hpp b/src/include/umath/promoters.hpp index 168e5d9..d9545bc 100644 --- a/src/include/umath/promoters.hpp +++ b/src/include/umath/promoters.hpp @@ -16,14 +16,11 @@ quad_ufunc_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], PyArray_DTypeMeta *const signature[], PyArray_DTypeMeta *new_op_dtypes[]) { PyUFuncObject *ufunc = (PyUFuncObject *)ufunc_obj; - int nin = ufunc->nin; int nargs = ufunc->nargs; - PyArray_DTypeMeta *common = NULL; - bool has_quad = false; // Handle the special case for reductions if (op_dtypes[0] == NULL) { - assert(nin == 2 && ufunc->nout == 1); /* must be reduction */ + assert(ufunc->nin == 2 && ufunc->nout == 1); /* must be reduction */ for (int i = 0; i < 3; i++) { Py_INCREF(op_dtypes[1]); new_op_dtypes[i] = op_dtypes[1]; @@ -31,59 +28,19 @@ quad_ufunc_promoter(PyObject *ufunc_obj, PyArray_DTypeMeta *const op_dtypes[], return 0; } - // Check if any input or signature is QuadPrecision - for (int i = 0; i < nin; i++) { - if (op_dtypes[i] == &QuadPrecDType) { - has_quad = true; - } - } - - if (has_quad) { - common = &QuadPrecDType; - } - else { - for (int i = nin; i < nargs; i++) { - if (signature[i] != NULL) { - if (common == NULL) { - Py_INCREF(signature[i]); - common = signature[i]; - } - else if (common != signature[i]) { - Py_CLEAR(common); // Not homogeneous, unset common - break; - } - } - } - } - // If no common output dtype, use standard promotion for inputs - if (common == NULL) { - common = PyArray_PromoteDTypeSequence(nin, op_dtypes); - if (common == NULL) { - if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_Clear(); // Do not propagate normal promotion errors - } - - return -1; - } - } - - // Set all new_op_dtypes to the common dtype + // This promoter is only registered for patterns where at least one + // input is QuadPrecDType, so we always promote all args to QuadPrecDType. for (int i = 0; i < nargs; i++) { if (signature[i]) { - // If signature is specified for this argument, use it Py_INCREF(signature[i]); new_op_dtypes[i] = signature[i]; } else { - // Otherwise, use the common dtype - Py_INCREF(common); - - new_op_dtypes[i] = common; + Py_INCREF(&QuadPrecDType); + new_op_dtypes[i] = &QuadPrecDType; } } - Py_XDECREF(common); - return 0; }