Skip to content

gh-125543: Add an internal C API for dynamic arrays #125554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
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
233 changes: 233 additions & 0 deletions Include/internal/pycore_dynarray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#ifndef Py_INTERNAL_DYNARRAY_H
#define Py_INTERNAL_DYNARRAY_H
#ifdef __cplusplus
extern "C" {
#endif

#include "Python.h" // Py_ssize_t

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

#define _PyDynArray_DEFAULT_SIZE 16

/*
* Deallocator for items on a _PyDynArray structure. A NULL pointer
* will never be given to the deallocator.
*/
typedef void (*_PyDynArray_Deallocator)(void *);

/*
* Internal only dynamic array for CPython.
*/
typedef struct {
/*
* The actual items in the dynamic array.
* Don't access this field publicly to get
* items--use _PyDynArray_GET_ITEM() instead.
*/
void **items;
/*
* The length of the actual items array allocation.
*/
Py_ssize_t capacity;
/*
* The number of items in the array.
* Don't use this field publicly--use _PyDynArray_LENGTH()
*/
Py_ssize_t length;
/*
* The deallocator, set by one of the initializer functions.
* This may be NULL.
*/
_PyDynArray_Deallocator deallocator;
} _PyDynArray;


static inline void
_PyDynArray_ASSERT_VALID(_PyDynArray *array)
{
assert(array != NULL);
assert(array->items != NULL);
}

static inline void
_PyDynArray_ASSERT_INDEX(_PyDynArray *array, Py_ssize_t index)
{
// Ensure the index is valid
assert(index >= 0);
assert(index < array->length);
}

/*
* Initialize a dynamic array with an initial size and deallocator.
*
* If the deallocator is NULL, then nothing happens to items upon
* removal and upon array clearing.
*
* Returns -1 upon failure, 0 otherwise.
*/
PyAPI_FUNC(int)
_PyDynArray_InitWithSize(_PyDynArray *array,
_PyDynArray_Deallocator deallocator,
Py_ssize_t initial);

/*
* Append to the array.
*
* Returns -1 upon failure, 0 otherwise.
* If this fails, the deallocator is not ran on the item.
*/
PyAPI_FUNC(int) _PyDynArray_Append(_PyDynArray *array, void *item);

/*
* Insert an item at the target index. The index
* must currently be a valid index in the array.
*
* Returns -1 upon failure, 0 otherwise.
* If this fails, the deallocator is not ran on the item.
*/
PyAPI_FUNC(int)
_PyDynArray_Insert(_PyDynArray *array, Py_ssize_t index, void *item);


/*
* Clear all the fields on the array.
*
* Note that this does *not* free the actual dynamic array
* structure--use _PyDynArray_Free() for that.
*
* It's safe to call _PyDynArray_Init() or InitWithSize() again
* on the array after calling this.
*/
PyAPI_FUNC(void) _PyDynArray_Clear(_PyDynArray *array);

/*
* Set a value at index in the array.
*
* If an item already exists at the target index, the deallocator
* is called on it, if the array has one set.
*
* This cannot fail.
*/
PyAPI_FUNC(void)
_PyDynArray_Set(_PyDynArray *array, Py_ssize_t index, void *item);

/*
* Remove the item at the index, and call the deallocator on it (if the array
* has one set).
*
* This cannot fail.
*/
PyAPI_FUNC(void)
_PyDynArray_Remove(_PyDynArray *array, Py_ssize_t index);

/*
* Remove the item at the index *without* deallocating it, and
* return the item.
*
* This cannot fail.
*/
PyAPI_FUNC(void *)
_PyDynArray_Pop(_PyDynArray *array, Py_ssize_t index);

/*
* Clear all the fields on a dynamic array, and then
* free the dynamic array structure itself.
*
* The array must have been created by _PyDynArray_New()
*/
static inline void
_PyDynArray_Free(_PyDynArray *array)
{
_PyDynArray_ASSERT_VALID(array);
_PyDynArray_Clear(array);
PyMem_RawFree(array);
}

/*
* Equivalent to _PyDynArray_InitWithSize() with a default size of 16.
*
* Returns -1 upon failure, 0 otherwise.
*/
static inline int
_PyDynArray_Init(_PyDynArray *array, _PyDynArray_Deallocator deallocator)
{
return _PyDynArray_InitWithSize(array, deallocator, _PyDynArray_DEFAULT_SIZE);
}

/*
* Allocate and create a new dynamic array on the heap.
*
* The returned pointer should be freed with _PyDynArray_Free()
* If this function fails, it returns NULL.
*/
static inline _PyDynArray *
_PyDynArray_NewWithSize(_PyDynArray_Deallocator deallocator, Py_ssize_t initial)
{
_PyDynArray *array = PyMem_RawMalloc(sizeof(_PyDynArray));
if (array == NULL)
{
return NULL;
}

if (_PyDynArray_InitWithSize(array, deallocator, initial) < 0)
{
PyMem_RawFree(array);
return NULL;
}

_PyDynArray_ASSERT_VALID(array); // Sanity check
return array;
}

/*
* Equivalent to _PyDynArray_NewWithSize() with a size of 16.
*
* The returned array must be freed with _PyDynArray_Free().
* Returns NULL on failure.
*/
static inline _PyDynArray *
_PyDynArray_New(_PyDynArray_Deallocator deallocator)
{
return _PyDynArray_NewWithSize(deallocator, _PyDynArray_DEFAULT_SIZE);
}

/*
* Get an item from the array. This cannot fail.
*
* If the index is not valid, this is undefined behavior.
*/
static inline void *
_PyDynArray_GET_ITEM(_PyDynArray *array, Py_ssize_t index)
{
_PyDynArray_ASSERT_VALID(array);
_PyDynArray_ASSERT_INDEX(array, index);
return array->items[index];
}

/*
* Get the length of the array. This cannot fail.
*/
static inline Py_ssize_t
_PyDynArray_LENGTH(_PyDynArray *array)
{
_PyDynArray_ASSERT_VALID(array);
return array->length;
}

/*
* Pop the item at the end the array.
* This function cannot fail.
*/
static inline void *
_PyDynArray_PopTop(_PyDynArray *array)
{
return _PyDynArray_Pop(array, _PyDynArray_LENGTH(array) - 1);
}

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_DYNARRAY_H */
2 changes: 2 additions & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ PYTHON_OBJS= \
Python/critical_section.o \
Python/crossinterp.o \
Python/dynamic_annotations.o \
Python/dynarray.o \
Python/errors.o \
Python/flowgraph.o \
Python/frame.o \
Expand Down Expand Up @@ -1197,6 +1198,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_dict.h \
$(srcdir)/Include/internal/pycore_dict_state.h \
$(srcdir)/Include/internal/pycore_dtoa.h \
$(srcdir)/Include/internal/pycore_dynarray.h \
$(srcdir)/Include/internal/pycore_exceptions.h \
$(srcdir)/Include/internal/pycore_faulthandler.h \
$(srcdir)/Include/internal/pycore_fileutils.h \
Expand Down
Loading
Loading