Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 120 additions & 17 deletions Doc/c-api/memory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,37 @@ The default raw memory block allocator uses the following functions:
If *p* is *NULL*, no operation is performed.


.. c:function:: void* PyMem_RawAlignedAlloc(size_t alignment, size_t n)

Allocates *n* bytes aligned to *alignment* bytes and returns a pointer of
type :c:type:`void\*` to the allocated memory, or *NULL* if the request
fails.

*alignment* must be a power of 2, multiple of ``sizeof(void*)`` and greater
than 0. If the *alignment* is invalid, the function fails with an assertion
error in debug mode, or returns *NULL* in release mode.

Requesting zero bytes returns a distinct non-*NULL* pointer if possible, as
if ``PyMem_RawAlignedAlloc(alignment, 1)`` had been called instead. The
memory will not have been initialized in any way.

The allocated memory block must be released by :c:func:`PyMem_RawAlignedFree`.

.. versionadded:: 3.7


.. c:function:: void PyMem_RawAlignedFree(void *p)

Frees the memory block pointed to by *p*, which must have been returned by a
previous call to :c:func:`PyMem_RawAlignedAlloc`. Otherwise, or if
``PyMem_RawAlignedFree(p)`` has been called before, undefined behavior
occurs.

If *p* is *NULL*, no operation is performed.

.. versionadded:: 3.7


.. _memoryinterface:

Memory Interface
Expand Down Expand Up @@ -224,6 +255,37 @@ By default, these functions use :ref:`pymalloc memory allocator <pymalloc>`.

If *p* is *NULL*, no operation is performed.

.. c:function:: void* PyMem_AlignedAlloc(size_t alignment, size_t n)

Allocates *n* bytes aligned to *alignment* bytes and returns a pointer of
type :c:type:`void\*` to the allocated memory, or *NULL* if the request
fails.

*alignment* must be a power of 2, multiple of ``sizeof(void*)`` and greater
than 0. If the *alignment* is invalid, the function fails with an assertion
error in debug mode, or returns *NULL* in release mode.

Requesting zero bytes returns a distinct non-*NULL* pointer if possible, as
if ``PyMem_AlignedAlloc(alignment, 1)`` had been called instead. The
memory will not have been initialized in any way.

The allocated memory block must be released by :c:func:`PyMem_AlignedFree`.

.. versionadded:: 3.7


.. c:function:: void PyMem_AlignedFree(void *p)

Frees the memory block pointed to by *p*, which must have been returned by a
previous call to :c:func:`PyMem_AlignedAlloc`. Otherwise, or if
``PyMem_AlignedFree(p)`` has been called before, undefined behavior
occurs.

If *p* is *NULL*, no operation is performed.

.. versionadded:: 3.7


The following type-oriented macros are provided for convenience. Note that
*TYPE* refers to any C type.

Expand Down Expand Up @@ -326,30 +388,71 @@ By default, these functions use :ref:`pymalloc memory allocator <pymalloc>`.
If *p* is *NULL*, no operation is performed.


.. c:function:: void* PyObject_AlignedAlloc(size_t alignment, size_t n)

Allocates *n* bytes aligned to *alignment* bytes and returns a pointer of
type :c:type:`void\*` to the allocated memory, or *NULL* if the request
fails.

*alignment* must be a power of 2, multiple of ``sizeof(void*)`` and greater
than 0. If the *alignment* is invalid, the function fails with an assertion
error in debug mode, or returns *NULL* in release mode.

Requesting zero bytes returns a distinct non-*NULL* pointer if possible, as
if ``PyObject_AlignedAlloc(alignment, 1)`` had been called instead. The
memory will not have been initialized in any way.

The allocated memory block must be released by
:c:func:`PyObject_AlignedFree`.

.. versionadded:: 3.7


.. c:function:: void PyObject_AlignedFree(void *p)

Frees the memory block pointed to by *p*, which must have been returned by a
previous call to :c:func:`PyObject_AlignedAlloc`. Otherwise, or if
``PyObject_AlignedFree(p)`` has been called before, undefined behavior
occurs.

If *p* is *NULL*, no operation is performed.

.. versionadded:: 3.7


Customize Memory Allocators
===========================

.. versionadded:: 3.4

.. c:type:: PyMemAllocatorEx
.. c:type:: PyMemAllocatorEx2

Structure used to describe a memory block allocator. The structure has
four fields:

