978 lines
31 KiB
C
978 lines
31 KiB
C
|
#include "listobject.h"
|
||
|
|
||
|
/* This implements the C component of the Numba typed list. It is loosely
|
||
|
* inspired by the list implementation of the cpython list with some parts
|
||
|
* taken from the cpython slice implementation. The exact commit-id of the
|
||
|
* relevant files are:
|
||
|
*
|
||
|
* https://github.com/python/cpython/blob/51ddab8dae056867f3595ab3400bffc93f67c8d4/Objects/listobject.c
|
||
|
* https://github.com/python/cpython/blob/51ddab8dae056867f3595ab3400bffc93f67c8d4/Objects/sliceobject.c
|
||
|
*
|
||
|
* Algorithmically, this list is very similar to the cpython implementation so
|
||
|
* it should have the same performance (Big-O) characteristics for accessing,
|
||
|
* adding and removing elements/items. Specifically, it implements the same
|
||
|
* algorithms for list overallocation and growth. However, it never deals with
|
||
|
* PyObject types and instead must be typed with a type-size. As a result, the
|
||
|
* typed-list is type homogeneous and in contrast to the cpython version can
|
||
|
* not store a mixture of arbitrarily typed objects. Reference counting via the
|
||
|
* Numba Runtime (NRT) is supported and incrementing and decrementing functions
|
||
|
* are store as part of the struct and can be setup from the compiler level.
|
||
|
*
|
||
|
* Importantly, only a very limited subset of the cpython c functions have been
|
||
|
* ported over and the rest have been implemented (in Python) at the compiler
|
||
|
* level using the c functions provided. Additionally, initialization of, and
|
||
|
* iteration over, a ListIter is provided
|
||
|
*
|
||
|
* The following functions are implemented for the list:
|
||
|
*
|
||
|
* - Check valid index valid_index
|
||
|
* - Creation numba_list_new
|
||
|
* - Deletion numba_list_free
|
||
|
* - Accessing the length numba_list_length
|
||
|
* - Appending to the list numba_list_append
|
||
|
* - Getting an item numba_list_setitem
|
||
|
* - Setting an item numba_list_getitem
|
||
|
* - Resizing the list numba_list_resize
|
||
|
* - Deleting an item numba_list_delitem
|
||
|
* - Deleting a slice numba_list_delete_slice
|
||
|
*
|
||
|
* As you can see, only a single function for slices is implemented. The rest
|
||
|
* is all done entirely at the compiler level which then calls the c functions
|
||
|
* to mutate the list accordingly. Since slicing allows for replace, insert and
|
||
|
* delete operations over multiple items, we can simply implement those using
|
||
|
* the basic functions above.
|
||
|
*
|
||
|
* The following additional functions are implemented for the list, these are
|
||
|
* needed to make the list work within Numba.
|
||
|
*
|
||
|
* - Accessing the allocation numba_list_allocated
|
||
|
* - Copying an item copy_item
|
||
|
* - Calling incref on item list_incref_item
|
||
|
* - Calling decref on item list_decref_item
|
||
|
* - Set method table numba_list_set_method_table
|
||
|
*
|
||
|
* The following functions are implemented for the iterator:
|
||
|
*
|
||
|
* - Size of the iterator numba_list_iter_size
|
||
|
* - Initialization of iter numba_list_iter
|
||
|
* - Get next item from iter numba_list_iter_next
|
||
|
*
|
||
|
* Two methods are provided to query and set the 'is_mutable':
|
||
|
*
|
||
|
* - Query numba_list_is_mutable
|
||
|
* - Set numba_list_set_is_mutable
|
||
|
*
|
||
|
* Lastly a set of pure C level tests are provided which come in handy when
|
||
|
* needing to use valgrind and friends.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* Return status for the list functions.
|
||
|
*/
|
||
|
typedef enum {
|
||
|
LIST_OK = 0,
|
||
|
LIST_ERR_INDEX = -1,
|
||
|
LIST_ERR_NO_MEMORY = -2,
|
||
|
LIST_ERR_MUTATED = -3,
|
||
|
LIST_ERR_ITER_EXHAUSTED = -4,
|
||
|
LIST_ERR_IMMUTABLE = -5,
|
||
|
} ListStatus;
|
||
|
|
||
|
/* Copy an item from a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* dst: destination pointer
|
||
|
* src: source pointer
|
||
|
*/
|
||
|
static void
|
||
|
copy_item(NB_List *lp, char *dst, const char *src){
|
||
|
memcpy(dst, src, lp->item_size);
|
||
|
}
|
||
|
|
||
|
/* Increment a reference to an item in a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* item: the item to increment the reference for
|
||
|
*/
|
||
|
static void
|
||
|
list_incref_item(NB_List *lp, const char *item){
|
||
|
if (lp->methods.item_incref) {
|
||
|
lp->methods.item_incref(item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Decrement a reference to an item in a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* item: the item to decrement the reference for
|
||
|
*/
|
||
|
static void
|
||
|
list_decref_item(NB_List *lp, const char *item){
|
||
|
if (lp->methods.item_decref) {
|
||
|
lp->methods.item_decref(item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Setup the method table for a list.
|
||
|
*
|
||
|
* This function is used from the compiler level to initialize the internal
|
||
|
* method table.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* methods: the methods table to set up
|
||
|
*/
|
||
|
void
|
||
|
numba_list_set_method_table(NB_List *lp, list_type_based_methods_table *methods)
|
||
|
{
|
||
|
memcpy(&lp->methods, methods, sizeof(list_type_based_methods_table));
|
||
|
}
|
||
|
|
||
|
/* Check if a list index is valid.
|
||
|
*
|
||
|
* i: the index to check
|
||
|
* limit: the size of a list
|
||
|
*
|
||
|
* Adapted from CPython's valid_index().
|
||
|
*
|
||
|
* FIXME: need to find a way to inline this, even for Python 2.7 on Windows
|
||
|
*/
|
||
|
static int
|
||
|
valid_index(Py_ssize_t i, Py_ssize_t limit){
|
||
|
/* The cast to size_t lets us use just a single comparison
|
||
|
to check whether i is in the range: 0 <= i < limit.
|
||
|
|
||
|
See: Section 14.2 "Bounds Checking" in the Agner Fog
|
||
|
optimization manual found at:
|
||
|
https://www.agner.org/optimize/optimizing_cpp.pdf
|
||
|
*/
|
||
|
return (size_t) i < (size_t) limit;
|
||
|
}
|
||
|
|
||
|
/* Initialize a new list.
|
||
|
*
|
||
|
* out: pointer to hold an initialized list
|
||
|
* item_size: the size in bytes of the items in the list
|
||
|
* allocated: preallocation of the list in items
|
||
|
*
|
||
|
* This will allocate sufficient memory to hold the list structure and any
|
||
|
* items if requested (allocated != 0). See _listobject.h for more information
|
||
|
* on the NB_List struct.
|
||
|
*/
|
||
|
int
|
||
|
numba_list_new(NB_List **out, Py_ssize_t item_size, Py_ssize_t allocated){
|
||
|
NB_List *lp;
|
||
|
char *items;
|
||
|
// allocate memory to hold the struct
|
||
|
lp = malloc(aligned_size(sizeof(NB_List)));
|
||
|
if (lp == NULL) {
|
||
|
return LIST_ERR_NO_MEMORY;
|
||
|
}
|
||
|
// set up members
|
||
|
lp->size = 0;
|
||
|
lp->item_size = item_size;
|
||
|
lp->allocated = allocated;
|
||
|
lp->is_mutable = 1;
|
||
|
// set method table to zero */
|
||
|
memset(&lp->methods, 0x00, sizeof(list_type_based_methods_table));
|
||
|
// allocate memory to hold items, if requested
|
||
|
if (allocated != 0) {
|
||
|
items = malloc(aligned_size(lp->item_size * allocated));
|
||
|
// allocated was definitely not zero, if malloc returns NULL
|
||
|
// this is definitely an error
|
||
|
if (items == NULL) {
|
||
|
// free previously allocated struct to avoid leaking memory
|
||
|
free(lp);
|
||
|
return LIST_ERR_NO_MEMORY;
|
||
|
}
|
||
|
lp->items = items;
|
||
|
}
|
||
|
else {
|
||
|
// be explicit
|
||
|
lp->items = NULL;
|
||
|
}
|
||
|
*out = lp;
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
/* Free the memory associated with a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
*/
|
||
|
void
|
||
|
numba_list_free(NB_List *lp) {
|
||
|
// decref all items, if needed
|
||
|
Py_ssize_t i;
|
||
|
if (lp->methods.item_decref) {
|
||
|
for (i = 0; i < lp->size; i++) {
|
||
|
char *item = lp->items + lp->item_size * i;
|
||
|
list_decref_item(lp, item);
|
||
|
}
|
||
|
}
|
||
|
// free items and list
|
||
|
if (lp->items != NULL) {
|
||
|
free(lp->items);
|
||
|
}
|
||
|
free(lp);
|
||
|
}
|
||
|
|
||
|
/* Return the base pointer of the list items.
|
||
|
*/
|
||
|
char *
|
||
|
numba_list_base_ptr(NB_List *lp)
|
||
|
{
|
||
|
return lp->items;
|
||
|
}
|
||
|
|
||
|
/* Return the address of the list size.
|
||
|
*/
|
||
|
Py_ssize_t
|
||
|
numba_list_size_address(NB_List *lp)
|
||
|
{
|
||
|
return (Py_ssize_t)&lp->size;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Return the length of a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
*/
|
||
|
Py_ssize_t
|
||
|
numba_list_length(NB_List *lp) {
|
||
|
return lp->size;
|
||
|
}
|
||
|
|
||
|
/* Return the current allocation of a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
*/
|
||
|
Py_ssize_t
|
||
|
numba_list_allocated(NB_List *lp) {
|
||
|
return lp->allocated;
|
||
|
}
|
||
|
|
||
|
/* Return the mutability status of the list
|
||
|
*
|
||
|
* lp: a list
|
||
|
*
|
||
|
*/
|
||
|
int
|
||
|
numba_list_is_mutable(NB_List *lp){
|
||
|
return lp->is_mutable;
|
||
|
}
|
||
|
|
||
|
/* Set the is_mutable attribute
|
||
|
*
|
||
|
* lp: a list
|
||
|
* is_mutable: an int, 0(False) or 1(True)
|
||
|
*
|
||
|
*/
|
||
|
void
|
||
|
numba_list_set_is_mutable(NB_List *lp, int is_mutable){
|
||
|
lp->is_mutable = is_mutable;
|
||
|
}
|
||
|
|
||
|
/* Set an item in a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* index: the index of the item to set (must be in range 0 <= index < len(list))
|
||
|
* item: the item to set
|
||
|
*
|
||
|
* This assume there is already an element at the given index that will be
|
||
|
* overwritten and thereby have its reference decremented. DO NOT use this to
|
||
|
* write to an unassigned location.
|
||
|
*/
|
||
|
int
|
||
|
numba_list_setitem(NB_List *lp, Py_ssize_t index, const char *item) {
|
||
|
char *loc;
|
||
|
// check for mutability
|
||
|
if (!lp->is_mutable) {
|
||
|
return LIST_ERR_IMMUTABLE;
|
||
|
}
|
||
|
// check index is valid
|
||
|
// FIXME: this can be (and probably is) checked at the compiler level
|
||
|
if (!valid_index(index, lp->size)) {
|
||
|
return LIST_ERR_INDEX;
|
||
|
}
|
||
|
// set item at desired location
|
||
|
loc = lp->items + lp-> item_size * index;
|
||
|
list_decref_item(lp, loc);
|
||
|
copy_item(lp, loc, item);
|
||
|
list_incref_item(lp, loc);
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
/* Get an item from a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* index: the index of the item to get (must be in range 0 <= index < len(list))
|
||
|
* out: a pointer to hold the item
|
||
|
*/
|
||
|
int
|
||
|
numba_list_getitem(NB_List *lp, Py_ssize_t index, char *out) {
|
||
|
char *loc;
|
||
|
// check index is valid
|
||
|
// FIXME: this can be (and probably is) checked at the compiler level
|
||
|
if (!valid_index(index, lp->size)) {
|
||
|
return LIST_ERR_INDEX;
|
||
|
}
|
||
|
// get item at desired location
|
||
|
loc = lp->items + lp->item_size * index;
|
||
|
copy_item(lp, out, loc);
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
/* Append an item to the end of a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* item: the item to append.
|
||
|
*/
|
||
|
int
|
||
|
numba_list_append(NB_List *lp, const char *item) {
|
||
|
char *loc;
|
||
|
// check for mutability
|
||
|
if (!lp->is_mutable) {
|
||
|
return LIST_ERR_IMMUTABLE;
|
||
|
}
|
||
|
// resize by one, will change list size
|
||
|
int result = numba_list_resize(lp, lp->size + 1);
|
||
|
if(result < LIST_OK) {
|
||
|
return result;
|
||
|
}
|
||
|
// insert item at index: original size before resize
|
||
|
loc = lp->items + lp->item_size * (lp->size - 1);
|
||
|
copy_item(lp, loc, item);
|
||
|
list_incref_item(lp, loc);
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
/* Resize a list.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* newsize: the desired new size of the list.
|
||
|
*
|
||
|
* This will increase or decrease the size of the list, including reallocating
|
||
|
* the required memory and increasing the total allocation (additional free
|
||
|
* space to hold new items).
|
||
|
*
|
||
|
*
|
||
|
* Adapted from CPython's list_resize().
|
||
|
*
|
||
|
* Ensure lp->items has room for at least newsize elements, and set
|
||
|
* lp->size to newsize. If newsize > lp->size on entry, the content
|
||
|
* of the new slots at exit is undefined heap trash; it's the caller's
|
||
|
* responsibility to overwrite them with sane values.
|
||
|
* The number of allocated elements may grow, shrink, or stay the same.
|
||
|
* Failure is impossible if newsize <= lp->allocated on entry, although
|
||
|
* that partly relies on an assumption that the system realloc() never
|
||
|
* fails when passed a number of bytes <= the number of bytes last
|
||
|
* allocated (the C standard doesn't guarantee this, but it's hard to
|
||
|
* imagine a realloc implementation where it wouldn't be true).
|
||
|
* Note that lp->items may change, and even if newsize is less
|
||
|
* than lp->size on entry.
|
||
|
*/
|
||
|
int
|
||
|
numba_list_resize(NB_List *lp, Py_ssize_t newsize) {
|
||
|
char * items;
|
||
|
// check for mutability
|
||
|
if (!lp->is_mutable) {
|
||
|
return LIST_ERR_IMMUTABLE;
|
||
|
}
|
||
|
size_t new_allocated, num_allocated_bytes;
|
||
|
/* Bypass realloc() when a previous overallocation is large enough
|
||
|
to accommodate the newsize. If the newsize falls lower than half
|
||
|
the allocated size, then proceed with the realloc() to shrink the list.
|
||
|
*/
|
||
|
if (lp->allocated >= newsize && newsize >= (lp->allocated >> 1)) {
|
||
|
assert(lp->items != NULL || newsize == 0);
|
||
|
lp->size = newsize;
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
/* This over-allocates proportional to the list size, making room
|
||
|
* for additional growth. The over-allocation is mild, but is
|
||
|
* enough to give linear-time amortized behavior over a long
|
||
|
* sequence of appends() in the presence of a poorly-performing
|
||
|
* system realloc().
|
||
|
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
|
||
|
* Note: new_allocated won't overflow because the largest possible value
|
||
|
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
|
||
|
*/
|
||
|
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
|
||
|
if (new_allocated > (size_t)PY_SSIZE_T_MAX / lp->item_size) {
|
||
|
return LIST_ERR_NO_MEMORY;
|
||
|
}
|
||
|
|
||
|
if (newsize == 0)
|
||
|
new_allocated = 0;
|
||
|
num_allocated_bytes = new_allocated * lp->item_size;
|
||
|
items = realloc(lp->items, aligned_size(num_allocated_bytes));
|
||
|
// realloc may return NULL if requested size is 0
|
||
|
if (num_allocated_bytes != 0 && items == NULL) {
|
||
|
return LIST_ERR_NO_MEMORY;
|
||
|
}
|
||
|
lp->items = items;
|
||
|
lp->size = newsize;
|
||
|
lp->allocated = (Py_ssize_t)new_allocated;
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
/* Delete a single item.
|
||
|
*
|
||
|
* lp: a list
|
||
|
* index: the index of the item to delete
|
||
|
* (must be in range 0 <= index < len(list))
|
||
|
*
|
||
|
* */
|
||
|
int
|
||
|
numba_list_delitem(NB_List *lp, Py_ssize_t index) {
|
||
|
int result;
|
||
|
char *loc, *new_loc;
|
||
|
Py_ssize_t leftover_bytes;
|
||
|
// check for mutability
|
||
|
if (!lp->is_mutable) {
|
||
|
return LIST_ERR_IMMUTABLE;
|
||
|
}
|
||
|
// check index is valid
|
||
|
// FIXME: this can be (and probably is) checked at the compiler level
|
||
|
if (!valid_index(index, lp->size)) {
|
||
|
return LIST_ERR_INDEX;
|
||
|
}
|
||
|
// obtain item and decref if needed
|
||
|
loc = lp->items + lp->item_size * index;
|
||
|
list_decref_item(lp, loc);
|
||
|
if (index != lp->size - 1) {
|
||
|
// delitem from somewhere other than the end, incur the memory copy
|
||
|
leftover_bytes = (lp->size - 1 - index) * lp->item_size;
|
||
|
new_loc = lp->items + (lp->item_size * (index + 1));
|
||
|
// use memmove instead of memcpy since we may be dealing with
|
||
|
// overlapping regions of memory and the behaviour of memcpy is
|
||
|
// undefined in such situation (C99).
|
||
|
memmove(loc, new_loc, leftover_bytes);
|
||
|
}
|
||
|
// finally, shrink list by one
|
||
|
result = numba_list_resize(lp, lp->size - 1);
|
||
|
if(result < LIST_OK) {
|
||
|
// Since we are decreasing the size, this should never happen
|
||
|
return result;
|
||
|
}
|
||
|
return LIST_OK;
|
||
|
|
||
|
}
|
||
|
|
||
|
/* Delete a slice
|
||
|
*
|
||
|
* start: the start index of ths slice
|
||
|
* stop: the stop index of the slice (not included)
|
||
|
* step: the step to take
|
||
|
*
|
||
|
* This function assumes that the start and stop were clipped appropriately.
|
||
|
* I.e. if step > 0 start >= 0 and stop <= len(l) and
|
||
|
* if step < 0 start <= length and stop >= -1
|
||
|
* step != 0 and no Python negative indexing allowed.
|
||
|
*
|
||
|
* This code was copied and edited from the relevant section in
|
||
|
* list_ass_subscript from the cpython implementation, see the top of this file
|
||
|
* for the exact source
|
||
|
*/
|
||
|
int
|
||
|
numba_list_delete_slice(NB_List *lp,
|
||
|
Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step) {
|
||
|
int result, i, slicelength, new_length;
|
||
|
char *loc, *new_loc;
|
||
|
Py_ssize_t leftover_bytes, cur, lim;
|
||
|
// check for mutability
|
||
|
if (!lp->is_mutable) {
|
||
|
return LIST_ERR_IMMUTABLE;
|
||
|
}
|
||
|
// calculate the slicelength, taken from PySlice_AdjustIndices, see the top
|
||
|
// of this file for the exact source
|
||
|
if (step > 0) {
|
||
|
slicelength = start < stop ? (stop - start - 1) / step + 1 : 0;
|
||
|
} else {
|
||
|
slicelength = stop < start ? (start - stop - 1) / -step + 1 : 0;
|
||
|
}
|
||
|
if (slicelength <= 0){
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
new_length = lp->size - slicelength;
|
||
|
// reverse step and indices
|
||
|
if (step < 0) {
|
||
|
stop = start + 1;
|
||
|
start = stop + step * (slicelength - 1) - 1;
|
||
|
step = -step;
|
||
|
}
|
||
|
if (step == 1) {
|
||
|
// decref if needed
|
||
|
if (lp->methods.item_decref) {
|
||
|
for (i = start ; i < stop ; i++){
|
||
|
loc = lp->items + lp->item_size * i;
|
||
|
lp->methods.item_decref(loc);
|
||
|
}
|
||
|
}
|
||
|
// memmove items into place
|
||
|
leftover_bytes = (lp->size - stop) * lp->item_size;
|
||
|
loc = lp->items + lp->item_size * start;
|
||
|
new_loc = lp->items + lp->item_size * stop;
|
||
|
memmove(loc, new_loc, leftover_bytes);
|
||
|
}
|
||
|
else { // step != 1
|
||
|
/* drawing pictures might help understand these for
|
||
|
* loops. Basically, we memmove the parts of the
|
||
|
* list that are *not* part of the slice: step-1
|
||
|
* items for each item that is part of the slice,
|
||
|
* and then tail end of the list that was not
|
||
|
* covered by the slice
|
||
|
*
|
||
|
* */
|
||
|
for (cur = start, // index of item to be deleted
|
||
|
i = 0; // counter of total items deleted so far
|
||
|
cur < stop;
|
||
|
cur += step,
|
||
|
i++) {
|
||
|
lim = step - 1; // number of leftover items after deletion of item
|
||
|
// clip limit, in case we are at the end of the slice, and there
|
||
|
// are now less than step-1 items to be moved
|
||
|
if (cur + step >= lp->size) {
|
||
|
lim = lp->size - cur - 1;
|
||
|
}
|
||
|
// decref item being removed
|
||
|
loc = lp->items + lp->item_size * cur;
|
||
|
list_decref_item(lp, loc);
|
||
|
/* memmove the aforementioned step-1 (or less) items
|
||
|
* dst : index of deleted item minus total deleted sofar
|
||
|
* src : index of deleted item plus one (next item)
|
||
|
*/
|
||
|
memmove(lp->items + lp->item_size * (cur - i),
|
||
|
lp->items + lp->item_size * (cur + 1),
|
||
|
lim * lp->item_size);
|
||
|
}
|
||
|
// memmove tail of the list
|
||
|
cur = start + slicelength * step;
|
||
|
if (cur < lp->size) {
|
||
|
memmove(lp->items + lp->item_size * (cur - slicelength),
|
||
|
lp->items + lp->item_size * cur,
|
||
|
(lp->size - cur) * lp->item_size);
|
||
|
}
|
||
|
}
|
||
|
// resize to correct size
|
||
|
result = numba_list_resize(lp, new_length);
|
||
|
if(result < LIST_OK) {
|
||
|
// Since we are decreasing the size, this should never happen
|
||
|
return result;
|
||
|
}
|
||
|
return LIST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Return the size of the list iterator (NB_ListIter) struct.
|
||
|
*/
|
||
|
size_t
|
||
|
numba_list_iter_sizeof() {
|
||
|
return sizeof(NB_ListIter);
|
||
|
}
|
||
|
|
||
|
/* Initialize a list iterator (NB_ListIter).
|
||
|
*
|
||
|
* it: an iterator
|
||
|
* lp: a list to iterate over
|
||
|
*/
|
||
|
void
|
||
|
numba_list_iter(NB_ListIter *it, NB_List *lp) {
|
||
|
// set members of iterator
|
||
|
it->parent = lp;
|
||
|
it->size = lp->size;
|
||
|
it->pos = 0;
|
||
|
}
|
||
|
|
||
|
/* Obtain the next item from a list iterator.
|
||
|
*
|
||
|
* it: an iterator
|
||
|
* item_ptr: pointer to hold the next item
|
||
|
*/
|
||
|
int
|
||
|
numba_list_iter_next(NB_ListIter *it, const char **item_ptr) {
|
||
|
NB_List *lp;
|
||
|
lp = it->parent;
|
||
|
/* FIXME: Detect list mutation during iteration */
|
||
|
if (lp->size != it->size) {
|
||
|
return LIST_ERR_MUTATED;
|
||
|
}
|
||
|
// get next element
|
||
|
if (it->pos < lp->size) {
|
||
|
*item_ptr = lp->items + lp->item_size * it->pos++;
|
||
|
return LIST_OK;
|
||
|
}else{
|
||
|
return LIST_ERR_ITER_EXHAUSTED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#define CHECK(CASE) { \
|
||
|
if ( !(CASE) ) { \
|
||
|
printf("'%s' failed file %s:%d\n", #CASE, __FILE__, __LINE__); \
|
||
|
return -1; \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
/* Basic C based tests for the list.
|
||
|
*/
|
||
|
int
|
||
|
numba_test_list(void) {
|
||
|
NB_List *lp = NULL;
|
||
|
int status, i;
|
||
|
Py_ssize_t it_count;
|
||
|
const char *it_item = NULL;
|
||
|
NB_ListIter iter;
|
||
|
char got_item[4] = "\x00\x00\x00\x00";
|
||
|
const char *test_items_1 = NULL, *test_items_2 = NULL;
|
||
|
char *test_items_3 = NULL;
|
||
|
puts("test_list");
|
||
|
|
||
|
|
||
|
status = numba_list_new(&lp, 4, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->item_size == 4);
|
||
|
CHECK(lp->size == 0);
|
||
|
CHECK(lp->allocated == 0);
|
||
|
CHECK(lp->is_mutable == 1);
|
||
|
|
||
|
// flip and check the is_mutable bit
|
||
|
CHECK(numba_list_is_mutable(lp) == 1);
|
||
|
numba_list_set_is_mutable(lp, 0);
|
||
|
CHECK(numba_list_is_mutable(lp) == 0);
|
||
|
numba_list_set_is_mutable(lp, 1);
|
||
|
CHECK(numba_list_is_mutable(lp) == 1);
|
||
|
|
||
|
// append 1st item, this will cause a realloc
|
||
|
status = numba_list_append(lp, "abc");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 1);
|
||
|
CHECK(lp->allocated == 4);
|
||
|
status = numba_list_getitem(lp, 0, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "abc", 4) == 0);
|
||
|
|
||
|
// append 2nd item
|
||
|
status = numba_list_append(lp, "def");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 2);
|
||
|
CHECK(lp->allocated == 4);
|
||
|
status = numba_list_getitem(lp, 1, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "def", 4) == 0);
|
||
|
|
||
|
// append 3rd item
|
||
|
status = numba_list_append(lp, "ghi");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 3);
|
||
|
CHECK(lp->allocated == 4);
|
||
|
status = numba_list_getitem(lp, 2, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "ghi", 4) == 0);
|
||
|
|
||
|
// append 4th item
|
||
|
status = numba_list_append(lp, "jkl");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 4);
|
||
|
CHECK(lp->allocated == 4);
|
||
|
status = numba_list_getitem(lp, 3, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "jkl", 4) == 0);
|
||
|
|
||
|
// append 5th item, this will cause another realloc
|
||
|
status = numba_list_append(lp, "mno");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 5);
|
||
|
CHECK(lp->allocated == 8);
|
||
|
status = numba_list_getitem(lp, 4, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "mno", 4) == 0);
|
||
|
|
||
|
// overwrite 1st item
|
||
|
status = numba_list_setitem(lp, 0, "pqr");
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 5);
|
||
|
CHECK(lp->allocated == 8);
|
||
|
status = numba_list_getitem(lp, 0, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "pqr", 4) == 0);
|
||
|
|
||
|
// get and del 1st item, check item shift
|
||
|
status = numba_list_getitem(lp, 0, got_item);
|
||
|
status = numba_list_delitem(lp, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 4);
|
||
|
CHECK(lp->allocated == 8);
|
||
|
CHECK(memcmp(got_item, "pqr", 4) == 0);
|
||
|
CHECK(memcmp(lp->items, "def\x00ghi\x00jkl\x00mno\x00", 16) == 0);
|
||
|
|
||
|
// get and del last (4th) item, no shift since only last item affected
|
||
|
status = numba_list_getitem(lp, 3, got_item);
|
||
|
status = numba_list_delitem(lp, 3);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 3);
|
||
|
CHECK(lp->allocated == 6); // this also shrinks the allocation
|
||
|
CHECK(memcmp(got_item, "mno", 4) == 0);
|
||
|
CHECK(memcmp(lp->items, "def\x00ghi\x00jkl\x00", 12) == 0);
|
||
|
|
||
|
// flip and check the is_mutable member
|
||
|
CHECK(numba_list_is_mutable(lp) == 1);
|
||
|
numba_list_set_is_mutable(lp, 0);
|
||
|
CHECK(numba_list_is_mutable(lp) == 0);
|
||
|
|
||
|
// ensure that any attempts to mutate an immutable list fail
|
||
|
CHECK(numba_list_setitem(lp, 0, "zzz") == LIST_ERR_IMMUTABLE);
|
||
|
CHECK(numba_list_append(lp, "zzz") == LIST_ERR_IMMUTABLE);
|
||
|
CHECK(numba_list_delitem(lp, 0) == LIST_ERR_IMMUTABLE);
|
||
|
CHECK(numba_list_resize(lp, 23) == LIST_ERR_IMMUTABLE);
|
||
|
CHECK(numba_list_delete_slice(lp, 0, 3, 1) == LIST_ERR_IMMUTABLE);
|
||
|
|
||
|
// ensure that all attempts to query/read from and immutable list succeed
|
||
|
CHECK(numba_list_length(lp) == 3);
|
||
|
status = numba_list_getitem(lp, 0, got_item);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(memcmp(got_item, "def", 4) == 0);
|
||
|
|
||
|
// flip the is_mutable member back and check
|
||
|
numba_list_set_is_mutable(lp, 1);
|
||
|
CHECK(numba_list_is_mutable(lp) == 1);
|
||
|
|
||
|
// test iterator
|
||
|
CHECK(lp->size > 0);
|
||
|
numba_list_iter(&iter, lp);
|
||
|
it_count = 0;
|
||
|
CHECK(iter.parent == lp);
|
||
|
CHECK(iter.pos == it_count);
|
||
|
|
||
|
// current contents of list
|
||
|
test_items_1 = "def\x00ghi\x00jkl\x00";
|
||
|
while ( (status = numba_list_iter_next(&iter, &it_item)) == LIST_OK) {
|
||
|
it_count += 1;
|
||
|
CHECK(iter.pos == it_count); // check iterator position
|
||
|
CHECK(it_item != NULL); // quick check item is non-null
|
||
|
// go fishing in test_items_1
|
||
|
CHECK(memcmp((const char *)test_items_1 + ((it_count - 1) * 4), it_item, 4) == 0);
|
||
|
}
|
||
|
|
||
|
CHECK(status == LIST_ERR_ITER_EXHAUSTED);
|
||
|
CHECK(lp->size == it_count);
|
||
|
|
||
|
// free existing list
|
||
|
numba_list_free(lp);
|
||
|
|
||
|
// test growth upon append and shrink during delitem
|
||
|
status = numba_list_new(&lp, 1, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->item_size == 1);
|
||
|
CHECK(lp->size == 0);
|
||
|
CHECK(lp->allocated == 0);
|
||
|
|
||
|
// first, grow the list
|
||
|
// Use exactly 17 elements, should go through the allocation pattern:
|
||
|
// 0, 4, 8, 16, 25
|
||
|
for (i = 0; i < 17 ; i++) {
|
||
|
switch(i) {
|
||
|
// Check the allocation before
|
||
|
case 0: CHECK(lp->allocated == 0); break;
|
||
|
case 4: CHECK(lp->allocated == 4); break;
|
||
|
case 8: CHECK(lp->allocated == 8); break;
|
||
|
case 16: CHECK(lp->allocated == 16); break;
|
||
|
}
|
||
|
status = numba_list_append(lp, (const char*)&i);
|
||
|
CHECK(status == LIST_OK);
|
||
|
switch(i) {
|
||
|
// Check that the growth happened accordingly
|
||
|
case 0: CHECK(lp->allocated == 4); break;
|
||
|
case 4: CHECK(lp->allocated == 8); break;
|
||
|
case 8: CHECK(lp->allocated == 16); break;
|
||
|
case 16: CHECK(lp->allocated == 25); break;
|
||
|
}
|
||
|
}
|
||
|
CHECK(lp->size == 17);
|
||
|
|
||
|
// Check current contents of list
|
||
|
test_items_2 = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10";
|
||
|
CHECK(memcmp(lp->items, test_items_2, 17) == 0);
|
||
|
|
||
|
// Now, delete them again and check that list shrinks
|
||
|
for (i = 17; i > 0 ; i--) {
|
||
|
switch(i) {
|
||
|
// Check the allocation before delitem
|
||
|
case 17: CHECK(lp->allocated == 25); break;
|
||
|
case 12: CHECK(lp->allocated == 25); break;
|
||
|
case 9: CHECK(lp->allocated == 18); break;
|
||
|
case 6: CHECK(lp->allocated == 12); break;
|
||
|
case 4: CHECK(lp->allocated == 8); break;
|
||
|
case 3: CHECK(lp->allocated == 6); break;
|
||
|
case 2: CHECK(lp->allocated == 5); break;
|
||
|
case 1: CHECK(lp->allocated == 4); break;
|
||
|
}
|
||
|
status = numba_list_getitem(lp, i-1, got_item);
|
||
|
status = numba_list_delitem(lp, i-1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
switch(i) {
|
||
|
// Check that the shrink happened accordingly
|
||
|
case 17: CHECK(lp->allocated == 25); break;
|
||
|
case 12: CHECK(lp->allocated == 18); break;
|
||
|
case 9: CHECK(lp->allocated == 12); break;
|
||
|
case 6: CHECK(lp->allocated == 8); break;
|
||
|
case 4: CHECK(lp->allocated == 6); break;
|
||
|
case 3: CHECK(lp->allocated == 5); break;
|
||
|
case 2: CHECK(lp->allocated == 4); break;
|
||
|
case 1: CHECK(lp->allocated == 0); break;
|
||
|
}
|
||
|
}
|
||
|
// free existing list
|
||
|
numba_list_free(lp);
|
||
|
|
||
|
|
||
|
// Setup list for testing delete_slice
|
||
|
status = numba_list_new(&lp, 1, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->item_size == 1);
|
||
|
CHECK(lp->size == 0);
|
||
|
CHECK(lp->allocated == 0);
|
||
|
for (i = 0; i < 17 ; i++) {
|
||
|
status = numba_list_append(lp, (const char*)&i);
|
||
|
CHECK(status == LIST_OK);
|
||
|
}
|
||
|
CHECK(lp->size == 17);
|
||
|
test_items_3 = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 17) == 0);
|
||
|
|
||
|
// delete multiple elements from the middle
|
||
|
status = numba_list_delete_slice(lp, 2, 5, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 14);
|
||
|
test_items_3 = "\x00\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 14) == 0);
|
||
|
|
||
|
// delete single element from start
|
||
|
status = numba_list_delete_slice(lp, 0, 1, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 13);
|
||
|
test_items_3 = "\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 13) == 0);
|
||
|
|
||
|
// delete single element from end
|
||
|
status = numba_list_delete_slice(lp, 12, 13, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 12);
|
||
|
test_items_3 = "\x01\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 12) == 0);
|
||
|
|
||
|
// delete single element from middle
|
||
|
status = numba_list_delete_slice(lp, 4, 5, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 11);
|
||
|
test_items_3 = "\x01\x05\x06\x07\x09\x0a\x0b\x0c\x0d\x0e\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 11) == 0);
|
||
|
|
||
|
// delete all elements except first and last
|
||
|
status = numba_list_delete_slice(lp, 1, 10, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 2);
|
||
|
test_items_3 = "\x01\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 2) == 0);
|
||
|
|
||
|
// delete all remaining elements
|
||
|
status = numba_list_delete_slice(lp, 0, lp->size, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 0);
|
||
|
test_items_3 = "";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 0) == 0);
|
||
|
|
||
|
// free existing list
|
||
|
numba_list_free(lp);
|
||
|
|
||
|
// Setup list for testing delete_slice with non unary step
|
||
|
status = numba_list_new(&lp, 1, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->item_size == 1);
|
||
|
CHECK(lp->size == 0);
|
||
|
CHECK(lp->allocated == 0);
|
||
|
for (i = 0; i < 17 ; i++) {
|
||
|
status = numba_list_append(lp, (const char*)&i);
|
||
|
CHECK(status == LIST_OK);
|
||
|
}
|
||
|
CHECK(lp->size == 17);
|
||
|
|
||
|
// delete all items with odd index
|
||
|
status = numba_list_delete_slice(lp, 0, 17, 2);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 8);
|
||
|
test_items_3 = "\x01\x03\x05\x07\x09\x0b\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 8) == 0);
|
||
|
|
||
|
// delete with a step of 4, starting at index 1
|
||
|
status = numba_list_delete_slice(lp, 1, 8, 4);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 6);
|
||
|
test_items_3 = "\x01\x05\x07\x09\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 6) == 0);
|
||
|
|
||
|
// delete with a step of 2, but finish before end of list
|
||
|
status = numba_list_delete_slice(lp, 0, 4, 2);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 4);
|
||
|
test_items_3 = "\x05\x09\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 4) == 0);
|
||
|
|
||
|
// no-op on empty slice
|
||
|
status = numba_list_delete_slice(lp, 0, 0, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 4);
|
||
|
test_items_3 = "\x05\x09\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 4) == 0);
|
||
|
|
||
|
// no-op on empty slice, non-zero index
|
||
|
status = numba_list_delete_slice(lp, 2, 2, 1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 4);
|
||
|
test_items_3 = "\x05\x09\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 4) == 0);
|
||
|
|
||
|
// free list and return 0
|
||
|
numba_list_free(lp);
|
||
|
|
||
|
// Setup list for testing delete_slice with negative step
|
||
|
status = numba_list_new(&lp, 1, 0);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->item_size == 1);
|
||
|
CHECK(lp->size == 0);
|
||
|
CHECK(lp->allocated == 0);
|
||
|
for (i = 0; i < 17 ; i++) {
|
||
|
status = numba_list_append(lp, (const char*)&i);
|
||
|
CHECK(status == LIST_OK);
|
||
|
}
|
||
|
CHECK(lp->size == 17);
|
||
|
|
||
|
// delete all items using unary negative slice
|
||
|
status = numba_list_delete_slice(lp, 16, -1, -1);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 0);
|
||
|
|
||
|
// refill list
|
||
|
for (i = 0; i < 17 ; i++) {
|
||
|
status = numba_list_append(lp, (const char*)&i);
|
||
|
CHECK(status == LIST_OK);
|
||
|
}
|
||
|
|
||
|
// delete all items using unary negative slice
|
||
|
// need to start at index of last item (16) and
|
||
|
// go beyond first item, i.e. -1 in Cd
|
||
|
status = numba_list_delete_slice(lp, 16, -1, -2);
|
||
|
CHECK(status == LIST_OK);
|
||
|
CHECK(lp->size == 8);
|
||
|
test_items_3 = "\x01\x03\x05\x07\x09\x0b\x0d\x0f";
|
||
|
CHECK(memcmp(lp->items, test_items_3, 8) == 0);
|
||
|
|
||
|
// free list and return 0
|
||
|
numba_list_free(lp);
|
||
|
return 0;
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
#undef CHECK
|