summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2022-10-28 13:54:57 +0200
committerThomas Haller <thaller@redhat.com>2023-11-22 16:28:44 +0100
commit3e8ec66df647afa232fb1c57425982bf96483e68 (patch)
treede7859dd072558c56423163c18d495106f64dbed
parent7bdb606a919ced712d2d300861565090299c099e (diff)
glib-aux: add NMPtrArray helper
Add a simple structure that can own a list of pointers. The main purpose is to return one or more pointers from a function, where ownership gets transferred. This is a more limited form of a GPtrArray. Except, it actually NULL terminates the list, and it requires only one heap allocation for the structure and the list. With using NMPtrArrayStack, it even can track the first 8 pointers (in x64_86) on the stack and avoiding heap allocation altogether. That is of course only useful, when the ptr array is not to be returned from the function (to the caller), but instead passed down to called functions which can fill it.
-rw-r--r--Makefile.am2
-rw-r--r--src/libnm-glib-aux/meson.build1
-rw-r--r--src/libnm-glib-aux/nm-ptr-array.c153
-rw-r--r--src/libnm-glib-aux/nm-ptr-array.h105
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c65
5 files changed, 326 insertions, 0 deletions
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();
}