diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2021-07-29 17:30:40 -0700 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2021-07-29 17:58:57 -0700 |
commit | 77246a8d8e16f83777eca34a90cad7bd7b506cda (patch) | |
tree | d418672b2c15a090579133580c4831d4031e3143 | |
parent | 2c1a34daa024398cdf9c6e1fbeff52b4eb280551 (diff) | |
download | numpy-77246a8d8e16f83777eca34a90cad7bd7b506cda.tar.gz |
MAINT: Refactor DType slots into an opaque, allocated struct
Using an allocated struct (and adding some reserved empty fields)
should allow to make the size of the DType(Type) fixed, which allows
much easier definition of C-side defined DTypes (so long they are
static types).
Also organizes legacy, abstract, and parametric into flags and
introduces Macros to access all of these.
This is probably not the final word on the structure, but hiding
these into an allocated, opaque, struct seems necessary.
It also changes the allocation to use Pythons allocator rather
than malloc, just because mixing the two is not that great probably.
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 54 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_multiarray_tests.c.src | 6 | ||||
-rw-r--r-- | numpy/core/src/multiarray/abstractdtypes.c | 53 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_coercion.c | 17 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_method.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common.c | 4 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common_dtype.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert_datatype.c | 94 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/descriptor.c | 7 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtypemeta.c | 110 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtypemeta.h | 58 | ||||
-rw-r--r-- | numpy/core/src/multiarray/usertypes.c | 6 | ||||
-rw-r--r-- | numpy/core/src/umath/_scaled_float_dtype.c | 40 | ||||
-rw-r--r-- | numpy/core/src/umath/dispatching.c | 20 | ||||
-rw-r--r-- | numpy/core/src/umath/legacy_array_method.c | 5 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 6 |
17 files changed, 283 insertions, 219 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 5acd24a7e..e2aef6033 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -1879,8 +1879,6 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size, typedef PyArray_Descr *(default_descr_function)(PyArray_DTypeMeta *cls); typedef PyArray_DTypeMeta *(common_dtype_function)( PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtyep2); - typedef PyArray_DTypeMeta *(common_dtype_with_value_function)( - PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtyep2, PyObject *value); typedef PyArray_Descr *(common_instance_function)( PyArray_Descr *dtype1, PyArray_Descr *dtyep2); @@ -1903,55 +1901,25 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size, * may be a pointer to the *prototype* instance? */ PyArray_Descr *singleton; - /* - * Is this DType created using the old API? This exists mainly to - * allow for assertions in paths specific to wrapping legacy types. - */ - npy_bool legacy; - /* The values stored by a parametric datatype depend on its instance */ - npy_bool parametric; - /* whether the DType can be instantiated (i.e. np.dtype cannot) */ - npy_bool abstract; + /* Copy of the legacy DTypes type number, usually invalid. */ + int type_num; - /* - * The following fields replicate the most important dtype information. - * In the legacy implementation most of these are stored in the - * PyArray_Descr struct. - */ /* The type object of the scalar instances (may be NULL?) */ PyTypeObject *scalar_type; - /* kind for this type */ - char kind; - /* unique-character representing this type */ - char type; - /* flags describing data type */ - char flags; - /* number representing this type */ - int type_num; /* - * Point to the original ArrFuncs. - * NOTE: We could make a copy to detect changes to `f`. + * Flags about the DType mainly to signel legacy, parametric, or + * abstract. But plenty of space for additional information/flags. */ - PyArray_ArrFuncs *f; + npy_uint64 flags; - /* DType methods, these could be moved into its own struct */ - discover_descr_from_pyobject_function *discover_descr_from_pyobject; - is_known_scalar_type_function *is_known_scalar_type; - default_descr_function *default_descr; - common_dtype_function *common_dtype; - common_dtype_with_value_function *common_dtype_with_value; - common_instance_function *common_instance; - /* - * The casting implementation (ArrayMethod) to convert between two - * instances of this DType, stored explicitly for fast access: - */ - PyObject *within_dtype_castingimpl; /* - * Dictionary of ArrayMethods representing most possible casts - * (structured and object are exceptions). - * This should potentially become a weak mapping in the future. + * Private storage for all slots and reserved fields to allow a stable + * size. A stable ABI size makes creating a static DType less painful + * while also ensuring flexibility for all opaque API (with one + * indirection due the pointer lookup). */ - PyObject *castingimpls; + void *dt_slots; + void *reserved[3]; }; #endif /* NPY_INTERNAL_BUILD */ diff --git a/numpy/core/src/multiarray/_multiarray_tests.c.src b/numpy/core/src/multiarray/_multiarray_tests.c.src index 4b9a4f9dd..f4764b371 100644 --- a/numpy/core/src/multiarray/_multiarray_tests.c.src +++ b/numpy/core/src/multiarray/_multiarray_tests.c.src @@ -14,6 +14,7 @@ #include "npy_extint128.h" #include "array_method.h" #include "npy_hashtable.h" +#include "dtypemeta.h" #if defined(MS_WIN32) || defined(__CYGWIN__) #define EXPORT(x) __declspec(dllexport) x @@ -1066,7 +1067,7 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args)) for (Py_ssize_t i = 0; i < nclass; i++) { PyArray_DTypeMeta *from_dtype = ( (PyArray_DTypeMeta *)PySequence_Fast_GET_ITEM(classes, i)); - if (from_dtype->abstract) { + if (NPY_DT_is_abstract(from_dtype)) { /* * TODO: In principle probably needs to recursively check this, * also we may allow casts to abstract dtypes at some point. @@ -1077,7 +1078,8 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args)) PyObject *to_dtype, *cast_obj; Py_ssize_t pos = 0; - while (PyDict_Next(from_dtype->castingimpls, &pos, &to_dtype, &cast_obj)) { + while (PyDict_Next(NPY_DT_SLOTS(from_dtype)->castingimpls, + &pos, &to_dtype, &cast_obj)) { if (cast_obj == Py_None) { continue; } diff --git a/numpy/core/src/multiarray/abstractdtypes.c b/numpy/core/src/multiarray/abstractdtypes.c index 587d91c49..3fa354ddc 100644 --- a/numpy/core/src/multiarray/abstractdtypes.c +++ b/numpy/core/src/multiarray/abstractdtypes.c @@ -150,7 +150,7 @@ initialize_and_map_pytypes_to_dtypes() static PyArray_DTypeMeta * int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other) { - if (other->legacy && other->type_num < NPY_NTYPES) { + if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) { if (other->type_num == NPY_BOOL) { /* Use the default integer for bools: */ return PyArray_DTypeFromTypeNum(NPY_LONG); @@ -162,7 +162,7 @@ int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other) return other; } } - else if (other->legacy) { + else if (NPY_DT_is_legacy(other)) { /* This is a back-compat fallback to usually do the right thing... */ return PyArray_DTypeFromTypeNum(NPY_UINT8); } @@ -174,7 +174,7 @@ int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other) static PyArray_DTypeMeta * float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { - if (other->legacy && other->type_num < NPY_NTYPES) { + if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) { if (other->type_num == NPY_BOOL || PyTypeNum_ISINTEGER(other->type_num)) { /* Use the default integer for bools and ints: */ return PyArray_DTypeFromTypeNum(NPY_DOUBLE); @@ -189,7 +189,7 @@ float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) Py_INCREF(cls); return cls; } - else if (other->legacy) { + else if (NPY_DT_is_legacy(other)) { /* This is a back-compat fallback to usually do the right thing... */ return PyArray_DTypeFromTypeNum(NPY_HALF); } @@ -201,7 +201,7 @@ float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) static PyArray_DTypeMeta * complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { - if (other->legacy && other->type_num < NPY_NTYPES) { + if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) { if (other->type_num == NPY_BOOL || PyTypeNum_ISINTEGER(other->type_num)) { /* Use the default integer for bools and ints: */ @@ -227,7 +227,7 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) return other; } } - else if (other->legacy) { + else if (NPY_DT_is_legacy(other)) { /* This is a back-compat fallback to usually do the right thing... */ return PyArray_DTypeFromTypeNum(NPY_CFLOAT); } @@ -246,17 +246,27 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) * `Floating`, `Complex`, and `Integer` (both signed and unsigned). * They will have to be renamed and exposed in that capacity. */ +NPY_DType_Slots pyintabstractdtype_slots = { + .default_descr = int_default_descriptor, + .discover_descr_from_pyobject = discover_descriptor_from_pyint, + .common_dtype = int_common_dtype, +}; + NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyIntAbstractDType = {{{ PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) .tp_basicsize = sizeof(PyArray_Descr), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_name = "numpy._IntegerAbstractDType", },}, - .abstract = 1, - .default_descr = int_default_descriptor, - .discover_descr_from_pyobject = discover_descriptor_from_pyint, - .common_dtype = int_common_dtype, - .kind = 'i', + .flags = NPY_DT_ABSTRACT, + .dt_slots = &pyintabstractdtype_slots, +}; + + +NPY_DType_Slots pyfloatabstractdtype_slots = { + .default_descr = float_default_descriptor, + .discover_descr_from_pyobject = discover_descriptor_from_pyfloat, + .common_dtype = float_common_dtype, }; NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{ @@ -265,11 +275,15 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{ .tp_flags = Py_TPFLAGS_DEFAULT, .tp_name = "numpy._FloatAbstractDType", },}, - .abstract = 1, - .default_descr = float_default_descriptor, - .discover_descr_from_pyobject = discover_descriptor_from_pyfloat, - .common_dtype = float_common_dtype, - .kind = 'f', + .flags = NPY_DT_ABSTRACT, + .dt_slots = &pyfloatabstractdtype_slots, +}; + + +NPY_DType_Slots pycomplexabstractdtype_slots = { + .default_descr = complex_default_descriptor, + .discover_descr_from_pyobject = discover_descriptor_from_pycomplex, + .common_dtype = complex_common_dtype, }; NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{ @@ -278,9 +292,6 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{ .tp_flags = Py_TPFLAGS_DEFAULT, .tp_name = "numpy._ComplexAbstractDType", },}, - .abstract = 1, - .default_descr = complex_default_descriptor, - .discover_descr_from_pyobject = discover_descriptor_from_pycomplex, - .common_dtype = complex_common_dtype, - .kind = 'c', + .flags = NPY_DT_ABSTRACT, + .dt_slots = &pycomplexabstractdtype_slots, }; diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c index 22050a56f..2c10274d1 100644 --- a/numpy/core/src/multiarray/array_coercion.c +++ b/numpy/core/src/multiarray/array_coercion.c @@ -154,7 +154,7 @@ _PyArray_MapPyTypeToDType( * We expect that user dtypes (for now) will subclass some numpy * scalar class to allow automatic discovery. */ - if (DType->legacy) { + if (NPY_DT_is_legacy(DType)) { /* * For legacy user dtypes, discovery relied on subclassing, but * arbitrary type objects are supported, so do nothing. @@ -257,8 +257,7 @@ discover_dtype_from_pyobject( * asked to attempt to do so later, if no other matching DType exists.) */ if ((Py_TYPE(obj) == fixed_DType->scalar_type) || - (fixed_DType->is_known_scalar_type != NULL && - fixed_DType->is_known_scalar_type(fixed_DType, Py_TYPE(obj)))) { + NPY_DT_call_is_known_scalar_type(fixed_DType, Py_TYPE(obj))) { Py_INCREF(fixed_DType); return fixed_DType; } @@ -346,10 +345,10 @@ find_scalar_descriptor( * chance. This allows for example string, to call `str(obj)` to * figure out the length for arbitrary objects. */ - descr = fixed_DType->discover_descr_from_pyobject(fixed_DType, obj); + descr = NPY_DT_call_discover_descr_from_pyobject(fixed_DType, obj); } else { - descr = DType->discover_descr_from_pyobject(DType, obj); + descr = NPY_DT_call_discover_descr_from_pyobject(DType, obj); } if (descr == NULL) { return NULL; @@ -425,7 +424,7 @@ PyArray_Pack(PyArray_Descr *descr, char *item, PyObject *value) return descr->f->setitem(value, item, &arr_fields); } PyArray_Descr *tmp_descr; - tmp_descr = DType->discover_descr_from_pyobject(DType, value); + tmp_descr = NPY_DT_call_discover_descr_from_pyobject(DType, value); Py_DECREF(DType); if (tmp_descr == NULL) { return -1; @@ -713,7 +712,7 @@ find_descriptor_from_array( return 0; } - if (NPY_UNLIKELY(DType->parametric && PyArray_ISOBJECT(arr))) { + if (NPY_UNLIKELY(NPY_DT_is_parametric(DType) && PyArray_ISOBJECT(arr))) { /* * We have one special case, if (and only if) the input array is of * object DType and the dtype is not fixed already but parametric. @@ -833,7 +832,7 @@ PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype) } if (new_dtype == NULL) { /* This is an object array but contained no elements, use default */ - new_dtype = new_DType->default_descr(new_DType); + new_dtype = NPY_DT_call_default_descr(new_DType); } } Py_DECREF(new_DType); @@ -1376,7 +1375,7 @@ PyArray_DiscoverDTypeAndShape( * the correct default. */ if (fixed_DType != NULL) { - *out_descr = fixed_DType->default_descr(fixed_DType); + *out_descr = NPY_DT_call_default_descr(fixed_DType); if (*out_descr == NULL) { goto fail; } diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c index c1b6d4e71..05a498160 100644 --- a/numpy/core/src/multiarray/array_method.c +++ b/numpy/core/src/multiarray/array_method.c @@ -72,7 +72,7 @@ default_resolve_descriptors( output_descrs[i] = ensure_dtype_nbo(input_descrs[i]); } else { - output_descrs[i] = dtype->default_descr(dtype); + output_descrs[i] = NPY_DT_call_default_descr(dtype); } if (NPY_UNLIKELY(output_descrs[i] == NULL)) { goto fail; @@ -106,7 +106,7 @@ default_resolve_descriptors( output_descrs[i] = ensure_dtype_nbo(input_descrs[i]); } else { - output_descrs[i] = common_dtype->default_descr(common_dtype); + output_descrs[i] = NPY_DT_call_default_descr(common_dtype); } if (NPY_UNLIKELY(output_descrs[i] == NULL)) { goto fail; @@ -232,7 +232,7 @@ validate_spec(PyArrayMethod_Spec *spec) "(method: %s)", spec->dtypes[i], spec->name); return -1; } - if (spec->dtypes[i]->abstract && i < spec->nin) { + if (NPY_DT_is_abstract(spec->dtypes[i]) && i < spec->nin) { PyErr_Format(PyExc_TypeError, "abstract DType %S are currently not allowed for inputs." "(method: %s defined at %s)", spec->dtypes[i], spec->name); @@ -328,7 +328,7 @@ fill_arraymethod_from_slots( return -1; } } - if (i >= meth->nin && res->dtypes[i]->parametric) { + if (i >= meth->nin && NPY_DT_is_parametric(res->dtypes[i])) { PyErr_Format(PyExc_TypeError, "must provide a `resolve_descriptors` function if any " "output DType is parametric. (method: %s)", @@ -585,7 +585,7 @@ boundarraymethod__resolve_descripors( */ int parametric = 0; for (int i = 0; i < nin + nout; i++) { - if (self->dtypes[i]->parametric) { + if (NPY_DT_is_parametric(self->dtypes[i])) { parametric = 1; break; } diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 841ed799d..f87520640 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -46,8 +46,8 @@ _array_find_python_scalar_type(PyObject *op) return PyArray_DescrFromType(NPY_CDOUBLE); } else if (PyLong_Check(op)) { - return PyArray_PyIntAbstractDType.discover_descr_from_pyobject( - &PyArray_PyIntAbstractDType, op); + return NPY_DT_call_discover_descr_from_pyobject( + &PyArray_PyIntAbstractDType, op); } return NULL; } diff --git a/numpy/core/src/multiarray/common_dtype.c b/numpy/core/src/multiarray/common_dtype.c index a88085f6f..109ed65d6 100644 --- a/numpy/core/src/multiarray/common_dtype.c +++ b/numpy/core/src/multiarray/common_dtype.c @@ -50,10 +50,10 @@ PyArray_CommonDType(PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtype2) PyArray_DTypeMeta *common_dtype; - common_dtype = dtype1->common_dtype(dtype1, dtype2); + common_dtype = NPY_DT_call_common_dtype(dtype1, dtype2); if (common_dtype == (PyArray_DTypeMeta *)Py_NotImplemented) { Py_DECREF(common_dtype); - common_dtype = dtype2->common_dtype(dtype2, dtype1); + common_dtype = NPY_DT_call_common_dtype(dtype2, dtype1); } if (common_dtype == NULL) { return NULL; @@ -128,7 +128,7 @@ reduce_dtypes_to_most_knowledgeable( Py_XSETREF(res, dtypes[low]); } else { - if (dtypes[high]->abstract) { + if (NPY_DT_is_abstract(dtypes[high])) { /* * Priority inversion, start with abstract, because if it * returns `other`, we can let other pass instead. @@ -138,7 +138,7 @@ reduce_dtypes_to_most_knowledgeable( dtypes[high] = tmp; } - Py_XSETREF(res, dtypes[low]->common_dtype(dtypes[low], dtypes[high])); + Py_XSETREF(res, NPY_DT_call_common_dtype(dtypes[low], dtypes[high])); if (res == NULL) { return NULL; } @@ -270,7 +270,7 @@ PyArray_PromoteDTypeSequence( * a higher category). We assume that the result is not in a lower * category. */ - PyArray_DTypeMeta *promotion = main_dtype->common_dtype( + PyArray_DTypeMeta *promotion = NPY_DT_call_common_dtype( main_dtype, dtypes[i]); if (promotion == NULL) { Py_XSETREF(result, NULL); diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index 067c356ca..8913f6451 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -70,10 +70,10 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) { PyObject *res; if (from == to) { - res = from->within_dtype_castingimpl; + res = NPY_DT_SLOTS(from)->within_dtype_castingimpl; } else { - res = PyDict_GetItemWithError(from->castingimpls, (PyObject *)to); + res = PyDict_GetItemWithError(NPY_DT_SLOTS(from)->castingimpls, (PyObject *)to); } if (res != NULL || PyErr_Occurred()) { Py_XINCREF(res); @@ -105,11 +105,11 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) return NULL; } else { - if (from->parametric || to->parametric) { + if (NPY_DT_is_parametric(from) || NPY_DT_is_parametric(to)) { Py_RETURN_NONE; } /* Reject non-legacy dtypes (they need to use the new API) */ - if (!from->legacy || !to->legacy) { + if (!NPY_DT_is_legacy(from) || !NPY_DT_is_legacy(to)) { Py_RETURN_NONE; } if (from != to) { @@ -119,7 +119,8 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) if (castfunc == NULL) { PyErr_Clear(); /* Remember that this cast is not possible */ - if (PyDict_SetItem(from->castingimpls, (PyObject *) to, Py_None) < 0) { + if (PyDict_SetItem(NPY_DT_SLOTS(from)->castingimpls, + (PyObject *) to, Py_None) < 0) { return NULL; } Py_RETURN_NONE; @@ -147,7 +148,8 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) Py_DECREF(res); return NULL; } - if (PyDict_SetItem(from->castingimpls, (PyObject *)to, res) < 0) { + if (PyDict_SetItem(NPY_DT_SLOTS(from)->castingimpls, + (PyObject *)to, res) < 0) { Py_DECREF(res); return NULL; } @@ -946,12 +948,12 @@ PyArray_CastDescrToDType(PyArray_Descr *descr, PyArray_DTypeMeta *given_DType) Py_INCREF(descr); return descr; } - if (!given_DType->parametric) { + if (!NPY_DT_is_parametric(given_DType)) { /* * Don't actually do anything, the default is always the result * of any cast. */ - return given_DType->default_descr(given_DType); + return NPY_DT_call_default_descr(given_DType); } if (PyObject_TypeCheck((PyObject *)descr, (PyTypeObject *)given_DType)) { Py_INCREF(descr); @@ -1040,7 +1042,7 @@ PyArray_FindConcatenationDescriptor( Py_SETREF(result, NULL); goto finish; } - Py_SETREF(result, common_dtype->common_instance(result, curr)); + Py_SETREF(result, NPY_DT_SLOTS(common_dtype)->common_instance(result, curr)); Py_DECREF(curr); if (result == NULL) { goto finish; @@ -1074,9 +1076,9 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) return NULL; } - if (!common_dtype->parametric) { + if (!NPY_DT_is_parametric(common_dtype)) { /* Note that this path loses all metadata */ - res = common_dtype->default_descr(common_dtype); + res = NPY_DT_call_default_descr(common_dtype); Py_DECREF(common_dtype); return res; } @@ -1098,7 +1100,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) * And find the common instance of the two inputs * NOTE: Common instance preserves metadata (normally and of one input) */ - res = common_dtype->common_instance(type1, type2); + res = NPY_DT_SLOTS(common_dtype)->common_instance(type1, type2); Py_DECREF(type1); Py_DECREF(type2); Py_DECREF(common_dtype); @@ -1503,7 +1505,7 @@ should_use_min_scalar(npy_intp narrs, PyArrayObject **arr, /* Compute the maximum "kinds" and whether everything is scalar */ for (npy_intp i = 0; i < narrs; ++i) { - if (!NPY_DTYPE(PyArray_DESCR(arr[i]))->legacy) { + if (!NPY_DT_is_legacy(NPY_DTYPE(PyArray_DESCR(arr[i])))) { return 0; } if (PyArray_NDIM(arr[i]) == 0) { @@ -1527,7 +1529,7 @@ should_use_min_scalar(npy_intp narrs, PyArrayObject **arr, * finish computing the max array kind */ for (npy_intp i = 0; i < ndtypes; ++i) { - if (!NPY_DTYPE(dtypes[i])->legacy) { + if (!NPY_DT_is_legacy(NPY_DTYPE(dtypes[i]))) { return 0; } int kind = dtype_kind_to_simplified_ordering(dtypes[i]->kind); @@ -1661,9 +1663,9 @@ PyArray_ResultType( goto error; } - if (common_dtype->abstract) { + if (NPY_DT_is_abstract(common_dtype)) { /* (ab)use default descriptor to define a default */ - PyArray_Descr *tmp_descr = common_dtype->default_descr(common_dtype); + PyArray_Descr *tmp_descr = NPY_DT_call_default_descr(common_dtype); if (tmp_descr == NULL) { goto error; } @@ -1676,9 +1678,9 @@ PyArray_ResultType( * NOTE: Code duplicates `PyArray_CastToDTypeAndPromoteDescriptors`, but * supports special handling of the abstract values. */ - if (!common_dtype->parametric) { + if (!NPY_DT_is_parametric(common_dtype)) { /* Note that this "fast" path loses all metadata */ - result = common_dtype->default_descr(common_dtype); + result = NPY_DT_call_default_descr(common_dtype); } else { result = PyArray_CastDescrToDType(all_descriptors[0], common_dtype); @@ -1700,13 +1702,13 @@ PyArray_ResultType( if (tmp == NULL) { goto error; } - curr = common_dtype->discover_descr_from_pyobject(common_dtype, tmp); + curr = NPY_DT_call_discover_descr_from_pyobject(common_dtype, tmp); Py_DECREF(tmp); } if (curr == NULL) { goto error; } - Py_SETREF(result, common_dtype->common_instance(result, curr)); + Py_SETREF(result, NPY_DT_SLOTS(common_dtype)->common_instance(result, curr)); Py_DECREF(curr); if (result == NULL) { goto error; @@ -1884,10 +1886,10 @@ PyArray_CastToDTypeAndPromoteDescriptors( if (result == NULL || ndescr == 1) { return result; } - if (!DType->parametric) { + if (!NPY_DT_is_parametric(DType)) { /* Note that this "fast" path loses all metadata */ Py_DECREF(result); - return DType->default_descr(DType); + return NPY_DT_call_default_descr(DType); } for (npy_intp i = 1; i < ndescr; i++) { @@ -1896,7 +1898,7 @@ PyArray_CastToDTypeAndPromoteDescriptors( Py_DECREF(result); return NULL; } - Py_SETREF(result, DType->common_instance(result, curr)); + Py_SETREF(result, NPY_DT_SLOTS(DType)->common_instance(result, curr)); Py_DECREF(curr); if (result == NULL) { return NULL; @@ -2066,7 +2068,7 @@ PyArray_ObjectType(PyObject *op, int minimum_type) if (dtype == NULL) { ret = NPY_DEFAULT_TYPE; } - else if (!NPY_DTYPE(dtype)->legacy) { + else if (!NPY_DT_is_legacy(NPY_DTYPE(dtype))) { /* * TODO: If we keep all type number style API working, by defining * type numbers always. We may be able to allow this again. @@ -2210,25 +2212,26 @@ PyArray_AddCastingImplementation(PyBoundArrayMethodObject *meth) meth->method->name); return -1; } - if (meth->dtypes[0]->within_dtype_castingimpl != NULL) { + if (NPY_DT_SLOTS(meth->dtypes[0])->within_dtype_castingimpl != NULL) { PyErr_Format(PyExc_RuntimeError, "A cast was already added for %S -> %S. (method: %s)", meth->dtypes[0], meth->dtypes[1], meth->method->name); return -1; } Py_INCREF(meth->method); - meth->dtypes[0]->within_dtype_castingimpl = (PyObject *)meth->method; + NPY_DT_SLOTS(meth->dtypes[0])->within_dtype_castingimpl = ( + (PyObject *)meth->method); return 0; } - if (PyDict_Contains(meth->dtypes[0]->castingimpls, + if (PyDict_Contains(NPY_DT_SLOTS(meth->dtypes[0])->castingimpls, (PyObject *)meth->dtypes[1])) { PyErr_Format(PyExc_RuntimeError, "A cast was already added for %S -> %S. (method: %s)", meth->dtypes[0], meth->dtypes[1], meth->method->name); return -1; } - if (PyDict_SetItem(meth->dtypes[0]->castingimpls, + if (PyDict_SetItem(NPY_DT_SLOTS(meth->dtypes[0])->castingimpls, (PyObject *)meth->dtypes[1], (PyObject *)meth->method) < 0) { return -1; } @@ -2331,7 +2334,7 @@ simple_cast_resolve_descriptors( PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2]) { - assert(dtypes[0]->legacy && dtypes[1]->legacy); + assert(NPY_DT_is_legacy(dtypes[0]) && NPY_DT_is_legacy(dtypes[1])); loop_descrs[0] = ensure_dtype_nbo(given_descrs[0]); if (loop_descrs[0] == NULL) { @@ -2345,7 +2348,7 @@ simple_cast_resolve_descriptors( } } else { - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); } if (self->casting != NPY_NO_CASTING) { @@ -2480,7 +2483,8 @@ add_numeric_cast(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) assert(slots[1].pfunc && slots[2].pfunc && slots[3].pfunc && slots[4].pfunc); /* Find the correct casting level, and special case no-cast */ - if (dtypes[0]->kind == dtypes[1]->kind && from_itemsize == to_itemsize) { + if (dtypes[0]->singleton->kind == dtypes[1]->singleton->kind + && from_itemsize == to_itemsize) { spec.casting = NPY_EQUIV_CASTING; /* When there is no casting (equivalent C-types) use byteswap loops */ @@ -2497,8 +2501,8 @@ add_numeric_cast(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to) else if (_npy_can_cast_safely_table[from->type_num][to->type_num]) { spec.casting = NPY_SAFE_CASTING; } - else if (dtype_kind_to_ordering(dtypes[0]->kind) <= - dtype_kind_to_ordering(dtypes[1]->kind)) { + else if (dtype_kind_to_ordering(dtypes[0]->singleton->kind) <= + dtype_kind_to_ordering(dtypes[1]->singleton->kind)) { spec.casting = NPY_SAME_KIND_CASTING; } else { @@ -2556,7 +2560,7 @@ cast_to_string_resolve_descriptors( * a multiple of eight. */ npy_intp size = -1; - switch (dtypes[0]->type_num) { + switch (given_descrs[0]->type_num) { case NPY_BOOL: case NPY_UBYTE: case NPY_BYTE: @@ -2568,18 +2572,18 @@ cast_to_string_resolve_descriptors( case NPY_LONG: case NPY_ULONGLONG: case NPY_LONGLONG: - assert(dtypes[0]->singleton->elsize <= 8); - assert(dtypes[0]->singleton->elsize > 0); - if (dtypes[0]->kind == 'b') { + assert(given_descrs[0]->elsize <= 8); + assert(given_descrs[0]->elsize > 0); + if (given_descrs[0]->kind == 'b') { /* 5 chars needed for cast to 'True' or 'False' */ size = 5; } - else if (dtypes[0]->kind == 'u') { - size = REQUIRED_STR_LEN[dtypes[0]->singleton->elsize]; + else if (given_descrs[0]->kind == 'u') { + size = REQUIRED_STR_LEN[given_descrs[0]->elsize]; } - else if (dtypes[0]->kind == 'i') { + else if (given_descrs[0]->kind == 'i') { /* Add character for sign symbol */ - size = REQUIRED_STR_LEN[dtypes[0]->singleton->elsize] + 1; + size = REQUIRED_STR_LEN[given_descrs[0]->elsize] + 1; } break; case NPY_HALF: @@ -3067,7 +3071,7 @@ structured_to_nonstructured_resolve_descriptors( /* Void dtypes always do the full cast. */ if (given_descrs[1] == NULL) { - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); /* * Special case strings here, it should be useless (and only actually * work for empty arrays). Possibly this should simply raise for @@ -3426,7 +3430,7 @@ object_to_any_resolve_descriptors( * here is that e.g. "M8" input is considered to be the DType class, * and by allowing it here, we go back to the "M8" instance. */ - if (dtypes[1]->parametric) { + if (NPY_DT_is_parametric(dtypes[1])) { PyErr_Format(PyExc_TypeError, "casting from object to the parametric DType %S requires " "the specified output dtype instance. " @@ -3434,7 +3438,7 @@ object_to_any_resolve_descriptors( "should be discovered automatically, however.", dtypes[1]); return -1; } - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); if (loop_descrs[1] == NULL) { return -1; } @@ -3490,7 +3494,7 @@ any_to_object_resolve_descriptors( PyArray_Descr *loop_descrs[2]) { if (given_descrs[1] == NULL) { - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); if (loop_descrs[1] == NULL) { return -1; } diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index b9d81e836..a46ab2f67 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -3996,7 +3996,7 @@ string_to_datetime_cast_resolve_descriptors( { if (given_descrs[1] == NULL) { /* NOTE: This doesn't actually work, and will error during the cast */ - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); if (loop_descrs[1] == NULL) { return -1; } diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 58aa608c3..50964dab8 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -20,6 +20,7 @@ #include "alloc.h" #include "assert.h" #include "npy_buffer.h" +#include "dtypemeta.h" /* * offset: A starting offset. @@ -3544,9 +3545,7 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArrayDescr_TypeFull = { .tp_new = arraydescr_new, },}, .type_num = -1, - .kind = '\0', - .abstract = 1, - .parametric = 0, - .singleton = 0, + .flags = NPY_DT_ABSTRACT, + .singleton = NULL, .scalar_type = NULL, }; diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c index 4ee721964..597468c50 100644 --- a/numpy/core/src/multiarray/dtypemeta.c +++ b/numpy/core/src/multiarray/dtypemeta.c @@ -27,7 +27,8 @@ dtypemeta_dealloc(PyArray_DTypeMeta *self) { Py_XDECREF(self->scalar_type); Py_XDECREF(self->singleton); - Py_XDECREF(self->castingimpls); + Py_XDECREF(NPY_DT_SLOTS(self)->castingimpls); + PyMem_Free(self->dt_slots); PyType_Type.tp_dealloc((PyObject *) self); } @@ -89,7 +90,7 @@ dtypemeta_traverse(PyArray_DTypeMeta *type, visitproc visit, void *arg) * defined types). It should be revised at that time. */ assert(0); - assert(!type->legacy && (PyTypeObject *)type != &PyArrayDescr_Type); + assert(!NPY_DT_is_legacy(type) && (PyTypeObject *)type != &PyArrayDescr_Type); Py_VISIT(type->singleton); Py_VISIT(type->scalar_type); return PyType_Type.tp_traverse((PyObject *)type, visit, arg); @@ -101,7 +102,7 @@ legacy_dtype_default_new(PyArray_DTypeMeta *self, PyObject *args, PyObject *kwargs) { /* TODO: This should allow endianess and possibly metadata */ - if (self->parametric) { + if (NPY_DT_is_parametric(self)) { /* reject parametric ones since we would need to get unit, etc. info */ PyErr_Format(PyExc_TypeError, "Preliminary-API: Flexible/Parametric legacy DType '%S' can " @@ -126,7 +127,7 @@ nonparametric_discover_descr_from_pyobject( PyArray_DTypeMeta *cls, PyObject *obj) { /* If the object is of the correct scalar type return our singleton */ - assert(!cls->parametric); + assert(!NPY_DT_is_parametric(cls)); Py_INCREF(cls->singleton); return cls->singleton; } @@ -382,7 +383,7 @@ static PyArray_DTypeMeta * default_builtin_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { assert(cls->type_num < NPY_NTYPES); - if (!other->legacy || other->type_num > cls->type_num) { + if (!NPY_DT_is_legacy(other) || other->type_num > cls->type_num) { /* * Let the more generic (larger type number) DType handle this * (note that half is after all others, which works out here.) @@ -409,7 +410,7 @@ static PyArray_DTypeMeta * string_unicode_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { assert(cls->type_num < NPY_NTYPES && cls != other); - if (!other->legacy || (!PyTypeNum_ISNUMBER(other->type_num) && + if (!NPY_DT_is_legacy(other) || (!PyTypeNum_ISNUMBER(other->type_num) && /* Not numeric so defer unless cls is unicode and other is string */ !(cls->type_num == NPY_UNICODE && other->type_num == NPY_STRING))) { Py_INCREF(Py_NotImplemented); @@ -536,7 +537,7 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) } Py_ssize_t name_length = strlen(scalar_name) + 14; - char *tp_name = malloc(name_length); + char *tp_name = PyMem_Malloc(name_length); if (tp_name == NULL) { PyErr_NoMemory(); return -1; @@ -544,11 +545,20 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) snprintf(tp_name, name_length, "numpy.dtype[%s]", scalar_name); - PyArray_DTypeMeta *dtype_class = malloc(sizeof(PyArray_DTypeMeta)); + NPY_DType_Slots *dt_slots = PyMem_Malloc(sizeof(NPY_DType_Slots)); + if (dt_slots == NULL) { + PyMem_Free(tp_name); + return -1; + } + memset(dt_slots, '\0', sizeof(NPY_DType_Slots)); + + PyArray_DTypeMeta *dtype_class = PyMem_Malloc(sizeof(PyArray_DTypeMeta)); if (dtype_class == NULL) { - PyDataMem_FREE(tp_name); + PyMem_Free(tp_name); + PyMem_Free(dt_slots); return -1; } + /* * Initialize the struct fields identically to static code by copying * a prototype instances for everything except our own fields which @@ -567,21 +577,21 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) .tp_base = &PyArrayDescr_Type, .tp_new = (newfunc)legacy_dtype_default_new, },}, - .legacy = 1, - .abstract = 0, /* this is a concrete DType */ + .flags = NPY_DT_LEGACY, /* Further fields are not common between DTypes */ }; memcpy(dtype_class, &prototype, sizeof(PyArray_DTypeMeta)); /* Fix name of the Type*/ ((PyTypeObject *)dtype_class)->tp_name = tp_name; + dtype_class->dt_slots = dt_slots; /* Let python finish the initialization (probably unnecessary) */ if (PyType_Ready((PyTypeObject *)dtype_class) < 0) { Py_DECREF(dtype_class); return -1; } - dtype_class->castingimpls = PyDict_New(); - if (dtype_class->castingimpls == NULL) { + dt_slots->castingimpls = PyDict_New(); + if (dt_slots->castingimpls == NULL) { Py_DECREF(dtype_class); return -1; } @@ -594,56 +604,54 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) Py_INCREF(descr->typeobj); dtype_class->scalar_type = descr->typeobj; dtype_class->type_num = descr->type_num; - dtype_class->type = descr->type; - dtype_class->f = descr->f; - dtype_class->kind = descr->kind; + dt_slots->f = *(descr->f); /* Set default functions (correct for most dtypes, override below) */ - dtype_class->default_descr = nonparametric_default_descr; - dtype_class->discover_descr_from_pyobject = ( + dt_slots->default_descr = nonparametric_default_descr; + dt_slots->discover_descr_from_pyobject = ( nonparametric_discover_descr_from_pyobject); - dtype_class->is_known_scalar_type = python_builtins_are_known_scalar_types; - dtype_class->common_dtype = default_builtin_common_dtype; - dtype_class->common_instance = NULL; + dt_slots->is_known_scalar_type = python_builtins_are_known_scalar_types; + dt_slots->common_dtype = default_builtin_common_dtype; + dt_slots->common_instance = NULL; if (PyTypeNum_ISSIGNED(dtype_class->type_num)) { /* Convert our scalars (raise on too large unsigned and NaN, etc.) */ - dtype_class->is_known_scalar_type = signed_integers_is_known_scalar_types; + dt_slots->is_known_scalar_type = signed_integers_is_known_scalar_types; } if (PyTypeNum_ISUSERDEF(descr->type_num)) { - dtype_class->common_dtype = legacy_userdtype_common_dtype_function; + dt_slots->common_dtype = legacy_userdtype_common_dtype_function; } else if (descr->type_num == NPY_OBJECT) { - dtype_class->common_dtype = object_common_dtype; + dt_slots->common_dtype = object_common_dtype; } else if (PyTypeNum_ISDATETIME(descr->type_num)) { /* Datetimes are flexible, but were not considered previously */ - dtype_class->parametric = NPY_TRUE; - dtype_class->default_descr = datetime_and_timedelta_default_descr; - dtype_class->discover_descr_from_pyobject = ( + dtype_class->flags |= NPY_DT_PARAMETRIC; + dt_slots->default_descr = datetime_and_timedelta_default_descr; + dt_slots->discover_descr_from_pyobject = ( discover_datetime_and_timedelta_from_pyobject); - dtype_class->common_dtype = datetime_common_dtype; - dtype_class->common_instance = datetime_type_promotion; + dt_slots->common_dtype = datetime_common_dtype; + dt_slots->common_instance = datetime_type_promotion; if (descr->type_num == NPY_DATETIME) { - dtype_class->is_known_scalar_type = datetime_known_scalar_types; + dt_slots->is_known_scalar_type = datetime_known_scalar_types; } } else if (PyTypeNum_ISFLEXIBLE(descr->type_num)) { - dtype_class->parametric = NPY_TRUE; + dtype_class->flags |= NPY_DT_PARAMETRIC; if (descr->type_num == NPY_VOID) { - dtype_class->default_descr = void_default_descr; - dtype_class->discover_descr_from_pyobject = ( + dt_slots->default_descr = void_default_descr; + dt_slots->discover_descr_from_pyobject = ( void_discover_descr_from_pyobject); - dtype_class->common_instance = void_common_instance; + dt_slots->common_instance = void_common_instance; } else { - dtype_class->default_descr = string_and_unicode_default_descr; - dtype_class->is_known_scalar_type = string_known_scalar_types; - dtype_class->discover_descr_from_pyobject = ( + dt_slots->default_descr = string_and_unicode_default_descr; + dt_slots->is_known_scalar_type = string_known_scalar_types; + dt_slots->discover_descr_from_pyobject = ( string_discover_descr_from_pyobject); - dtype_class->common_dtype = string_unicode_common_dtype; - dtype_class->common_instance = string_unicode_common_instance; + dt_slots->common_dtype = string_unicode_common_dtype; + dt_slots->common_instance = string_unicode_common_instance; } } @@ -660,17 +668,28 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) } +static PyObject * +dtypemeta_get_abstract(PyArray_DTypeMeta *self) { + return PyBool_FromLong(NPY_DT_is_abstract(self)); +} + +static PyObject * +dtypemeta_get_parametric(PyArray_DTypeMeta *self) { + return PyBool_FromLong(NPY_DT_is_parametric(self)); +} + /* - * Simple exposed information, defined for each DType (class). This is - * preliminary (the flags should also return bools). + * Simple exposed information, defined for each DType (class). */ +static PyGetSetDef dtypemeta_getset[] = { + {"_abstract", (getter)dtypemeta_get_abstract, NULL, NULL, NULL}, + {"_parametric", (getter)dtypemeta_get_parametric, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} +}; + static PyMemberDef dtypemeta_members[] = { - {"_abstract", - T_BYTE, offsetof(PyArray_DTypeMeta, abstract), READONLY, NULL}, {"type", T_OBJECT, offsetof(PyArray_DTypeMeta, scalar_type), READONLY, NULL}, - {"_parametric", - T_BYTE, offsetof(PyArray_DTypeMeta, parametric), READONLY, NULL}, {NULL, 0, 0, 0, NULL}, }; @@ -683,6 +702,7 @@ NPY_NO_EXPORT PyTypeObject PyArrayDTypeMeta_Type = { /* Types are garbage collected (see dtypemeta_is_gc documentation) */ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_doc = "Preliminary NumPy API: The Type of NumPy DTypes (metaclass)", + .tp_getset = dtypemeta_getset, .tp_members = dtypemeta_members, .tp_base = NULL, /* set to PyType_Type at import time */ .tp_alloc = dtypemeta_alloc, diff --git a/numpy/core/src/multiarray/dtypemeta.h b/numpy/core/src/multiarray/dtypemeta.h index 83cf7c07e..13e8f5e33 100644 --- a/numpy/core/src/multiarray/dtypemeta.h +++ b/numpy/core/src/multiarray/dtypemeta.h @@ -1,7 +1,65 @@ #ifndef _NPY_DTYPEMETA_H #define _NPY_DTYPEMETA_H + +/* DType flags, currently private, since we may just expose functions */ +#define NPY_DT_LEGACY 1 << 0 +#define NPY_DT_ABSTRACT 1 << 1 +#define NPY_DT_PARAMETRIC 1 << 2 + + +typedef struct { + /* DType methods, these could be moved into its own struct */ + discover_descr_from_pyobject_function *discover_descr_from_pyobject; + is_known_scalar_type_function *is_known_scalar_type; + default_descr_function *default_descr; + common_dtype_function *common_dtype; + common_instance_function *common_instance; + /* + * The casting implementation (ArrayMethod) to convert between two + * instances of this DType, stored explicitly for fast access: + */ + PyObject *within_dtype_castingimpl; + /* + * Dictionary of ArrayMethods representing most possible casts + * (structured and object are exceptions). + * This should potentially become a weak mapping in the future. + */ + PyObject *castingimpls; + + /* + * Storage for `descr->f`, since we may need to allow some customizatoin + * here at least in a transition period and we need to set it on every + * dtype instance for backward compatibility. (Keep this at end) + */ + PyArray_ArrFuncs f; +} NPY_DType_Slots; + + #define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr)) +#define NPY_DT_SLOTS(dtype) ((NPY_DType_Slots *)(dtype)->dt_slots) + +#define NPY_DT_is_legacy(dtype) ((dtype)->flags & NPY_DT_LEGACY) +#define NPY_DT_is_abstract(dtype) ((dtype)->flags & NPY_DT_ABSTRACT) +#define NPY_DT_is_parametric(dtype) ((dtype)->flags & NPY_DT_PARAMETRIC) + +/* + * Macros for convenient classmethod calls, since these require + * the DType both for the slot lookup and as first arguments. + * + * (Macros may include NULL checks where appropriate) + */ +#define NPY_DT_call_discover_descr_from_pyobject(dtype, obj) \ + NPY_DT_SLOTS(dtype)->discover_descr_from_pyobject(dtype, obj) +#define NPY_DT_call_is_known_scalar_type(dtype, obj) \ + (NPY_DT_SLOTS(dtype)->is_known_scalar_type != NULL \ + && NPY_DT_SLOTS(dtype)->is_known_scalar_type(dtype, obj)) +#define NPY_DT_call_default_descr(dtype) \ + NPY_DT_SLOTS(dtype)->default_descr(dtype) +#define NPY_DT_call_common_dtype(dtype, other) \ + NPY_DT_SLOTS(dtype)->common_dtype(dtype, other) + + /* * This function will hopefully be phased out or replaced, but was convenient * for incremental implementation of new DTypes based on DTypeMeta. diff --git a/numpy/core/src/multiarray/usertypes.c b/numpy/core/src/multiarray/usertypes.c index 15d46800c..5602304e9 100644 --- a/numpy/core/src/multiarray/usertypes.c +++ b/numpy/core/src/multiarray/usertypes.c @@ -388,7 +388,7 @@ legacy_userdtype_common_dtype_function( { int skind1 = NPY_NOSCALAR, skind2 = NPY_NOSCALAR, skind; - if (!other->legacy) { + if (!NPY_DT_is_legacy(other)) { /* legacy DTypes can always defer to new style ones */ Py_INCREF(Py_NotImplemented); return (PyArray_DTypeMeta *)Py_NotImplemented; @@ -422,7 +422,7 @@ legacy_userdtype_common_dtype_function( */ /* Convert the 'kind' char into a scalar kind */ - switch (cls->kind) { + switch (cls->singleton->kind) { case 'b': skind1 = NPY_BOOL_SCALAR; break; @@ -439,7 +439,7 @@ legacy_userdtype_common_dtype_function( skind1 = NPY_COMPLEX_SCALAR; break; } - switch (other->kind) { + switch (other->singleton->kind) { case 'b': skind2 = NPY_BOOL_SCALAR; break; diff --git a/numpy/core/src/umath/_scaled_float_dtype.c b/numpy/core/src/umath/_scaled_float_dtype.c index 6ef7ea88f..4eb8eb0a9 100644 --- a/numpy/core/src/umath/_scaled_float_dtype.c +++ b/numpy/core/src/umath/_scaled_float_dtype.c @@ -56,14 +56,14 @@ sfloat_default_descr(PyArray_DTypeMeta *NPY_UNUSED(cls)) static PyArray_Descr * sfloat_discover_from_pyobject(PyArray_DTypeMeta *cls, PyObject *NPY_UNUSED(obj)) { - return cls->default_descr(cls); + return sfloat_default_descr(cls); } static PyArray_DTypeMeta * sfloat_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { - if (other->legacy && other->type_num == NPY_DOUBLE) { + if (NPY_DT_is_legacy(other) && other->type_num == NPY_DOUBLE) { Py_INCREF(cls); return cls; } @@ -122,9 +122,17 @@ sfloat_setitem(PyObject *obj, char *data, PyArrayObject *arr) } -static PyArray_ArrFuncs arrfuncs = { - .getitem = (PyArray_GetItemFunc *)&sfloat_getitem, - .setitem = (PyArray_SetItemFunc *)&sfloat_setitem, +/* Special DType methods and the descr->f slot storage */ +NPY_DType_Slots sfloat_slots = { + .default_descr = &sfloat_default_descr, + .discover_descr_from_pyobject = &sfloat_discover_from_pyobject, + .is_known_scalar_type = &sfloat_is_known_scalar_type, + .common_dtype = &sfloat_common_dtype, + .common_instance = &sfloat_common_instance, + .f = { + .getitem = (PyArray_GetItemFunc *)&sfloat_getitem, + .setitem = (PyArray_SetItemFunc *)&sfloat_setitem, + } }; @@ -133,7 +141,7 @@ static PyArray_SFloatDescr SFloatSingleton = {{ .alignment = _ALIGN(double), .flags = NPY_USE_GETITEM|NPY_USE_SETITEM, .type_num = -1, - .f = &arrfuncs, + .f = &sfloat_slots.f, .byteorder = '|', /* do not bother with byte-swapping... */ }, .scaling = 1, @@ -231,17 +239,9 @@ static PyArray_DTypeMeta PyArray_SFloatDType = {{{ .tp_basicsize = sizeof(PyArray_SFloatDescr), }}, .type_num = -1, - .abstract = 0, - .legacy = 0, - .parametric = 1, - .f = &arrfuncs, .scalar_type = NULL, - /* Special methods: */ - .default_descr = &sfloat_default_descr, - .discover_descr_from_pyobject = &sfloat_discover_from_pyobject, - .is_known_scalar_type = &sfloat_is_known_scalar_type, - .common_dtype = &sfloat_common_dtype, - .common_instance = &sfloat_common_instance, + .flags = NPY_DT_PARAMETRIC, + .dt_slots = &sfloat_slots, }; @@ -385,11 +385,11 @@ float_to_from_sfloat_resolve_descriptors( PyArray_Descr *NPY_UNUSED(given_descrs[2]), PyArray_Descr *loop_descrs[2]) { - loop_descrs[0] = dtypes[0]->default_descr(dtypes[0]); + loop_descrs[0] = NPY_DT_call_default_descr(dtypes[0]); if (loop_descrs[0] == NULL) { return -1; } - loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]); + loop_descrs[1] = NPY_DT_call_default_descr(dtypes[1]); if (loop_descrs[1] == NULL) { return -1; } @@ -476,8 +476,8 @@ get_sfloat_dtype(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args)) if (PyType_Ready((PyTypeObject *)&PyArray_SFloatDType) < 0) { return NULL; } - PyArray_SFloatDType.castingimpls = PyDict_New(); - if (PyArray_SFloatDType.castingimpls == NULL) { + NPY_DT_SLOTS(&PyArray_SFloatDType)->castingimpls = PyDict_New(); + if (NPY_DT_SLOTS(&PyArray_SFloatDType)->castingimpls == NULL) { return NULL; } diff --git a/numpy/core/src/umath/dispatching.c b/numpy/core/src/umath/dispatching.c index e63780458..6ac4cb913 100644 --- a/numpy/core/src/umath/dispatching.c +++ b/numpy/core/src/umath/dispatching.c @@ -194,7 +194,7 @@ resolve_implementation_info(PyUFuncObject *ufunc, if (given_dtype == resolver_dtype) { continue; } - if (!resolver_dtype->abstract) { + if (!NPY_DT_is_abstract(resolver_dtype)) { matches = NPY_FALSE; break; } @@ -208,10 +208,11 @@ resolve_implementation_info(PyUFuncObject *ufunc, * * Continuing here allows a promoter to handle reduce-like * promotions explicitly if necessary. - * TODO: The `!resolver_dtype->abstract` currently ensures that - * this is a promoter. If we allow ArrayMethods to use - * abstract DTypes, we may have to reject it here or the - * ArrayMethod has to implement the reduce promotion. + * TODO: The `!NPY_DT_is_abstract(resolver_dtype)` currently + * ensures that this is a promoter. If we allow + * `ArrayMethods` to use abstract DTypes, we may have to + * reject it here or the `ArrayMethod` has to implement + * the reduce promotion. */ continue; } @@ -277,8 +278,8 @@ resolve_implementation_info(PyUFuncObject *ufunc, * If both are concrete and not identical, this is * ambiguous. */ - else if (!((PyArray_DTypeMeta *)prev_dtype)->abstract && - !((PyArray_DTypeMeta *)new_dtype)->abstract) { + else if (!NPY_DT_is_abstract((PyArray_DTypeMeta *)prev_dtype) && + !NPY_DT_is_abstract((PyArray_DTypeMeta *)new_dtype)) { /* * Ambiguous unless the are identical (checked above), * but since they are concrete it does not matter which @@ -389,7 +390,8 @@ _make_new_typetup( none_count++; } else { - if (!signature[i]->legacy || signature[i]->abstract) { + if (!NPY_DT_is_legacy(signature[i]) + || NPY_DT_is_abstract(signature[i])) { /* * The legacy type resolution can't deal with these. * This path will return `None` or so in the future to @@ -643,7 +645,7 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc, */ Py_INCREF(signature[i]); Py_XSETREF(op_dtypes[i], signature[i]); - assert(i >= ufunc->nin || !signature[i]->abstract); + assert(i >= ufunc->nin || !NPY_DT_is_abstract(signature[i])); } } diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c index e5043aa71..c7719957b 100644 --- a/numpy/core/src/umath/legacy_array_method.c +++ b/numpy/core/src/umath/legacy_array_method.c @@ -14,6 +14,7 @@ #include "array_method.h" #include "dtype_transfer.h" #include "legacy_array_method.h" +#include "dtypemeta.h" typedef struct { @@ -134,7 +135,7 @@ simple_legacy_resolve_descriptors( output_descrs[i] = output_descrs[0]; } else { - output_descrs[i] = dtypes[i]->default_descr(dtypes[i]); + output_descrs[i] = NPY_DT_call_default_descr(dtypes[i]); } if (output_descrs[i] == NULL) { goto fail; @@ -221,7 +222,7 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc, NPY_ITEM_REFCOUNT | NPY_ITEM_IS_POINTER | NPY_NEEDS_PYAPI)) { flags |= NPY_METH_REQUIRES_PYAPI; } - if (signature[i]->parametric) { + if (NPY_DT_is_parametric(signature[i])) { any_output_flexible = 1; } } diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 5a32ae603..bed303a86 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -957,7 +957,7 @@ convert_ufunc_arguments(PyUFuncObject *ufunc, out_op_DTypes[i] = NPY_DTYPE(PyArray_DESCR(out_op[i])); Py_INCREF(out_op_DTypes[i]); - if (!out_op_DTypes[i]->legacy) { + if (!NPY_DT_is_legacy(out_op_DTypes[i])) { *allow_legacy_promotion = NPY_FALSE; } if (PyArray_NDIM(out_op[i]) == 0) { @@ -4274,7 +4274,7 @@ _get_dtype(PyObject *dtype_obj) { return NULL; } PyArray_DTypeMeta *out = NPY_DTYPE(descr); - if (NPY_UNLIKELY(!out->legacy)) { + if (NPY_UNLIKELY(!NPY_DT_is_legacy(out))) { /* TODO: this path was unreachable when added. */ PyErr_SetString(PyExc_TypeError, "Cannot pass a new user DType instance to the `dtype` or " @@ -4392,7 +4392,7 @@ _get_fixed_signature(PyUFuncObject *ufunc, if (signature[i] == NULL) { return -1; } - else if (i < nin && signature[i]->abstract) { + else if (i < nin && NPY_DT_is_abstract(signature[i])) { /* * We reject abstract input signatures for now. These * can probably be defined by finding the common DType with |