+----------------------------------------------------------+---------------------------------------+
| Field | Meaning |
+==========================================================+=======================================+
| ``void *ctx`` | user context passed as first argument |
+----------------------------------------------------------+---------------------------------------+
| ``void* malloc(void *ctx, size_t size)`` | allocate a memory block |
+----------------------------------------------------------+---------------------------------------+
| ``void* calloc(void *ctx, size_t nelem, size_t elsize)`` | allocate a memory block initialized |
| | with zeros |
+----------------------------------------------------------+---------------------------------------+
| ``void* realloc(void *ctx, void *ptr, size_t new_size)`` | allocate or resize a memory block |
+----------------------------------------------------------+---------------------------------------+
| ``void free(void *ctx, void *ptr)`` | free a memory block |
+----------------------------------------------------------+---------------------------------------+
+-------------------------------------------------------------------+---------------------------------------+
| Field | Meaning |
+===================================================================+=======================================+
| ``void *ctx`` | user context passed as first argument |
+-------------------------------------------------------------------+---------------------------------------+
| ``void* malloc(void *ctx, size_t size)`` | allocate a memory block |
+-------------------------------------------------------------------+---------------------------------------+
| ``void* calloc(void *ctx, size_t nelem, size_t elsize)`` | allocate a memory block initialized |
| | with zeros |
+-------------------------------------------------------------------+---------------------------------------+
| ``void* realloc(void *ctx, void *ptr, size_t new_size)`` | allocate or resize a memory block |
+-------------------------------------------------------------------+---------------------------------------+
| ``void free(void *ctx, void *ptr)`` | free a memory block |
+-------------------------------------------------------------------+---------------------------------------+
| ``void* aligned_alloc(void *ctx, size_t alignment, size_t size)`` | allocate an aligned memory block |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do people have to provide aligned_alloc and aligned_free? Or can they leave those pointers NULL and get a default implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, you must provide all allocator functions, included aligned_alloc and aligned_free.

Technically, we can implement a fallback, but I'm not sure that I want to do that :-)

+-------------------------------------------------------------------+---------------------------------------+
| ``void aligned_free(void *ctx, void *ptr)`` | free an aligned memory block |
+-------------------------------------------------------------------+---------------------------------------+

.. versionchanged:: 3.7
The :c:type:`PyMemAllocatorEx` structure was renamed to
:c:type:`PyMemAllocatorEx2` and new ``aligned_alloc`` and
``aligned_free`` fields were added.

.. versionchanged:: 3.5
The :c:type:`PyMemAllocator` structure was renamed to
Expand Down Expand Up @@ -387,12 +490,12 @@ Customize Memory Allocators
* :c:func:`PyObject_Calloc`
* :c:func:`PyObject_Free`

.. c:function:: void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
.. c:function:: void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx2 *allocator)

Get the memory block allocator of the specified domain.


.. c:function:: void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
.. c:function:: void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx2 *allocator)

Set the memory block allocator of the specified domain.

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,10 @@ Deprecated
Changes in the C API
--------------------

- The :c:type:`PyMemAllocatorEx` structure was renamed to
:c:type:`PyMemAllocatorEx2` and new ``aligned_alloc`` and ``aligned_free``
fields were added.

