-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
listobject.c
977 lines (897 loc) · 31.2 KB
/
listobject.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
#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