diff options
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/include/numpy/experimental_dtype_api.h | 143 | ||||
-rw-r--r-- | numpy/core/setup.py | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_method.c | 28 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_method.h | 61 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert_datatype.c | 12 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtype_transfer.c | 8 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtype_transfer.h | 4 | ||||
-rw-r--r-- | numpy/core/src/multiarray/experimental_public_dtype_api.c | 71 | ||||
-rw-r--r-- | numpy/core/src/umath/legacy_array_method.c | 2 | ||||
-rw-r--r-- | numpy/core/src/umath/legacy_array_method.h | 2 | ||||
-rw-r--r-- | numpy/core/src/umath/wrapping_array_method.c | 301 |
11 files changed, 594 insertions, 39 deletions
diff --git a/numpy/core/include/numpy/experimental_dtype_api.h b/numpy/core/include/numpy/experimental_dtype_api.h index 88565cd52..1dd6215e6 100644 --- a/numpy/core/include/numpy/experimental_dtype_api.h +++ b/numpy/core/include/numpy/experimental_dtype_api.h @@ -24,6 +24,12 @@ * Register a new loop for a ufunc. This uses the `PyArrayMethod_Spec` * which must be filled in (see in-line comments). * + * - PyUFunc_AddWrappingLoop: + * + * Register a new loop which reuses an existing one, but modifies the + * result dtypes. Please search the internal NumPy docs for more info + * at this point. (Used for physical units dtype.) + * * - PyUFunc_AddPromoter: * * Register a new promoter for a ufunc. A promoter is a function stored @@ -58,6 +64,16 @@ * also promote C; where "promotes" means implements the promotion. * (There are some exceptions for abstract DTypes) * + * - PyArray_GetDefaultDescr: + * + * Given a DType class, returns the default instance (descriptor). + * This is an inline function checking for `singleton` first and only + * calls the `default_descr` function if necessary. + * + * - PyArray_DoubleDType, etc.: + * + * Aliases to the DType classes for the builtin NumPy DTypes. + * * WARNING * ======= * @@ -101,20 +117,41 @@ /* - * Just a hack so I don't forget importing as much myself, I spend way too - * much time noticing it the first time around :). + * There must be a better way?! -- Oh well, this is experimental + * (my issue with it, is that I cannot undef those helpers). */ -static void -__not_imported(void) -{ - printf("*****\nCritical error, dtype API not imported\n*****\n"); -} -static void *__uninitialized_table[] = { - &__not_imported, &__not_imported, &__not_imported, &__not_imported, - &__not_imported, &__not_imported, &__not_imported, &__not_imported}; +#if defined(PY_ARRAY_UNIQUE_SYMBOL) + #define NPY_EXP_DTYPE_API_CONCAT_HELPER2(x, y) x ## y + #define NPY_EXP_DTYPE_API_CONCAT_HELPER(arg) NPY_EXP_DTYPE_API_CONCAT_HELPER2(arg, __experimental_dtype_api_table) + #define __experimental_dtype_api_table NPY_EXP_DTYPE_API_CONCAT_HELPER(PY_ARRAY_UNIQUE_SYMBOL) +#else + #define __experimental_dtype_api_table __experimental_dtype_api_table +#endif + +/* Support for correct multi-file projects: */ +#if defined(NO_IMPORT) || defined(NO_IMPORT_ARRAY) + extern void **__experimental_dtype_api_table; +#else + /* + * Just a hack so I don't forget importing as much myself, I spend way too + * much time noticing it the first time around :). + */ + static void + __not_imported(void) + { + printf("*****\nCritical error, dtype API not imported\n*****\n"); + } + static void *__uninitialized_table[] = { + &__not_imported, &__not_imported, &__not_imported, &__not_imported, + &__not_imported, &__not_imported, &__not_imported, &__not_imported}; -static void **__experimental_dtype_api_table = __uninitialized_table; + #if defined(PY_ARRAY_UNIQUE_SYMBOL) + void **__experimental_dtype_api_table = __uninitialized_table; + #else + static void **__experimental_dtype_api_table = __uninitialized_table; + #endif +#endif /* @@ -172,7 +209,7 @@ typedef struct { int nin, nout; NPY_CASTING casting; NPY_ARRAYMETHOD_FLAGS flags; - PyObject **dtypes; /* array of DType class objects */ + PyArray_DTypeMeta **dtypes; PyType_Slot *slots; } PyArrayMethod_Spec; @@ -187,6 +224,21 @@ typedef PyObject *_ufunc_addloop_fromspec_func( (*(_ufunc_addloop_fromspec_func *)(__experimental_dtype_api_table[0])) +/* Please see the NumPy definitions in `array_method.h` for details on these */ +typedef int translate_given_descrs_func(int nin, int nout, + PyArray_DTypeMeta *wrapped_dtypes[], + PyArray_Descr *given_descrs[], PyArray_Descr *new_descrs[]); +typedef int translate_loop_descrs_func(int nin, int nout, + PyArray_DTypeMeta *new_dtypes[], PyArray_Descr *given_descrs[], + PyArray_Descr *original_descrs[], PyArray_Descr *loop_descrs[]); + +typedef int _ufunc_wrapping_loop_func(PyObject *ufunc_obj, + PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[], + translate_given_descrs_func *translate_given_descrs, + translate_loop_descrs_func *translate_loop_descrs); +#define PyUFunc_AddWrappingLoop \ + (*(_ufunc_wrapping_loop_func *)(__experimental_dtype_api_table[7])) + /* * Type of the C promoter function, which must be wrapped into a * PyCapsule with name "numpy._ufunc_promoter". @@ -334,6 +386,65 @@ typedef PyArray_DTypeMeta *__promote_dtype_sequence( ((__promote_dtype_sequence *)(__experimental_dtype_api_table[5])) +typedef PyArray_Descr *__get_default_descr( + PyArray_DTypeMeta *DType); +#define _PyArray_GetDefaultDescr \ + ((__get_default_descr *)(__experimental_dtype_api_table[6])) + +static NPY_INLINE PyArray_Descr * +PyArray_GetDefaultDescr(PyArray_DTypeMeta *DType) +{ + if (DType->singleton != NULL) { + Py_INCREF(DType->singleton); + return DType->singleton; + } + return _PyArray_GetDefaultDescr(DType); +} + + +/* + * NumPy's builtin DTypes: + */ +#define PyArray_BoolDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[10]) +/* Integers */ +#define PyArray_ByteDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[11]) +#define PyArray_UByteDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[12]) +#define PyArray_ShortDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[13]) +#define PyArray_UShortDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[14]) +#define PyArray_IntDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[15]) +#define PyArray_UIntDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[16]) +#define PyArray_LongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[17]) +#define PyArray_ULongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[18]) +#define PyArray_LongLongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[19]) +#define PyArray_ULongLongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[20]) +/* Integer aliases */ +#define PyArray_Int8Type (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[21]) +#define PyArray_UInt8DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[22]) +#define PyArray_Int16DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[23]) +#define PyArray_UInt16DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[24]) +#define PyArray_Int32DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[25]) +#define PyArray_UInt32DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[26]) +#define PyArray_Int64DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[27]) +#define PyArray_UInt64DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[28]) +#define PyArray_IntpDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[29]) +#define PyArray_UIntpDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[30]) +/* Floats */ +#define PyArray_HalfType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[31]) +#define PyArray_FloatDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[32]) +#define PyArray_DoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[33]) +#define PyArray_LongDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[34]) +/* Complex */ +#define PyArray_CFloatDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[35]) +#define PyArray_CDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[36]) +#define PyArray_CLongDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[37]) +/* String/Bytes */ +#define PyArray_StringDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[38]) +#define PyArray_UnicodeDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[39]) +/* Datetime/Timedelta */ +#define PyArray_DatetimeDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[40]) +#define PyArray_TimedeltaDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[41]) + + /* * ******************************** * Initialization @@ -344,7 +455,9 @@ typedef PyArray_DTypeMeta *__promote_dtype_sequence( * runtime-check this. * You must call this function to use the symbols defined in this file. */ -#define __EXPERIMENTAL_DTYPE_VERSION 2 +#if !defined(NO_IMPORT) && !defined(NO_IMPORT_ARRAY) + +#define __EXPERIMENTAL_DTYPE_VERSION 4 static int import_experimental_dtype_api(int version) @@ -372,7 +485,7 @@ import_experimental_dtype_api(int version) if (api == NULL) { return -1; } - __experimental_dtype_api_table = PyCapsule_GetPointer(api, + __experimental_dtype_api_table = (void **)PyCapsule_GetPointer(api, "experimental_dtype_api_table"); Py_DECREF(api); @@ -383,4 +496,6 @@ import_experimental_dtype_api(int version) return 0; } +#endif /* !defined(NO_IMPORT) && !defined(NO_IMPORT_ARRAY) */ + #endif /* NUMPY_CORE_INCLUDE_NUMPY_EXPERIMENTAL_DTYPE_API_H_ */ diff --git a/numpy/core/setup.py b/numpy/core/setup.py index c97747606..63962ab79 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -1009,6 +1009,7 @@ def configuration(parent_package='',top_path=None): join('src', 'umath', 'clip.cpp'), join('src', 'umath', 'dispatching.c'), join('src', 'umath', 'legacy_array_method.c'), + join('src', 'umath', 'wrapping_array_method.c'), join('src', 'umath', 'ufunc_object.c'), join('src', 'umath', 'extobj.c'), join('src', 'umath', 'scalarmath.c.src'), diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c index b421d9e4f..e3436c573 100644 --- a/numpy/core/src/multiarray/array_method.c +++ b/numpy/core/src/multiarray/array_method.c @@ -133,7 +133,7 @@ is_contiguous( NPY_NO_EXPORT int npy_default_get_strided_loop( PyArrayMethod_Context *context, - int aligned, int NPY_UNUSED(move_references), npy_intp *strides, + int aligned, int NPY_UNUSED(move_references), const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) { @@ -261,12 +261,17 @@ fill_arraymethod_from_slots( meth->resolve_descriptors = slot->pfunc; continue; case NPY_METH_get_loop: - if (private) { - /* Only allow override for private functions initially */ - meth->get_strided_loop = slot->pfunc; - continue; - } - break; + /* + * NOTE: get_loop is considered "unstable" in the public API, + * I do not like the signature, and the `move_references` + * parameter must NOT be used. + * (as in: we should not worry about changing it, but of + * course that would not break it immediately.) + */ + /* Only allow override for private functions initially */ + meth->get_strided_loop = slot->pfunc; + continue; + /* "Typical" loops, supported used by the default `get_loop` */ case NPY_METH_strided_loop: meth->strided_loop = slot->pfunc; continue; @@ -460,6 +465,15 @@ arraymethod_dealloc(PyObject *self) PyMem_Free(meth->name); + if (meth->wrapped_meth != NULL) { + /* Cleanup for wrapping array method (defined in umath) */ + Py_DECREF(meth->wrapped_meth); + for (int i = 0; i < meth->nin + meth->nout; i++) { + Py_XDECREF(meth->wrapped_dtypes[i]); + } + PyMem_Free(meth->wrapped_dtypes); + } + Py_TYPE(self)->tp_free(self); } diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h index 35b9033e0..30dd94a80 100644 --- a/numpy/core/src/multiarray/array_method.h +++ b/numpy/core/src/multiarray/array_method.h @@ -77,13 +77,65 @@ typedef NPY_CASTING (resolve_descriptors_function)( typedef int (get_loop_function)( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags); /* + * The following functions are only used be the wrapping array method defined + * in umath/wrapping_array_method.c + */ + +/* + * The function to convert the given descriptors (passed in to + * `resolve_descriptors`) and translates them for the wrapped loop. + * The new descriptors MUST be viewable with the old ones, `NULL` must be + * supported (for outputs) and should normally be forwarded. + * + * The function must clean up on error. + * + * NOTE: We currently assume that this translation gives "viewable" results. + * I.e. there is no additional casting related to the wrapping process. + * In principle that could be supported, but not sure it is useful. + * This currently also means that e.g. alignment must apply identically + * to the new dtypes. + * + * TODO: Due to the fact that `resolve_descriptors` is also used for `can_cast` + * there is no way to "pass out" the result of this function. This means + * it will be called twice for every ufunc call. + * (I am considering including `auxdata` as an "optional" parameter to + * `resolve_descriptors`, so that it can be filled there if not NULL.) + */ +typedef int translate_given_descrs_func(int nin, int nout, + PyArray_DTypeMeta *wrapped_dtypes[], + PyArray_Descr *given_descrs[], PyArray_Descr *new_descrs[]); + +/** + * The function to convert the actual loop descriptors (as returned by the + * original `resolve_descriptors` function) to the ones the output array + * should use. + * This function must return "viewable" types, it must not mutate them in any + * form that would break the inner-loop logic. Does not need to support NULL. + * + * The function must clean up on error. + * + * @param nargs Number of arguments + * @param new_dtypes The DTypes of the output (usually probably not needed) + * @param given_descrs Original given_descrs to the resolver, necessary to + * fetch any information related to the new dtypes from the original. + * @param original_descrs The `loop_descrs` returned by the wrapped loop. + * @param loop_descrs The output descriptors, compatible to `original_descrs`. + * + * @returns 0 on success, -1 on failure. + */ +typedef int translate_loop_descrs_func(int nin, int nout, + PyArray_DTypeMeta *new_dtypes[], PyArray_Descr *given_descrs[], + PyArray_Descr *original_descrs[], PyArray_Descr *loop_descrs[]); + + +/* * This struct will be public and necessary for creating a new ArrayMethod * object (casting and ufuncs). * We could version the struct, although since we allow passing arbitrary @@ -125,6 +177,11 @@ typedef struct PyArrayMethodObject_tag { PyArrayMethod_StridedLoop *contiguous_loop; PyArrayMethod_StridedLoop *unaligned_strided_loop; PyArrayMethod_StridedLoop *unaligned_contiguous_loop; + /* Chunk only used for wrapping array method defined in umath */ + struct PyArrayMethodObject_tag *wrapped_meth; + PyArray_DTypeMeta **wrapped_dtypes; + translate_given_descrs_func *translate_given_descrs; + translate_loop_descrs_func *translate_loop_descrs; } PyArrayMethodObject; @@ -167,7 +224,7 @@ extern NPY_NO_EXPORT PyTypeObject PyBoundArrayMethod_Type; NPY_NO_EXPORT int npy_default_get_strided_loop( PyArrayMethod_Context *context, - int aligned, int NPY_UNUSED(move_references), npy_intp *strides, + int aligned, int NPY_UNUSED(move_references), const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags); diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index 5387a0970..b8d443752 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -2458,7 +2458,7 @@ get_byteswap_loop( NPY_NO_EXPORT int complex_to_noncomplex_get_loop( PyArrayMethod_Context *context, - int aligned, int move_references, npy_intp *strides, + int aligned, int move_references, const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) { @@ -2799,7 +2799,7 @@ string_to_string_resolve_descriptors( NPY_NO_EXPORT int string_to_string_get_loop( PyArrayMethod_Context *context, - int aligned, int NPY_UNUSED(move_references), npy_intp *strides, + int aligned, int NPY_UNUSED(move_references), const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) { @@ -3048,7 +3048,7 @@ static int nonstructured_to_structured_get_loop( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) @@ -3210,7 +3210,7 @@ static int structured_to_nonstructured_get_loop( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) @@ -3510,7 +3510,7 @@ NPY_NO_EXPORT int void_to_void_get_loop( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) @@ -3726,7 +3726,7 @@ static int object_to_object_get_loop( PyArrayMethod_Context *NPY_UNUSED(context), int NPY_UNUSED(aligned), int move_references, - npy_intp *NPY_UNUSED(strides), + const npy_intp *NPY_UNUSED(strides), PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index b0db94817..91b8aac98 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -237,7 +237,7 @@ NPY_NO_EXPORT int any_to_object_get_loop( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) @@ -343,7 +343,7 @@ NPY_NO_EXPORT int object_to_any_get_loop( PyArrayMethod_Context *context, int NPY_UNUSED(aligned), int move_references, - npy_intp *NPY_UNUSED(strides), + const npy_intp *NPY_UNUSED(strides), PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) @@ -3023,9 +3023,9 @@ init_cast_info( if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Cannot cast array data from %R to %R.", src_dtype, dst_dtype); - Py_DECREF(meth); - return -1; } + Py_DECREF(meth); + return -1; } assert(PyArray_DescrCheck(cast_info->descriptors[0])); assert(PyArray_DescrCheck(cast_info->descriptors[1])); diff --git a/numpy/core/src/multiarray/dtype_transfer.h b/numpy/core/src/multiarray/dtype_transfer.h index c7e0a029f..9ae332e38 100644 --- a/numpy/core/src/multiarray/dtype_transfer.h +++ b/numpy/core/src/multiarray/dtype_transfer.h @@ -132,7 +132,7 @@ NPY_NO_EXPORT int any_to_object_get_loop( PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *strides, + const npy_intp *strides, PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags); @@ -141,7 +141,7 @@ NPY_NO_EXPORT int object_to_any_get_loop( PyArrayMethod_Context *context, int NPY_UNUSED(aligned), int move_references, - npy_intp *NPY_UNUSED(strides), + const npy_intp *NPY_UNUSED(strides), PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags); diff --git a/numpy/core/src/multiarray/experimental_public_dtype_api.c b/numpy/core/src/multiarray/experimental_public_dtype_api.c index e9d191002..cf5f152ab 100644 --- a/numpy/core/src/multiarray/experimental_public_dtype_api.c +++ b/numpy/core/src/multiarray/experimental_public_dtype_api.c @@ -16,7 +16,7 @@ #include "common_dtype.h" -#define EXPERIMENTAL_DTYPE_API_VERSION 3 +#define EXPERIMENTAL_DTYPE_API_VERSION 4 typedef struct{ @@ -332,6 +332,16 @@ PyUFunc_AddLoopFromSpec(PyObject *ufunc, PyArrayMethod_Spec *spec) return PyUFunc_AddLoop((PyUFuncObject *)ufunc, info, 0); } +/* + * Function is defined in umath/wrapping_array_method.c + * (same/one compilation unit) + */ +NPY_NO_EXPORT int +PyUFunc_AddWrappingLoop(PyObject *ufunc_obj, + PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[], + translate_given_descrs_func *translate_given_descrs, + translate_loop_descrs_func *translate_loop_descrs); + static int PyUFunc_AddPromoter( @@ -358,18 +368,75 @@ PyUFunc_AddPromoter( } +/* + * Lightweight function fetch a default instance of a DType class. + * Note that this version is named `_PyArray_GetDefaultDescr` with an + * underscore. The `singleton` slot is public, so an inline version is + * provided that checks `singleton != NULL` first. + */ +static PyArray_Descr * +_PyArray_GetDefaultDescr(PyArray_DTypeMeta *DType) +{ + return NPY_DT_CALL_default_descr(DType); +} + + NPY_NO_EXPORT PyObject * _get_experimental_dtype_api(PyObject *NPY_UNUSED(mod), PyObject *arg) { - static void *experimental_api_table[] = { + static void *experimental_api_table[42] = { &PyUFunc_AddLoopFromSpec, &PyUFunc_AddPromoter, &PyArrayDTypeMeta_Type, &PyArrayInitDTypeMeta_FromSpec, &PyArray_CommonDType, &PyArray_PromoteDTypeSequence, + &_PyArray_GetDefaultDescr, + &PyUFunc_AddWrappingLoop, + NULL, NULL, + /* NumPy's builtin DTypes (starting at offset 10 going to 41) */ }; + if (experimental_api_table[10] == NULL) { + experimental_api_table[10] = PyArray_DTypeFromTypeNum(NPY_BOOL); + /* Integers */ + experimental_api_table[11] = PyArray_DTypeFromTypeNum(NPY_BYTE); + experimental_api_table[12] = PyArray_DTypeFromTypeNum(NPY_UBYTE); + experimental_api_table[13] = PyArray_DTypeFromTypeNum(NPY_SHORT); + experimental_api_table[14] = PyArray_DTypeFromTypeNum(NPY_USHORT); + experimental_api_table[15] = PyArray_DTypeFromTypeNum(NPY_INT); + experimental_api_table[16] = PyArray_DTypeFromTypeNum(NPY_UINT); + experimental_api_table[17] = PyArray_DTypeFromTypeNum(NPY_LONG); + experimental_api_table[18] = PyArray_DTypeFromTypeNum(NPY_ULONG); + experimental_api_table[19] = PyArray_DTypeFromTypeNum(NPY_LONGLONG); + experimental_api_table[20] = PyArray_DTypeFromTypeNum(NPY_ULONGLONG); + /* Integer aliases */ + experimental_api_table[21] = PyArray_DTypeFromTypeNum(NPY_INT8); + experimental_api_table[22] = PyArray_DTypeFromTypeNum(NPY_UINT8); + experimental_api_table[23] = PyArray_DTypeFromTypeNum(NPY_INT16); + experimental_api_table[24] = PyArray_DTypeFromTypeNum(NPY_UINT16); + experimental_api_table[25] = PyArray_DTypeFromTypeNum(NPY_INT32); + experimental_api_table[26] = PyArray_DTypeFromTypeNum(NPY_UINT32); + experimental_api_table[27] = PyArray_DTypeFromTypeNum(NPY_INT64); + experimental_api_table[28] = PyArray_DTypeFromTypeNum(NPY_UINT64); + experimental_api_table[29] = PyArray_DTypeFromTypeNum(NPY_INTP); + experimental_api_table[30] = PyArray_DTypeFromTypeNum(NPY_UINTP); + /* Floats */ + experimental_api_table[31] = PyArray_DTypeFromTypeNum(NPY_HALF); + experimental_api_table[32] = PyArray_DTypeFromTypeNum(NPY_FLOAT); + experimental_api_table[33] = PyArray_DTypeFromTypeNum(NPY_DOUBLE); + experimental_api_table[34] = PyArray_DTypeFromTypeNum(NPY_LONGDOUBLE); + /* Complex */ + experimental_api_table[35] = PyArray_DTypeFromTypeNum(NPY_CFLOAT); + experimental_api_table[36] = PyArray_DTypeFromTypeNum(NPY_CDOUBLE); + experimental_api_table[37] = PyArray_DTypeFromTypeNum(NPY_CLONGDOUBLE); + /* String/Bytes */ + experimental_api_table[38] = PyArray_DTypeFromTypeNum(NPY_STRING); + experimental_api_table[39] = PyArray_DTypeFromTypeNum(NPY_UNICODE); + /* Datetime/Timedelta */ + experimental_api_table[40] = PyArray_DTypeFromTypeNum(NPY_DATETIME); + experimental_api_table[41] = PyArray_DTypeFromTypeNum(NPY_TIMEDELTA); + } char *env = getenv("NUMPY_EXPERIMENTAL_DTYPE_API"); if (env == NULL || strcmp(env, "1") != 0) { diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c index f4b2aed96..171b53efd 100644 --- a/numpy/core/src/umath/legacy_array_method.c +++ b/numpy/core/src/umath/legacy_array_method.c @@ -192,7 +192,7 @@ simple_legacy_resolve_descriptors( NPY_NO_EXPORT int get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *NPY_UNUSED(strides), + const npy_intp *NPY_UNUSED(strides), PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags) diff --git a/numpy/core/src/umath/legacy_array_method.h b/numpy/core/src/umath/legacy_array_method.h index d20b4fb08..498fb1aa2 100644 --- a/numpy/core/src/umath/legacy_array_method.h +++ b/numpy/core/src/umath/legacy_array_method.h @@ -20,7 +20,7 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc, NPY_NO_EXPORT int get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context, int aligned, int move_references, - npy_intp *NPY_UNUSED(strides), + const npy_intp *NPY_UNUSED(strides), PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, NPY_ARRAYMETHOD_FLAGS *flags); diff --git a/numpy/core/src/umath/wrapping_array_method.c b/numpy/core/src/umath/wrapping_array_method.c new file mode 100644 index 000000000..9f8f036e8 --- /dev/null +++ b/numpy/core/src/umath/wrapping_array_method.c @@ -0,0 +1,301 @@ +/* + * This file defines most of the machinery in order to wrap an existing ufunc + * loop for use with a different set of dtypes. + * + * There are two approaches for this, one is to teach the NumPy core about + * the possibility that the loop descriptors do not match exactly the result + * descriptors. + * The other is to handle this fully by "wrapping", so that NumPy core knows + * nothing about this going on. + * The slight difficulty here is that `context` metadata needs to be mutated. + * It also adds a tiny bit of overhead, since we have to "fix" the descriptors + * and unpack the auxdata. + * + * This means that this currently needs to live within NumPy, as it needs both + * extensive API exposure to do it outside, as well as some thoughts on how to + * expose the `context` without breaking ABI forward compatibility. + * (I.e. we probably need to allocate the context and provide a copy function + * or so.) + */ +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _MULTIARRAYMODULE +#define _UMATHMODULE + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#include "numpy/ndarraytypes.h" + +#include "common.h" +#include "array_method.h" +#include "legacy_array_method.h" +#include "dtypemeta.h" +#include "dispatching.h" + + +static NPY_CASTING +wrapping_method_resolve_descriptors( + PyArrayMethodObject *self, + PyArray_DTypeMeta *dtypes[], + PyArray_Descr *given_descrs[], + PyArray_Descr *loop_descrs[], + npy_intp *view_offset) +{ + int nin = self->nin, nout = self->nout, nargs = nin + nout; + PyArray_Descr *orig_given_descrs[NPY_MAXARGS]; + PyArray_Descr *orig_loop_descrs[NPY_MAXARGS]; + + if (self->translate_given_descrs( + nin, nout, self->wrapped_dtypes, + given_descrs, orig_given_descrs) < 0) { + return -1; + } + NPY_CASTING casting = self->wrapped_meth->resolve_descriptors( + self->wrapped_meth, self->wrapped_dtypes, + orig_given_descrs, orig_loop_descrs, view_offset); + for (int i = 0; i < nargs; i++) { + Py_XDECREF(orig_given_descrs); + } + if (casting < 0) { + return -1; + } + int res = self->translate_loop_descrs( + nin, nout, dtypes, given_descrs, orig_loop_descrs, loop_descrs); + for (int i = 0; i < nargs; i++) { + Py_DECREF(orig_given_descrs); + } + if (res < 0) { + return -1; + } + return casting; +} + + +typedef struct { + NpyAuxData base; + /* Note that if context is expanded this may become trickier: */ + PyArrayMethod_Context orig_context; + PyArrayMethod_StridedLoop *orig_loop; + NpyAuxData *orig_auxdata; + PyArray_Descr *descriptors[NPY_MAXARGS]; +} wrapping_auxdata; + + +#define WRAPPING_AUXDATA_FREELIST_SIZE 5 +static int wrapping_auxdata_freenum = 0; +static wrapping_auxdata *wrapping_auxdata_freelist[WRAPPING_AUXDATA_FREELIST_SIZE] = {NULL}; + + +static void +wrapping_auxdata_free(wrapping_auxdata *wrapping_auxdata) +{ + /* Free auxdata, everything else is borrowed: */ + NPY_AUXDATA_FREE(wrapping_auxdata->orig_auxdata); + wrapping_auxdata->orig_auxdata = NULL; + + if (wrapping_auxdata_freenum < WRAPPING_AUXDATA_FREELIST_SIZE) { + wrapping_auxdata_freelist[wrapping_auxdata_freenum] = wrapping_auxdata; + } + else { + PyMem_Free(wrapping_auxdata); + } +} + + +static wrapping_auxdata * +get_wrapping_auxdata(void) +{ + wrapping_auxdata *res; + if (wrapping_auxdata_freenum > 0) { + wrapping_auxdata_freenum--; + res = wrapping_auxdata_freelist[wrapping_auxdata_freenum]; + } + else { + res = PyMem_Calloc(1, sizeof(wrapping_auxdata)); + if (res < 0) { + PyErr_NoMemory(); + return NULL; + } + res->base.free = (void *)wrapping_auxdata_free; + res->orig_context.descriptors = res->descriptors; + } + + return res; +} + + +static int +wrapping_method_strided_loop(PyArrayMethod_Context *NPY_UNUSED(context), + char *const data[], npy_intp const dimensions[], + npy_intp const strides[], wrapping_auxdata *auxdata) +{ + /* + * If more things get stored on the context, it could be possible that + * we would have to copy it here. But currently, we do not. + */ + return auxdata->orig_loop( + &auxdata->orig_context, data, dimensions, strides, + auxdata->orig_auxdata); +} + + +static int +wrapping_method_get_loop( + PyArrayMethod_Context *context, + int aligned, int move_references, const npy_intp *strides, + PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata, + NPY_ARRAYMETHOD_FLAGS *flags) +{ + assert(move_references == 0); /* only used internally for "decref" funcs */ + int nin = context->method->nin, nout = context->method->nout; + + wrapping_auxdata *auxdata = get_wrapping_auxdata(); + if (auxdata == NULL) { + return -1; + } + + auxdata->orig_context.method = context->method->wrapped_meth; + auxdata->orig_context.caller = context->caller; + + if (context->method->translate_given_descrs( + nin, nout, context->method->wrapped_dtypes, + context->descriptors, auxdata->orig_context.descriptors) < 0) { + NPY_AUXDATA_FREE((NpyAuxData *)auxdata); + return -1; + } + if (context->method->wrapped_meth->get_strided_loop( + &auxdata->orig_context, aligned, 0, strides, + &auxdata->orig_loop, &auxdata->orig_auxdata, + flags) < 0) { + NPY_AUXDATA_FREE((NpyAuxData *)auxdata); + return -1; + } + + *out_loop = (PyArrayMethod_StridedLoop *)&wrapping_method_strided_loop; + *out_transferdata = (NpyAuxData *)auxdata; + return 0; +} + + +/** + * Allows creating of a fairly lightweight wrapper around an existing ufunc + * loop. The idea is mainly for units, as this is currently slightly limited + * in that it enforces that you cannot use a loop from another ufunc. + * + * @param ufunc_obj + * @param new_dtypes + * @param wrapped_dtypes + * @param translate_given_descrs See typedef comment + * @param translate_loop_descrs See typedef comment + * @return 0 on success -1 on failure + */ +NPY_NO_EXPORT int +PyUFunc_AddWrappingLoop(PyObject *ufunc_obj, + PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[], + translate_given_descrs_func *translate_given_descrs, + translate_loop_descrs_func *translate_loop_descrs) +{ + int res = -1; + PyUFuncObject *ufunc = (PyUFuncObject *)ufunc_obj; + PyObject *wrapped_dt_tuple = NULL; + PyObject *new_dt_tuple = NULL; + PyArrayMethodObject *meth = NULL; + + if (!PyObject_TypeCheck(ufunc_obj, &PyUFunc_Type)) { + PyErr_SetString(PyExc_TypeError, + "ufunc object passed is not a ufunc!"); + return -1; + } + + wrapped_dt_tuple = PyArray_TupleFromItems( + ufunc->nargs, (PyObject **)wrapped_dtypes, 1); + if (wrapped_dt_tuple == NULL) { + goto finish; + } + + PyArrayMethodObject *wrapped_meth = NULL; + PyObject *loops = ufunc->_loops; + Py_ssize_t length = PyList_Size(loops); + for (Py_ssize_t i = 0; i < length; i++) { + PyObject *item = PyList_GetItem(loops, i); + PyObject *cur_DType_tuple = PyTuple_GetItem(item, 0); + int cmp = PyObject_RichCompareBool(cur_DType_tuple, wrapped_dt_tuple, Py_EQ); + if (cmp < 0) { + goto finish; + } + if (cmp == 0) { + continue; + } + wrapped_meth = (PyArrayMethodObject *)PyTuple_GET_ITEM(item, 1); + if (!PyObject_TypeCheck(wrapped_meth, &PyArrayMethod_Type)) { + PyErr_SetString(PyExc_TypeError, + "Matching loop was not an ArrayMethod."); + goto finish; + } + break; + } + if (wrapped_meth == NULL) { + PyErr_SetString(PyExc_TypeError, + "Did not find the to-be-wrapped loop in the ufunc."); + goto finish; + } + + PyType_Slot slots[] = { + {NPY_METH_resolve_descriptors, &wrapping_method_resolve_descriptors}, + {NPY_METH_get_loop, &wrapping_method_get_loop}, + {0, NULL} + }; + + PyArrayMethod_Spec spec = { + .name = "wrapped-method", + .nin = wrapped_meth->nin, + .nout = wrapped_meth->nout, + .casting = wrapped_meth->casting, + .flags = wrapped_meth->flags, + .dtypes = new_dtypes, + .slots = slots, + }; + PyBoundArrayMethodObject *bmeth = PyArrayMethod_FromSpec_int(&spec, 1); + if (bmeth == NULL) { + goto finish; + } + + Py_INCREF(bmeth->method); + meth = bmeth->method; + Py_SETREF(bmeth, NULL); + + /* Finalize the "wrapped" part of the new ArrayMethod */ + meth->wrapped_dtypes = PyMem_Malloc(ufunc->nargs * sizeof(PyArray_DTypeMeta *)); + if (meth->wrapped_dtypes == NULL) { + goto finish; + } + + Py_INCREF(wrapped_meth); + meth->wrapped_meth = wrapped_meth; + meth->translate_given_descrs = translate_given_descrs; + meth->translate_loop_descrs = translate_loop_descrs; + for (int i = 0; i < ufunc->nargs; i++) { + Py_XINCREF(wrapped_dtypes[i]); + meth->wrapped_dtypes[i] = wrapped_dtypes[i]; + } + + new_dt_tuple = PyArray_TupleFromItems( + ufunc->nargs, (PyObject **)new_dtypes, 1); + if (new_dt_tuple == NULL) { + goto finish; + } + + PyObject *info = PyTuple_Pack(2, new_dt_tuple, meth); + if (info == NULL) { + goto finish; + } + + res = PyUFunc_AddLoop(ufunc, info, 0); + Py_DECREF(info); + + finish: + Py_XDECREF(wrapped_dt_tuple); + Py_XDECREF(new_dt_tuple); + Py_XDECREF(meth); + return res; +} |