#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