| 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 26 * POSSIBILITY OF SUCH DAMAGE. |
26 * POSSIBILITY OF SUCH DAMAGE. |
| 27 */ |
27 */ |
| 28 |
28 |
| |
29 #ifdef WITH_MEMRCHR |
| |
30 #define _GNU_SOURCE |
| |
31 #endif |
| |
32 |
| 29 #include "cx/array_list.h" |
33 #include "cx/array_list.h" |
| 30 #include "cx/compare.h" |
34 #include "cx/compare.h" |
| 31 #include <assert.h> |
35 #include <assert.h> |
| 32 #include <string.h> |
36 #include <string.h> |
| 33 #include <errno.h> |
37 #include <errno.h> |
| 34 |
38 |
| 35 // Default array reallocator |
39 // LOW LEVEL ARRAY LIST FUNCTIONS |
| 36 |
40 |
| 37 static void *cx_array_default_realloc( |
41 /** |
| 38 void *array, |
42 * Intelligently calculates a new capacity, reserving some more |
| 39 cx_attr_unused size_t old_capacity, |
43 * elements than required to prevent too many allocations. |
| 40 size_t new_capacity, |
44 * |
| |
45 * @param current_capacity the current capacity of the array |
| |
46 * @param needed_capacity the required capacity of the array |
| |
47 * @return the new capacity |
| |
48 */ |
| |
49 static size_t cx_array_grow_capacity( |
| |
50 size_t current_capacity, |
| |
51 size_t needed_capacity |
| |
52 ) { |
| |
53 if (current_capacity >= needed_capacity) { |
| |
54 return current_capacity; |
| |
55 } |
| |
56 size_t cap = needed_capacity; |
| |
57 size_t alignment; |
| |
58 if (cap < 128) alignment = 16; |
| |
59 else if (cap < 1024) alignment = 64; |
| |
60 else if (cap < 8192) alignment = 512; |
| |
61 else alignment = 1024; |
| |
62 return cap - (cap % alignment) + alignment; |
| |
63 } |
| |
64 |
| |
65 int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { |
| |
66 memset(array, 0, sizeof(CxArray)); |
| |
67 return cx_array_reserve_(allocator, array, elem_size, capacity); |
| |
68 } |
| |
69 |
| |
70 void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size) { |
| |
71 array->data = (void*) data; |
| |
72 array->capacity = capacity; |
| |
73 array->size = size; |
| |
74 } |
| |
75 |
| |
76 int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { |
| |
77 if (cxReallocateArray(allocator, &array->data, capacity, elem_size)) { |
| |
78 return -1; // LCOV_EXCL_LINE |
| |
79 } |
| |
80 array->capacity = capacity; |
| |
81 if (array->size > capacity) { |
| |
82 array->size = capacity; |
| |
83 } |
| |
84 return 0; |
| |
85 } |
| |
86 |
| |
87 int cx_array_copy_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) { |
| |
88 CxArray heap_array; |
| |
89 if (cx_array_init_(allocator, &heap_array, elem_size, capacity)) { |
| |
90 return -1; // LCOV_EXCL_LINE |
| |
91 } |
| |
92 heap_array.size = array->size; |
| |
93 memcpy(heap_array.data, array->data, elem_size * array->size); |
| |
94 *array = heap_array; |
| |
95 return 0; |
| |
96 } |
| |
97 |
| |
98 int cx_array_insert_(const CxAllocator *allocator, CxArray *array, |
| |
99 size_t elem_size, size_t index, const void *other, size_t n) { |
| |
100 // out of bounds and special case check |
| |
101 if (index > array->size) return -1; |
| |
102 if (n == 0) return 0; |
| |
103 |
| |
104 // guarantee enough capacity |
| |
105 if (array->capacity < array->size + n) { |
| |
106 const size_t new_capacity = cx_array_grow_capacity(array->capacity,array->size + n); |
| |
107 if (cxReallocateArray(allocator, &array->data, new_capacity, elem_size)) { |
| |
108 return -1; // LCOV_EXCL_LINE |
| |
109 } |
| |
110 array->capacity = new_capacity; |
| |
111 } |
| |
112 |
| |
113 // determine insert position |
| |
114 char *dst = array->data; |
| |
115 dst += index * elem_size; |
| |
116 |
| |
117 // do we need to move some elements? |
| |
118 if (index < array->size) { |
| |
119 size_t elems_to_move = array->size - index; |
| |
120 char *target = dst + n * elem_size; |
| |
121 memmove(target, dst, elems_to_move * elem_size); |
| |
122 } |
| |
123 |
| |
124 // place the new elements, if any |
| |
125 // otherwise, this function just reserved the memory (a.k.a emplace) |
| |
126 if (other != NULL) { |
| |
127 memcpy(dst, other, n * elem_size); |
| |
128 } |
| |
129 array->size += n; |
| |
130 |
| |
131 return 0; |
| |
132 } |
| |
133 |
| |
134 int cx_array_insert_sorted_c_( |
| |
135 const CxAllocator *allocator, |
| |
136 CxArray *array, |
| 41 size_t elem_size, |
137 size_t elem_size, |
| 42 cx_attr_unused CxArrayReallocator *alloc |
138 const void *sorted_data, |
| 43 ) { |
139 size_t n, |
| 44 size_t n; |
140 cx_compare_func2 cmp_func, |
| 45 if (cx_szmul(new_capacity, elem_size, &n)) { |
141 void *context, |
| 46 errno = EOVERFLOW; |
142 bool allow_duplicates |
| 47 return NULL; |
|
| 48 } |
|
| 49 return cxReallocDefault(array, n); |
|
| 50 } |
|
| 51 |
|
| 52 CxArrayReallocator cx_array_default_reallocator_impl = { |
|
| 53 cx_array_default_realloc, NULL, NULL |
|
| 54 }; |
|
| 55 |
|
| 56 CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; |
|
| 57 |
|
| 58 // Stack-aware array reallocator |
|
| 59 |
|
| 60 static void *cx_array_advanced_realloc( |
|
| 61 void *array, |
|
| 62 size_t old_capacity, |
|
| 63 size_t new_capacity, |
|
| 64 size_t elem_size, |
|
| 65 cx_attr_unused CxArrayReallocator *alloc |
|
| 66 ) { |
|
| 67 // check for overflow |
|
| 68 size_t n; |
|
| 69 if (cx_szmul(new_capacity, elem_size, &n)) { |
|
| 70 errno = EOVERFLOW; |
|
| 71 return NULL; |
|
| 72 } |
|
| 73 |
|
| 74 // retrieve the pointer to the actual allocator |
|
| 75 const CxAllocator *al = alloc->allocator; |
|
| 76 |
|
| 77 // check if the array is still located on the stack |
|
| 78 void *newmem; |
|
| 79 if (array == alloc->stack_ptr) { |
|
| 80 newmem = cxMalloc(al, n); |
|
| 81 if (newmem != NULL && array != NULL) { |
|
| 82 memcpy(newmem, array, old_capacity*elem_size); |
|
| 83 } |
|
| 84 } else { |
|
| 85 newmem = cxRealloc(al, array, n); |
|
| 86 } |
|
| 87 return newmem; |
|
| 88 } |
|
| 89 |
|
| 90 struct cx_array_reallocator_s cx_array_reallocator( |
|
| 91 const struct cx_allocator_s *allocator, |
|
| 92 const void *stack_ptr |
|
| 93 ) { |
|
| 94 if (allocator == NULL) { |
|
| 95 allocator = cxDefaultAllocator; |
|
| 96 } |
|
| 97 return (struct cx_array_reallocator_s) { |
|
| 98 cx_array_advanced_realloc, |
|
| 99 allocator, stack_ptr, |
|
| 100 }; |
|
| 101 } |
|
| 102 |
|
| 103 // LOW LEVEL ARRAY LIST FUNCTIONS |
|
| 104 |
|
| 105 /** |
|
| 106 * Increases the capacity until it is a multiple of a some alignment or reaches the maximum. |
|
| 107 * |
|
| 108 * @param cap the required capacity (must not be larger than @p max) |
|
| 109 * @param alignment the alignment |
|
| 110 * @param max the maximum capacity |
|
| 111 * @return the aligned capacity |
|
| 112 */ |
|
| 113 static size_t cx_array_align_capacity( |
|
| 114 size_t cap, |
|
| 115 size_t alignment, |
|
| 116 size_t max |
|
| 117 ) { |
|
| 118 if (cap > max - alignment) { |
|
| 119 return cap; |
|
| 120 } else { |
|
| 121 return cap - (cap % alignment) + alignment; |
|
| 122 } |
|
| 123 } |
|
| 124 |
|
| 125 int cx_array_reserve( |
|
| 126 void **array, |
|
| 127 void *size, |
|
| 128 void *capacity, |
|
| 129 unsigned width, |
|
| 130 size_t elem_size, |
|
| 131 size_t elem_count, |
|
| 132 CxArrayReallocator *reallocator |
|
| 133 ) { |
143 ) { |
| 134 // assert pointers |
144 // assert pointers |
| |
145 assert(allocator != NULL); |
| 135 assert(array != NULL); |
146 assert(array != NULL); |
| 136 assert(size != NULL); |
147 assert(cmp_func != NULL); |
| 137 assert(capacity != NULL); |
148 assert(sorted_data != NULL); |
| 138 |
149 |
| 139 // default reallocator |
150 // corner case |
| 140 if (reallocator == NULL) { |
151 if (n == 0) return 0; |
| 141 reallocator = cx_array_default_reallocator; |
152 |
| 142 } |
153 // overflow check |
| 143 |
154 // LCOV_EXCL_START |
| 144 // determine size and capacity |
155 if (n > SIZE_MAX - array->size) { |
| 145 size_t oldcap; |
|
| 146 size_t oldsize; |
|
| 147 size_t max_size; |
|
| 148 if (width == 0 || width == sizeof(size_t)) { |
|
| 149 oldcap = *(size_t*) capacity; |
|
| 150 oldsize = *(size_t*) size; |
|
| 151 max_size = SIZE_MAX; |
|
| 152 } else if (width == sizeof(uint16_t)) { |
|
| 153 oldcap = *(uint16_t*) capacity; |
|
| 154 oldsize = *(uint16_t*) size; |
|
| 155 max_size = UINT16_MAX; |
|
| 156 } else if (width == sizeof(uint8_t)) { |
|
| 157 oldcap = *(uint8_t*) capacity; |
|
| 158 oldsize = *(uint8_t*) size; |
|
| 159 max_size = UINT8_MAX; |
|
| 160 } |
|
| 161 #if CX_WORDSIZE == 64 |
|
| 162 else if (width == sizeof(uint32_t)) { |
|
| 163 oldcap = *(uint32_t*) capacity; |
|
| 164 oldsize = *(uint32_t*) size; |
|
| 165 max_size = UINT32_MAX; |
|
| 166 } |
|
| 167 #endif |
|
| 168 else { |
|
| 169 errno = EINVAL; |
|
| 170 return 1; |
|
| 171 } |
|
| 172 |
|
| 173 // assert that the array is allocated when it has capacity |
|
| 174 assert(*array != NULL || oldcap == 0); |
|
| 175 |
|
| 176 // check for overflow |
|
| 177 if (elem_count > max_size - oldsize) { |
|
| 178 errno = EOVERFLOW; |
156 errno = EOVERFLOW; |
| 179 return 1; |
157 return 1; |
| 180 } |
158 } |
| 181 |
159 // LCOV_EXCL_STOP |
| 182 // determine new capacity |
|
| 183 size_t newcap = oldsize + elem_count; |
|
| 184 |
|
| 185 // reallocate if possible |
|
| 186 if (newcap > oldcap) { |
|
| 187 // calculate new capacity (next number divisible by 16) |
|
| 188 newcap = cx_array_align_capacity(newcap, 16, max_size); |
|
| 189 |
|
| 190 // perform reallocation |
|
| 191 void *newmem = reallocator->realloc( |
|
| 192 *array, oldcap, newcap, elem_size, reallocator |
|
| 193 ); |
|
| 194 if (newmem == NULL) { |
|
| 195 return 1; // LCOV_EXCL_LINE |
|
| 196 } |
|
| 197 |
|
| 198 // store new pointer |
|
| 199 *array = newmem; |
|
| 200 |
|
| 201 // store new capacity |
|
| 202 if (width == 0 || width == sizeof(size_t)) { |
|
| 203 *(size_t*) capacity = newcap; |
|
| 204 } else if (width == sizeof(uint16_t)) { |
|
| 205 *(uint16_t*) capacity = (uint16_t) newcap; |
|
| 206 } else if (width == sizeof(uint8_t)) { |
|
| 207 *(uint8_t*) capacity = (uint8_t) newcap; |
|
| 208 } |
|
| 209 #if CX_WORDSIZE == 64 |
|
| 210 else if (width == sizeof(uint32_t)) { |
|
| 211 *(uint32_t*) capacity = (uint32_t) newcap; |
|
| 212 } |
|
| 213 #endif |
|
| 214 } |
|
| 215 |
|
| 216 return 0; |
|
| 217 } |
|
| 218 |
|
| 219 int cx_array_copy( |
|
| 220 void **target, |
|
| 221 void *size, |
|
| 222 void *capacity, |
|
| 223 unsigned width, |
|
| 224 size_t index, |
|
| 225 const void *src, |
|
| 226 size_t elem_size, |
|
| 227 size_t elem_count, |
|
| 228 CxArrayReallocator *reallocator |
|
| 229 ) { |
|
| 230 // assert pointers |
|
| 231 assert(target != NULL); |
|
| 232 assert(size != NULL); |
|
| 233 assert(capacity != NULL); |
|
| 234 assert(src != NULL); |
|
| 235 |
|
| 236 // default reallocator |
|
| 237 if (reallocator == NULL) { |
|
| 238 reallocator = cx_array_default_reallocator; |
|
| 239 } |
|
| 240 |
|
| 241 // determine size and capacity |
|
| 242 size_t oldcap; |
|
| 243 size_t oldsize; |
|
| 244 size_t max_size; |
|
| 245 if (width == 0 || width == sizeof(size_t)) { |
|
| 246 oldcap = *(size_t*) capacity; |
|
| 247 oldsize = *(size_t*) size; |
|
| 248 max_size = SIZE_MAX; |
|
| 249 } else if (width == sizeof(uint16_t)) { |
|
| 250 oldcap = *(uint16_t*) capacity; |
|
| 251 oldsize = *(uint16_t*) size; |
|
| 252 max_size = UINT16_MAX; |
|
| 253 } else if (width == sizeof(uint8_t)) { |
|
| 254 oldcap = *(uint8_t*) capacity; |
|
| 255 oldsize = *(uint8_t*) size; |
|
| 256 max_size = UINT8_MAX; |
|
| 257 } |
|
| 258 #if CX_WORDSIZE == 64 |
|
| 259 else if (width == sizeof(uint32_t)) { |
|
| 260 oldcap = *(uint32_t*) capacity; |
|
| 261 oldsize = *(uint32_t*) size; |
|
| 262 max_size = UINT32_MAX; |
|
| 263 } |
|
| 264 #endif |
|
| 265 else { |
|
| 266 errno = EINVAL; |
|
| 267 return 1; |
|
| 268 } |
|
| 269 |
|
| 270 // assert that the array is allocated when it has capacity |
|
| 271 assert(*target != NULL || oldcap == 0); |
|
| 272 |
|
| 273 // check for overflow |
|
| 274 if (index > max_size || elem_count > max_size - index) { |
|
| 275 errno = EOVERFLOW; |
|
| 276 return 1; |
|
| 277 } |
|
| 278 |
|
| 279 // check if resize is required |
|
| 280 size_t minsize = index + elem_count; |
|
| 281 size_t newsize = oldsize < minsize ? minsize : oldsize; |
|
| 282 |
|
| 283 // reallocate if possible |
|
| 284 size_t newcap = oldcap; |
|
| 285 if (newsize > oldcap) { |
|
| 286 // check, if we need to repair the src pointer |
|
| 287 uintptr_t targetaddr = (uintptr_t) *target; |
|
| 288 uintptr_t srcaddr = (uintptr_t) src; |
|
| 289 bool repairsrc = targetaddr <= srcaddr |
|
| 290 && srcaddr < targetaddr + oldcap * elem_size; |
|
| 291 |
|
| 292 // calculate new capacity (next number divisible by 16) |
|
| 293 newcap = cx_array_align_capacity(newsize, 16, max_size); |
|
| 294 assert(newcap > newsize); |
|
| 295 |
|
| 296 // perform reallocation |
|
| 297 void *newmem = reallocator->realloc( |
|
| 298 *target, oldcap, newcap, elem_size, reallocator |
|
| 299 ); |
|
| 300 if (newmem == NULL) { |
|
| 301 return 1; // LCOV_EXCL_LINE |
|
| 302 } |
|
| 303 |
|
| 304 // repair src pointer, if necessary |
|
| 305 if (repairsrc) { |
|
| 306 src = ((char *) newmem) + (srcaddr - targetaddr); |
|
| 307 } |
|
| 308 |
|
| 309 // store new pointer |
|
| 310 *target = newmem; |
|
| 311 } |
|
| 312 |
|
| 313 // determine target pointer |
|
| 314 char *start = *target; |
|
| 315 start += index * elem_size; |
|
| 316 |
|
| 317 // copy elements and set new size |
|
| 318 // note: no overflow check here, b/c we cannot get here w/o allocation |
|
| 319 memmove(start, src, elem_count * elem_size); |
|
| 320 |
|
| 321 // if any of size or capacity changed, store them back |
|
| 322 if (newsize != oldsize || newcap != oldcap) { |
|
| 323 if (width == 0 || width == sizeof(size_t)) { |
|
| 324 *(size_t*) capacity = newcap; |
|
| 325 *(size_t*) size = newsize; |
|
| 326 } else if (width == sizeof(uint16_t)) { |
|
| 327 *(uint16_t*) capacity = (uint16_t) newcap; |
|
| 328 *(uint16_t*) size = (uint16_t) newsize; |
|
| 329 } else if (width == sizeof(uint8_t)) { |
|
| 330 *(uint8_t*) capacity = (uint8_t) newcap; |
|
| 331 *(uint8_t*) size = (uint8_t) newsize; |
|
| 332 } |
|
| 333 #if CX_WORDSIZE == 64 |
|
| 334 else if (width == sizeof(uint32_t)) { |
|
| 335 *(uint32_t*) capacity = (uint32_t) newcap; |
|
| 336 *(uint32_t*) size = (uint32_t) newsize; |
|
| 337 } |
|
| 338 #endif |
|
| 339 } |
|
| 340 |
|
| 341 // return successfully |
|
| 342 return 0; |
|
| 343 } |
|
| 344 |
|
| 345 static int cx_array_insert_sorted_impl( |
|
| 346 void **target, |
|
| 347 size_t *size, |
|
| 348 size_t *capacity, |
|
| 349 cx_compare_func cmp_func, |
|
| 350 const void *sorted_data, |
|
| 351 size_t elem_size, |
|
| 352 size_t elem_count, |
|
| 353 CxArrayReallocator *reallocator, |
|
| 354 bool allow_duplicates |
|
| 355 ) { |
|
| 356 // assert pointers |
|
| 357 assert(target != NULL); |
|
| 358 assert(size != NULL); |
|
| 359 assert(capacity != NULL); |
|
| 360 assert(cmp_func != NULL); |
|
| 361 assert(sorted_data != NULL); |
|
| 362 |
|
| 363 // default reallocator |
|
| 364 if (reallocator == NULL) { |
|
| 365 reallocator = cx_array_default_reallocator; |
|
| 366 } |
|
| 367 |
|
| 368 // corner case |
|
| 369 if (elem_count == 0) return 0; |
|
| 370 |
|
| 371 // overflow check |
|
| 372 if (elem_count > SIZE_MAX - *size) { |
|
| 373 errno = EOVERFLOW; |
|
| 374 return 1; |
|
| 375 } |
|
| 376 |
160 |
| 377 // store some counts |
161 // store some counts |
| 378 size_t old_size = *size; |
162 const size_t old_size = array->size; |
| 379 size_t old_capacity = *capacity; |
163 const size_t old_capacity = array->capacity; |
| 380 // the necessary capacity is the worst case assumption, including duplicates |
164 // the necessary capacity is the worst case assumption, including duplicates |
| 381 size_t needed_capacity = old_size + elem_count; |
165 const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + n); |
| 382 |
166 |
| 383 // if we need more than we have, try a reallocation |
167 // if we need more than we have, try a reallocation |
| 384 if (needed_capacity > old_capacity) { |
168 if (needed_capacity > old_capacity) { |
| 385 size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); |
169 if (cxReallocateArray(allocator, &array->data, needed_capacity, elem_size)) { |
| 386 void *new_mem = reallocator->realloc( |
170 return -1; // LCOV_EXCL_LINE |
| 387 *target, old_capacity, new_capacity, elem_size, reallocator |
171 } |
| 388 ); |
172 array->capacity = needed_capacity; |
| 389 if (new_mem == NULL) { |
|
| 390 // give it up right away, there is no contract |
|
| 391 // that requires us to insert as much as we can |
|
| 392 return 1; // LCOV_EXCL_LINE |
|
| 393 } |
|
| 394 *target = new_mem; |
|
| 395 *capacity = new_capacity; |
|
| 396 } |
173 } |
| 397 |
174 |
| 398 // now we have guaranteed that we can insert everything |
175 // now we have guaranteed that we can insert everything |
| 399 size_t new_size = old_size + elem_count; |
176 size_t new_size = old_size + n; |
| 400 *size = new_size; |
177 array->size = new_size; |
| 401 |
178 |
| 402 // declare the source and destination indices/pointers |
179 // declare the source and destination indices/pointers |
| 403 size_t si = 0, di = 0; |
180 size_t si = 0, di = 0; |
| 404 const char *src = sorted_data; |
181 const char *src = sorted_data; |
| 405 char *dest = *target; |
182 char *dest = array->data; |
| 406 |
183 |
| 407 // find the first insertion point |
184 // find the first insertion point |
| 408 di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func); |
185 di = cx_array_binary_search_sup_c(dest, old_size, elem_size, src, cmp_func, context); |
| 409 dest += di * elem_size; |
186 dest += di * elem_size; |
| 410 |
187 |
| 411 // move the remaining elements in the array completely to the right |
188 // move the remaining elements in the array completely to the right |
| 412 // we will call it the "buffer" for parked elements |
189 // we will call it the "buffer" for parked elements |
| 413 size_t buf_size = old_size - di; |
190 size_t buf_size = old_size - di; |
| 414 size_t bi = new_size - buf_size; |
191 size_t bi = new_size - buf_size; |
| 415 char *bptr = ((char *) *target) + bi * elem_size; |
192 char *bptr = ((char *) array->data) + bi * elem_size; |
| 416 memmove(bptr, dest, buf_size * elem_size); |
193 memmove(bptr, dest, buf_size * elem_size); |
| 417 |
194 |
| 418 // while there are both source and buffered elements left, |
195 // while there are both source and buffered elements left, |
| 419 // copy them interleaving |
196 // copy them interleaving |
| 420 while (si < elem_count && bi < new_size) { |
197 while (si < n && bi < new_size) { |
| 421 // determine how many source elements can be inserted |
198 // determine how many source elements can be inserted. |
| |
199 // the first element that shall not be inserted is the smallest element |
| |
200 // that is strictly larger than the first buffered element |
| |
201 // (located at the index of the infimum plus one). |
| |
202 // the infimum is guaranteed to exist: |
| |
203 // - if all src elements are larger, |
| |
204 // there is no buffer, and this loop is skipped |
| |
205 // - if any src element is smaller or equal, the infimum exists |
| |
206 // - when all src elements that are smaller are copied, the second part |
| |
207 // of this loop body will copy the remaining buffer (emptying it) |
| |
208 // Therefore, the buffer can never contain an element that is smaller |
| |
209 // than any element in the source and the infimum exists. |
| 422 size_t copy_len, bytes_copied; |
210 size_t copy_len, bytes_copied; |
| 423 copy_len = cx_array_binary_search_sup( |
211 copy_len = cx_array_binary_search_inf_c( |
| 424 src, |
212 src, n - si, elem_size, bptr, cmp_func, context |
| 425 elem_count - si, |
|
| 426 elem_size, |
|
| 427 bptr, |
|
| 428 cmp_func |
|
| 429 ); |
213 ); |
| 430 // binary search gives us the smallest index; |
214 copy_len++; |
| 431 // we also want to include equal elements here |
|
| 432 while (si + copy_len < elem_count |
|
| 433 && cmp_func(bptr, src+copy_len*elem_size) == 0) { |
|
| 434 copy_len++; |
|
| 435 } |
|
| 436 |
215 |
| 437 // copy the source elements |
216 // copy the source elements |
| 438 if (copy_len > 0) { |
217 if (copy_len > 0) { |
| 439 if (allow_duplicates) { |
218 if (allow_duplicates) { |
| 440 // we can copy the entire chunk |
219 // we can copy the entire chunk |
| 551 memcpy(dest, src, bytes_copied); |
322 memcpy(dest, src, bytes_copied); |
| 552 dest += bytes_copied; |
323 dest += bytes_copied; |
| 553 src += bytes_copied + skip_len * elem_size; |
324 src += bytes_copied + skip_len * elem_size; |
| 554 si += copy_len + skip_len; |
325 si += copy_len + skip_len; |
| 555 di += copy_len; |
326 di += copy_len; |
| 556 *size -= skip_len; |
327 array->size -= skip_len; |
| 557 } |
328 } |
| 558 } |
329 } |
| 559 } |
330 } |
| 560 |
331 |
| 561 // buffered elements need to be moved when we skipped duplicates |
332 // buffered elements need to be moved when we skipped duplicates |
| 562 size_t total_skipped = new_size - *size; |
333 size_t total_skipped = new_size - array->size; |
| 563 if (bi < new_size && total_skipped > 0) { |
334 if (bi < new_size && total_skipped > 0) { |
| 564 // move the remaining buffer to the end of the array |
335 // move the remaining buffer to the end of the array |
| 565 memmove(dest, bptr, elem_size * (new_size - bi)); |
336 memmove(dest, bptr, elem_size * (new_size - bi)); |
| 566 } |
337 } |
| 567 |
338 |
| 568 return 0; |
339 return 0; |
| 569 } |
340 } |
| 570 |
341 |
| 571 int cx_array_insert_sorted( |
342 int cx_array_insert_sorted_( |
| 572 void **target, |
343 const CxAllocator *allocator, |
| 573 size_t *size, |
344 CxArray *array, |
| 574 size_t *capacity, |
345 size_t elem_size, |
| |
346 const void *sorted_data, |
| |
347 size_t n, |
| 575 cx_compare_func cmp_func, |
348 cx_compare_func cmp_func, |
| 576 const void *sorted_data, |
349 bool allow_duplicates |
| 577 size_t elem_size, |
350 ) { |
| 578 size_t elem_count, |
351 cx_compare_func_wrapper wrapper = {cmp_func}; |
| 579 CxArrayReallocator *reallocator |
352 return cx_array_insert_sorted_c_(allocator, array, elem_size, sorted_data, |
| 580 ) { |
353 n, cx_ccmp_wrap, &wrapper, allow_duplicates); |
| 581 return cx_array_insert_sorted_impl(target, size, capacity, |
354 } |
| 582 cmp_func, sorted_data, elem_size, elem_count, reallocator, true); |
355 |
| 583 } |
356 #ifndef WITH_QSORT_R |
| 584 |
357 static thread_local cx_compare_func2 cx_array_fn_for_qsort; |
| 585 int cx_array_insert_unique( |
358 static thread_local void *cx_array_context_for_qsort; |
| 586 void **target, |
359 static int cx_array_qsort_wrapper(const void *l, const void *r) { |
| 587 size_t *size, |
360 return cx_array_fn_for_qsort(l, r, cx_array_context_for_qsort); |
| 588 size_t *capacity, |
361 } |
| 589 cx_compare_func cmp_func, |
362 #endif |
| 590 const void *sorted_data, |
363 |
| 591 size_t elem_size, |
364 void cx_array_qsort_c(void *array, size_t nmemb, size_t size, |
| 592 size_t elem_count, |
365 cx_compare_func2 fn, void *context) { |
| 593 CxArrayReallocator *reallocator |
366 #ifdef WITH_QSORT_R |
| 594 ) { |
367 qsort_r(array, nmemb, size, fn, context); |
| 595 return cx_array_insert_sorted_impl(target, size, capacity, |
368 #else |
| 596 cmp_func, sorted_data, elem_size, elem_count, reallocator, false); |
369 cx_array_fn_for_qsort = fn; |
| 597 } |
370 cx_array_context_for_qsort = context; |
| 598 |
371 qsort(array, nmemb, size, cx_array_qsort_wrapper); |
| 599 size_t cx_array_binary_search_inf( |
372 #endif |
| |
373 } |
| |
374 |
| |
375 void cx_array_sort_(CxArray *array, size_t elem_size, |
| |
376 cx_compare_func fn) { |
| |
377 qsort(array->data, array->size, elem_size, fn); |
| |
378 } |
| |
379 |
| |
380 void cx_array_sort_c_(CxArray *array, size_t elem_size, |
| |
381 cx_compare_func2 fn, void *context) { |
| |
382 cx_array_qsort_c(array->data, array->size, elem_size, fn, context); |
| |
383 } |
| |
384 |
| |
385 CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) { |
| |
386 return cxIterator(array->data, elem_size, array->size); |
| |
387 } |
| |
388 |
| |
389 CxIterator cx_array_iterator_ptr_(CxArray *array) { |
| |
390 return cxIteratorPtr(array->data, array->size); |
| |
391 } |
| |
392 |
| |
393 void cx_array_remove_(CxArray *array, size_t elem_size, size_t index, size_t n, bool fast) { |
| |
394 if (n == 0) return; |
| |
395 if (index >= array->size) return; |
| |
396 if (index + n >= array->size) { |
| |
397 // only tail elements are removed |
| |
398 array->size = index; |
| |
399 return; |
| |
400 } |
| |
401 array->size -= n; |
| |
402 size_t remaining = array->size - index; |
| |
403 char *dest = ((char*)array->data) + index * elem_size; |
| |
404 if (fast) { |
| |
405 char *src = dest + remaining * elem_size; |
| |
406 if (n == 1 && elem_size <= CX_WORDSIZE/8) { |
| |
407 // try to optimize int-sized values |
| |
408 // (from likely to unlikely) |
| |
409 if (elem_size == sizeof(int32_t)) { |
| |
410 *(int32_t*)dest = *(int32_t*)src; |
| |
411 return; |
| |
412 } |
| |
413 #if CX_WORDSIZE == 64 |
| |
414 if (elem_size == sizeof(int64_t)) { |
| |
415 *(int64_t*)dest = *(int64_t*)src; |
| |
416 return; |
| |
417 } |
| |
418 #endif |
| |
419 if (elem_size == sizeof(int8_t)) { |
| |
420 *(int8_t*)dest = *(int8_t*)src; |
| |
421 return; |
| |
422 } |
| |
423 if (elem_size == sizeof(int16_t)) { |
| |
424 *(int16_t*)dest = *(int16_t*)src; |
| |
425 return; |
| |
426 } |
| |
427 // note we cannot optimize the last branch, because |
| |
428 // the elem_size could be crazily misaligned |
| |
429 } |
| |
430 memcpy(dest, src, n * elem_size); |
| |
431 } else { |
| |
432 char *src = dest + n * elem_size; |
| |
433 memmove(dest, src, remaining * elem_size); |
| |
434 } |
| |
435 } |
| |
436 |
| |
437 void cx_array_free_(const CxAllocator *allocator, CxArray *array) { |
| |
438 cxFree(allocator, array->data); |
| |
439 array->data = NULL; |
| |
440 array->size = array->capacity = 0; |
| |
441 } |
| |
442 |
| |
443 |
| |
444 // implementation that finds ANY index |
| |
445 static size_t cx_array_binary_search_inf_impl( |
| 600 const void *arr, |
446 const void *arr, |
| 601 size_t size, |
447 size_t size, |
| 602 size_t elem_size, |
448 size_t elem_size, |
| 603 const void *elem, |
449 const void *elem, |
| 604 cx_compare_func cmp_func |
450 cx_compare_func2 cmp_func, |
| |
451 void *context |
| 605 ) { |
452 ) { |
| 606 // special case: empty array |
453 // special case: empty array |
| 607 if (size == 0) return 0; |
454 if (size == 0) return 0; |
| 608 |
455 |
| 609 // declare a variable that will contain the compare results |
456 // declare a variable that will contain the compare results |
| 779 struct cx_list_s *list, |
681 struct cx_list_s *list, |
| 780 size_t index, |
682 size_t index, |
| 781 const void *array, |
683 const void *array, |
| 782 size_t n |
684 size_t n |
| 783 ) { |
685 ) { |
| 784 // out of bounds and special case check |
|
| 785 if (index > list->collection.size || n == 0) return 0; |
|
| 786 |
|
| 787 // get a correctly typed pointer to the list |
|
| 788 cx_array_list *arl = (cx_array_list *) list; |
686 cx_array_list *arl = (cx_array_list *) list; |
| 789 |
687 CxArray wrap = { |
| 790 // guarantee enough capacity |
688 arl->data, list->collection.size, arl->capacity |
| 791 if (arl->capacity < list->collection.size + n) { |
689 }; |
| 792 size_t new_capacity = list->collection.size + n; |
690 if (cx_array_insert_(list->collection.allocator, &wrap, |
| 793 new_capacity = cx_array_align_capacity(new_capacity, 16, SIZE_MAX); |
691 list->collection.elem_size, index, array, n)) { |
| 794 if (cxReallocateArray( |
692 return 0; |
| 795 list->collection.allocator, |
693 } |
| 796 &arl->data, new_capacity, |
694 arl->data = wrap.data; |
| 797 list->collection.elem_size) |
695 arl->capacity = wrap.capacity; |
| 798 ) { |
696 list->collection.size = wrap.size; |
| 799 return 0; |
697 return n; |
| 800 } |
698 } |
| 801 arl->capacity = new_capacity; |
699 |
| 802 } |
700 static size_t cx_arl_insert_sorted_impl( |
| 803 |
701 struct cx_list_s *list, |
| 804 // determine insert position |
702 const void *sorted_data, |
| 805 char *arl_data = arl->data; |
703 size_t n, |
| 806 char *insert_pos = arl_data + index * list->collection.elem_size; |
704 bool allow_duplicates |
| 807 |
705 ) { |
| 808 // do we need to move some elements? |
706 cx_array_list *arl = (cx_array_list *) list; |
| 809 if (index < list->collection.size) { |
707 CxArray wrap = { |
| 810 size_t elems_to_move = list->collection.size - index; |
708 arl->data, list->collection.size, arl->capacity |
| 811 char *target = insert_pos + n * list->collection.elem_size; |
709 }; |
| 812 memmove(target, insert_pos, elems_to_move * list->collection.elem_size); |
710 |
| 813 } |
711 if (cx_array_insert_sorted_c_( |
| 814 |
712 list->collection.allocator, |
| 815 // place the new elements, if any |
713 &wrap, |
| 816 if (array != NULL) { |
714 list->collection.elem_size, |
| 817 memcpy(insert_pos, array, n * list->collection.elem_size); |
715 sorted_data, |
| 818 } |
716 n, |
| 819 list->collection.size += n; |
717 cx_list_compare_wrapper, |
| 820 |
718 list, |
| |
719 allow_duplicates |
| |
720 )) { |
| |
721 // array list implementation is "all or nothing" |
| |
722 return 0; // LCOV_EXCL_LINE |
| |
723 } |
| |
724 arl->data = wrap.data; |
| |
725 arl->capacity = wrap.capacity; |
| |
726 list->collection.size = wrap.size; |
| 821 return n; |
727 return n; |
| 822 } |
728 } |
| 823 |
729 |
| 824 static size_t cx_arl_insert_sorted( |
730 static size_t cx_arl_insert_sorted( |
| 825 struct cx_list_s *list, |
731 struct cx_list_s *list, |
| 826 const void *sorted_data, |
732 const void *sorted_data, |
| 827 size_t n |
733 size_t n |
| 828 ) { |
734 ) { |
| 829 // get a correctly typed pointer to the list |
735 return cx_arl_insert_sorted_impl(list, sorted_data, n, true); |
| 830 cx_array_list *arl = (cx_array_list *) list; |
|
| 831 |
|
| 832 if (cx_array_insert_sorted( |
|
| 833 &arl->data, |
|
| 834 &list->collection.size, |
|
| 835 &arl->capacity, |
|
| 836 list->collection.cmpfunc, |
|
| 837 sorted_data, |
|
| 838 list->collection.elem_size, |
|
| 839 n, |
|
| 840 &arl->reallocator |
|
| 841 )) { |
|
| 842 // array list implementation is "all or nothing" |
|
| 843 return 0; |
|
| 844 } else { |
|
| 845 return n; |
|
| 846 } |
|
| 847 } |
736 } |
| 848 |
737 |
| 849 static size_t cx_arl_insert_unique( |
738 static size_t cx_arl_insert_unique( |
| 850 struct cx_list_s *list, |
739 struct cx_list_s *list, |
| 851 const void *sorted_data, |
740 const void *sorted_data, |
| 852 size_t n |
741 size_t n |
| 853 ) { |
742 ) { |
| 854 // get a correctly typed pointer to the list |
743 return cx_arl_insert_sorted_impl(list, sorted_data, n, false); |
| 855 cx_array_list *arl = (cx_array_list *) list; |
|
| 856 |
|
| 857 if (cx_array_insert_unique( |
|
| 858 &arl->data, |
|
| 859 &list->collection.size, |
|
| 860 &arl->capacity, |
|
| 861 list->collection.cmpfunc, |
|
| 862 sorted_data, |
|
| 863 list->collection.elem_size, |
|
| 864 n, |
|
| 865 &arl->reallocator |
|
| 866 )) { |
|
| 867 // array list implementation is "all or nothing" |
|
| 868 return 0; |
|
| 869 } else { |
|
| 870 return n; |
|
| 871 } |
|
| 872 } |
744 } |
| 873 |
745 |
| 874 static void *cx_arl_insert_element( |
746 static void *cx_arl_insert_element( |
| 875 struct cx_list_s *list, |
747 struct cx_list_s *list, |
| 876 size_t index, |
748 size_t index, |