Skip to content

Commit f353371

Browse files
authored
Merge pull request numpy#19404 from mattip/nep49-3
NEP: update NEP with the PyDataMem_Handler struct as implemented in the PR
2 parents e02af0b + e65c6e0 commit f353371

File tree

1 file changed

+62
-81
lines changed

1 file changed

+62
-81
lines changed

doc/neps/nep-0049.rst

Lines changed: 62 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,31 @@ NumPy C-API functions
118118
119119
typedef struct {
120120
char name[128]; /* multiple of 64 to keep the struct aligned */
121-
PyDataMem_AllocFunc *alloc;
122-
PyDataMem_ZeroedAllocFunc *zeroed_alloc;
123-
PyDataMem_FreeFunc *free;
124-
PyDataMem_ReallocFunc *realloc;
121+
PyDataMemAllocator allocator;
125122
} PyDataMem_Handler;
126123
127-
where the function's signatures are
124+
where the allocator structure is
128125

129126
.. code-block:: c
130127
131-
typedef void *(PyDataMem_AllocFunc)(size_t size);
132-
typedef void *(PyDataMem_ZeroedAllocFunc)(size_t nelems, size_t elsize);
133-
typedef void (PyDataMem_FreeFunc)(void *ptr, size_t size);
134-
typedef void *(PyDataMem_ReallocFunc)(void *ptr, size_t size);
128+
/* The declaration of free differs from PyMemAllocatorEx */
129+
typedef struct {
130+
void *ctx;
131+
void* (*malloc) (void *ctx, size_t size);
132+
void* (*calloc) (void *ctx, size_t nelem, size_t elsize);
133+
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
134+
void (*free) (void *ctx, void *ptr, size_t size);
135+
} PyDataMemAllocator;
136+
137+
The use of a ``size`` parameter in ``free`` differentiates this struct from
138+
the :c:type:`PyMemAllocatorEx` struct in Python. This call signature is
139+
used internally in NumPy currently, and also in other places for instance
140+
`C++98 <https://en.cppreference.com/w/cpp/memory/allocator/deallocate>`,
141+
`C++11 <https://en.cppreference.com/w/cpp/memory/allocator_traits/deallocate>`, and
142+
`Rust (allocator_api) <https://doc.rust-lang.org/std/alloc/trait.Allocator.html#tymethod.deallocate>`.
143+
144+
The consumer of the `PyDataMemAllocator` interface must keep track of ``size`` and make sure it is
145+
consistent with the parameter passed to the ``(m|c|re)alloc`` functions.
135146

