summaryrefslogtreecommitdiff
path: root/libmisc
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc')
-rw-r--r--libmisc/CMakeLists.txt13
-rw-r--r--libmisc/assert.c7
-rw-r--r--libmisc/include/libmisc/_intercept.h27
-rw-r--r--libmisc/include/libmisc/alloc.h26
-rw-r--r--libmisc/include/libmisc/assert.h17
-rw-r--r--libmisc/include/libmisc/endian.h81
-rw-r--r--libmisc/include/libmisc/linkedlist.h108
-rw-r--r--libmisc/include/libmisc/log.h29
-rw-r--r--libmisc/include/libmisc/macro.h111
-rw-r--r--libmisc/include/libmisc/map.h146
-rw-r--r--libmisc/include/libmisc/private.h32
-rw-r--r--libmisc/include/libmisc/rand.h6
-rw-r--r--libmisc/include/libmisc/vcall.h28
-rw-r--r--libmisc/intercept.c34
-rw-r--r--libmisc/linkedlist.c64
-rw-r--r--libmisc/log.c18
-rw-r--r--libmisc/map.c230
-rw-r--r--libmisc/tests/test_assert.c36
-rw-r--r--libmisc/tests/test_assert_min.c17
-rw-r--r--libmisc/tests/test_endian.c39
-rw-r--r--libmisc/tests/test_log.c49
-rw-r--r--libmisc/tests/test_macro.c54
-rw-r--r--libmisc/tests/test_map.c60
-rw-r--r--libmisc/tests/test_private.c10
-rw-r--r--libmisc/tests/test_rand.c25
-rw-r--r--libmisc/tests/test_vcall.c74
26 files changed, 1093 insertions, 248 deletions
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 02b19d5..fdbe949 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -1,20 +1,25 @@
-# libmisc/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions
+# libmisc/CMakeLists.txt - Low-level C programming utilities; sort of an augmented "libc"
#
-# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
add_library(libmisc INTERFACE)
-target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_sources(libmisc INTERFACE
assert.c
+ intercept.c
+ linkedlist.c
log.c
+ map.c
)
target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
add_lib_test(libmisc test_assert)
+add_lib_test(libmisc test_assert_min)
add_lib_test(libmisc test_endian)
add_lib_test(libmisc test_hash)
add_lib_test(libmisc test_log)
+add_lib_test(libmisc test_macro)
+add_lib_test(libmisc test_map)
add_lib_test(libmisc test_private)
add_lib_test(libmisc test_rand)
-add_lib_test(libmisc test_vcall)
diff --git a/libmisc/assert.c b/libmisc/assert.c
index 8231c85..fdd8154 100644
--- a/libmisc/assert.c
+++ b/libmisc/assert.c
@@ -1,11 +1,10 @@
/* libmisc/assert.c - More assertions
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <stdbool.h> /* for bool, true, false */
-#include <stdlib.h> /* for abort() */
#define LOG_NAME ASSERT
#include <libmisc/log.h> /* for errorf() */
@@ -13,7 +12,7 @@
#include <libmisc/assert.h>
#ifndef NDEBUG
-[[noreturn, gnu::weak]]
+#define __lm_printf __lm_light_printf
void __assert_msg_fail(const char *expr,
const char *file, unsigned int line, const char *func,
const char *msg) {
@@ -26,6 +25,6 @@ void __assert_msg_fail(const char *expr,
msg ? ": " : "", msg ?: "");
in_fail = false;
}
- abort();
+ __lm_abort();
}
#endif
diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h
new file mode 100644
index 0000000..a264144
--- /dev/null
+++ b/libmisc/include/libmisc/_intercept.h
@@ -0,0 +1,27 @@
+/* libmisc/_intercept.h - Interceptable ("weak") functions
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC__INTERCEPT_H_
+#define _LIBMISC__INTERCEPT_H_
+
+/* pico-sdk/newlib define these to be [[gnu:weak]] already, but
+ * depending on optimization options glibc might not, and GCC might
+ * assume it knows what they do and optimize them out. So define our
+ * own `__lm_` wrappers that GCC/glibc won't interfere with.
+ */
+
+[[gnu::format(printf, 1, 2)]]
+int __lm_printf(const char *format, ...);
+
+[[noreturn]] void __lm_abort(void);
+
+/* __lm_light_printf is expected to have less stack use than regular
+ * __lm_printf, if possible. */
+
+[[gnu::format(printf, 1, 2)]]
+int __lm_light_printf(const char *format, ...);
+
+#endif /* _LIBMISC__INTERCEPT_H_ */
diff --git a/libmisc/include/libmisc/alloc.h b/libmisc/include/libmisc/alloc.h
new file mode 100644
index 0000000..afddbce
--- /dev/null
+++ b/libmisc/include/libmisc/alloc.h
@@ -0,0 +1,26 @@
+/* libmisc/alloc.h - Type-safe wrappers around alloca and malloc
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_ALLOC_H_
+#define _LIBMISC_ALLOC_H_
+
+#include <alloca.h> /* for alloca() */
+#include <stdlib.h> /* for calloc(), free() */
+#include <string.h> /* for memset() */
+
+#define stack_alloc(N, TYP) ({ \
+ size_t _size; \
+ TYP *_ret = NULL; \
+ if (!__builtin_mul_overflow(N, sizeof(TYP), &_size)) { \
+ _ret = alloca(_size); \
+ memset(_ret, 0, _size); \
+ } \
+ _ret; \
+})
+
+#define heap_alloc(N, TYP) ((TYP *)calloc(N, sizeof(TYP)))
+
+#endif /* _LIBMISC_ALLOC_H_ */
diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h
index da2ba2b..ccdb288 100644
--- a/libmisc/include/libmisc/assert.h
+++ b/libmisc/include/libmisc/assert.h
@@ -1,6 +1,6 @@
/* libmisc/assert.h - More assertions
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -10,15 +10,20 @@
#ifdef NDEBUG
# define __assert_msg(expr, expr_str, msg) ((void)0)
#else
-# define __assert_msg(expr, expr_str, msg) do { if (!(expr)) __assert_msg_fail(expr_str, __FILE__, __LINE__, __func__, msg); } while (0)
+# define __assert_msg(expr, expr_str, msg) \
+ do { \
+ if (!(expr)) \
+ __assert_msg_fail(expr_str, __FILE__, __LINE__, __func__, msg); \
+ } while (0)
[[noreturn]] void __assert_msg_fail(const char *expr,
const char *file, unsigned int line, const char *func,
const char *msg);
#endif
-#define assert_msg(expr, msg) __assert_msg(expr, #expr, msg) /* libmisc */
-#define assert(expr) __assert_msg(expr, #expr, NULL) /* C89, POSIX-2001 */
-#define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */
-#define static_assert _Static_assert /* C11 */
+#define assert_msg(expr, msg) __assert_msg(expr, #expr, msg) /* libmisc */
+#define assert(expr) __assert_msg(expr, #expr, 0) /* C89, POSIX-2001 */
+#define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */
+#define static_assert _Static_assert /* C11 */
+#define static_assert_as_expr(...) (sizeof(struct {static_assert(__VA_ARGS__);})) /* libmisc */
#endif /* _LIBMISC_ASSERT_H_ */
diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h
index b1bc55c..75240fe 100644
--- a/libmisc/include/libmisc/endian.h
+++ b/libmisc/include/libmisc/endian.h
@@ -1,12 +1,13 @@
/* libmisc/endian.h - Endian-conversion helpers
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#ifndef _LIBMISC_ENDIAN_H_
#define _LIBMISC_ENDIAN_H_
+#include <stddef.h> /* for size_t */
#include <stdint.h> /* for uint{n}_t */
#include <libmisc/assert.h>
@@ -71,6 +72,45 @@ static inline uint32_t uint32be_unmarshal(uint32be_t in) {
return uint32be_decode(in.octets);
}
+typedef struct {
+ uint8_t octets[8];
+} uint64be_t;
+static_assert(sizeof(uint64be_t) == 8);
+
+static inline size_t uint64be_encode(uint8_t *out, uint64_t in) {
+ out[0] = (uint8_t)((in >> 56) & 0xFF);
+ out[1] = (uint8_t)((in >> 48) & 0xFF);
+ out[2] = (uint8_t)((in >> 40) & 0xFF);
+ out[3] = (uint8_t)((in >> 32) & 0xFF);
+ out[4] = (uint8_t)((in >> 24) & 0xFF);
+ out[5] = (uint8_t)((in >> 16) & 0xFF);
+ out[6] = (uint8_t)((in >> 8) & 0xFF);
+ out[7] = (uint8_t)((in >> 0) & 0xFF);
+ return 8;
+}
+
+static inline uint64_t uint64be_decode(uint8_t *in) {
+ return (((uint64_t)(in[0])) << 56)
+ | (((uint64_t)(in[1])) << 48)
+ | (((uint64_t)(in[2])) << 40)
+ | (((uint64_t)(in[3])) << 32)
+ | (((uint64_t)(in[4])) << 24)
+ | (((uint64_t)(in[5])) << 16)
+ | (((uint64_t)(in[6])) << 8)
+ | (((uint64_t)(in[7])) << 0)
+ ;
+}
+
+static inline uint64be_t uint64be_marshal(uint64_t in) {
+ uint64be_t out;
+ uint64be_encode(out.octets, in);
+ return out;
+}
+
+static inline uint64_t uint64be_unmarshal(uint64be_t in) {
+ return uint64be_decode(in.octets);
+}
+
/* Little endian **************************************************************/
typedef struct {
@@ -131,4 +171,43 @@ static inline uint32_t uint32le_unmarshal(uint32le_t in) {
return uint32le_decode(in.octets);
}
+typedef struct {
+ uint8_t octets[8];
+} uint64le_t;
+static_assert(sizeof(uint64le_t) == 8);
+
+static inline size_t uint64le_encode(uint8_t *out, uint64_t in) {
+ out[0] = (uint8_t)((in >> 0) & 0xFF);
+ out[1] = (uint8_t)((in >> 8) & 0xFF);
+ out[2] = (uint8_t)((in >> 16) & 0xFF);
+ out[3] = (uint8_t)((in >> 24) & 0xFF);
+ out[4] = (uint8_t)((in >> 32) & 0xFF);
+ out[5] = (uint8_t)((in >> 40) & 0xFF);
+ out[6] = (uint8_t)((in >> 48) & 0xFF);
+ out[7] = (uint8_t)((in >> 56) & 0xFF);
+ return 8;
+}
+
+static inline uint64_t uint64le_decode(uint8_t *in) {
+ return (((uint64_t)(in[0])) << 0)
+ | (((uint64_t)(in[1])) << 8)
+ | (((uint64_t)(in[2])) << 16)
+ | (((uint64_t)(in[3])) << 24)
+ | (((uint64_t)(in[4])) << 32)
+ | (((uint64_t)(in[5])) << 40)
+ | (((uint64_t)(in[6])) << 48)
+ | (((uint64_t)(in[7])) << 56)
+ ;
+}
+
+static inline uint64le_t uint64le_marshal(uint64_t in) {
+ uint64le_t out;
+ uint64le_encode(out.octets, in);
+ return out;
+}
+
+static inline uint64_t uint64le_unmarshal(uint64le_t in) {
+ return uint64le_decode(in.octets);
+}
+
#endif /* _LIBMISC_ENDIAN_H_ */
diff --git a/libmisc/include/libmisc/linkedlist.h b/libmisc/include/libmisc/linkedlist.h
new file mode 100644
index 0000000..b6ff688
--- /dev/null
+++ b/libmisc/include/libmisc/linkedlist.h
@@ -0,0 +1,108 @@
+/* libmisc/linkedlist.h - Singly- and doubly- linked lists
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_LINKEDLIST_H_
+#define _LIBMISC_LINKEDLIST_H_
+
+/* low-level (intrusive) singly linked list ***********************************/
+
+struct _slist_node {
+ struct _slist_node *rear;
+};
+
+struct _slist_root {
+ struct _slist_node *front, *rear;
+};
+
+void _slist_push_to_rear(struct _slist_root *root, struct _slist_node *node);
+void _slist_pop_from_front(struct _slist_root *root);
+
+/* low-level (intrusive) doubly linked list ***********************************/
+
+struct _dlist_node {
+ struct _dlist_node *front, *rear;
+};
+
+struct _dlist_root {
+ struct _dlist_node *front, *rear;
+};
+
+void _dlist_push_to_rear(struct _dlist_root *root, struct _dlist_node *node);
+void _dlist_remove(struct _dlist_root *root, struct _dlist_node *node);
+void _dlist_pop_from_front(struct _dlist_root *root);
+
+/* singly linked list (non-intrusive) *****************************************/
+
+#define SLIST_DECLARE(NAME) \
+ struct NAME##_node; \
+ struct NAME { \
+ struct NAME##_node *front, *rear; \
+ struct NAME *_slist_root_typ[0]; \
+ }
+
+#define SLIST_DECLARE_NODE(NAME, VAL_T) \
+ struct NAME##_node { \
+ struct NAME##_node *rear; \
+ VAL_T val; \
+ }
+
+#define slist_push_to_rear(ROOT, NODE) { \
+ /* These temporary variables are to get the compiler to check \
+ * the types. */ \
+ typeof(*(ROOT)->_slist_root_typ[0]) *_rootp = ROOT; \
+ typeof(*_rootp->front) *_nodep = NODE; \
+ _slist_push_to_rear((struct _slist_root *)_rootp, \
+ (struct _slist_node *)_nodep); \
+} while (0)
+
+#define slist_pop_from_front(ROOT) { \
+ /* This temporary variables are to get the compiler to check \
+ * the type. */ \
+ typeof(*(ROOT)->_slist_root_typ[0]) *_rootp = ROOT; \
+ _slist_pop_from_front((struct _slist_root *)_rootp); \
+} while (0)
+
+/* doubly linked list (non-intrusive) *****************************************/
+
+#define DLIST_DECLARE(NAME) \
+ struct NAME##_node; \
+ struct NAME { \
+ struct NAME##_node *front, *rear; \
+ struct NAME *_dlist_root_typ[0]; \
+ }
+
+#define DLIST_DECLARE_NODE(NAME, VAL_T) \
+ struct NAME##_node { \
+ struct NAME##_node *front, *rear; \
+ VAL_T val; \
+ }
+
+#define dlist_push_to_rear(ROOT, NODE) { \
+ /* These temporary variables are to get the compiler to check \
+ * the types. */ \
+ typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \
+ typeof(*_rootp->front) *_nodep = NODE; \
+ _dlist_push_to_rear((struct _dlist_root *)_rootp, \
+ (struct _dlist_node *)_nodep); \
+} while (0)
+
+#define dlist_remove(ROOT, NODE) { \
+ /* These temporary variables are to get the compiler to check \
+ * the types. */ \
+ typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \
+ typeof(*_rootp->front) *_nodep = NODE; \
+ _dlist_remove((struct _dlist_root *)_rootp, \
+ (struct _dlist_node *)_nodep); \
+} while (0)
+
+#define dlist_pop_from_front(ROOT) { \
+ /* This temporary variables are to get the compiler to check \
+ * the type. */ \
+ typeof(*(ROOT)->_dlist_root_typ[0]) *_rootp = ROOT; \
+ _dlist_pop_from_front((struct _dlist_root *)_rootp); \
+} while (0)
+
+#endif /* _LIBMISC_LINKEDLIST_H_ */
diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h
index b4f5461..79c0ab6 100644
--- a/libmisc/include/libmisc/log.h
+++ b/libmisc/include/libmisc/log.h
@@ -1,6 +1,6 @@
/* libmisc/log.h - stdio logging
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,31 +9,26 @@
#include <stdint.h> /* for uint8_t */
-#ifndef LOG_NAME
- #error "each compilation unit that includes <libmisc/log.h> must define LOG_NAME"
-#endif
+#include <libmisc/macro.h>
+#include <libmisc/_intercept.h>
#ifdef NDEBUG
#define _LOG_NDEBUG 1
#else
#define _LOG_NDEBUG 0
#endif
-#define __LOG_STR(x) #x
-#define _LOG_STR(x) __LOG_STR(x)
-#define __LOG_CAT3(a, b, c) a ## b ## c
-#define _LOG_CAT3(a, b, c) __LOG_CAT3(a, b, c)
-[[format(printf, 1, 2)]] int _log_printf(const char *format, ...);
+const char *const_byte_str(uint8_t b);
-#define n_errorf(nam, fmt, ...) do { _log_printf("error: " _LOG_STR(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
-#define n_infof(nam, fmt, ...) do { _log_printf("info : " _LOG_STR(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
-#define n_debugf(nam, fmt, ...) do { if (_LOG_CAT3(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
- _log_printf("debug: " _LOG_STR(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#define n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#define n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#define n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
+ __lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+#endif /* _LIBMISC_LOG_H_ */
+
+#if defined(LOG_NAME) && !defined(errorf)
#define errorf(fmt, ...) n_errorf(LOG_NAME, fmt, __VA_ARGS__)
#define infof(fmt, ...) n_infof(LOG_NAME, fmt, __VA_ARGS__)
#define debugf(fmt, ...) n_debugf(LOG_NAME, fmt, __VA_ARGS__)
-
-const char *const_byte_str(uint8_t b);
-
-#endif /* _LIBMISC_LOG_H_ */
+#endif
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
new file mode 100644
index 0000000..a95ac82
--- /dev/null
+++ b/libmisc/include/libmisc/macro.h
@@ -0,0 +1,111 @@
+/* libmisc/macro.h - Useful C preprocessor macros
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_MACRO_H_
+#define _LIBMISC_MACRO_H_
+
+#include <libmisc/assert.h>
+
+/* for function definitions */
+
+#define LM_UNUSED(argname)
+#define LM_ALWAYS_INLINE [[gnu::always_inline]] inline
+#define LM_NEVER_INLINE [[gnu::noinline]]
+#define LM_FLATTEN [[gnu::flatten]]
+
+/* types */
+
+/* If it's a pointer instead of an array, then typeof(&ptr[0]) == typeof(ptr) */
+#define _LM_IS_ARRAY(ary) (!__builtin_types_compatible_p(typeof(&(ary)[0]), typeof(ary)))
+#define LM_ARRAY_LEN(ary) ( (sizeof(ary)/sizeof((ary)[0])) + static_assert_as_expr(_LM_IS_ARRAY(ary)) )
+
+#define LM_CAST_FIELD_TO_STRUCT(STRUCT_TYP, FIELD_NAME, PTR_TO_FIELD) ({ \
+ /* The _fptr assignment is to get the compiler to do type checking. */ \
+ typeof(((STRUCT_TYP *)NULL)->FIELD_NAME) *_fptr = (PTR_TO_FIELD); \
+ _fptr \
+ ? ((STRUCT_TYP*)( ((void*)_fptr) - offsetof(STRUCT_TYP, FIELD_NAME))) \
+ : NULL; \
+})
+
+/* numeric */
+
+#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */
+#define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */
+#define LM_ROUND_DOWN(n, d) ( ((n)/(d)) * (d) ) /** Return `n` rounded down to the nearest multiple of `d` */
+#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1) /** Return the lowest power of 2 that is > x */
+#define LM_FLOORLOG2(x) ((sizeof(unsigned long long)*8)-__builtin_clzll(x)-1) /** Return floor(log_2(x) */
+
+/* strings */
+
+#define LM_STR(x) #x
+#define LM_STR_(x) LM_STR(x)
+
+/* token pasting */
+
+#define LM_CAT2(a, b) a ## b
+#define LM_CAT3(a, b, c) a ## b ## c
+
+#define LM_CAT2_(a, b) LM_CAT2(a, b)
+#define LM_CAT3_(a, b, c) LM_CAT3(a, b, c)
+
+/* macro arguments */
+
+#define LM_FIRST(a, ...) a
+#define LM_SECOND(a, b, ...) b
+#define LM_EAT(...)
+#define LM_EXPAND(...) __VA_ARGS__
+
+/* conditionals */
+
+#define LM_T xxTxx
+#define LM_F xxFxx
+
+#define LM_SENTINEL() bogus, LM_T /* a magic sentinel value */
+#define LM_IS_SENTINEL(...) LM_SECOND(__VA_ARGS__, LM_F)
+
+#define LM_IF(cond) LM_CAT2(_LM_IF__, cond) /* LM_IF(cond)(then)(else) */
+#define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT
+#define _LM_IF__xxFxx(...) LM_EXPAND
+
+/* tuples */
+
+#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
+#define _LM_IS_TUPLE(...) LM_SENTINEL()
+
+/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */
+#define _LM_TUPLES_COMMA(tuple...) (tuple),
+#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples)
+#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples))
+#define LM_TUPLES_TAIL(tuples) LM_EAT tuples
+
+/* iteration */
+
+/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */
+#define LM_FOREACH_TUPLE(tuples, func, ...) \
+ _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__))
+#define _LM_FOREACH_TUPLE(tuples, func, ...) \
+ LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
+ _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
+ _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
+ )()
+#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE
+
+#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
+
+#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */
+#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__))
+#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__))
+#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__))
+#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__))
+#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__))
+#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__))
+#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__))
+#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__))
+#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__))
+#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__))
+#define _LM_EVAL__1(...) __VA_ARGS__
+
+#endif /* _LIBMISC_MACRO_H_ */
diff --git a/libmisc/include/libmisc/map.h b/libmisc/include/libmisc/map.h
new file mode 100644
index 0000000..6622595
--- /dev/null
+++ b/libmisc/include/libmisc/map.h
@@ -0,0 +1,146 @@
+/* libmisc/map.h - A map/dict data structure
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_MAP_H_
+#define _LIBMISC_MAP_H_
+
+#include <stdbool.h>
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint8_t */
+
+#include <libmisc/linkedlist.h>
+
+/* Type ***********************************************************************/
+
+DLIST_DECLARE(_map_kv_list);
+
+struct _map {
+ size_t len;
+ size_t nbuckets;
+ struct _map_kv_list *buckets;
+
+ unsigned int iterating;
+
+ size_t sizeof_kv;
+ size_t offsetof_k, sizeof_k;
+ size_t offsetof_v, sizeof_v;
+};
+
+/**
+ * MAP_DECLARE(MAPNAME, KEY_T, VAL_T) declares `struct MAPNAME`.
+ */
+#define MAP_DECLARE(MAPNAME, KEY_T, VAL_T) \
+ struct _##MAPNAME##_kv { \
+ uint8_t flags; \
+ KEY_T key; \
+ VAL_T val; \
+ }; \
+ DLIST_DECLARE_NODE(_##MAPNAME##_kv_list, struct _##MAPNAME##_kv); \
+ struct MAPNAME { \
+ struct _map core; \
+ struct _##MAPNAME##_kv_list_node kv_typ[0]; \
+ }
+
+#define _map_init(M) do { \
+ if (!(M)->core.sizeof_kv) { \
+ (M)->core.sizeof_kv = sizeof((M)->kv_typ[0]); \
+ (M)->core.sizeof_k = sizeof((M)->kv_typ[0].val.key); \
+ (M)->core.sizeof_v = sizeof((M)->kv_typ[0].val.val); \
+ (M)->core.offsetof_k = offsetof(typeof((M)->kv_typ[0]), val.key); \
+ (M)->core.offsetof_v = offsetof(typeof((M)->kv_typ[0]), val.val); \
+ } \
+} while (0)
+
+/* Methods ********************************************************************/
+
+/**
+ * map_len(map) returns the number of entries currently in `map`.
+ */
+#define map_len(M) ((M)->core.len)
+
+/**
+ * map_load(map, key) returns a pointer to the value in `map`
+ * associated with `key`, or else NULL.
+ */
+#define map_load(M, K) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].val.key) _k = K; \
+ (typeof((M)->kv_typ[0].val.val)*)_map_load(&(M)->core, &_k); \
+})
+void *_map_load(struct _map *m, void *kp);
+
+/**
+ * map_del(map, key) ensures that `key` is not present in `map`.
+ * Returns whether `key` was in `map` before the call.
+ */
+#define map_del(M, K) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].val.key) _k = K; \
+ _map_del(&(M)->core, &_k); \
+})
+bool _map_del(struct _map *m, void *kp);
+
+/**
+ * map_store(map, key, val) sets a value in the map. Returns a
+ * pointer to the map's copy of `val`.
+ */
+#define map_store(M, K, ...) ({ \
+ _map_init(M); \
+ typeof((M)->kv_typ[0].val.key) _k = K; \
+ typeof((M)->kv_typ[0].val.val) _v = __VA_ARGS__; \
+ (typeof((M)->kv_typ[0].val.val)*)_map_store(&(M)->core, &_k, &_v); \
+})
+void *_map_store(struct _map *m, void *kp, void *vp);
+
+/**
+ * map_free(map) frees the memory associated with the map.
+ */
+#define map_free(M) _map_free(&(M)->core);
+void _map_free(struct _map *m);
+
+/* Iteration ******************************************************************/
+
+struct _map_iter {
+ struct _map *m;
+ void *keyp;
+ void **valpp;
+
+ size_t i;
+ struct _map_kv_list_node *kv;
+};
+
+/**
+ * MAP_FOREACH iterates over a map:
+ *
+ * MAP_FOREACH(MAP_EACH(MAP, key, valp)) {
+ * ...
+ * }
+ *
+ * It is safe to mutate the map with map_store() and, map_del() while
+ * iterating, but entries added by map_store() may or may not be
+ * visited by the iteration.
+ */
+#define MAP_FOREACH(M, KNAME, VNAME) _MAP_FOREACH(__COUNTER__, M, KNAME, VNAME)
+#define _MAP_FOREACH(CNT, M, KNAME, VNAME) \
+ for (bool _once_##CNT = true; _once_##CNT;) \
+ for (typeof((M)->kv_typ[0].val.key) KNAME; _once_##CNT;) \
+ for (typeof((M)->kv_typ[0].val.val) *VNAME; _once_##CNT;) \
+ for ( \
+ struct _map_iter _iter_##CNT = ({ \
+ _map_init(M); \
+ _map_iter_before(&(M)->core, &KNAME, (void**)&VNAME); \
+ }); \
+ _once_##CNT; \
+ ({ \
+ _once_##CNT = false; \
+ _map_iter_after(&_iter_##CNT); \
+ })) \
+ while (_map_iter_next(&_iter_##CNT))
+struct _map_iter _map_iter_before(struct _map *m, void *keyp, void **valpp);
+bool _map_iter_next(struct _map_iter *iter);
+void _map_iter_after(struct _map_iter *iter);
+
+#endif /* _LIBMISC_MAP_H_ */
diff --git a/libmisc/include/libmisc/private.h b/libmisc/include/libmisc/private.h
index bc5e7ad..5518d1f 100644
--- a/libmisc/include/libmisc/private.h
+++ b/libmisc/include/libmisc/private.h
@@ -1,37 +1,17 @@
/* libmisc/private.h - Utilities to hide struct fields
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#ifndef _LIBMISC_PRIVATE_H_
#define _LIBMISC_PRIVATE_H_
-/* primitive utilities */
+#include <libmisc/macro.h>
-#define _SECOND(a, b, ...) b
-
-#define _CAT(a, b) a ## b
-#define _CAT2(a, b) _CAT(a, b)
-#define _EAT(...)
-#define _EXPAND(...) __VA_ARGS__
-
-/* conditionals */
-
-#define _T xxTxx
-#define _F xxFxx
-
-#define _SENTINEL() bogus, _T /* a magic sentinel value */
-#define _IS_SENTINEL(...) _SECOND(__VA_ARGS__, _F)
-
-#define _IF(cond) _CAT(_IF__, cond) /* _IF(cond)(then)(else) */
-#define _IF__xxTxx(...) __VA_ARGS__ _EAT
-#define _IF__xxFxx(...) _EXPAND
-
-/* high-level functionality */
-
-#define YES _SENTINEL()
-#define BEGIN_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(struct {)
-#define END_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(} _CAT2(_HIDDEN_, __COUNTER__);)
+#define YES LM_SENTINEL()
+#define IS_IMPLEMENTATION_FOR(name) LM_IS_SENTINEL(IMPLEMENTATION_FOR_##name)
+#define BEGIN_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))()(struct {) struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__)
+#define END_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))(struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__))(} LM_CAT2_(_PRIVATE_, __COUNTER__))
#endif /* _LIBMISC_PRIVATE_H_ */
diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h
index 8072841..7ef238b 100644
--- a/libmisc/include/libmisc/rand.h
+++ b/libmisc/include/libmisc/rand.h
@@ -1,6 +1,6 @@
/* libmisc/rand.h - Non-crytpographic random-number utilities
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -29,14 +29,14 @@ static inline uint64_t rand_uint63n(uint64_t cnt) {
uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt;
uint64_t rnd;
do {
- rnd = (random() << 31) | random();
+ rnd = (((uint64_t)random()) << 31) | random();
} while (rnd >= fair_cnt);
return rnd % cnt;
} else if (cnt <= UINT64_C(1)<<63) {
uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt;
uint64_t rnd;
do {
- rnd = (random() << 62) | (random() << 31) | random();
+ rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random();
} while (rnd >= fair_cnt);
return rnd % cnt;
}
diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h
deleted file mode 100644
index 31a8c7e..0000000
--- a/libmisc/include/libmisc/vcall.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* libmisc/vcall.h - A simple Go-ish object system built on GCC -fplan9-extensions
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIBMISC_VCALL_H_
-#define _LIBMISC_VCALL_H_
-
-#include <stddef.h> /* for offsetof() */
-
-#include <libmisc/assert.h>
-
-#define VCALL(o, m, ...) \
- ({ \
- assert(o); \
- (o)->vtable->m(o __VA_OPT__(,) __VA_ARGS__); \
- })
-
-#define VCALL_SELF(obj_typ, iface_typ, iface_ptr) \
- ({ \
- static_assert(_Generic(iface_ptr, iface_typ *: 1, default: 0), \
- "typeof("#iface_ptr") != "#iface_typ" *"); \
- assert(iface_ptr); \
- ((obj_typ*)(((void*)iface_ptr)-offsetof(obj_typ,iface_typ))); \
- })
-
-#endif /* _LIBMISC_VCALL_H_ */
diff --git a/libmisc/intercept.c b/libmisc/intercept.c
new file mode 100644
index 0000000..85a3801
--- /dev/null
+++ b/libmisc/intercept.c
@@ -0,0 +1,34 @@
+/* libmisc/intercept.c - Interceptable ("weak") functions
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdarg.h> /* for va_list, va_start(), va_end() */
+#include <stdio.h> /* for vprintf() */
+#include <stdlib.h> /* for abort() */
+
+#include <libmisc/_intercept.h>
+
+[[gnu::weak]]
+int __lm_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vprintf(format, va);
+ va_end(va);
+ return ret;
+}
+
+[[gnu::weak]]
+int __lm_light_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vprintf(format, va);
+ va_end(va);
+ return ret;
+}
+
+[[gnu::weak]]
+void __lm_abort(void) {
+ abort();
+}
diff --git a/libmisc/linkedlist.c b/libmisc/linkedlist.c
new file mode 100644
index 0000000..71a0aa9
--- /dev/null
+++ b/libmisc/linkedlist.c
@@ -0,0 +1,64 @@
+/* libmisc/linkedlist.c - Singly- and doubly- linked lists
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stddef.h> /* for NULL */
+
+#include <libmisc/assert.h>
+
+#include <libmisc/linkedlist.h>
+
+/* singly linked list *********************************************************/
+
+void _slist_push_to_rear(struct _slist_root *root, struct _slist_node *node) {
+ assert(root);
+ node->rear = NULL;
+ if (root->rear)
+ root->rear->rear = node;
+ else
+ root->front = node;
+ root->rear = node;
+}
+
+void _slist_pop_from_front(struct _slist_root *root) {
+ assert(root);
+ assert(root->front);
+ root->front = root->front->rear;
+ if (!root->front)
+ root->rear = NULL;
+}
+
+/* doubly linked list *********************************************************/
+
+void _dlist_push_to_rear(struct _dlist_root *root, struct _dlist_node *node) {
+ assert(root);
+ assert(node);
+ node->front = root->rear;
+ node->rear = NULL;
+ if (root->rear)
+ root->rear->rear = node;
+ else
+ root->front = node;
+ root->rear = node;
+}
+
+void _dlist_remove(struct _dlist_root *root, struct _dlist_node *node) {
+ assert(root);
+ assert(node);
+ if (node->front)
+ node->front->rear = node->rear;
+ else
+ root->front = node->rear;
+ if (node->rear)
+ node->rear->front = node->front;
+ else
+ root->rear = node->front;
+}
+
+void _dlist_pop_from_front(struct _dlist_root *root) {
+ assert(root);
+ assert(root->front);
+ _dlist_remove(root, root->front);
+}
diff --git a/libmisc/log.c b/libmisc/log.c
index 9bf5366..be87de6 100644
--- a/libmisc/log.c
+++ b/libmisc/log.c
@@ -1,24 +1,12 @@
/* libmisc/log.c - stdio logging
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <stdio.h> /* for vprintf() */
-#include <stdarg.h> /* for va_list, va_start(), va_end() */
+#include <stdint.h> /* for uint{n}_t */
-#include <libmisc/assert.h>
-
-#define LOG_NAME
-#include <libmisc/log.h>
-
-int _log_printf(const char *format, ...) {
- va_list va;
- va_start(va, format);
- int ret = vprintf(format, va);
- va_end(va);
- return ret;
-}
+#include <libmisc/assert.h> /* for static_assert() */
static const char *byte_strs[] = {
"0x00",
diff --git a/libmisc/map.c b/libmisc/map.c
new file mode 100644
index 0000000..cc34c16
--- /dev/null
+++ b/libmisc/map.c
@@ -0,0 +1,230 @@
+/* libmisc/map.c - A map/dict data structure
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmisc/hash.h>
+#include <libmisc/alloc.h>
+#include <libmisc/assert.h>
+#include <libmisc/map.h>
+
+#define FLAG_ITER (UINT8_C(1)<<0)
+#define FLAG_DEL (UINT8_C(1)<<1)
+
+/* Internal utilities *********************************************************/
+
+struct _map_kv {
+ uint8_t flags;
+ /* opaque key; */
+ /* opaque val; */
+};
+DLIST_DECLARE_NODE(_map_kv_list, struct _map_kv);
+
+static inline void *_map_kv_keyp(struct _map *m, struct _map_kv_list_node *kv) {
+ assert(m);
+ assert(kv);
+ return ((void*)kv)+m->offsetof_k;
+}
+static inline void *_map_kv_valp(struct _map *m, struct _map_kv_list_node *kv) {
+ assert(m);
+ assert(kv);
+ return ((void*)kv)+m->offsetof_v;
+}
+
+static inline void _map_lookup(struct _map *m, void *keyp,
+ hash_t *ret_hash,
+ struct _map_kv_list **ret_bucket,
+ struct _map_kv_list_node **ret_kv) {
+ assert(m);
+ assert(keyp);
+ assert(ret_hash);
+ assert(ret_bucket);
+ assert(ret_kv);
+ *ret_hash = hash(keyp, m->sizeof_k);
+ if (m->nbuckets == 0) {
+ *ret_bucket = NULL;
+ *ret_kv = NULL;
+ return;
+ }
+ *ret_bucket = &m->buckets[*ret_hash % m->nbuckets];
+ for (struct _map_kv_list_node *kv = (*ret_bucket)->front; kv; kv = kv->rear) {
+ if (!(kv->val.flags & FLAG_DEL) &&
+ memcmp(_map_kv_keyp(m, kv), keyp, m->sizeof_k) == 0) {
+ *ret_kv = kv;
+ return;
+ }
+ }
+ *ret_kv = NULL;
+}
+
+static inline void _map_resize(struct _map *m, size_t new_nbuckets) {
+ assert(m);
+ assert(new_nbuckets);
+ struct _map_kv_list *new_buckets = heap_alloc(new_nbuckets, struct _map_kv_list);
+ for (size_t i = 0; i < m->nbuckets; i++) {
+ while (m->buckets[i].front) {
+ struct _map_kv_list_node *kv = m->buckets[i].front;
+ dlist_pop_from_front(&m->buckets[i]);
+ hash_t h = hash(_map_kv_keyp(m, kv), m->sizeof_k);
+ dlist_push_to_rear(&new_buckets[h % new_nbuckets], kv);
+ }
+ }
+ m->nbuckets = new_nbuckets;
+ free(m->buckets);
+ m->buckets = new_buckets;
+}
+
+static bool _map_autoresize(struct _map *m) {
+ assert(m);
+ if (m->len > (m->nbuckets * 8/10)) {
+ size_t nbuckets = 1;
+ while (m->len > (nbuckets * 8/10))
+ nbuckets <<= 1;
+ _map_resize(m, nbuckets);
+ return true;
+ }
+ return false;
+}
+
+/* Methods ********************************************************************/
+
+void *_map_load(struct _map *m, void *keyp) {
+ assert(m);
+ assert(keyp);
+
+ hash_t h;
+ struct _map_kv_list *bucket;
+ struct _map_kv_list_node *kv;
+ _map_lookup(m, keyp, &h, &bucket, &kv);
+
+ if (!kv)
+ return NULL;
+ return _map_kv_valp(m, kv);
+}
+
+bool _map_del(struct _map *m, void *keyp) {
+ assert(m);
+ assert(keyp);
+
+ hash_t h;
+ struct _map_kv_list *bucket;
+ struct _map_kv_list_node *kv;
+ _map_lookup(m, keyp, &h, &bucket, &kv);
+
+ if (!kv)
+ return false;
+ if (kv->val.flags & FLAG_ITER) {
+ kv->val.flags |= FLAG_DEL;
+ } else {
+ dlist_remove(bucket, kv);
+ free(kv);
+ }
+ m->len--;
+ return true;
+}
+
+void *_map_store(struct _map *m, void *keyp, void *valp) {
+ assert(m);
+ assert(keyp);
+ assert(valp);
+
+ hash_t h;
+ struct _map_kv_list *bucket;
+ struct _map_kv_list_node *old;
+ _map_lookup(m, keyp, &h, &bucket, &old);
+
+ if (old) {
+ dlist_remove(bucket, old);
+ free(old);
+ m->len--;
+ }
+ m->len++;
+ if (!m->iterating && _map_autoresize(m)) {
+ h = hash(keyp, m->sizeof_k);
+ bucket = &m->buckets[h % m->nbuckets];
+ }
+ struct _map_kv_list_node *kv = calloc(1, m->sizeof_kv);
+ memcpy(_map_kv_keyp(m, kv), keyp, m->sizeof_k);
+ memcpy(_map_kv_valp(m, kv), valp, m->sizeof_v);
+ dlist_push_to_rear(bucket, kv);
+ return _map_kv_valp(m, kv);
+}
+
+void _map_free(struct _map *m) {
+ assert(m);
+
+ for (size_t i = 0; i < m->nbuckets; i++) {
+ while (m->buckets[i].front) {
+ struct _map_kv_list_node *kv = m->buckets[i].front;
+ dlist_pop_from_front(&m->buckets[i]);
+ free(kv);
+ }
+ }
+ free(m->buckets);
+ m->len = 0;
+ m->nbuckets = 0;
+ m->buckets = NULL;
+}
+
+/* Iteration ******************************************************************/
+
+struct _map_iter _map_iter_before(struct _map *m, void *keyp, void **valpp) {
+ assert(m);
+ assert(keyp);
+ assert(valpp);
+
+ struct _map_iter state = {
+ .m = m,
+ .keyp = keyp,
+ .valpp = valpp,
+ };
+ m->iterating++;
+ return state;
+}
+
+void _map_iter_after(struct _map_iter *state) {
+ assert(state);
+ assert(state->m);
+
+ state->m->iterating--;
+ if (!state->m->iterating)
+ _map_autoresize(state->m);
+}
+
+bool _map_iter_next(struct _map_iter *state) {
+ assert(state);
+ assert(state->m);
+ assert(state->valpp);
+
+ if (!state->kv) {
+ if (!state->m->len)
+ return false;
+ while (!state->m->buckets[state->i].front)
+ state->i++;
+ state->kv = state->m->buckets[state->i].front;
+ } else {
+ struct _map_kv_list_node *old_kv = state->kv;
+ state->kv = old_kv->rear;
+
+ old_kv->val.flags &= ~FLAG_ITER;
+ if (old_kv->val.flags & FLAG_DEL) {
+ dlist_remove(&state->m->buckets[state->i], old_kv);
+ free(old_kv);
+ }
+
+ while (!state->kv) {
+ state->i++;
+ if (state->i == state->m->nbuckets)
+ return false;
+ state->kv = state->m->buckets[state->i].front;
+ }
+ }
+ state->kv->val.flags |= FLAG_ITER;
+ memcpy(state->keyp, _map_kv_keyp(state->m, state->kv), state->m->sizeof_k);
+ *(state->valpp) = _map_kv_valp(state->m, state->kv);
+ return true;
+}
diff --git a/libmisc/tests/test_assert.c b/libmisc/tests/test_assert.c
index 5b28561..15f9446 100644
--- a/libmisc/tests/test_assert.c
+++ b/libmisc/tests/test_assert.c
@@ -1,20 +1,21 @@
/* libmisc/tests/test_assert.c - Tests for <libmisc/assert.h>
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <setjmp.h>
+#include <stdarg.h> /* for va_list, va_start(), va_end() */
#include <stdbool.h>
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
+#include <libmisc/macro.h>
#include <libmisc/assert.h>
+#include <libmisc/_intercept.h>
#include "test.h"
-#define UNUSED(name)
-
/* Intercept failures and logging *********************************************/
bool global_failed;
@@ -29,17 +30,21 @@ jmp_buf global_env;
setjmp(global_env) == 0; \
})
-[[noreturn]] void abort(void) {
+void __lm_abort(void) {
global_failed = true;
longjmp(global_env, 1);
}
-#define __builtin_unreachable() test_assert(0)
-
-int vprintf(const char *format, va_list ap) {
- return vasprintf(&global_log, format, ap);
+int __lm_light_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vasprintf(&global_log, format, va);
+ va_end(va);
+ return ret;
}
+#define __builtin_unreachable() test_assert(0)
+
/* Utilities ******************************************************************/
#define test_should_succeed(test) do { \
@@ -64,27 +69,26 @@ int vprintf(const char *format, va_list ap) {
} \
} while (0)
-#define _STR(x) #x
-#define STR(x) _STR(x)
-
/* Actual tests ***************************************************************/
static_assert(sizeof(char) == 1);
int main() {
+#ifndef NDEBUG
test_should_succeed(assert(true));
- test_should_fail(assert(false), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed\n");
+ test_should_fail(assert(false), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n");
test_should_succeed(assert_msg(true, "foo"));
- test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed: foo\n");
+ test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed: foo\n");
test_should_succeed(assert_msg(true, NULL));
- test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"false\" failed\n");
+ test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n");
- test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"STR(__LINE__)":main(): assertion \"notreached\" failed: xxx\n");
+ test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"notreached\" failed: xxx\n");
if (global_log) {
free(global_log);
global_log = NULL;
}
+#endif
return 0;
}
diff --git a/libmisc/tests/test_assert_min.c b/libmisc/tests/test_assert_min.c
new file mode 100644
index 0000000..9c0394b
--- /dev/null
+++ b/libmisc/tests/test_assert_min.c
@@ -0,0 +1,17 @@
+/* libmisc/tests/test_assert_min.c - Tests for minimal <libmisc/assert.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/* Don't include *anything* else. */
+#include <libmisc/assert.h>
+
+static_assert(1 == 1);
+
+int main() {
+ assert_msg(1, "foo");
+ assert(1);
+ return 0;
+ assert_notreached("ret");
+}
diff --git a/libmisc/tests/test_endian.c b/libmisc/tests/test_endian.c
index d0b547c..dcb3cc2 100644
--- a/libmisc/tests/test_endian.c
+++ b/libmisc/tests/test_endian.c
@@ -1,6 +1,6 @@
/* libmisc/tests/test_endian.c - Tests for <libmisc/endian.h>
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -11,22 +11,31 @@
#include "test.h"
int main() {
- uint8_t act[12] = {0};
- uint16be_encode(&act[0], UINT16_C(0x1234));
- uint32be_encode(&act[2], UINT32_C(0x56789ABC));
- uint16le_encode(&act[6], UINT16_C(0x1234));
- uint32le_encode(&act[8], UINT32_C(0x56789ABC));
+ uint8_t act[(2+4+8)*2] = {0};
+ size_t pos = 0;
+ pos += uint16be_encode(&act[pos], UINT16_C(0x1234));
+ pos += uint32be_encode(&act[pos], UINT32_C(0x56789ABC));
+ pos += uint64be_encode(&act[pos], UINT64_C(0xAC589A93278CB30A));
+ pos += uint16le_encode(&act[pos], UINT16_C(0x1234));
+ pos += uint32le_encode(&act[pos], UINT32_C(0x56789ABC));
+ pos += uint64le_encode(&act[pos], UINT64_C(0xAC589A93278CB30A));
- uint8_t exp[12] = { 0x12, 0x34,
- 0x56, 0x78, 0x9A, 0xBC,
- 0x34, 0x12,
- 0xBC, 0x9A, 0x78, 0x56 };
- test_assert(memcmp(act, exp, 12) == 0);
+ test_assert(pos == sizeof(act));
+ uint8_t exp[(2+4+8)*2] = { 0x12, 0x34,
+ 0x56, 0x78, 0x9A, 0xBC,
+ 0xAC, 0x58, 0x9A, 0x93, 0x27, 0x8C, 0xB3, 0x0A,
+ 0x34, 0x12,
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x0A, 0xB3, 0x8C, 0x27, 0x93, 0x9A, 0x58, 0xAC};
+ test_assert(memcmp(act, exp, sizeof(act)) == 0);
- test_assert(uint16be_decode(&act[0]) == UINT16_C(0x1234));
- test_assert(uint32be_decode(&act[2]) == UINT32_C(0x56789ABC));
- test_assert(uint16le_decode(&act[6]) == UINT16_C(0x1234));
- test_assert(uint32le_decode(&act[8]) == UINT32_C(0x56789ABC));
+ pos = 0;
+ test_assert(uint16be_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2;
+ test_assert(uint32be_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4;
+ test_assert(uint64be_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8;
+ test_assert(uint16le_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2;
+ test_assert(uint32le_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4;
+ test_assert(uint64le_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8;
return 0;
}
diff --git a/libmisc/tests/test_log.c b/libmisc/tests/test_log.c
index 286738d..49a76ca 100644
--- a/libmisc/tests/test_log.c
+++ b/libmisc/tests/test_log.c
@@ -13,36 +13,43 @@
#define LOG_NAME FROBNICATE
#include <libmisc/log.h>
+#include <libmisc/_intercept.h>
+
#include "test.h"
/* Intercept output ***********************************************************/
static char *log_output = NULL;
-int vprintf(const char *format, va_list ap) {
- return vasprintf(&log_output, format, ap);
+int __lm_printf(const char *format, ...) {
+ va_list va;
+ va_start(va, format);
+ int ret = vasprintf(&log_output, format, va);
+ va_end(va);
+ return ret;
}
/* Actual tests ***************************************************************/
-#define should_print(_exp, cmd) do { \
- char *exp = _exp; \
- test_assert(!log_output); \
- cmd; \
- if (!exp) \
- test_assert(!log_output); \
- else \
- if (!(log_output != NULL && \
- strcmp(log_output, exp) == 0)) { \
- printf("exp = \"%s\"\n" \
- "act = \"%s\"\n", \
- exp, log_output); \
- test_assert(0); \
- } \
- if (log_output) { \
- free(log_output); \
- log_output = NULL; \
- } \
+#define should_print(_exp, cmd) do { \
+ char *exp = _exp; \
+ test_assert(!log_output); \
+ cmd; \
+ if (!exp) \
+ test_assert(!log_output); \
+ else { \
+ test_assert(log_output); \
+ if (strcmp(log_output, exp)) { \
+ printf("exp = \"%s\"\n" \
+ "act = \"%s\"\n", \
+ exp, log_output); \
+ test_assert(0); \
+ } \
+ } \
+ if (log_output) { \
+ free(log_output); \
+ log_output = NULL; \
+ } \
} while (0)
int main() {
@@ -50,6 +57,7 @@ int main() {
errorf("val=%d", 42));
should_print("info : FROBNICATE: val=0\n",
infof("val=%d", 0));
+#ifndef NDEBUG
#define CONFIG_FROBNICATE_DEBUG 1
should_print("debug: FROBNICATE: val=-2\n",
debugf("val=%d", -2));
@@ -57,5 +65,6 @@ int main() {
#define CONFIG_FROBNICATE_DEBUG 0
should_print(NULL,
debugf("val=%d", -2));
+#endif
return 0;
}
diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c
new file mode 100644
index 0000000..1320eb3
--- /dev/null
+++ b/libmisc/tests/test_macro.c
@@ -0,0 +1,54 @@
+/* libmisc/tests/test_macro.c - Tests for <libmisc/macro.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/macro.h>
+
+#include "test.h"
+
+int main() {
+ printf("== LM_NEXT_POWER_OF_2 =====================================\n");
+ /* valid down to 0. */
+ test_assert(LM_NEXT_POWER_OF_2(0) == 1);
+ test_assert(LM_NEXT_POWER_OF_2(1) == 2);
+ test_assert(LM_NEXT_POWER_OF_2(2) == 4);
+ test_assert(LM_NEXT_POWER_OF_2(3) == 4);
+ test_assert(LM_NEXT_POWER_OF_2(4) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(5) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(6) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(7) == 8);
+ test_assert(LM_NEXT_POWER_OF_2(8) == 16);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(16) == 32);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(0x7000000000000000) == 0x8000000000000000);
+ /* ... */
+ test_assert(LM_NEXT_POWER_OF_2(0x8000000000000000-1) == 0x8000000000000000);
+ /* Valid up to 0x8000000000000000-1 = (1<<63)-1 */
+
+ printf("== LM_FLOORLOG2 ===========================================\n");
+ /* valid down to 1. */
+ test_assert(LM_FLOORLOG2(1) == 0);
+ test_assert(LM_FLOORLOG2(2) == 1);
+ test_assert(LM_FLOORLOG2(3) == 1);
+ test_assert(LM_FLOORLOG2(4) == 2);
+ test_assert(LM_FLOORLOG2(5) == 2);
+ test_assert(LM_FLOORLOG2(6) == 2);
+ test_assert(LM_FLOORLOG2(7) == 2);
+ test_assert(LM_FLOORLOG2(8) == 3);
+ /* ... */
+ test_assert(LM_FLOORLOG2(16) == 4);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0x80000000) == 31);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0xFFFFFFFF) == 31);
+ test_assert(LM_FLOORLOG2(0x100000000) == 32);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0x8000000000000000) == 63);
+ /* ... */
+ test_assert(LM_FLOORLOG2(0xFFFFFFFFFFFFFFFF) == 63);
+
+ return 0;
+}
diff --git a/libmisc/tests/test_map.c b/libmisc/tests/test_map.c
new file mode 100644
index 0000000..855dace
--- /dev/null
+++ b/libmisc/tests/test_map.c
@@ -0,0 +1,60 @@
+/* libmisc/tests/test_map.c - Tests for <libmisc/map.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/map.h>
+
+#include "test.h"
+
+MAP_DECLARE(intmap, int, int);
+
+int main() {
+ struct intmap m = {};
+ test_assert(map_len(&m) == 0);
+
+ int *v = map_store(&m, 3, 8);
+ test_assert(v && *v == 8);
+ test_assert(map_len(&m) == 1);
+
+ v = map_load(&m, 3);
+ test_assert(v);
+ test_assert(*v == 8);
+
+ v = NULL;
+
+ test_assert(map_del(&m, 3));
+ test_assert(map_len(&m) == 0);
+ test_assert(!map_del(&m, 3));
+
+ map_store(&m, 1, 11);
+ map_store(&m, 2, 12);
+ map_store(&m, 3, 13);
+ test_assert(map_len(&m) == 3);
+ bool seen_1 = false, seen_2 = false, seen_3 = false;
+ MAP_FOREACH(&m, ik, iv) {
+ switch (ik) {
+ case 1: seen_1 = true; break;
+ case 2: seen_2 = true; break;
+ case 3: seen_3 = true; break;
+ }
+ switch (ik) {
+ case 1: case 2: case 3:
+ map_store(&m, ik+20, (*iv)+20);
+ test_assert(map_del(&m, ik));
+ test_assert(!map_del(&m, ik));
+ test_assert(map_load(&m, ik) == NULL);
+ break;
+ }
+ }
+ test_assert(map_len(&m) == 3);
+ test_assert(seen_1); v = map_load(&m, 21); test_assert(v && *v == 31); v = map_load(&m, 1); test_assert(!v);
+ test_assert(seen_2); v = map_load(&m, 22); test_assert(v && *v == 32); v = map_load(&m, 2); test_assert(!v);
+ test_assert(seen_3); v = map_load(&m, 23); test_assert(v && *v == 33); v = map_load(&m, 3); test_assert(!v);
+
+ map_free(&m);
+ test_assert(map_len(&m) == 0);
+
+ return 0;
+}
diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c
index 7aaf1ee..024dddb 100644
--- a/libmisc/tests/test_private.c
+++ b/libmisc/tests/test_private.c
@@ -1,6 +1,6 @@
/* libmisc/tests/test_private.c - Tests for <libmisc/private.h>
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -8,18 +8,18 @@
struct a {
int foo;
- BEGIN_PRIVATE(A)
+ BEGIN_PRIVATE(A);
int bar;
- END_PRIVATE(A)
+ END_PRIVATE(A);
};
#define IMPLEMENTATION_FOR_B YES
struct b {
int foo;
- BEGIN_PRIVATE(B)
+ BEGIN_PRIVATE(B);
int bar;
- END_PRIVATE(B)
+ END_PRIVATE(B);
};
int main() {
diff --git a/libmisc/tests/test_rand.c b/libmisc/tests/test_rand.c
index fff1b27..8076155 100644
--- a/libmisc/tests/test_rand.c
+++ b/libmisc/tests/test_rand.c
@@ -8,26 +8,18 @@
#include <setjmp.h>
#include <libmisc/rand.h>
+#include <libmisc/_intercept.h>
#include "test.h"
/* Intercept failures *********************************************************/
+#ifndef NDEBUG
jmp_buf *__catch;
-void __assert_msg_fail(const char *expr,
- const char *file, unsigned int line, const char *func,
- const char *msg) {
- static bool in_fail = false;
+void __lm_abort(void) {
if (__catch)
longjmp(*__catch, 1);
- if (!in_fail) {
- in_fail = true;
- printf("error: %s:%u:%s(): assertion \"%s\" failed%s%s\n",
- file, line, func,
- expr,
- msg ? ": " : "", msg);
- }
abort();
}
@@ -43,6 +35,7 @@ void __assert_msg_fail(const char *expr,
__catch = old_catch; \
} \
} while (0);
+#endif
/* Actual tests ***************************************************************/
@@ -51,20 +44,24 @@ void __assert_msg_fail(const char *expr,
static void test_n(uint64_t cnt) {
if (cnt == 0 || cnt > UINT64_C(1)<<63) {
+#ifndef NDEBUG
should_abort(rand_uint63n(cnt));
+#else
+ return;
+#endif
} else {
double sum = 0;
bool seen[MAX_SEE_ALL] = {0};
for (int i = 0; i < ROUNDS; i++) {
uint64_t val = rand_uint63n(cnt);
- sum += ((double)val)/(cnt-1);
+ sum += val;
test_assert(val < cnt);
if (cnt < MAX_SEE_ALL)
seen[val] = true;
}
if (cnt > 1) {
- test_assert(sum/ROUNDS > 0.45);
- test_assert(sum/ROUNDS < 0.55);
+ test_assert(sum/ROUNDS > 0.45*(cnt-1));
+ test_assert(sum/ROUNDS < 0.55*(cnt-1));
}
if (cnt < MAX_SEE_ALL) {
for (uint64_t i = 0; i < cnt; i++)
diff --git a/libmisc/tests/test_vcall.c b/libmisc/tests/test_vcall.c
deleted file mode 100644
index f36fc4b..0000000
--- a/libmisc/tests/test_vcall.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/* libmisc/tests/test_vcall.c - Tests for <libmisc/vcall.h>
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
-
-#include "test.h"
-
-/******************************************************************************/
-
-struct frobber_vtable;
-
-typedef struct {
- struct frobber_vtable *vtable;
-} implements_frobber;
-
-struct frobber_vtable {
- int (*frob)(implements_frobber *);
- int (*frob1)(implements_frobber *, int);
- void (*frob0)(implements_frobber *);
-};
-
-/******************************************************************************/
-
-struct myclass {
- int a;
- implements_frobber;
-};
-static_assert(offsetof(struct myclass, implements_frobber) != 0);
-
-static int myclass_frob(implements_frobber *_self) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
- return self->a;
-}
-
-static int myclass_frob1(implements_frobber *_self, int arg) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
- return arg;
-}
-
-static void myclass_frob0(implements_frobber *_self) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
-}
-
-struct frobber_vtable myclass_vtable = {
- .frob = myclass_frob,
- .frob1 = myclass_frob1,
- .frob0 = myclass_frob0,
-};
-
-/******************************************************************************/
-
-#define MAGIC1 909837
-#define MAGIC2 657441
-
-int main() {
- struct myclass obj = {
- .implements_frobber = { .vtable = &myclass_vtable },
- .a = MAGIC1,
- };
- test_assert(VCALL(&obj, frob) == MAGIC1);
- test_assert(VCALL(&obj, frob1, MAGIC2) == MAGIC2);
- VCALL(&obj, frob0);
- return 0;
-}