| 40 size_t new_capacity, |
40 size_t new_capacity, |
| 41 size_t elem_size, |
41 size_t elem_size, |
| 42 cx_attr_unused CxArrayReallocator *alloc |
42 cx_attr_unused CxArrayReallocator *alloc |
| 43 ) { |
43 ) { |
| 44 size_t n; |
44 size_t n; |
| |
45 // LCOV_EXCL_START |
| 45 if (cx_szmul(new_capacity, elem_size, &n)) { |
46 if (cx_szmul(new_capacity, elem_size, &n)) { |
| 46 errno = EOVERFLOW; |
47 errno = EOVERFLOW; |
| 47 return NULL; |
48 return NULL; |
| 48 } |
49 } // LCOV_EXCL_STOP |
| 49 return cxReallocDefault(array, n); |
50 return cxReallocDefault(array, n); |
| 50 } |
51 } |
| 51 |
52 |
| 52 CxArrayReallocator cx_array_default_reallocator_impl = { |
53 CxArrayReallocator cx_array_default_reallocator_impl = { |
| 53 cx_array_default_realloc, NULL, NULL |
54 cx_array_default_realloc, NULL, NULL |
| 64 size_t elem_size, |
65 size_t elem_size, |
| 65 cx_attr_unused CxArrayReallocator *alloc |
66 cx_attr_unused CxArrayReallocator *alloc |
| 66 ) { |
67 ) { |
| 67 // check for overflow |
68 // check for overflow |
| 68 size_t n; |
69 size_t n; |
| |
70 // LCOV_EXCL_START |
| 69 if (cx_szmul(new_capacity, elem_size, &n)) { |
71 if (cx_szmul(new_capacity, elem_size, &n)) { |
| 70 errno = EOVERFLOW; |
72 errno = EOVERFLOW; |
| 71 return NULL; |
73 return NULL; |
| 72 } |
74 } // LCOV_EXCL_STOP |
| 73 |
75 |
| 74 // retrieve the pointer to the actual allocator |
76 // retrieve the pointer to the actual allocator |
| 75 const CxAllocator *al = alloc->allocator; |
77 const CxAllocator *al = alloc->allocator; |
| 76 |
78 |
| 77 // check if the array is still located on the stack |
79 // check if the array is still located on the stack |
| 100 }; |
102 }; |
| 101 } |
103 } |
| 102 |
104 |
| 103 // LOW LEVEL ARRAY LIST FUNCTIONS |
105 // LOW LEVEL ARRAY LIST FUNCTIONS |
| 104 |
106 |
| 105 static size_t cx_array_align_capacity( |
107 /** |
| 106 size_t cap, |
108 * Intelligently calculates a new capacity, reserving some more |
| 107 size_t alignment, |
109 * elements than required to prevent too many allocations. |
| 108 size_t max |
110 * |
| 109 ) { |
111 * @param current_capacity the current capacity of the array |
| 110 if (cap > max - alignment) { |
112 * @param needed_capacity the required capacity of the array |
| 111 return cap; |
113 * @return the new capacity |
| 112 } else { |
114 */ |
| 113 return cap - (cap % alignment) + alignment; |
115 static size_t cx_array_grow_capacity( |
| 114 } |
116 size_t current_capacity, |
| |
117 size_t needed_capacity |
| |
118 ) { |
| |
119 if (current_capacity >= needed_capacity) { |
| |
120 return current_capacity; |
| |
121 } |
| |
122 size_t cap = needed_capacity; |
| |
123 size_t alignment; |
| |
124 if (cap < 128) alignment = 16; |
| |
125 else if (cap < 1024) alignment = 64; |
| |
126 else if (cap < 8192) alignment = 512; |
| |
127 else alignment = 1024; |
| |
128 return cap - (cap % alignment) + alignment; |
| 115 } |
129 } |
| 116 |
130 |
| 117 int cx_array_reserve( |
131 int cx_array_reserve( |
| 118 void **array, |
132 void **array, |
| 119 void *size, |
133 void *size, |
| 174 // determine new capacity |
188 // determine new capacity |
| 175 size_t newcap = oldsize + elem_count; |
189 size_t newcap = oldsize + elem_count; |
| 176 |
190 |
| 177 // reallocate if possible |
191 // reallocate if possible |
| 178 if (newcap > oldcap) { |
192 if (newcap > oldcap) { |
| 179 // calculate new capacity (next number divisible by 16) |
|
| 180 newcap = cx_array_align_capacity(newcap, 16, max_size); |
|
| 181 |
|
| 182 // perform reallocation |
|
| 183 void *newmem = reallocator->realloc( |
193 void *newmem = reallocator->realloc( |
| 184 *array, oldcap, newcap, elem_size, reallocator |
194 *array, oldcap, newcap, elem_size, reallocator |
| 185 ); |
195 ); |
| 186 if (newmem == NULL) { |
196 if (newmem == NULL) { |
| 187 return 1; // LCOV_EXCL_LINE |
197 return 1; // LCOV_EXCL_LINE |
| 267 errno = EOVERFLOW; |
277 errno = EOVERFLOW; |
| 268 return 1; |
278 return 1; |
| 269 } |
279 } |
| 270 |
280 |
| 271 // check if resize is required |
281 // check if resize is required |
| 272 size_t minsize = index + elem_count; |
282 const size_t minsize = index + elem_count; |
| 273 size_t newsize = oldsize < minsize ? minsize : oldsize; |
283 const size_t newsize = oldsize < minsize ? minsize : oldsize; |
| 274 |
284 |
| 275 // reallocate if possible |
285 // reallocate if necessary |
| 276 size_t newcap = oldcap; |
286 const size_t newcap = cx_array_grow_capacity(oldcap, newsize); |
| 277 if (newsize > oldcap) { |
287 if (newcap > oldcap) { |
| 278 // check, if we need to repair the src pointer |
288 // check if we need to repair the src pointer |
| 279 uintptr_t targetaddr = (uintptr_t) *target; |
289 uintptr_t targetaddr = (uintptr_t) *target; |
| 280 uintptr_t srcaddr = (uintptr_t) src; |
290 uintptr_t srcaddr = (uintptr_t) src; |
| 281 bool repairsrc = targetaddr <= srcaddr |
291 bool repairsrc = targetaddr <= srcaddr |
| 282 && srcaddr < targetaddr + oldcap * elem_size; |
292 && srcaddr < targetaddr + oldcap * elem_size; |
| 283 |
|
| 284 // calculate new capacity (next number divisible by 16) |
|
| 285 newcap = cx_array_align_capacity(newsize, 16, max_size); |
|
| 286 assert(newcap > newsize); |
|
| 287 |
293 |
| 288 // perform reallocation |
294 // perform reallocation |
| 289 void *newmem = reallocator->realloc( |
295 void *newmem = reallocator->realloc( |
| 290 *target, oldcap, newcap, elem_size, reallocator |
296 *target, oldcap, newcap, elem_size, reallocator |
| 291 ); |
297 ); |
| 359 |
365 |
| 360 // corner case |
366 // corner case |
| 361 if (elem_count == 0) return 0; |
367 if (elem_count == 0) return 0; |
| 362 |
368 |
| 363 // overflow check |
369 // overflow check |
| |
370 // LCOV_EXCL_START |
| 364 if (elem_count > SIZE_MAX - *size) { |
371 if (elem_count > SIZE_MAX - *size) { |
| 365 errno = EOVERFLOW; |
372 errno = EOVERFLOW; |
| 366 return 1; |
373 return 1; |
| 367 } |
374 } |
| |
375 // LCOV_EXCL_STOP |
| 368 |
376 |
| 369 // store some counts |
377 // store some counts |
| 370 size_t old_size = *size; |
378 const size_t old_size = *size; |
| 371 size_t old_capacity = *capacity; |
379 const size_t old_capacity = *capacity; |
| 372 // the necessary capacity is the worst case assumption, including duplicates |
380 // the necessary capacity is the worst case assumption, including duplicates |
| 373 size_t needed_capacity = old_size + elem_count; |
381 const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count); |
| 374 |
382 |
| 375 // if we need more than we have, try a reallocation |
383 // if we need more than we have, try a reallocation |
| 376 if (needed_capacity > old_capacity) { |
384 if (needed_capacity > old_capacity) { |
| 377 size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); |
|
| 378 void *new_mem = reallocator->realloc( |
385 void *new_mem = reallocator->realloc( |
| 379 *target, old_capacity, new_capacity, elem_size, reallocator |
386 *target, old_capacity, needed_capacity, elem_size, reallocator |
| 380 ); |
387 ); |
| 381 if (new_mem == NULL) { |
388 if (new_mem == NULL) { |
| 382 // give it up right away, there is no contract |
389 // give it up right away, there is no contract |
| 383 // that requires us to insert as much as we can |
390 // that requires us to insert as much as we can |
| 384 return 1; // LCOV_EXCL_LINE |
391 return 1; // LCOV_EXCL_LINE |
| 385 } |
392 } |
| 386 *target = new_mem; |
393 *target = new_mem; |
| 387 *capacity = new_capacity; |
394 *capacity = needed_capacity; |
| 388 } |
395 } |
| 389 |
396 |
| 390 // now we have guaranteed that we can insert everything |
397 // now we have guaranteed that we can insert everything |
| 391 size_t new_size = old_size + elem_count; |
398 size_t new_size = old_size + elem_count; |
| 392 *size = new_size; |
399 *size = new_size; |
| 408 memmove(bptr, dest, buf_size * elem_size); |
415 memmove(bptr, dest, buf_size * elem_size); |
| 409 |
416 |
| 410 // while there are both source and buffered elements left, |
417 // while there are both source and buffered elements left, |
| 411 // copy them interleaving |
418 // copy them interleaving |
| 412 while (si < elem_count && bi < new_size) { |
419 while (si < elem_count && bi < new_size) { |
| 413 // determine how many source elements can be inserted |
420 // determine how many source elements can be inserted. |
| |
421 // the first element that shall not be inserted is the smallest element |
| |
422 // that is strictly larger than the first buffered element |
| |
423 // (located at the index of the infimum plus one). |
| |
424 // the infimum is guaranteed to exist: |
| |
425 // - if all src elements are larger, |
| |
426 // there is no buffer, and this loop is skipped |
| |
427 // - if any src element is smaller or equal, the infimum exists |
| |
428 // - when all src elements that are smaller are copied, the second part |
| |
429 // of this loop body will copy the remaining buffer (emptying it) |
| |
430 // Therefore, the buffer can never contain an element that is smaller |
| |
431 // than any element in the source and the infimum exists. |
| 414 size_t copy_len, bytes_copied; |
432 size_t copy_len, bytes_copied; |
| 415 copy_len = cx_array_binary_search_sup( |
433 copy_len = cx_array_binary_search_inf( |
| 416 src, |
434 src, elem_count - si, elem_size, bptr, cmp_func |
| 417 elem_count - si, |
|
| 418 elem_size, |
|
| 419 bptr, |
|
| 420 cmp_func |
|
| 421 ); |
435 ); |
| 422 // binary search gives us the smallest index; |
436 copy_len++; |
| 423 // we also want to include equal elements here |
|
| 424 while (si + copy_len < elem_count |
|
| 425 && cmp_func(bptr, src+copy_len*elem_size) == 0) { |
|
| 426 copy_len++; |
|
| 427 } |
|
| 428 |
437 |
| 429 // copy the source elements |
438 // copy the source elements |
| 430 if (copy_len > 0) { |
439 if (copy_len > 0) { |
| 431 if (allow_duplicates) { |
440 if (allow_duplicates) { |
| 432 // we can copy the entire chunk |
441 // we can copy the entire chunk |
| 499 if (si < elem_count) { |
508 if (si < elem_count) { |
| 500 if (allow_duplicates) { |
509 if (allow_duplicates) { |
| 501 // duplicates allowed or nothing inserted yet: simply copy everything |
510 // duplicates allowed or nothing inserted yet: simply copy everything |
| 502 memcpy(dest, src, elem_size * (elem_count - si)); |
511 memcpy(dest, src, elem_size * (elem_count - si)); |
| 503 } else { |
512 } else { |
| 504 if (dest != *target) { |
513 // we must check the remaining source elements one by one |
| 505 // skip all source elements that equal the last element |
514 // to skip the duplicates. |
| 506 char *last = dest - elem_size; |
515 // Note that no source element can equal the last element in the |
| 507 while (si < elem_count) { |
516 // destination, because that would have created an insertion point |
| 508 if (last != NULL && cmp_func(last, src) == 0) { |
517 // and a buffer, s.t. the above loop already handled the duplicates |
| 509 src += elem_size; |
|
| 510 si++; |
|
| 511 (*size)--; |
|
| 512 } else { |
|
| 513 break; |
|
| 514 } |
|
| 515 } |
|
| 516 } |
|
| 517 // we must check the elements in the chunk one by one |
|
| 518 while (si < elem_count) { |
518 while (si < elem_count) { |
| 519 // find a chain of elements that can be copied |
519 // find a chain of elements that can be copied |
| 520 size_t copy_len = 1, skip_len = 0; |
520 size_t copy_len = 1, skip_len = 0; |
| 521 { |
521 { |
| 522 const char *left_src = src; |
522 const char *left_src = src; |
| 523 while (si + copy_len < elem_count) { |
523 while (si + copy_len + skip_len < elem_count) { |
| 524 const char *right_src = left_src + elem_size; |
524 const char *right_src = left_src + elem_size; |
| 525 int d = cmp_func(left_src, right_src); |
525 int d = cmp_func(left_src, right_src); |
| 526 if (d < 0) { |
526 if (d < 0) { |
| 527 if (skip_len > 0) { |
527 if (skip_len > 0) { |
| 528 // new larger element found; |
528 // new larger element found; |
| 586 ) { |
586 ) { |
| 587 return cx_array_insert_sorted_impl(target, size, capacity, |
587 return cx_array_insert_sorted_impl(target, size, capacity, |
| 588 cmp_func, sorted_data, elem_size, elem_count, reallocator, false); |
588 cmp_func, sorted_data, elem_size, elem_count, reallocator, false); |
| 589 } |
589 } |
| 590 |
590 |
| 591 size_t cx_array_binary_search_inf( |
591 // implementation that finds ANY index |
| |
592 static size_t cx_array_binary_search_inf_impl( |
| 592 const void *arr, |
593 const void *arr, |
| 593 size_t size, |
594 size_t size, |
| 594 size_t elem_size, |
595 size_t elem_size, |
| 595 const void *elem, |
596 const void *elem, |
| 596 cx_compare_func cmp_func |
597 cx_compare_func cmp_func |
| 631 pivot_index = left_index + (right_index - left_index) / 2; |
632 pivot_index = left_index + (right_index - left_index) / 2; |
| 632 const char *arr_elem = array + pivot_index * elem_size; |
633 const char *arr_elem = array + pivot_index * elem_size; |
| 633 result = cmp_func(elem, arr_elem); |
634 result = cmp_func(elem, arr_elem); |
| 634 if (result == 0) { |
635 if (result == 0) { |
| 635 // found it! |
636 // found it! |
| 636 // check previous elements; |
|
| 637 // when they are equal, report the smallest index |
|
| 638 arr_elem -= elem_size; |
|
| 639 while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) { |
|
| 640 pivot_index--; |
|
| 641 arr_elem -= elem_size; |
|
| 642 } |
|
| 643 return pivot_index; |
637 return pivot_index; |
| 644 } else if (result < 0) { |
638 } else if (result < 0) { |
| 645 // element is smaller than pivot, continue search left |
639 // element is smaller than pivot, continue search left |
| 646 right_index = pivot_index - 1; |
640 right_index = pivot_index - 1; |
| 647 } else { |
641 } else { |
| 650 } |
644 } |
| 651 } |
645 } |
| 652 |
646 |
| 653 // report the largest upper bound |
647 // report the largest upper bound |
| 654 return result < 0 ? (pivot_index - 1) : pivot_index; |
648 return result < 0 ? (pivot_index - 1) : pivot_index; |
| |
649 } |
| |
650 |
| |
651 size_t cx_array_binary_search_inf( |
| |
652 const void *arr, |
| |
653 size_t size, |
| |
654 size_t elem_size, |
| |
655 const void *elem, |
| |
656 cx_compare_func cmp_func |
| |
657 ) { |
| |
658 size_t index = cx_array_binary_search_inf_impl( |
| |
659 arr, size, elem_size, elem, cmp_func); |
| |
660 // in case of equality, report the largest index |
| |
661 const char *e = ((const char *) arr) + (index + 1) * elem_size; |
| |
662 while (index + 1 < size && cmp_func(e, elem) == 0) { |
| |
663 e += elem_size; |
| |
664 index++; |
| |
665 } |
| |
666 return index; |
| 655 } |
667 } |
| 656 |
668 |
| 657 size_t cx_array_binary_search( |
669 size_t cx_array_binary_search( |
| 658 const void *arr, |
670 const void *arr, |
| 659 size_t size, |
671 size_t size, |
| 677 size_t size, |
689 size_t size, |
| 678 size_t elem_size, |
690 size_t elem_size, |
| 679 const void *elem, |
691 const void *elem, |
| 680 cx_compare_func cmp_func |
692 cx_compare_func cmp_func |
| 681 ) { |
693 ) { |
| 682 size_t inf = cx_array_binary_search_inf( |
694 size_t index = cx_array_binary_search_inf_impl( |
| 683 arr, size, elem_size, elem, cmp_func |
695 arr, size, elem_size, elem, cmp_func |
| 684 ); |
696 ); |
| 685 if (inf == size) { |
697 const char *e = ((const char *) arr) + index * elem_size; |
| 686 // no infimum means, first element is supremum |
698 if (index == size) { |
| |
699 // no infimum means the first element is supremum |
| 687 return 0; |
700 return 0; |
| 688 } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { |
701 } else if (cmp_func(e, elem) == 0) { |
| 689 return inf; |
702 // found an equal element, search the smallest index |
| |
703 e -= elem_size; // e now contains the element at index-1 |
| |
704 while (index > 0 && cmp_func(e, elem) == 0) { |
| |
705 e -= elem_size; |
| |
706 index--; |
| |
707 } |
| |
708 return index; |
| 690 } else { |
709 } else { |
| 691 return inf + 1; |
710 // we already have the largest index of the infimum (by design) |
| |
711 // the next element is the supremum (or there is no supremum) |
| |
712 return index + 1; |
| 692 } |
713 } |
| 693 } |
714 } |
| 694 |
715 |
| 695 #ifndef CX_ARRAY_SWAP_SBO_SIZE |
716 #ifndef CX_ARRAY_SWAP_SBO_SIZE |
| 696 #define CX_ARRAY_SWAP_SBO_SIZE 128 |
717 #define CX_ARRAY_SWAP_SBO_SIZE 128 |
| 779 // get a correctly typed pointer to the list |
800 // get a correctly typed pointer to the list |
| 780 cx_array_list *arl = (cx_array_list *) list; |
801 cx_array_list *arl = (cx_array_list *) list; |
| 781 |
802 |
| 782 // guarantee enough capacity |
803 // guarantee enough capacity |
| 783 if (arl->capacity < list->collection.size + n) { |
804 if (arl->capacity < list->collection.size + n) { |
| 784 size_t new_capacity = list->collection.size + n; |
805 const size_t new_capacity = cx_array_grow_capacity(arl->capacity,list->collection.size + n); |
| 785 new_capacity = new_capacity - (new_capacity % 16) + 16; |
|
| 786 if (cxReallocateArray( |
806 if (cxReallocateArray( |
| 787 list->collection.allocator, |
807 list->collection.allocator, |
| 788 &arl->data, new_capacity, |
808 &arl->data, new_capacity, |
| 789 list->collection.elem_size) |
809 list->collection.elem_size) |
| 790 ) { |
810 ) { |
| 791 return 0; |
811 return 0; // LCOV_EXCL_LINE |
| 792 } |
812 } |
| 793 arl->capacity = new_capacity; |
813 arl->capacity = new_capacity; |
| 794 } |
814 } |
| 795 |
815 |
| 796 // determine insert position |
816 // determine insert position |
| 882 ) { |
902 ) { |
| 883 struct cx_list_s *list = iter->src_handle; |
903 struct cx_list_s *list = iter->src_handle; |
| 884 if (iter->index < list->collection.size) { |
904 if (iter->index < list->collection.size) { |
| 885 if (cx_arl_insert_element(list, |
905 if (cx_arl_insert_element(list, |
| 886 iter->index + 1 - prepend, elem) == NULL) { |
906 iter->index + 1 - prepend, elem) == NULL) { |
| 887 return 1; |
907 return 1; // LCOV_EXCL_LINE |
| 888 } |
908 } |
| 889 iter->elem_count++; |
909 iter->elem_count++; |
| 890 if (prepend != 0) { |
910 if (prepend != 0) { |
| 891 iter->index++; |
911 iter->index++; |
| 892 iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; |
912 iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; |
| 893 } |
913 } |
| 894 return 0; |
914 return 0; |
| 895 } else { |
915 } else { |
| 896 if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) { |
916 if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) { |
| 897 return 1; |
917 return 1; // LCOV_EXCL_LINE |
| 898 } |
918 } |
| 899 iter->elem_count++; |
919 iter->elem_count++; |
| 900 iter->index = list->collection.size; |
920 iter->index = list->collection.size; |
| 901 return 0; |
921 return 0; |
| 902 } |
922 } |
| 1127 iter->elem_handle = ((char *) list->data) |
1147 iter->elem_handle = ((char *) list->data) |
| 1128 + iter->index * list->base.collection.elem_size; |
1148 + iter->index * list->base.collection.elem_size; |
| 1129 } |
1149 } |
| 1130 } |
1150 } |
| 1131 |
1151 |
| |
1152 static int cx_arl_change_capacity( |
| |
1153 struct cx_list_s *list, |
| |
1154 size_t new_capacity |
| |
1155 ) { |
| |
1156 cx_array_list *arl = (cx_array_list *)list; |
| |
1157 return cxReallocateArray(list->collection.allocator, |
| |
1158 &arl->data, new_capacity, list->collection.elem_size); |
| |
1159 } |
| 1132 |
1160 |
| 1133 static struct cx_iterator_s cx_arl_iterator( |
1161 static struct cx_iterator_s cx_arl_iterator( |
| 1134 const struct cx_list_s *list, |
1162 const struct cx_list_s *list, |
| 1135 size_t index, |
1163 size_t index, |
| 1136 bool backwards |
1164 bool backwards |