Skip to content

Commit 2c1a34d

Browse files
authored
Merge pull request numpy#19571 from seberg/ufunc-refactor-2021-normal
MAINT: Refactor UFunc core to use NEP 43 style dispatching
2 parents e12112e + c6fddde commit 2c1a34d

File tree

15 files changed

+1604
-354
lines changed

15 files changed

+1604
-354
lines changed

doc/source/reference/c-api/types-and-structures.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,7 @@ PyUFunc_Type and PyUFuncObject
818818
npy_intp *core_dim_sizes;
819819
npy_uint32 *core_dim_flags;
820820
PyObject *identity_value;
821+
/* Further private slots (size depends on the NumPy version) */
821822
} PyUFuncObject;
822823
823824
.. c:macro: PyObject_HEAD
@@ -957,9 +958,12 @@ PyUFunc_Type and PyUFuncObject
957958
958959
.. c:member:: PyUFunc_LegacyInnerLoopSelectionFunc *legacy_inner_loop_selector
959960
960-
A function which returns an inner loop. The ``legacy`` in the name arises
961-
because for NumPy 1.6 a better variant had been planned. This variant
962-
has not yet come about.
961+
.. deprecated:: 1.22
962+
963+
Some fallback support for this slot exists, but will be removed
964+
eventually. A univiersal function which relied on this will have
965+
eventually have to be ported.
966+
See ref:`NEP 41 <NEP41>` and ref:`NEP 43 <NEP43>`
963967
964968
.. c:member:: void *reserved2
965969

numpy/core/include/numpy/ufuncobject.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ typedef struct _tagPyUFuncObject {
211211
/* Identity for reduction, when identity == PyUFunc_IdentityValue */
212212
PyObject *identity_value;
213213

214+
/* New in NPY_API_VERSION 0x0000000F and above */
215+
216+
/* New private fields related to dispatching */
217+
void *_dispatch_cache;
218+
/* A PyListObject of `(tuple of DTypes, ArrayMethod/Promoter)` */
219+
PyObject *_loops;
214220
} PyUFuncObject;
215221

216222
#include "arrayobject.h"

numpy/core/setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,8 @@ def generate_umath_c(ext, build_dir):
928928
join('src', 'umath', 'matmul.c.src'),
929929
join('src', 'umath', 'clip.h.src'),
930930
join('src', 'umath', 'clip.c.src'),
931+
join('src', 'umath', 'dispatching.c'),
932+
join('src', 'umath', 'legacy_array_method.c'),
931933
join('src', 'umath', 'ufunc_object.c'),
932934
join('src', 'umath', 'extobj.c'),
933935
join('src', 'umath', 'scalarmath.c.src'),

