/* C Extension module to test all aspects of PEP-3118. Written by Stefan Krah. */ #include "Python.h" /* struct module */ static PyObject *structmodule = …; static PyObject *Struct = …; static PyObject *calcsize = …; /* cache simple format string */ static const char *simple_fmt = …; static PyObject *simple_format = …; #define SIMPLE_FORMAT(fmt) … #define FIX_FORMAT(fmt) … /**************************************************************************/ /* NDArray Object */ /**************************************************************************/ static PyTypeObject NDArray_Type; #define NDArray_Check(v) … #define CHECK_LIST_OR_TUPLE(v) … #define PyMem_XFree(v) … /* Maximum number of dimensions. */ #define ND_MAX_NDIM … /* Check for the presence of suboffsets in the first dimension. */ #define HAVE_PTR(suboffsets) … /* Adjust ptr if suboffsets are present. */ #define ADJUST_PTR(ptr, suboffsets) … /* Default: NumPy style (strides), read-only, no var-export, C-style layout */ #define ND_DEFAULT … /* User configurable flags for the ndarray */ #define ND_VAREXPORT … /* User configurable flags for each base buffer */ #define ND_WRITABLE … #define ND_FORTRAN … #define ND_SCALAR … #define ND_PIL … #define ND_REDIRECT … #define ND_GETBUF_FAIL … #define ND_GETBUF_UNDEFINED … /* Internal flags for the base buffer */ #define ND_C … #define ND_OWN_ARRAYS … /* ndarray properties */ #define ND_IS_CONSUMER(nd) … /* ndbuf->flags properties */ #define ND_C_CONTIGUOUS(flags) … #define ND_FORTRAN_CONTIGUOUS(flags) … #define ND_ANY_CONTIGUOUS(flags) … /* getbuffer() requests */ #define REQ_INDIRECT(flags) … #define REQ_C_CONTIGUOUS(flags) … #define REQ_F_CONTIGUOUS(flags) … #define REQ_ANY_CONTIGUOUS(flags) … #define REQ_STRIDES(flags) … #define REQ_SHAPE(flags) … #define REQ_WRITABLE(flags) … #define REQ_FORMAT(flags) … /* Single node of a list of base buffers. The list is needed to implement changes in memory layout while exported buffers are active. */ static PyTypeObject NDArray_Type; struct ndbuf; ndbuf_t; NDArrayObject; static ndbuf_t * ndbuf_new(Py_ssize_t nitems, Py_ssize_t itemsize, Py_ssize_t offset, int flags) { … } static void ndbuf_free(ndbuf_t *ndbuf) { … } static void ndbuf_push(NDArrayObject *nd, ndbuf_t *elt) { … } static void ndbuf_delete(NDArrayObject *nd, ndbuf_t *elt) { … } static void ndbuf_pop(NDArrayObject *nd) { … } static PyObject * ndarray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { … } static void ndarray_dealloc(NDArrayObject *self) { … } static int ndarray_init_staticbuf(PyObject *exporter, NDArrayObject *nd, int flags) { … } static void init_flags(ndbuf_t *ndbuf) { … } /****************************************************************************/ /* Buffer/List conversions */ /****************************************************************************/ static Py_ssize_t *strides_from_shape(const ndbuf_t *, int flags); /* Get number of members in a struct: see issue #12740 */ PyPartialStructObject; static Py_ssize_t get_nmemb(PyObject *s) { … } /* Pack all items into the buffer of 'obj'. The 'format' parameter must be in struct module syntax. For standard C types, a single item is an integer. For compound types, a single item is a tuple of integers. */ static int pack_from_list(PyObject *obj, PyObject *items, PyObject *format, Py_ssize_t itemsize) { … } /* Pack single element */ static int pack_single(char *ptr, PyObject *item, const char *fmt, Py_ssize_t itemsize) { … } static void copy_rec(const Py_ssize_t *shape, Py_ssize_t ndim, Py_ssize_t itemsize, char *dptr, const Py_ssize_t *dstrides, const Py_ssize_t *dsuboffsets, char *sptr, const Py_ssize_t *sstrides, const Py_ssize_t *ssuboffsets, char *mem) { … } static int cmp_structure(Py_buffer *dest, Py_buffer *src) { … } /* Copy src to dest. Both buffers must have the same format, itemsize, ndim and shape. Copying is atomic, the function never fails with a partial copy. */ static int copy_buffer(Py_buffer *dest, Py_buffer *src) { … } /* Unpack single element */ static PyObject * unpack_single(char *ptr, const char *fmt, Py_ssize_t itemsize) { … } /* Unpack a multi-dimensional matrix into a nested list. Return a scalar for ndim = 0. */ static PyObject * unpack_rec(PyObject *unpack_from, char *ptr, PyObject *mview, char *item, const Py_ssize_t *shape, const Py_ssize_t *strides, const Py_ssize_t *suboffsets, Py_ssize_t ndim, Py_ssize_t itemsize) { … } static PyObject * ndarray_as_list(NDArrayObject *nd) { … } /****************************************************************************/ /* Initialize ndbuf */ /****************************************************************************/ /* State of a new ndbuf during initialization. 'OK' means that initialization is complete. 'PTR' means that a pointer has been initialized, but the state of the memory is still undefined and ndbuf->offset is disregarded. +-----------------+-----------+-------------+----------------+ | | ndbuf_new | init_simple | init_structure | +-----------------+-----------+-------------+----------------+ | next | OK (NULL) | OK | OK | +-----------------+-----------+-------------+----------------+ | prev | OK (NULL) | OK | OK | +-----------------+-----------+-------------+----------------+ | len | OK | OK | OK | +-----------------+-----------+-------------+----------------+ | offset | OK | OK | OK | +-----------------+-----------+-------------+----------------+ | data | PTR | OK | OK | +-----------------+-----------+-------------+----------------+ | flags | user | user | OK | +-----------------+-----------+-------------+----------------+ | exports | OK (0) | OK | OK | +-----------------+-----------+-------------+----------------+ | base.obj | OK (NULL) | OK | OK | +-----------------+-----------+-------------+----------------+ | base.buf | PTR | PTR | OK | +-----------------+-----------+-------------+----------------+ | base.len | len(data) | len(data) | OK | +-----------------+-----------+-------------+----------------+ | base.itemsize | 1 | OK | OK | +-----------------+-----------+-------------+----------------+ | base.readonly | 0 | OK | OK | +-----------------+-----------+-------------+----------------+ | base.format | NULL | OK | OK | +-----------------+-----------+-------------+----------------+ | base.ndim | 1 | 1 | OK | +-----------------+-----------+-------------+----------------+ | base.shape | NULL | NULL | OK | +-----------------+-----------+-------------+----------------+ | base.strides | NULL | NULL | OK | +-----------------+-----------+-------------+----------------+ | base.suboffsets | NULL | NULL | OK | +-----------------+-----------+-------------+----------------+ | base.internal | OK | OK | OK | +-----------------+-----------+-------------+----------------+ */ static Py_ssize_t get_itemsize(PyObject *format) { … } static char * get_format(PyObject *format) { … } static int init_simple(ndbuf_t *ndbuf, PyObject *items, PyObject *format, Py_ssize_t itemsize) { … } static Py_ssize_t * seq_as_ssize_array(PyObject *seq, Py_ssize_t len, int is_shape) { … } static Py_ssize_t * strides_from_shape(const ndbuf_t *ndbuf, int flags) { … } /* Bounds check: len := complete length of allocated memory offset := start of the array A single array element is indexed by: i = indices[0] * strides[0] + indices[1] * strides[1] + ... imin is reached when all indices[n] combined with positive strides are 0 and all indices combined with negative strides are shape[n]-1, which is the maximum index for the nth dimension. imax is reached when all indices[n] combined with negative strides are 0 and all indices combined with positive strides are shape[n]-1. */ static int verify_structure(Py_ssize_t len, Py_ssize_t itemsize, Py_ssize_t offset, const Py_ssize_t *shape, const Py_ssize_t *strides, Py_ssize_t ndim) { … } /* Convert a NumPy-style array to an array using suboffsets to stride in the first dimension. Requirements: ndim > 0. Contiguous example ================== Input: ------ shape = {2, 2, 3}; strides = {6, 3, 1}; suboffsets = NULL; data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; buf = &data[0] Output: ------- shape = {2, 2, 3}; strides = {sizeof(char *), 3, 1}; suboffsets = {0, -1, -1}; data = {p1, p2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; | | ^ ^ `---'---' | | | `---------------------' buf = &data[0] So, in the example the input resembles the three-dimensional array char v[2][2][3], while the output resembles an array of two pointers to two-dimensional arrays: char (*v[2])[2][3]. Non-contiguous example: ======================= Input (with offset and negative strides): ----------------------------------------- shape = {2, 2, 3}; strides = {-6, 3, -1}; offset = 8 suboffsets = NULL; data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; Output: ------- shape = {2, 2, 3}; strides = {-sizeof(char *), 3, -1}; suboffsets = {2, -1, -1}; newdata = {p1, p2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; | | ^ ^ ^ ^ `---'---' | | `- p2+suboffsets[0] | `-----------|--- p1+suboffsets[0] `---------------------' buf = &newdata[1] # striding backwards over the pointers. suboffsets[0] is the same as the offset that one would specify if the two {2, 3} subarrays were created directly, hence the name. */ static int init_suboffsets(ndbuf_t *ndbuf) { … } static void init_len(Py_buffer *base) { … } static int init_structure(ndbuf_t *ndbuf, PyObject *shape, PyObject *strides, Py_ssize_t ndim) { … } static ndbuf_t * init_ndbuf(PyObject *items, PyObject *shape, PyObject *strides, Py_ssize_t offset, PyObject *format, int flags) { … } /* initialize and push a new base onto the linked list */ static int ndarray_push_base(NDArrayObject *nd, PyObject *items, PyObject *shape, PyObject *strides, Py_ssize_t offset, PyObject *format, int flags) { … } #define PyBUF_UNUSED … static int ndarray_init(PyObject *self, PyObject *args, PyObject *kwds) { … } /* Push an additional base onto the linked list. */ static PyObject * ndarray_push(PyObject *self, PyObject *args, PyObject *kwds) { … } /* Pop a base from the linked list (if possible). */ static PyObject * ndarray_pop(PyObject *self, PyObject *dummy) { … } /**************************************************************************/ /* getbuffer */ /**************************************************************************/ static int ndarray_getbuf(NDArrayObject *self, Py_buffer *view, int flags) { … } static void ndarray_releasebuf(NDArrayObject *self, Py_buffer *view) { … } static PyBufferProcs ndarray_as_buffer = …; /**************************************************************************/ /* indexing/slicing */ /**************************************************************************/ static char * ptr_from_index(Py_buffer *base, Py_ssize_t index) { … } static PyObject * ndarray_item(NDArrayObject *self, Py_ssize_t index) { … } /* For each dimension, we get valid (start, stop, step, slicelength) quadruples from PySlice_GetIndicesEx(). Slicing NumPy arrays ==================== A pointer to an element in a NumPy array is defined by: ptr = (char *)buf + indices[0] * strides[0] + ... + indices[ndim-1] * strides[ndim-1] Adjust buf: ----------- Adding start[n] for each dimension effectively adds the constant: c = start[0] * strides[0] + ... + start[ndim-1] * strides[ndim-1] Therefore init_slice() adds all start[n] directly to buf. Adjust shape: ------------- Obviously shape[n] = slicelength[n] Adjust strides: --------------- In the original array, the next element in a dimension is reached by adding strides[n] to the pointer. In the sliced array, elements may be skipped, so the next element is reached by adding: strides[n] * step[n] Slicing PIL arrays ================== Layout: ------- In the first (zeroth) dimension, PIL arrays have an array of pointers to sub-arrays of ndim-1. Striding in the first dimension is done by getting the index of the nth pointer, dereference it and then add a suboffset to it. The arrays pointed to can best be seen a regular NumPy arrays. Adjust buf: ----------- In the original array, buf points to a location (usually the start) in the array of pointers. For the sliced array, start[0] can be added to buf in the same manner as for NumPy arrays. Adjust suboffsets: ------------------ Due to the dereferencing step in the addressing scheme, it is not possible to adjust buf for higher dimensions. Recall that the sub-arrays pointed to are regular NumPy arrays, so for each of those arrays adding start[n] effectively adds the constant: c = start[1] * strides[1] + ... + start[ndim-1] * strides[ndim-1] This constant is added to suboffsets[0]. suboffsets[0] in turn is added to each pointer right after dereferencing. Adjust shape and strides: ------------------------- Shape and strides are not influenced by the dereferencing step, so they are adjusted in the same manner as for NumPy arrays. Multiple levels of suboffsets ============================= For a construct like an array of pointers to array of pointers to sub-arrays of ndim-2: suboffsets[0] = start[1] * strides[1] suboffsets[1] = start[2] * strides[2] + ... */ static int init_slice(Py_buffer *base, PyObject *key, int dim) { … } static int copy_structure(Py_buffer *base) { … } static PyObject * ndarray_subscript(NDArrayObject *self, PyObject *key) { … } static int ndarray_ass_subscript(NDArrayObject *self, PyObject *key, PyObject *value) { … } static PyObject * slice_indices(PyObject *self, PyObject *args) { … } static PyMappingMethods ndarray_as_mapping = …; static PySequenceMethods ndarray_as_sequence = …; /**************************************************************************/ /* getters */ /**************************************************************************/ static PyObject * ssize_array_as_tuple(Py_ssize_t *array, Py_ssize_t len) { … } static PyObject * ndarray_get_flags(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_offset(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_obj(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_nbytes(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_readonly(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_itemsize(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_format(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_ndim(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_shape(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_strides(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_get_suboffsets(NDArrayObject *self, void *closure) { … } static PyObject * ndarray_c_contig(PyObject *self, PyObject *dummy) { … } static PyObject * ndarray_fortran_contig(PyObject *self, PyObject *dummy) { … } static PyObject * ndarray_contig(PyObject *self, PyObject *dummy) { … } static PyGetSetDef ndarray_getset [] = …; static PyObject * ndarray_tolist(PyObject *self, PyObject *dummy) { … } static PyObject * ndarray_tobytes(PyObject *self, PyObject *dummy) { … } /* add redundant (negative) suboffsets for testing */ static PyObject * ndarray_add_suboffsets(PyObject *self, PyObject *dummy) { … } /* Test PyMemoryView_FromBuffer(): return a memoryview from a static buffer. Obviously this is fragile and only one such view may be active at any time. Never use anything like this in real code! */ static char *infobuf = …; static PyObject * ndarray_memoryview_from_buffer(PyObject *self, PyObject *dummy) { … } /* Get a single item from bufobj at the location specified by seq. seq is a list or tuple of indices. The purpose of this function is to check other functions against PyBuffer_GetPointer(). */ static PyObject * get_pointer(PyObject *self, PyObject *args) { … } static PyObject * get_sizeof_void_p(PyObject *self, PyObject *Py_UNUSED(ignored)) { … } static char get_ascii_order(PyObject *order) { … } /* Get a contiguous memoryview. */ static PyObject * get_contiguous(PyObject *self, PyObject *args) { … } /* PyBuffer_ToContiguous() */ static PyObject * py_buffer_to_contiguous(PyObject *self, PyObject *args) { … } static int fmtcmp(const char *fmt1, const char *fmt2) { … } static int arraycmp(const Py_ssize_t *a1, const Py_ssize_t *a2, const Py_ssize_t *shape, Py_ssize_t ndim) { … } /* Compare two contiguous buffers for physical equality. */ static PyObject * cmp_contig(PyObject *self, PyObject *args) { … } static PyObject * is_contiguous(PyObject *self, PyObject *args) { … } static Py_hash_t ndarray_hash(PyObject *self) { … } static PyMethodDef ndarray_methods [] = …; static PyTypeObject NDArray_Type = …; /**************************************************************************/ /* StaticArray Object */ /**************************************************************************/ static PyTypeObject StaticArray_Type; StaticArrayObject; static char static_mem[12] = …; static Py_ssize_t static_shape[1] = …; static Py_ssize_t static_strides[1] = …; static Py_buffer static_buffer = …; static PyObject * staticarray_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { … } static int staticarray_init(PyObject *self, PyObject *args, PyObject *kwds) { … } static void staticarray_dealloc(StaticArrayObject *self) { … } /* Return a buffer for a PyBUF_FULL_RO request. Flags are not checked, which makes this object a non-compliant exporter! */ static int staticarray_getbuf(StaticArrayObject *self, Py_buffer *view, int flags) { … } static PyBufferProcs staticarray_as_buffer = …; static PyTypeObject StaticArray_Type = …; static struct PyMethodDef _testbuffer_functions[] = …; static struct PyModuleDef _testbuffermodule = …; static int _testbuffer_exec(PyObject *mod) { … } PyMODINIT_FUNC PyInit__testbuffer(void) { … }