136147
.. c:function:: const PyDataMem_Handler * PyDataMem_SetHandler(PyDataMem_Handler *handler)
137148
@@ -162,8 +173,16 @@ the ``sz`` argument is correct.
162173
#include <numpy/arrayobject.h>
163174
NPY_NO_EXPORT void *
164175
165-
shift_alloc(size_t sz) {
166-
char *real = (char *)malloc(sz + 64);
176+
typedef struct {
177+
void *(*malloc)(size_t);
178+
void *(*calloc)(size_t, size_t);
179+
void *(*realloc)(void *, size_t);
180+
void (*free)(void *);
181+
} Allocator;
182+
183+
NPY_NO_EXPORT void *
184+
shift_alloc(Allocator *ctx, size_t sz) {
185+
char *real = (char *)ctx->malloc(sz + 64);
167186
if (real == NULL) {
168187
return NULL;
169188
}
@@ -172,8 +191,8 @@ the ``sz`` argument is correct.
172191
}
173192
174193
NPY_NO_EXPORT void *
175-
shift_zero(size_t sz, size_t cnt) {
176-
char *real = (char *)calloc(sz + 64, cnt);
194+
shift_zero(Allocator *ctx, size_t sz, size_t cnt) {
195+
char *real = (char *)ctx->calloc(sz + 64, cnt);
177196
if (real == NULL) {
178197
return NULL;
179198
}
@@ -183,45 +202,44 @@ the ``sz`` argument is correct.
183202
}
184203
185204
NPY_NO_EXPORT void
186-
shift_free(void * p, npy_uintp sz) {
205+
shift_free(Allocator *ctx, void * p, npy_uintp sz) {
187206
if (p == NULL) {
188207
return ;
189208
}
190209
char *real = (char *)p - 64;
191210
if (strncmp(real, "originally allocated", 20) != 0) {
192211
fprintf(stdout, "uh-oh, unmatched shift_free, "
193212
"no appropriate prefix\\n");
194-
/* Make the C runtime crash by calling free on the wrong address */
195-
free((char *)p + 10);
196-
/* free(real); */
213+
/* Make C runtime crash by calling free on the wrong address */
214+
ctx->free((char *)p + 10);
215+
/* ctx->free(real); */
197216
}
198217
else {
199-
int i = atoi(real +20);
218+
npy_uintp i = (npy_uintp)atoi(real +20);
200219
if (i != sz) {
201-
fprintf(stderr, "uh-oh, unmatched "
202-
"shift_free(ptr, %d) but allocated %d\\n", sz, i);
203-
/* Make the C runtime crash by calling free on the wrong address */
204-
/* free((char *)p + 10); */
205-
free(real);
220+
fprintf(stderr, "uh-oh, unmatched shift_free"
221+
"(ptr, %ld) but allocated %ld\\n", sz, i);
222+
/* This happens in some places, only print */
223+
ctx->free(real);
206224
}
207225
else {
208-
free(real);
226+
ctx->free(real);
209227
}
210228
}
211229
}
212230
213231
NPY_NO_EXPORT void *
214-
shift_realloc(void * p, npy_uintp sz) {
232+
shift_realloc(Allocator *ctx, void * p, npy_uintp sz) {
215233
if (p != NULL) {
216234
char *real = (char *)p - 64;
217235
if (strncmp(real, "originally allocated", 20) != 0) {
218236
fprintf(stdout, "uh-oh, unmatched shift_realloc\\n");
219237
return realloc(p, sz);
220238
}
221-
return (void *)((char *)realloc(real, sz + 64) + 64);
239+
return (void *)((char *)ctx->realloc(real, sz + 64) + 64);
222240
}
223241
else {
224-
char *real = (char *)realloc(p, sz + 64);
242+
char *real = (char *)ctx->realloc(p, sz + 64);
225243
if (real == NULL) {
226244
return NULL;
227245
}
@@ -231,63 +249,24 @@ the ``sz`` argument is correct.
231249
}
232250
}
233251
234-
static PyDataMem_Handler new_handler = {
235-
"secret_data_allocator",
236-
shift_alloc, /* alloc */
237-
shift_zero, /* zeroed_alloc */
238-
shift_free, /* free */
239-
shift_realloc /* realloc */
252+
static Allocator new_handler_ctx = {
253+
malloc,
254+
calloc,
255+
realloc,
256+
free
240257
};
241258
242-
static PyObject* mem_policy_test_prefix(PyObject *self, PyObject *args)
243-
{
244-
245-
if (!PyArray_Check(args)) {
246-
PyErr_SetString(PyExc_ValueError,
247-
"must be called with a numpy scalar or ndarray");
259+
static PyDataMem_Handler new_handler = {
260+
"secret_data_allocator",
261+
{
262+
&new_handler_ctx,
263+
shift_alloc, /* malloc */
264+
shift_zero, /* calloc */
265+
shift_realloc, /* realloc */
266+
shift_free /* free */
248267
}
249-
return PyUnicode_FromString(
250-
PyDataMem_GetHandlerName((PyArrayObject*)args));
251-
};
252-
253-
static PyObject* mem_policy_set_new_policy(PyObject *self, PyObject *args)
254-
{
255-
256-
const PyDataMem_Handler *old = PyDataMem_SetHandler(&new_handler);
257-
return PyUnicode_FromString(old->name);
258-
259268
};
260-
261-
static PyObject* mem_policy_set_old_policy(PyObject *self, PyObject *args)
262-
{
263-
264-
const PyDataMem_Handler *old = PyDataMem_SetHandler(NULL);
265-
return PyUnicode_FromString(old->name);
266-
267-
};
268-
269-
static PyMethodDef methods[] = {
270-
{"test_prefix", (PyCFunction)mem_policy_test_prefix, METH_O},
271-
{"set_new_policy", (PyCFunction)mem_policy_set_new_policy, METH_NOARGS},
272-
{"set_old_policy", (PyCFunction)mem_policy_set_old_policy, METH_NOARGS},
273-
{ NULL }
274-
};
275-
276-
static struct PyModuleDef moduledef = {
277-
PyModuleDef_HEAD_INIT,
278-
"mem_policy", /* m_name */
279-
NULL, /* m_doc */
280-
-1, /* m_size */
281-
methods, /* m_methods */
282-
};
283-
284-
PyMODINIT_FUNC
285-
PyInit_mem_policy(void) {
286-
PyObject *mod = PyModule_Create(&moduledef);
287-
import_array();
288-
return mod;
289-
}
290-
269+
'''
291270
292271
Related Work
293272
------------
@@ -315,7 +294,9 @@ mechanism. The PR was merged with no example code for using these features.
315294
Discussion
316295
----------
317296
318-
Not yet discussed on the mailing list.
297+
The discussion on the mailing list led to the ``PyDataMemAllocator`` struct
298+
with a ``context`` field like :c:type:`PyMemAllocatorEx` but with a different
299+
signature for ``free``.
319300
320301
321302
References and Footnotes

0 commit comments

Comments
 (0)