numpy/core/src/multiarray/array_method.c

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,6 @@ boundarraymethod__simple_strided_call(
757757

758758

759759
/*
760-
* TODO: Currently still based on the old ufunc system and not ArrayMethod!
761-
* This requires fixing the ufunc code first.
762-
*
763760
* Support for masked inner-strided loops. Masked inner-strided loops are
764761
* only used in the ufunc machinery. So this special cases them.
765762
* In the future it probably makes sense to create an::
@@ -770,8 +767,8 @@ boundarraymethod__simple_strided_call(
770767
*/
771768
typedef struct {
772769
NpyAuxData base;
773-
PyUFuncGenericFunction unmasked_stridedloop;
774-
void *innerloopdata;
770+
PyArrayMethod_StridedLoop *unmasked_stridedloop;
771+
NpyAuxData *unmasked_auxdata;
775772
int nargs;
776773
char *dataptrs[];
777774
} _masked_stridedloop_data;
@@ -781,6 +778,7 @@ static void
781778
_masked_stridedloop_data_free(NpyAuxData *auxdata)
782779
{
783780
_masked_stridedloop_data *data = (_masked_stridedloop_data *)auxdata;
781+
NPY_AUXDATA_FREE(data->unmasked_auxdata);
784782
PyMem_Free(data);
785783
}
786784

@@ -790,15 +788,15 @@ _masked_stridedloop_data_free(NpyAuxData *auxdata)
790788
* masked strided-loop, only calling the function for elements
791789
* where the mask is True.
792790
*/
793-
static void
794-
unmasked_ufunc_loop_as_masked(
795-
char **data, const npy_intp *dimensions,
796-
const npy_intp *strides, void *_auxdata)
791+
static int
792+
generic_masked_strided_loop(PyArrayMethod_Context *context,
793+
char *const *data, const npy_intp *dimensions,
794+
const npy_intp *strides, NpyAuxData *_auxdata)
797795
{
798796
_masked_stridedloop_data *auxdata = (_masked_stridedloop_data *)_auxdata;
799797
int nargs = auxdata->nargs;
800-
PyUFuncGenericFunction strided_loop = auxdata->unmasked_stridedloop;
801-
void *innerloopdata = auxdata->innerloopdata;
798+
PyArrayMethod_StridedLoop *strided_loop = auxdata->unmasked_stridedloop;
799+
NpyAuxData *strided_loop_auxdata = auxdata->unmasked_auxdata;
802800

803801
char **dataptrs = auxdata->dataptrs;
804802
memcpy(dataptrs, data, nargs * sizeof(char *));
@@ -819,39 +817,37 @@ unmasked_ufunc_loop_as_masked(
819817

820818
/* Process unmasked values */
821819
mask = npy_memchr(mask, 0, mask_stride, N, &subloopsize, 0);
822-
strided_loop(dataptrs, &subloopsize, strides, innerloopdata);
820+
int res = strided_loop(context,
821+
dataptrs, &subloopsize, strides, strided_loop_auxdata);
822+
if (res != 0) {
823+
return res;
824+
}
823825
for (int i = 0; i < nargs; i++) {
824826
dataptrs[i] += subloopsize * strides[i];
825827
}
826828
N -= subloopsize;
827829
} while (N > 0);
830+
831+
return 0;
828832
}
829833

830834

831835
/*
832-
* TODO: This function will be the masked equivalent to `get_loop`.
833-
* This function wraps a legacy inner loop so it becomes masked.
834-
*
835-
* Returns 0 on success, -1 on error.
836+
* Identical to the `get_loop` functions and wraps it. This adds support
837+
* to a boolean mask being passed in as a last, additional, operand.
838+
* The wrapped loop will only be called for unmasked elements.
839+
* (Does not support `move_references` or inner dimensions!)
836840
*/
837841
NPY_NO_EXPORT int
838-
PyUFunc_DefaultMaskedInnerLoopSelector(PyUFuncObject *ufunc,
839-
PyArray_Descr **dtypes,
840-
PyUFuncGenericFunction *out_innerloop,
841-
NpyAuxData **out_innerloopdata,
842-
int *out_needs_api)
842+
PyArrayMethod_GetMaskedStridedLoop(
843+
PyArrayMethod_Context *context,
844+
int aligned, npy_intp *fixed_strides,
845+
PyArrayMethod_StridedLoop **out_loop,
846+
NpyAuxData **out_transferdata,
847+
NPY_ARRAYMETHOD_FLAGS *flags)
843848
{
844-
int retcode;
845849
_masked_stridedloop_data *data;
846-
int nargs = ufunc->nin + ufunc->nout;
847-
848-
if (ufunc->legacy_inner_loop_selector == NULL) {
849-
PyErr_SetString(PyExc_RuntimeError,
850-
"the ufunc default masked inner loop selector doesn't "
851-
"yet support wrapping the new inner loop selector, it "
852-
"still only wraps the legacy inner loop selector");
853-
return -1;
854-
}
850+
int nargs = context->method->nin + context->method->nout;
855851

856852
/* Add working memory for the data pointers, to modify them in-place */
857853
data = PyMem_Malloc(sizeof(_masked_stridedloop_data) +
@@ -865,18 +861,14 @@ PyUFunc_DefaultMaskedInnerLoopSelector(PyUFuncObject *ufunc,
865861
data->unmasked_stridedloop = NULL;
866862
data->nargs = nargs;
867863

868-
/* Get the unmasked ufunc inner loop */
869-
retcode = ufunc->legacy_inner_loop_selector(ufunc, dtypes,
870-
&data->unmasked_stridedloop, &data->innerloopdata,
871-
out_needs_api);
872-
if (retcode < 0) {
873-
PyArray_free(data);
874-
return retcode;
864+
if (context->method->get_strided_loop(context,
865+
aligned, 0, fixed_strides,
866+
&data->unmasked_stridedloop, &data->unmasked_auxdata, flags) < 0) {
867+
PyMem_Free(data);
868+
return -1;
875869
}
876-
877-
/* Return the loop function + aux data */
878-
*out_innerloop = &unmasked_ufunc_loop_as_masked;
879-
*out_innerloopdata = (NpyAuxData *)data;
870+
*out_transferdata = (NpyAuxData *)data;
871+
*out_loop = generic_masked_strided_loop;
880872
return 0;
881873
}
882874

numpy/core/src/multiarray/array_method.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ typedef enum {
1717
* setup/check. No function should set error flags and ignore them
1818
* since it would interfere with chaining operations (e.g. casting).
1919
*/
20+
/* TODO: Change this into a positive flag */
2021
NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 2,
2122
/* Whether the method supports unaligned access (not runtime) */
2223
NPY_METH_SUPPORTS_UNALIGNED = 1 << 3,
@@ -158,17 +159,16 @@ npy_default_get_strided_loop(
158159
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
159160
NPY_ARRAYMETHOD_FLAGS *flags);
160161

161-
/*
162-
* TODO: This function will not rely on the current ufunc code after the
163-
* ufunc refactor.
164-
*/
165-
#include "numpy/ufuncobject.h"
162+
166163
NPY_NO_EXPORT int
167-
PyUFunc_DefaultMaskedInnerLoopSelector(PyUFuncObject *ufunc,
168-
PyArray_Descr **dtypes,
169-
PyUFuncGenericFunction *out_innerloop,
170-
NpyAuxData **out_innerloopdata,
171-
int *out_needs_api);
164+
PyArrayMethod_GetMaskedStridedLoop(
165+
PyArrayMethod_Context *context,
166+
int aligned,
167+
npy_intp *fixed_strides,
168+
PyArrayMethod_StridedLoop **out_loop,
169+
NpyAuxData **out_transferdata,
170+
NPY_ARRAYMETHOD_FLAGS *flags);
171+
172172

173173
/*
174174
* TODO: This function is the internal version, and its error paths may

numpy/core/src/multiarray/nditer_constr.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ NpyIter_AdvancedNew(int nop, PyArrayObject **op_in, npy_uint32 flags,
449449
/*
450450
* If REFS_OK was specified, check whether there are any
451451
* reference arrays and flag it if so.
452+
*
453+
* NOTE: This really should be unnecessary, but chances are someone relies
454+
* on it. The iterator itself does not require the API here
455+
* as it only does so for casting/buffering. But in almost all
456+
* use-cases the API will be required for whatever operation is done.
452457
*/
453458
if (flags & NPY_ITER_REFS_OK) {
454459
for (iop = 0; iop < nop; ++iop) {

0 commit comments

Comments
 (0)