- The type of results of :c:func:`PyThread_start_new_thread` and
:c:func:`PyThread_get_thread_ident`, and the *id* parameter of
:c:func:`PyThreadState_SetAsyncExc` changed from :c:type:`long` to
Expand Down
6 changes: 3 additions & 3 deletions Include/internal/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ extern "C" {

struct _pymem_runtime_state {
struct _allocator_runtime_state {
PyMemAllocatorEx mem;
PyMemAllocatorEx obj;
PyMemAllocatorEx raw;
PyMemAllocatorEx2 mem;
PyMemAllocatorEx2 obj;
PyMemAllocatorEx2 raw;
} allocators;
#ifdef WITH_PYMALLOC
/* Array of objects used to track chunks of memory (arenas). */
Expand Down
2 changes: 2 additions & 0 deletions Include/objimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ PyAPI_FUNC(void *) PyObject_Calloc(size_t nelem, size_t elsize);
#endif
PyAPI_FUNC(void *) PyObject_Realloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyObject_Free(void *ptr);
PyAPI_FUNC(void*) PyObject_AlignedAlloc(size_t alignment, size_t size);
PyAPI_FUNC(void) PyObject_AlignedFree(void *ptr);

#ifndef Py_LIMITED_API
/* This function returns the number of allocated memory blocks, regardless of size */
Expand Down
36 changes: 23 additions & 13 deletions Include/pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ PyAPI_FUNC(void *) PyMem_RawMalloc(size_t size);
PyAPI_FUNC(void *) PyMem_RawCalloc(size_t nelem, size_t elsize);
PyAPI_FUNC(void *) PyMem_RawRealloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyMem_RawFree(void *ptr);
PyAPI_FUNC(void*) PyMem_RawAlignedAlloc(size_t alignment, size_t size);
PyAPI_FUNC(void) PyMem_RawAlignedFree(void *ptr);

/* Configure the Python memory allocators. Pass NULL to use default
allocators. */
Expand Down Expand Up @@ -103,6 +105,8 @@ PyAPI_FUNC(void *) PyMem_Calloc(size_t nelem, size_t elsize);
#endif
PyAPI_FUNC(void *) PyMem_Realloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyMem_Free(void *ptr);
PyAPI_FUNC(void*) PyMem_AlignedAlloc(size_t alignment, size_t size);
PyAPI_FUNC(void) PyMem_AlignedFree(void *ptr);

#ifndef Py_LIMITED_API
PyAPI_FUNC(char *) _PyMem_RawStrdup(const char *str);
Expand Down Expand Up @@ -132,11 +136,11 @@ PyAPI_FUNC(char *) _PyMem_Strdup(const char *str);
*/

#define PyMem_New(type, n) \
( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
#define PyMem_NEW(type, n) \
( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )
( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )

/*
* The value of (p) is always clobbered by this macro regardless of success.
Expand All @@ -145,17 +149,17 @@ PyAPI_FUNC(char *) _PyMem_Strdup(const char *str);
* caller's memory error handler to not lose track of it.
*/
#define PyMem_Resize(p, type, n) \
( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
(type *) PyMem_Realloc((p), (n) * sizeof(type)) )
( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
(type *) PyMem_Realloc((p), (n) * sizeof(type)) )
#define PyMem_RESIZE(p, type, n) \
( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
(type *) PyMem_REALLOC((p), (n) * sizeof(type)) )
( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
(type *) PyMem_REALLOC((p), (n) * sizeof(type)) )

/* PyMem{Del,DEL} are left over from ancient days, and shouldn't be used
* anymore. They're just confusing aliases for PyMem_{Free,FREE} now.
*/
#define PyMem_Del PyMem_Free
#define PyMem_DEL PyMem_FREE
#define PyMem_Del PyMem_Free
#define PyMem_DEL PyMem_FREE

#ifndef Py_LIMITED_API
typedef enum {
Expand Down Expand Up @@ -184,11 +188,17 @@ typedef struct {

/* release a memory block */
void (*free) (void *ctx, void *ptr);
} PyMemAllocatorEx;

/* allocate an aligned memory block */
void* (*aligned_alloc) (void *ctx, size_t alignment, size_t size);

/* free an aligned memory block */
void (*aligned_free) (void *ctx, void *ptr);
} PyMemAllocatorEx2;

/* Get the memory block allocator of the specified domain. */
PyAPI_FUNC(void) PyMem_GetAllocator(PyMemAllocatorDomain domain,
PyMemAllocatorEx *allocator);
PyMemAllocatorEx2 *allocator);

/* Set the memory block allocator of the specified domain.

Expand All @@ -202,7 +212,7 @@ PyAPI_FUNC(void) PyMem_GetAllocator(PyMemAllocatorDomain domain,
PyMem_SetupDebugHooks() function must be called to reinstall the debug hooks
on top on the new allocator. */
PyAPI_FUNC(void) PyMem_SetAllocator(PyMemAllocatorDomain domain,
PyMemAllocatorEx *allocator);
PyMemAllocatorEx2 *allocator);

/* Setup hooks to detect bugs in the following Python memory allocator
functions:
Expand Down
17 changes: 12 additions & 5 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,19 +793,26 @@ def test_buffer_overflow(self):
regex = re.compile(regex, flags=re.DOTALL)
self.assertRegex(out, regex)

def test_api_misuse(self):
out = self.check('import _testcapi; _testcapi.pymem_api_misuse()')
regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
def check_api_misuse(self, func, alloc_api, free_api):
out = self.check(f'import _testcapi; _testcapi.{func}()')
regex = (r"Debug memory block at address p={ptr}: API '{alloc_api}'\n"
r" 16 bytes originally requested\n"
r" The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
r" The [0-9] pad bytes at tail={ptr} are FORBIDDENBYTE, as expected.\n"
r" The block was made by call #[0-9]+ to debug malloc/realloc.\n"
r" Data at p: cb cb cb .*\n"
r"\n"
r"Fatal Python error: bad ID: Allocated using API 'm', verified using API 'r'\n")
regex = regex.format(ptr=self.PTR_REGEX)
r"Fatal Python error: bad ID: Allocated using API '{alloc_api}',"
r" verified using API '{free_api}'\n")
regex = regex.format(ptr=self.PTR_REGEX, alloc_api=alloc_api, free_api=free_api)
self.assertRegex(out, regex)

def test_pymem_api_misuse(self):
self.check_api_misuse('pymem_api_misuse', 'm', 'r')

def test_pymem_aligned_api_misuse(self):
self.check_api_misuse('pymem_aligned_api_misuse', 'M', 'm')

def check_malloc_without_gil(self, code):
out = self.check(code)
expected = ('Fatal Python error: Python memory allocator called '
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Add PyMem_AlignedAlloc():

* Add aligned_alloc and aligned_free fields to PyMemAllocatorEx
* Rename PyMemAllocatorEx structure to PyMemAllocatorEx2 to make sure
that C extensions are upgraded to fill the new aligned_alloc and
aligned_free fields
* Add 6 new functions: PyMem_RawAlignedAlloc(), PyMem_RawAlignedFree(),
PyMem_AlignedAlloc(), PyMem_AlignedFree(), PyObject_AlignedAlloc() and
PyObject_AlignedFree().
Loading