diff --git a/Makefile.am b/Makefile.am index 19242c14bc..91a9074681 100644 --- a/Makefile.am +++ b/Makefile.am @@ -462,6 +462,8 @@ src_libnm_glib_aux_libnm_glib_aux_la_SOURCES = \ src/libnm-glib-aux/nm-obj.h \ src/libnm-glib-aux/nm-prioq.c \ src/libnm-glib-aux/nm-prioq.h \ + src/libnm-glib-aux/nm-ptr-array.c \ + src/libnm-glib-aux/nm-ptr-array.h \ src/libnm-glib-aux/nm-random-utils.c \ src/libnm-glib-aux/nm-random-utils.h \ src/libnm-glib-aux/nm-ref-string.c \ diff --git a/src/libnm-glib-aux/meson.build b/src/libnm-glib-aux/meson.build index afd8687145..c8a0d2d5cd 100644 --- a/src/libnm-glib-aux/meson.build +++ b/src/libnm-glib-aux/meson.build @@ -18,6 +18,7 @@ libnm_glib_aux = static_library( 'nm-ref-string.c', 'nm-secret-utils.c', 'nm-shared-utils.c', + 'nm-ptr-array.c', 'nm-time-utils.c', 'nm-uuid.c', ), diff --git a/src/libnm-glib-aux/nm-ptr-array.c b/src/libnm-glib-aux/nm-ptr-array.c new file mode 100644 index 0000000000..2dff85520f --- /dev/null +++ b/src/libnm-glib-aux/nm-ptr-array.c @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-ptr-array.h" + +#include "libnm-std-aux/nm-std-utils.h" + +/*****************************************************************************/ + +#define _MALLOCSIZE_FROM_RESERVED(reserved) \ + (G_STRUCT_OFFSET(NMPtrArray, ptrs) + (sizeof(gpointer) * ((reserved) + 1u))) + +G_STATIC_ASSERT(sizeof(NMPtrArrayStack) <= NM_UTILS_GET_NEXT_REALLOC_SIZE_104); +G_STATIC_ASSERT(G_N_ELEMENTS(((NMPtrArrayStack *) NULL)->_ptrs) > 1); +G_STATIC_ASSERT(G_N_ELEMENTS(((NMPtrArrayStack *) NULL)->_ptrs) + == _NM_PTR_ARRAY_MALLOCSIZE_TO_RESERVED(NM_UTILS_GET_NEXT_REALLOC_SIZE_104) + 1u); + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPtrArray, ptrs) == G_STRUCT_OFFSET(NMPtrArrayStack, _ptrs)); + +static gsize +_mallocsize_from_reserved(gsize reserved) +{ + nm_assert(reserved < ((G_MAXSIZE - G_STRUCT_OFFSET(NMPtrArray, ptrs)) / sizeof(gpointer)) - 1u); + + return _MALLOCSIZE_FROM_RESERVED(reserved); +} + +static gsize +_mallocsize_to_reserved(gsize size) +{ + gsize reserved; + + reserved = _NM_PTR_ARRAY_MALLOCSIZE_TO_RESERVED(size); + nm_assert(size >= _mallocsize_from_reserved(reserved)); + return reserved; +} + +NMPtrArray * +nm_ptr_array_new(GDestroyNotify destroy_fcn, gsize reserved) +{ + NMPtrArray *arr; + + if (reserved < 3u) + reserved = 3u; + + arr = g_malloc(_mallocsize_from_reserved(reserved)); + + *((gsize *) &arr->len) = 0; + arr->_reserved = reserved; + arr->_destroy_fcn = destroy_fcn; + *((bool *) &arr->is_stack) = FALSE; + arr->ptrs[0] = NULL; + + return arr; +} + +void +nm_ptr_array_add_n(NMPtrArray **p_arr, gsize n, gpointer *ptrs) +{ + NMPtrArray *arr; + gsize new_reserved; + gsize new_len; + + nm_assert(p_arr); + nm_assert(*p_arr); + + if (n == 0) + return; + + arr = *p_arr; + + nm_assert(n < G_MAXSIZE - arr->len); + new_len = arr->len + n; + + nm_assert(new_len > arr->len); + + /* Note that arr->_reserved does not count the element for the + * last trailing NULL. That is, arr->ptrs[arr->_reserved] is valid. + * In other words, new_len may be as large as arr->reserved before + * we need to reallocate, and `arr->ptrs[new_len] = NULL` is correct. */ + + if (new_len > arr->_reserved) { + gsize n_bytes; + + /* We grow the total buffer size using nm_utils_get_next_realloc_size(). + * This quite aggressively increases the buffer size. The idea is that + * NMPtrArray is mostly used for short lived purposes, and it's OK to + * waste some space to reduce re-allocation. */ + n_bytes = nm_utils_get_next_realloc_size(TRUE, _mallocsize_from_reserved(new_len)); + + new_reserved = _mallocsize_to_reserved(n_bytes); + + nm_assert(new_len <= new_reserved); + nm_assert(n_bytes >= _mallocsize_from_reserved(new_reserved)); + + if (arr->is_stack) { + NMPtrArray *arr2 = arr; + + arr = g_malloc(_mallocsize_from_reserved(new_reserved)); + memcpy(arr, arr2, _mallocsize_from_reserved(arr2->_reserved)); + *((bool *) &arr->is_stack) = FALSE; + } else { + arr = g_realloc(arr, _mallocsize_from_reserved(new_reserved)); + } + arr->_reserved = new_reserved; + + *p_arr = arr; + } + + memcpy(&arr->ptrs[arr->len], ptrs, sizeof(gpointer) * n); + arr->ptrs[new_len] = NULL; + *((gsize *) &arr->len) = new_len; +} + +void +nm_ptr_array_clear(NMPtrArray *arr) +{ + if (!arr) + return; + + if (arr->len == 0) + return; + + if (!arr->_destroy_fcn) { + (*((gsize *) &arr->len)) = 0; + arr->ptrs[0] = NULL; + return; + } + + do { + gsize idx; + gpointer p; + + idx = (--(*((gsize *) &arr->len))); + + p = g_steal_pointer(&arr->ptrs[idx]); + + if (p) + arr->_destroy_fcn(p); + } while (arr->len > 0); +} + +void +nm_ptr_array_destroy(NMPtrArray *arr) +{ + if (!arr) + return; + + nm_ptr_array_clear(arr); + if (!arr->is_stack) + g_free(arr); +} diff --git a/src/libnm-glib-aux/nm-ptr-array.h b/src/libnm-glib-aux/nm-ptr-array.h new file mode 100644 index 0000000000..0a7f28a876 --- /dev/null +++ b/src/libnm-glib-aux/nm-ptr-array.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_PTR_ARRAY_H__ +#define __NM_PTR_ARRAY_H__ + +typedef struct _NMPtrArray { + const gsize len; + + /* How many elements are allocated/reserved for the ptrs array. + * Note that there is always an extra space reserved for the + * NULL termination afterwards. It means, "len" can grow up + * until (including) _reserved, before reallocation is necessary. + * + * In other words, arr->ptrs[arr->_reserved] is allocated and reserved + * for the trailing NULL (but may be uninitialized if the array is shorter). */ + gsize _reserved; + + GDestroyNotify _destroy_fcn; + + const bool is_stack; + + /* This will be the NULL terminated list of pointers. If you + * know what you are doing, you can also steal elements from + * the list. */ + gpointer ptrs[]; +} NMPtrArray; + +#define _NM_PTR_ARRAY_MALLOCSIZE_TO_RESERVED(size) \ + ((((size) -G_STRUCT_OFFSET(NMPtrArray, ptrs)) / sizeof(gpointer)) - 1u) + +#define NM_PTR_ARRAY_STACK_RESERVED \ + _NM_PTR_ARRAY_MALLOCSIZE_TO_RESERVED(NM_UTILS_GET_NEXT_REALLOC_SIZE_104) + +/* For cases where we don't need to pass a NMPtrArray to the caller, we can + * start with a stack-allocated array. That one starts with 8 pointers + * reserved (or 104 bytes) on the stack (on x64_86). 104 is also the magic + * number that is suitable for reallocation. See NM_UTILS_GET_NEXT_REALLOC_SIZE_104. + * + * Usage: + * NMPtrArrayStack arr_stack = NM_PTR_ARRAY_STACK_INIT(g_free); + * nm_auto_ptrarray *arr = &arr_stack.arr; + * ... + * collect_pointers(args, &arr); + * ... + * do_something_with_pointers(arr); + **/ +typedef struct { + NMPtrArray arr; + gpointer _ptrs[NM_PTR_ARRAY_STACK_RESERVED + 1]; +} NMPtrArrayStack; + +#define NM_PTR_ARRAY_STACK_INIT(destroy_fcn) \ + ((NMPtrArrayStack){ \ + .arr = \ + { \ + .len = 0, \ + ._reserved = NM_PTR_ARRAY_STACK_RESERVED, \ + ._destroy_fcn = ((GDestroyNotify) (destroy_fcn)), \ + .is_stack = TRUE, \ + }, \ + ._ptrs = {NULL}, \ + }) + +NMPtrArray *nm_ptr_array_new(GDestroyNotify destroy_fcn, gsize reserved); + +void nm_ptr_array_add_n(NMPtrArray **arr, gsize n, gpointer *ptrs); + +static inline void +nm_ptr_array_add(NMPtrArray **p_arr, gpointer ptr) +{ + NMPtrArray *arr; + + nm_assert(p_arr); + nm_assert(*p_arr); + + arr = *p_arr; + + if (G_LIKELY(arr->len < arr->_reserved)) { + /* Fast-path. We don't need to reallocate. */ + arr->ptrs[arr->len] = ptr; + *((gsize *) &arr->len) += 1; + arr->ptrs[arr->len] = NULL; + return; + } + + nm_ptr_array_add_n(p_arr, 1, &ptr); +} + +static inline NMPtrArray * +nm_ptr_array_set_free_func(NMPtrArray *arr, GDestroyNotify destroy_fcn) +{ + nm_assert(arr); + + arr->_destroy_fcn = destroy_fcn; + return arr; +} + +void nm_ptr_array_clear(NMPtrArray *arr); + +void nm_ptr_array_destroy(NMPtrArray *arr); + +NM_AUTO_DEFINE_FCN0(NMPtrArray *, _nm_auto_ptrarray, nm_ptr_array_destroy); +#define nm_auto_ptrarray nm_auto(_nm_auto_ptrarray) + +#endif /* __NM_PTR_ARRAY_H__ */ diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index 55f8cbb1d1..29fb479b8f 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -14,6 +14,7 @@ #include "libnm-glib-aux/nm-ref-string.h" #include "libnm-glib-aux/nm-io-utils.h" #include "libnm-glib-aux/nm-prioq.h" +#include "libnm-glib-aux/nm-ptr-array.h" #include "libnm-glib-aux/nm-test-utils.h" @@ -2630,6 +2631,69 @@ test_uid_to_name(void) /*****************************************************************************/ +static void +test_nm_ptr_array(void) +{ + const int N_RUN = 100; + int i_run; + + for (i_run = 0; i_run < N_RUN; i_run++) { + NMPtrArrayStack arr_stack = NM_PTR_ARRAY_STACK_INIT(g_free); + char sbuf_num[64]; + nm_auto_ptrarray NMPtrArray *arr = NULL; + int n_add; + int i_add; + guint counter; + guint n_ptr; + guint i_ptr; + + counter = 0; + if (nmtst_get_rand_bool()) + arr = nm_ptr_array_new(g_free, nmtst_get_rand_uint32() % 10); + else { + /* NMPtrArray can also start with using a stack allocated array, + * via NMPtrArrayStack/NM_PTR_ARRAY_STACK_INIT(). */ + arr = &arr_stack.arr; + } + + n_add = nmtst_get_rand_uint32() % 10u; + for (i_add = 0; i_add < n_add; i_add++) { + gpointer ptrs[NM_PTR_ARRAY_STACK_RESERVED + 10]; + + n_ptr = nmtst_get_rand_uint32() % G_N_ELEMENTS(ptrs); + for (i_ptr = 0; i_ptr < n_ptr; i_ptr++) + ptrs[i_ptr] = g_strdup(nm_sprintf_buf(sbuf_num, "%u", counter++)); + + if (n_ptr == 1 && nmtst_get_rand_bool()) + nm_ptr_array_add(&arr, ptrs[0]); + else + nm_ptr_array_add_n(&arr, n_ptr, ptrs); + + if (i_add == n_add - 1 || nmtst_get_rand_one_case_in(5)) { + g_assert_cmpint(arr->len, ==, counter); + g_assert_cmpint(arr->_reserved, >=, counter); + g_assert(arr->_destroy_fcn == g_free); + for (i_ptr = 0; i_ptr < counter; i_ptr++) { + nm_sprintf_buf(sbuf_num, "%u", i_ptr); + g_assert_cmpstr(arr->ptrs[i_ptr], ==, sbuf_num); + } + g_assert(!arr->ptrs[counter]); + } + } + + if (nmtst_get_rand_bool()) { + nm_ptr_array_set_free_func(arr, NULL); + for (i_ptr = 0; i_ptr < arr->len; i_ptr++) + nm_clear_g_free(&arr->ptrs[i_ptr]); + } + + if (nmtst_get_rand_bool()) + nm_clear_pointer(&arr, nm_ptr_array_destroy); + } +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -2682,6 +2746,7 @@ main(int argc, char **argv) g_test_add_func("/general/test_nm_prioq", test_nm_prioq); g_test_add_func("/general/test_nm_random", test_nm_random); g_test_add_func("/general/test_uid_to_name", test_uid_to_name); + g_test_add_func("/general/test_nm_ptr_array", test_nm_ptr_array); return g_test_run(); }