diff --git a/cmdline/elem.c b/cmdline/elem.c index 92993e2e..db02a55c 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -1604,8 +1604,8 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap extent = extent_alloc(parity_pos, file, file_pos, count); /* insert the extent in the trees */ - parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent); - file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent); + parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &extent->parity_node, extent); + file_extent = tommy_tree_insert_unique(&disk->fs_file, &extent->file_node, extent); if (parity_extent != extent || file_extent != extent) { /* LCOV_EXCL_START */ @@ -1689,8 +1689,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) second_extent = extent_alloc(extent->parity_pos + first_count + 1, extent->file, extent->file_pos + first_count + 1, second_count); /* insert the extent in the trees */ - parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent); - file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_node, second_extent); + parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &second_extent->parity_node, second_extent); + file_extent = tommy_tree_insert_unique(&disk->fs_file, &second_extent->file_node, second_extent); if (parity_extent != second_extent || file_extent != second_extent) { /* LCOV_EXCL_START */ diff --git a/cmdline/memory.c b/cmdline/memory.c index f2c395a3..ae600ea6 100644 --- a/cmdline/memory.c +++ b/cmdline/memory.c @@ -119,6 +119,23 @@ void* malloc_nofail(size_t size) return ptr; } +void* realloc_nofail(void* ptr, size_t size) +{ + /* keep zero-size requests allocated, as in malloc_nofail() */ + void* new_ptr = realloc(ptr, size ? size : 1); + + if (!new_ptr) { + /* LCOV_EXCL_START */ + malloc_fail(size); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + + malloc_counter_inc(size); + + return new_ptr; +} + void* nalloc_nofail(size_t count, size_t size) { if (size != 0 && count > SIZE_MAX / size) { diff --git a/cmdline/memory.h b/cmdline/memory.h index 0d05f376..ef3e187a 100644 --- a/cmdline/memory.h +++ b/cmdline/memory.h @@ -23,6 +23,12 @@ void memory_single_thread(int single_thread); */ void* malloc_nofail(size_t size); +/** + * Safe realloc. + * If no memory is available, it aborts. + */ +void* realloc_nofail(void* ptr, size_t size); + /** * Safe malloc with calloc calling arguments. * If no memory is available, it aborts. diff --git a/cmdline/museair.c b/cmdline/museair.c index 8bbf8e92..2f765e9e 100644 --- a/cmdline/museair.c +++ b/cmdline/museair.c @@ -150,7 +150,7 @@ static const uint64_t MUSEAIR_CONSTANT[13] = { do { \ l = a; \ h = b; \ - util_mum(&l, &h); \ + tommy_mum(&l, &h); \ } while (0) #define likely(a) tommy_likely(a) diff --git a/cmdline/selftest.c b/cmdline/selftest.c index e72d35dc..cf6d5ead 100644 --- a/cmdline/selftest.c +++ b/cmdline/selftest.c @@ -51,25 +51,25 @@ struct hash_test_vector { * Test vectors for tommy_hash32 */ static struct hash32_test_vector TEST_HASH32[] = { - { "", 0, 0x8614384c, 0xa766795d }, - { "a", 1, 0x12c16c36, 0xa766795d }, - { "abc", 3, 0xc58e8af5, 0xa766795d }, - { "message digest", 14, 0x006b32f1, 0xa766795d }, - { "abcdefghijklmnopqrstuvwxyz", 26, 0x7e6fcfe0, 0xa766795d }, - { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x8604adf8, 0xa766795d }, - { "The quick brown fox jumps over the lazy dog", 43, 0xdeba3d3a, 0xa766795d }, - { "\x00", 1, 0x4a7d1c33, 0xa766795d }, - { "\x16\x27", 2, 0x8b50899b, 0xa766795d }, - { "\xe2\x56\xb4", 3, 0x60406493, 0xa766795d }, - { "\xc9\x4d\x9c\xda", 4, 0xa049144a, 0xa766795d }, - { "\x79\xf1\x29\x69\x5d", 5, 0x4da2c2f1, 0xa766795d }, - { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x59de30cf, 0xa766795d }, - { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x219e149c, 0xa766795d }, - { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x25067520, 0xa766795d }, - { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa1f368d8, 0xa766795d }, - { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x805fc63d, 0xa766795d }, - { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x7f75dd0f, 0xa766795d }, - { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0xb9154382, 0xa766795d }, + { "", 0, 0xaffe48d7, 0xa766795d }, + { "a", 1, 0x2ae4a38d, 0xa766795d }, + { "abc", 3, 0x0aefd0ef, 0xa766795d }, + { "message digest", 14, 0x828c4a8a, 0xa766795d }, + { "abcdefghijklmnopqrstuvwxyz", 26, 0xc8072ea9, 0xa766795d }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xe8e24812, 0xa766795d }, + { "The quick brown fox jumps over the lazy dog", 43, 0xae34960f, 0xa766795d }, + { "\x00", 1, 0xac5373be, 0xa766795d }, + { "\x16\x27", 2, 0x321c4629, 0xa766795d }, + { "\xe2\x56\xb4", 3, 0x66b8fbd8, 0xa766795d }, + { "\xc9\x4d\x9c\xda", 4, 0xbc37eb40, 0xa766795d }, + { "\x79\xf1\x29\x69\x5d", 5, 0xb96d1628, 0xa766795d }, + { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x760cf131, 0xa766795d }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xd1d5a37a, 0xa766795d }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x995d591f, 0xa766795d }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x8c7cfa48, 0xa766795d }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xaa8fa306, 0xa766795d }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x9c1d4af8, 0xa766795d }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x1a2214ec, 0xa766795d }, { 0, 0, 0, 0 } }; @@ -77,29 +77,29 @@ static struct hash32_test_vector TEST_HASH32[] = { * Test vectors for tommy_strhash32 */ struct strhash32_test_vector TEST_STRHASH32[] = { - { "", 0x0af1416d, 0xa766795d }, - { "a", 0x68fa0f3f, 0xa766795d }, - { "abc", 0xfc68ffc5, 0xa766795d }, - { "message digest", 0x08477b63, 0xa766795d }, - { "abcdefghijklmnopqrstuvwxyz", 0x5b9c25e5, 0xa766795d }, - { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x1e530ce7, 0xa766795d }, - { "The quick brown fox jumps over the lazy dog", 0xaf93eefe, 0xa766795d }, - { "\xff", 0xfc88801b, 0xa766795d }, - { "\x16\x27", 0xcd7216db, 0xa766795d }, - { "\xe2\x56\xb4", 0x05f98d02, 0xa766795d }, - { "\xc9\x4d\x9c\xda", 0xf65206f8, 0xa766795d }, - { "\x79\xf1\x29\x69\x5d", 0x72bd6bda, 0xa766795d }, - { "\xff\x7e\xdf\x1e\x31\x1c", 0x57dfb9b4, 0xa766795d }, - { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0x499ff634, 0xa766795d }, - { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xe896b7ce, 0xa766795d }, - { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xfe3939f0, 0xa766795d }, - { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x4351d482, 0xa766795d }, - { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x88e92135, 0xa766795d }, - { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x01109c16, 0xa766795d }, - { "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xbcb050dc, 0xa766795d }, - { "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xbe5e1fd5, 0xa766795d }, - { "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x70d8c97f, 0xa766795d }, - { "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x957440a9, 0xa766795d }, + { "", 0xaffe48d7, 0xa766795d }, + { "a", 0x2ae4a38d, 0xa766795d }, + { "abc", 0x0aefd0ef, 0xa766795d }, + { "message digest", 0x828c4a8a, 0xa766795d }, + { "abcdefghijklmnopqrstuvwxyz", 0xc8072ea9, 0xa766795d }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xe8e24812, 0xa766795d }, + { "The quick brown fox jumps over the lazy dog", 0xae34960f, 0xa766795d }, + { "\xff", 0x9ab96198, 0xa766795d }, + { "\x16\x27", 0x321c4629, 0xa766795d }, + { "\xe2\x56\xb4", 0x66b8fbd8, 0xa766795d }, + { "\xc9\x4d\x9c\xda", 0xbc37eb40, 0xa766795d }, + { "\x79\xf1\x29\x69\x5d", 0xb96d1628, 0xa766795d }, + { "\xff\x7e\xdf\x1e\x31\x1c", 0xaf63217b, 0xa766795d }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0xd1d5a37a, 0xa766795d }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0x995d591f, 0xa766795d }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0x8c7cfa48, 0xa766795d }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0xaa8fa306, 0xa766795d }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x59437055, 0xa766795d }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x1a2214ec, 0xa766795d }, + { "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xb5d1be68, 0xa766795d }, + { "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xb2475cc8, 0xa766795d }, + { "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x3cf85197, 0xa766795d }, + { "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x8ec35c09, 0xa766795d }, { 0, 0, 0 } }; @@ -107,25 +107,25 @@ struct strhash32_test_vector TEST_STRHASH32[] = { * Test vectors for tommy_hash64 */ static struct hash64_test_vector TEST_HASH64[] = { - { "", 0, 0x8614384cb5165fbfULL, 0x2f022773a766795dULL }, - { "a", 1, 0x1a2e0298a8e94a3dULL, 0x2f022773a766795dULL }, - { "abc", 3, 0x7555796b7a7d21ebULL, 0x2f022773a766795dULL }, - { "message digest", 14, 0x9411a57d04b92fb4ULL, 0x2f022773a766795dULL }, - { "abcdefghijklmnopqrstuvwxyz", 26, 0x3ca3f8d2b4e69832ULL, 0x2f022773a766795dULL }, - { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6dae542ba0015a4dULL, 0x2f022773a766795dULL }, - { "The quick brown fox jumps over the lazy dog", 43, 0xe06d8cbb3d2ea1a6ULL, 0x2f022773a766795dULL }, - { "\x00", 1, 0x201e664fb5f2c021ULL, 0x2f022773a766795dULL }, - { "\x16\x27", 2, 0xef42fa8032c4b775ULL, 0x2f022773a766795dULL }, - { "\xe2\x56\xb4", 3, 0x6e6c498a6688466cULL, 0x2f022773a766795dULL }, - { "\xc9\x4d\x9c\xda", 4, 0x5195005419905423ULL, 0x2f022773a766795dULL }, - { "\x79\xf1\x29\x69\x5d", 5, 0x221235b48afee7c1ULL, 0x2f022773a766795dULL }, - { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x1b1f18b9266f095bULL, 0x2f022773a766795dULL }, - { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x2cbafa8e741d49caULL, 0x2f022773a766795dULL }, - { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x4677f04c06e0758dULL, 0x2f022773a766795dULL }, - { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x5afe09e8214e2163ULL, 0x2f022773a766795dULL }, - { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x115b6276d209fab6ULL, 0x2f022773a766795dULL }, - { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xd0636d2f01cf3a3eULL, 0x2f022773a766795dULL }, - { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x6d259f5fef74f93eULL, 0x2f022773a766795dULL }, + { "", 0, 0xf64d2d2164ca4e35ULL, 0x2f022773a766795dULL }, + { "a", 1, 0xcb0b7863fd912608ULL, 0x2f022773a766795dULL }, + { "abc", 3, 0x023adbd4412dfbc2ULL, 0x2f022773a766795dULL }, + { "message digest", 14, 0x8d16f620b58a030eULL, 0x2f022773a766795dULL }, + { "abcdefghijklmnopqrstuvwxyz", 26, 0xa615ea6b87f2b63fULL, 0x2f022773a766795dULL }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xb9f145676ebdb4fcULL, 0x2f022773a766795dULL }, + { "The quick brown fox jumps over the lazy dog", 43, 0x99b05c9548b523dcULL, 0x2f022773a766795dULL }, + { "\x00", 1, 0x432204e0ea8617fbULL, 0x2f022773a766795dULL }, + { "\x16\x27", 2, 0x5118c58166c6ab3eULL, 0x2f022773a766795dULL }, + { "\xe2\x56\xb4", 3, 0x29484604a9976acbULL, 0x2f022773a766795dULL }, + { "\xc9\x4d\x9c\xda", 4, 0xf000ac44a9f98f4aULL, 0x2f022773a766795dULL }, + { "\x79\xf1\x29\x69\x5d", 5, 0x9603fc74ec52e5aaULL, 0x2f022773a766795dULL }, + { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0xd39f549b363c8e2bULL, 0x2f022773a766795dULL }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x1be47d9f53a2a31dULL, 0x2f022773a766795dULL }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xe3fa24e31af8c4ebULL, 0x2f022773a766795dULL }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xef8c3dcec426a25dULL, 0x2f022773a766795dULL }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xed0be01a88bde7cbULL, 0x2f022773a766795dULL }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xf9b50d9db5dfa543ULL, 0x2f022773a766795dULL }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x695329dad2094e8aULL, 0x2f022773a766795dULL }, { 0, 0, 0, 0 } }; @@ -188,6 +188,13 @@ static void test_hash(void) exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } + digest = tommy_hash_u32(TEST_STRHASH32[i].seed, buffer_aligned, strlen(TEST_STRHASH32[i].data)); + if (digest != TEST_STRHASH32[i].digest) { + /* LCOV_EXCL_START */ + log_fatal(EINTERNAL, "Failed strhash32 test\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } } for (i = 0; TEST_HASH64[i].data; ++i) { @@ -456,7 +463,7 @@ static void test_tommy(void) tommy_arrayblkof_init(&arrayblkof, sizeof(unsigned)); for (i = 0; i < TOMMY_SIZE; ++i) { - tommy_array_insert(&array, &node[i]); + tommy_array_insert_tail(&array, &node[i]); tommy_arrayblkof_grow(&arrayblkof, i + 1); *(unsigned*)tommy_arrayblkof_ref(&arrayblkof, i) = i; } @@ -584,7 +591,11 @@ static void test_tommy(void) tommy_tree_insert(&tree, &node[i], (void*)(uintptr_t)(i + 1)); /* try to insert a duplicate, count should not change */ - tommy_tree_insert(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1); + if (tommy_tree_insert_unique(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1) != (void*)(uintptr_t)1) { + /* LCOV_EXCL_START */ + goto bail; + /* LCOV_EXCL_STOP */ + } if (tommy_tree_count(&tree) != TOMMY_SIZE) { /* LCOV_EXCL_START */ diff --git a/cmdline/util.c b/cmdline/util.c index 8cfbde5b..2cc659b6 100644 --- a/cmdline/util.c +++ b/cmdline/util.c @@ -7,6 +7,7 @@ #include "util.h" #include "raid/cpu.h" #include "raid/memory.h" +#include "tommyds/tommyhash.h" /****************************************************************************/ /* memory */ @@ -610,112 +611,6 @@ static inline void util_write64(void* ptr, uint64_t v) memcpy(ptr, &v, sizeof(v)); } -/** - * 64*64 -> 128bit multiply function. - * - * @param A Address of 64-bit number. - * @param B Address of 64-bit number. - * - * Calculates 128-bit C = *A * *B. - * - * Overwrites A contents with C's low 64 bits. - * Overwrites B contents with C's high 64 bits. - */ -#if defined(__x86_64__) || defined(_M_X64) -/** - * This is intentionally placed before the __SIZEOF_INT128__ implementation. - * - * This implementation happens to generate more efficient code. - * While this likely depends only on the compiler, as long as it remains faster, - * we will keep it in this order. - */ -static inline void util_mum(uint64_t* A, uint64_t* B) -{ - uint64_t a = *A; - uint64_t b = *B; - uint64_t low, high; - - __asm__ ( - "mulq %3" - : "=a" (low), "=d" (high) - : "a" (a), "rm" (b) - : "cc" - ); - - *A = low; - *B = high; -} -#elif defined(__SIZEOF_INT128__) -static inline void util_mum(uint64_t* A, uint64_t* B) -{ - __uint128_t product = ((__uint128_t)*A) * ((__uint128_t)*B); - *A = (uint64_t)product; - *B = (uint64_t)(product >> 64); -} -#elif defined(__aarch64__) || defined(_M_ARM64) -/** - * This is intentionally placed after the __SIZEOF_INT128__ implementation. - * - * Although the code looks correct, there is a report of this - * implementation failing the self-test on macOS (Darwin 25.4.0, arm64) - * with Apple clang 21.0.0 (clang-2100.0.123.102). - * - * See: https://github.com/amadvance/snapraid/issues/43 - * - * Note that the implementation is the same of OpenSSL: - * - * #elif defined(__aarch64__) && defined(SIXTY_FOUR_BIT_LONG) - * #if defined(__GNUC__) && __GNUC__ >= 2 - * #define BN_UMULT_HIGH(a, b) ({ \ - * register BN_ULONG ret; \ - * asm ("umulh %0,%1,%2" \ - * : "=r"(ret) \ - * : "r"(a), "r"(b)); \ - * ret; }) - * #endif - * #endif - * - * See: https://github.com/openssl/openssl/blob/0463cbf185680e66a3a8960f10cd33001377d28e/crypto/bn/bn_local.h#L439 - */ -static inline void util_mum(uint64_t* A, uint64_t* B) -{ - uint64_t a = *A; - uint64_t b = *B; - uint64_t low = a * b; - uint64_t high; - __asm__ ( - "umulh %0, %1, %2" - : "=r" (high) - : "r" (a), "r" (b) - ); - *A = low; - *B = high; -} -#else -static inline void util_mum(uint64_t* A, uint64_t* B) -{ - uint64_t a = *A; - uint64_t b = *B; - - uint64_t a_lo = (uint32_t)a; - uint64_t a_hi = a >> 32; - uint64_t b_lo = (uint32_t)b; - uint64_t b_hi = b >> 32; - - uint64_t p0 = a_lo * b_lo; - uint64_t p1 = a_lo * b_hi; - uint64_t p2 = a_hi * b_lo; - uint64_t p3 = a_hi * b_hi; - - uint64_t middle = p1 + (p0 >> 32) + (uint32_t)p2; - uint64_t low = (middle << 32) | (uint32_t)p0; - uint64_t high = p3 + (middle >> 32) + (p2 >> 32); - - *A = low; - *B = high; -} -#endif - int util_selftest(void) { /* rotate left 32-bit */ @@ -748,33 +643,33 @@ int util_selftest(void) if (util_rotr64(0x123456789ABCDEF0ULL, 0) != 0x123456789ABCDEF0ULL) return -1; if (util_rotr64(0x123456789ABCDEF0ULL, 64) != 0x123456789ABCDEF0ULL) return -1; - /* util_mum tests */ + /* tommy_mum tests */ uint64_t a, b; /* small values */ a = 3; b = 4; - util_mum(&a, &b); + tommy_mum(&a, &b); if (a != 12 || b != 0) return -1; /* 1 * anything */ a = 0xFFFFFFFFFFFFFFFFULL; b = 1; - util_mum(&a, &b); + tommy_mum(&a, &b); if (a != 0xFFFFFFFFFFFFFFFFULL || b != 0) return -1; /* (2^32) * (2^32) = 2^64 */ a = 1ULL << 32; b = 1ULL << 32; - util_mum(&a, &b); + tommy_mum(&a, &b); if (a != 0 || b != 1) return -1; /* max uint64 * max uint64 */ a = 0xFFFFFFFFFFFFFFFFULL; b = 0xFFFFFFFFFFFFFFFFULL; - util_mum(&a, &b); + tommy_mum(&a, &b); if (a != 1 || b != 0xFFFFFFFFFFFFFFFEULL) return -1; /* larger random-ish test */ a = 0x0123456789ABCDEFULL; b = 0xFEDCBA9876543210ULL; - util_mum(&a, &b); + tommy_mum(&a, &b); if (a != 0x2236D88FE5618CF0ULL || b != 0x121FA00AD77D742ULL) return -1; return 0; diff --git a/tommyds/tommy.c b/tommyds/tommy.c index f3c16d8c..51c2859b 100644 --- a/tommyds/tommy.c +++ b/tommyds/tommy.c @@ -4,10 +4,11 @@ /* redefine the malloc for tommy use */ #define tommy_malloc malloc_nofail #define tommy_calloc calloc_nofail +#define tommy_realloc realloc_nofail #define tommy_free free #include "os/portable.h" -#include "cmdline/support.h" /* for malloc/calloc_nofail() */ +#include "cmdline/support.h" /* for malloc/calloc/realloc_nofail() */ #include "tommyhash.c" #include "tommyarray.c" diff --git a/tommyds/tommyarray.c b/tommyds/tommyarray.c index 90445659..2b9c8261 100644 --- a/tommyds/tommyarray.c +++ b/tommyds/tommyarray.c @@ -3,18 +3,18 @@ #include "tommyarray.h" +#include /* for memset */ + /******************************************************************************/ /* array */ TOMMY_API void tommy_array_init(tommy_array* array) { - tommy_uint_t i; - /* fixed initial size */ array->bucket_bit = TOMMY_ARRAY_BIT; array->bucket_max = (tommy_size_t)1 << array->bucket_bit; array->bucket[0] = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*))); - for (i = 1; i < TOMMY_ARRAY_BIT; ++i) + for (tommy_uint_t i = 1; i < TOMMY_ARRAY_BIT; ++i) array->bucket[i] = array->bucket[0]; array->count = 0; @@ -22,26 +22,18 @@ TOMMY_API void tommy_array_init(tommy_array* array) TOMMY_API void tommy_array_done(tommy_array* array) { - tommy_uint_t i; - tommy_free(array->bucket[0]); - for (i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) { + for (tommy_uint_t i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) { void** segment = array->bucket[i]; tommy_free(&segment[(tommy_ptrdiff_t)1 << i]); } } -TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count) +TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size) { - if (array->count >= count) - return; - array->count = count; - - while (count > array->bucket_max) { - void** segment; - + while (size > array->bucket_max) { /* allocate one more segment */ - segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*))); + void** segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*))); /* store it adjusting the offset */ /* cast to ptrdiff_t to ensure to get a negative value */ @@ -52,7 +44,78 @@ TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count) } } -TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array) +TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size) +{ + if (size >= array->count) { + tommy_array_grow(array, size); + return; + } + + /* clear the unused elements to maintain the invariant that unused slots are zero */ + tommy_size_t pos = size; + while (pos < array->count) { + tommy_uint_t bsr = tommy_ilog2(pos | 1); + tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1)); + tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end; + + memset(&array->bucket[bsr][pos], 0, (chunk_end - pos) * sizeof(void*)); + pos = chunk_end; + } + + array->count = size; +} + +TOMMY_API void tommy_array_shrink(tommy_array* array) +{ + tommy_uint_t target_bucket_bit = TOMMY_ARRAY_BIT; + + if (array->count > (tommy_size_t)1 << TOMMY_ARRAY_BIT) + target_bucket_bit = tommy_ilog2(array->count - 1) + 1; + + while (array->bucket_bit > target_bucket_bit) { + --array->bucket_bit; + void** segment = array->bucket[array->bucket_bit]; + tommy_free(&segment[(tommy_ptrdiff_t)1 << array->bucket_bit]); + } + + array->bucket_max = (tommy_size_t)1 << array->bucket_bit; +} + +TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func) +{ + tommy_size_t pos = 0; + + while (pos < array->count) { + tommy_uint_t bsr = tommy_ilog2(pos | 1); + tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1)); + tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end; + void** ptr = array->bucket[bsr]; + + while (pos < chunk_end) { + func(ptr[pos]); + ++pos; + } + } +} + +TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg) +{ + tommy_size_t pos = 0; + + while (pos < array->count) { + tommy_uint_t bsr = tommy_ilog2(pos | 1); + tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1)); + tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end; + void** ptr = array->bucket[bsr]; + + while (pos < chunk_end) { + func(arg, ptr[pos]); + ++pos; + } + } +} + +TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array) { return array->bucket_max * (tommy_size_t)sizeof(void*); } diff --git a/tommyds/tommyarray.h b/tommyds/tommyarray.h index f5f4d726..ff92c762 100644 --- a/tommyds/tommyarray.h +++ b/tommyds/tommyarray.h @@ -7,14 +7,14 @@ * This array is able to grow dynamically upon request, without any reallocation. * * The grow operation involves an allocation of a new array segment, without reallocating - * the already used memory, and thus **not increasing** the heap fragmentation. - * This also implies that the address of the stored elements never change. + * the already used memory, and thus not increasing the heap fragmentation. + * This also implies that the address of the stored elements never changes. * * Allocated segments grow in size exponentially. */ -#ifndef __TOMMYARRAY_H -#define __TOMMYARRAY_H +#ifndef TOMMY_ARRAY_H +#define TOMMY_ARRAY_H #include "tommytypes.h" @@ -42,33 +42,83 @@ typedef struct tommy_array_struct { /** * Initializes the array. + * \param array The array. */ TOMMY_API void tommy_array_init(tommy_array* array); /** * Deinitializes the array. + * \param array The array. */ TOMMY_API void tommy_array_done(tommy_array* array); /** - * Grows the size up to the specified value. - * All the new elements in the array are initialized with the 0 value. + * Allocates space for at least the specified number of elements. + * The initialized size and existing elements are unchanged. + * Existing element references remain valid. + * \param array The array. + * \param size Number of elements to reserve space for. */ -TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t size); +TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size); /** - * Gets a reference of the element at the specified position. + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + * \param array The array. + * \param size New size of the array. + */ +tommy_inline void tommy_array_grow(tommy_array* array, tommy_size_t size) +{ + if (size > array->count) { + array->count = size; + + if (size > array->bucket_max) + tommy_array_reserve(array, size); + } +} + +/** + * Changes the initialized size, preserving the common prefix. + * New elements are initialized to zero. + * Reducing the size does not release allocated capacity. + * \param array The array. + * \param size New size of the array. + */ +TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size); + +/** + * Removes all elements, preserving the allocated capacity. + * Call tommy_array_shrink() after clearing to restore the initial capacity. + * Pointed-to elements are not freed. + * The array remains initialized and can be reused immediately. + * \param array The array. + */ +tommy_inline void tommy_array_clear(tommy_array* array) +{ + tommy_array_resize(array, 0); +} + +/** + * Releases unused allocated memory. + * Preserves the size, values, and addresses of existing elements. + * \param array The array. + */ +TOMMY_API void tommy_array_shrink(tommy_array* array); + +/** + * Gets a reference to the element at the specified position. * You must be sure that space for this position is already - * allocated calling tommy_array_grow(). + * allocated by calling tommy_array_grow(). + * \param array The array. + * \param pos Position of the element. + * \return Pointer to the slot at the specified position. */ tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos) { - tommy_uint_t bsr; - assert(pos < array->count); /* get the highest bit set, in case of all 0, return 0 */ - bsr = tommy_ilog2(pos | 1); + tommy_uint_t bsr = tommy_ilog2(pos | 1); return &array->bucket[bsr][pos]; } @@ -76,7 +126,10 @@ tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos) /** * Sets the element at the specified position. * You must be sure that space for this position is already - * allocated calling tommy_array_grow(). + * allocated by calling tommy_array_grow(). + * \param array The array. + * \param pos Position of the element. + * \param element Element to set. */ tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* element) { @@ -86,7 +139,10 @@ tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* el /** * Gets the element at the specified position. * You must be sure that space for this position is already - * allocated calling tommy_array_grow(). + * allocated by calling tommy_array_grow(). + * \param array The array. + * \param pos Position of the element. + * \return The element at the specified position. */ tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos) { @@ -94,28 +150,129 @@ tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos) } /** - * Grows and inserts a new element at the end of the array. + * Gets the last element. + * The array must not be empty. + * \param array The array. + * \return The last element. */ -tommy_inline void tommy_array_insert(tommy_array* array, void* element) +tommy_inline void* tommy_array_tail(tommy_array* array) +{ + assert(array->count != 0); + return tommy_array_get(array, array->count - 1); +} + +/** + * Grows and inserts a new element at the end of the array. + * \param array The array. + * \param element Element to insert. + */ +tommy_inline void tommy_array_insert_tail(tommy_array* array, void* element) { tommy_size_t pos = array->count; - tommy_array_grow(array, pos + 1); + ++array->count; + if (array->count > array->bucket_max) + tommy_array_reserve(array, array->count); tommy_array_set(array, pos, element); } /** - * Gets the initialized size of the array. + * Removes and returns the last element. + * The array must not be empty. + * The removed slot is cleared, and allocated capacity is preserved. + * \param array The array. + * \return The removed element. */ -tommy_inline tommy_size_t tommy_array_size(tommy_array* array) +tommy_inline void* tommy_array_remove_tail(tommy_array* array) +{ + assert(array->count != 0); + + tommy_size_t pos = array->count - 1; + void** ptr = tommy_array_ref(array, pos); + void* element = *ptr; + + /* keep unused slots zero so growing within capacity needs no initialization */ + *ptr = 0; + array->count = pos; + + return element; +} + +/** + * Checks whether the array is empty. + * \param array The array. + * \return Nonzero if the array is empty, 0 otherwise. + */ +tommy_inline tommy_bool_t tommy_array_empty(const tommy_array* array) +{ + return array->count == 0; +} + +/** + * Gets the initialized size of the array. + * \param array The array. + * \return The initialized size of the array. + */ +tommy_inline tommy_size_t tommy_array_size(const tommy_array* array) { return array->count; } /** - * Gets the size of allocated memory. + * Gets the number of elements that fit without further allocation. + * \param array The array. + * \return The capacity of the array. */ -TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array); +tommy_inline tommy_size_t tommy_array_capacity(const tommy_array* array) +{ + return array->bucket_max; +} + +/** + * Exchanges two initialized arrays without copying their elements. + * Element references remain valid and belong to the other array. + * Passing the same array twice has no effect. + * \param first The first array. + * \param second The second array. + * \note This operation is O(1). + */ +tommy_inline void tommy_array_swap(tommy_array* first, tommy_array* second) +{ + tommy_array tmp = *first; + *first = *second; + *second = tmp; +} + +/** + * Calls the specified function for each element in the array. + * + * You cannot add or remove elements, nor change the size of the array, + * from inside the callback. + * + * If the callback frees the elements, the array retains dangling + * pointers. Call tommy_array_clear() before reusing it, or + * tommy_array_done() to release its storage. + * \param array The array. + * \param func Function to call with each element. + */ +TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the array. + * + * \param array The array. + * \param func Function to call with each element. + * \param arg Argument to pass to the function. + */ +TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the size of allocated memory. + * \param array The array. + * \return Total memory allocated by the array, in bytes. + */ +TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array); #endif + diff --git a/tommyds/tommyarrayblkof.c b/tommyds/tommyarrayblkof.c index da2d43c6..a5b1f070 100644 --- a/tommyds/tommyarrayblkof.c +++ b/tommyds/tommyarrayblkof.c @@ -3,12 +3,16 @@ #include "tommyarrayblkof.h" +#include /* for memset */ + /******************************************************************************/ /* array */ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size) { - tommy_array_init(&array->block); + array->block_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT; + array->block = tommy_cast(unsigned char**, tommy_malloc(array->block_capacity * sizeof(array->block[0]))); + array->block_count = 0; array->element_size = element_size; array->count = 0; @@ -16,44 +20,127 @@ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t eleme TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array) { - tommy_size_t i; + for (tommy_size_t i = 0; i < array->block_count; ++i) + tommy_free(array->block[i]); - for (i = 0; i < tommy_array_size(&array->block); ++i) - tommy_free(tommy_array_get(&array->block, i)); - - tommy_array_done(&array->block); + tommy_free(array->block); } -TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t count) +TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size) { - tommy_size_t block_max; - tommy_size_t block_mac; - - if (array->count >= count) + if (size <= array->block_count * TOMMY_ARRAYBLKOF_SIZE) return; - array->count = count; - block_max = (count + TOMMY_ARRAYBLKOF_SIZE - 1) / TOMMY_ARRAYBLKOF_SIZE; - block_mac = tommy_array_size(&array->block); + tommy_size_t block_max = (size - 1) / TOMMY_ARRAYBLKOF_SIZE + 1; - if (block_mac < block_max) { - /* grow the block array */ - tommy_array_grow(&array->block, block_max); + if (array->block_capacity < block_max) { + /* only the directory moves; addresses inside existing blocks stay valid */ + tommy_size_t capacity = array->block_capacity; + while (capacity < block_max) + capacity *= 2; + array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0]))); + array->block_capacity = capacity; + } - /* allocate new blocks */ - while (block_mac < block_max) { - void** ptr = tommy_cast(void**, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size)); + /* allocate new blocks */ + while (array->block_count < block_max) { + unsigned char* ptr = tommy_cast(unsigned char*, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size)); - /* set the new block */ - tommy_array_set(&array->block, block_mac, ptr); - - ++block_mac; - } + array->block[array->block_count] = ptr; + ++array->block_count; } } -TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array) +TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size) { - return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLKOF_SIZE * array->element_size; + if (size >= array->count) { + tommy_arrayblkof_grow(array, size); + return; + } + + /* clear the unused elements to maintain the invariant that unused slots are zero */ + tommy_size_t pos = size; + while (pos < array->count) { + tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE; + tommy_size_t blk_offset = pos % TOMMY_ARRAYBLKOF_SIZE; + tommy_size_t blk_rem = TOMMY_ARRAYBLKOF_SIZE - blk_offset; + tommy_size_t count_rem = array->count - pos; + tommy_size_t chunk = blk_rem < count_rem ? blk_rem : count_rem; + + memset(array->block[blk_idx] + blk_offset * array->element_size, 0, chunk * array->element_size); + pos += chunk; + } + + array->count = size; +} + +TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array) +{ + tommy_size_t block_max = (array->count == 0) ? 0 : ((array->count - 1) / TOMMY_ARRAYBLKOF_SIZE + 1); + + while (array->block_count > block_max) { + --array->block_count; + tommy_free(array->block[array->block_count]); + } + + tommy_size_t min_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT; + tommy_size_t capacity = min_capacity; + while (capacity < block_max) + capacity *= 2; + if (capacity < array->block_capacity) { + array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0]))); + array->block_capacity = capacity; + } +} + +TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func) +{ + /* cache the element size to avoid reloading it after each callback */ + tommy_size_t element_size = array->element_size; + tommy_size_t pos = 0; + + while (pos < array->count) { + tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE; + tommy_size_t chunk = array->count - pos; + unsigned char* ptr = array->block[blk_idx]; + + if (chunk > TOMMY_ARRAYBLKOF_SIZE) + chunk = TOMMY_ARRAYBLKOF_SIZE; + + for (tommy_size_t i = 0; i < chunk; ++i) { + func(ptr); + ptr += element_size; + } + + pos += chunk; + } +} + +TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg) +{ + /* cache the element size to avoid reloading it after each callback */ + tommy_size_t element_size = array->element_size; + tommy_size_t pos = 0; + + while (pos < array->count) { + tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE; + tommy_size_t chunk = array->count - pos; + unsigned char* ptr = array->block[blk_idx]; + + if (chunk > TOMMY_ARRAYBLKOF_SIZE) + chunk = TOMMY_ARRAYBLKOF_SIZE; + + for (tommy_size_t i = 0; i < chunk; ++i) { + func(arg, ptr); + ptr += element_size; + } + + pos += chunk; + } +} + +TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array) +{ + return array->block_capacity * sizeof(array->block[0]) + array->block_count * TOMMY_ARRAYBLKOF_SIZE * array->element_size; } diff --git a/tommyds/tommyarrayblkof.h b/tommyds/tommyarrayblkof.h index 8c9edfa0..7178ae42 100644 --- a/tommyds/tommyarrayblkof.h +++ b/tommyds/tommyarrayblkof.h @@ -2,29 +2,43 @@ // Copyright (C) 2013 Andrea Mazzoleni /** \file - * Dynamic array based on blocks of fixed size. + * Dynamic array based on fixed-size blocks. * - * This array is able to grow dynamically upon request, without any reallocation. + * This array is able to grow dynamically without reallocating stored elements. + * The directory of block pointers may be reallocated, but element addresses + * never change. * - * This is very similar to ::tommy_arrayblk, but it allows to store elements of any + * This is very similar to ::tommy_arrayblk, but it allows storing elements of any * size and not just pointers. * - * Note that in this case tommy_arrayblkof_ref() returns a pointer to the element, - * that should be used for getting and setting elements in the array, - * as generic getter and setter are not available. + * The container allocates space for elements but never copies user data into + * or out of it. tommy_arrayblkof_ref(), tommy_arrayblkof_tail(), and + * tommy_arrayblkof_insert_tail() return an element's address so the caller can + * read or write it directly. + * + * \note Only types with fundamental alignment requirements are supported. + * Storage is allocated using calloc(), which does not guarantee the alignment + * required by over-aligned types (e.g. _Alignas(32) or _Alignas(64)). + * When storing typed elements, use sizeof(type) as the element size to + * preserve alignment for every element. */ -#ifndef __TOMMYARRAYBLKOF_H -#define __TOMMYARRAYBLKOF_H +#ifndef TOMMY_ARRAYBLKOF_H +#define TOMMY_ARRAYBLKOF_H #include "tommytypes.h" -#include "tommyarray.h" #include /* for assert */ +#include /* for memset */ /******************************************************************************/ /* array */ +/** + * Initial and minimal capacity of the block directory expressed as a power of 2. + */ +#define TOMMY_ARRAYBLKOF_BIT 6 + /** * Elements for each block. */ @@ -35,55 +49,214 @@ * \note Don't use internal fields directly, but access the container only using functions. */ typedef struct tommy_arrayblkof_struct { - tommy_array block; /**< Array of blocks. */ + unsigned char** block; /**< Directory of blocks. */ + tommy_size_t block_count; /**< Number of allocated blocks. */ + tommy_size_t block_capacity; /**< Number of directory entries allocated. */ tommy_size_t element_size; /**< Size of the stored element in bytes. */ tommy_size_t count; /**< Number of initialized elements in the array. */ } tommy_arrayblkof; /** * Initializes the array. - * \param element_size Size in byte of the element to store in the array. + * \param array The array. + * \param element_size Size in bytes of the element to store in the array. */ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size); /** * Deinitializes the array. + * \param array The array. */ TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array); /** - * Grows the size up to the specified value. - * All the new elements in the array are initialized with the 0 value. + * Allocates space for at least the specified number of elements. + * The initialized size and existing elements are unchanged. + * Existing element references remain valid. + * \param array The array. + * \param size Number of elements to reserve space for. */ -TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size); +TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size); /** - * Gets a reference of the element at the specified position. + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + * \param array The array. + * \param size New size of the array. + */ +tommy_inline void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size) +{ + if (size > array->count) { + array->count = size; + + if (size > array->block_count * TOMMY_ARRAYBLKOF_SIZE) + tommy_arrayblkof_reserve(array, size); + } +} + +/** + * Changes the initialized size, preserving the common prefix. + * New elements are initialized to zero. + * Reducing the size does not release allocated capacity. + * \param array The array. + * \param size New size of the array. + */ +TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size); + +/** + * Removes all elements, preserving the allocated capacity. + * Call tommy_arrayblkof_shrink() after clearing to restore the initial capacity. + * The array remains initialized and can be reused immediately. + * \param array The array. + */ +tommy_inline void tommy_arrayblkof_clear(tommy_arrayblkof* array) +{ + tommy_arrayblkof_resize(array, 0); +} + +/** + * Releases unused allocated memory. + * Preserves the size, values, and addresses of existing elements. + * \param array The array. + */ +TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array); + +/** + * Gets a reference to the element at the specified position. * You must be sure that space for this position is already - * allocated calling tommy_arrayblkof_grow(). + * allocated by calling tommy_arrayblkof_grow(). + * \param array The array. + * \param pos Position of the element. + * \return Pointer to the slot at the specified position. */ tommy_inline void* tommy_arrayblkof_ref(tommy_arrayblkof* array, tommy_size_t pos) { - unsigned char* base; - assert(pos < array->count); - base = tommy_cast(unsigned char*, tommy_array_get(&array->block, pos / TOMMY_ARRAYBLKOF_SIZE)); + return array->block[pos / TOMMY_ARRAYBLKOF_SIZE] + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size; +} - return base + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size; +/** + * Gets a reference to the last element. + * The array must not be empty. + * \param array The array. + * \return Pointer to the last element. + */ +tommy_inline void* tommy_arrayblkof_tail(tommy_arrayblkof* array) +{ + assert(array->count != 0); + return tommy_arrayblkof_ref(array, array->count - 1); +} + +/** + * Adds a zero-initialized element and returns its reference. + * \param array The array. + * \return Pointer to the new element. + */ +tommy_inline void* tommy_arrayblkof_insert_tail(tommy_arrayblkof* array) +{ + tommy_size_t pos = array->count; + + ++array->count; + if (array->count > array->block_count * TOMMY_ARRAYBLKOF_SIZE) + tommy_arrayblkof_reserve(array, array->count); + + return tommy_arrayblkof_ref(array, pos); +} + +/** + * Removes the last element without copying it. + * The array must not be empty. The removed slot is cleared. + * \param array The array. + */ +tommy_inline void tommy_arrayblkof_remove_tail(tommy_arrayblkof* array) +{ + assert(array->count != 0); + + tommy_size_t pos = array->count - 1; + void* ptr = tommy_arrayblkof_ref(array, pos); + + /* keep unused slots zero so growing within capacity needs no initialization */ + memset(ptr, 0, array->element_size); + array->count = pos; +} + +/** + * Checks whether the array is empty. + * \param array The array. + * \return Nonzero if the array is empty, 0 otherwise. + */ +tommy_inline tommy_bool_t tommy_arrayblkof_empty(const tommy_arrayblkof* array) +{ + return array->count == 0; } /** * Gets the initialized size of the array. + * \param array The array. + * \return The initialized size of the array. */ -tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array) +tommy_inline tommy_size_t tommy_arrayblkof_size(const tommy_arrayblkof* array) { return array->count; } /** - * Gets the size of allocated memory. + * Gets the number of elements that fit without further allocation. + * \param array The array. + * \return The capacity of the array. */ -TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array); +tommy_inline tommy_size_t tommy_arrayblkof_capacity(const tommy_arrayblkof* array) +{ + return array->block_count * TOMMY_ARRAYBLKOF_SIZE; +} + +/** + * Exchanges two initialized arrays without copying their elements. + * Element references remain valid and belong to the other array. + * Passing the same array twice has no effect. + * \param first The first array. + * \param second The second array. + * \note This operation is O(1). + */ +tommy_inline void tommy_arrayblkof_swap(tommy_arrayblkof* first, tommy_arrayblkof* second) +{ + /* keep record size with the storage it describes */ + tommy_arrayblkof tmp = *first; + *first = *second; + *second = tmp; +} + +/** + * Calls the specified function for each element in the array. + * + * You cannot add or remove elements, nor change the size of the array, + * from inside the callback. + * + * Elements are stored inside the array: passing free() as the callback + * is invalid. A callback may release resources owned by each element, + * but must not free the element itself. Call tommy_arrayblkof_clear() before + * reusing it, or tommy_arrayblkof_done() to release its storage. + * \param array The array. + * \param func Function to call with each element. + */ +TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the array. + * + * \param array The array. + * \param func Function to call with each element. + * \param arg Argument to pass to the function. + */ +TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the size of allocated memory. + * \param array The array. + * \return Total memory allocated by the array, in bytes. + */ +TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array); #endif + diff --git a/tommyds/tommychain.h b/tommyds/tommychain.h index c864dccd..d1c293f6 100644 --- a/tommyds/tommychain.h +++ b/tommyds/tommychain.h @@ -9,8 +9,8 @@ * Do not use this directly. Use lists instead. */ -#ifndef __TOMMYCHAIN_H -#define __TOMMYCHAIN_H +#ifndef TOMMY_CHAIN_H +#define TOMMY_CHAIN_H #include "tommytypes.h" @@ -65,31 +65,39 @@ tommy_inline void tommy_chain_concat(tommy_node* first_tail, tommy_node* second_ /** * Merges two chains. * \param first First chain (will contain the result). - * \param second Second chain (will be empty after the merge). + * \param second Second chain (consumed by the merge; its descriptor is not cleared + * and must not be used as an independent chain afterward). * \param cmp Comparison function. */ -tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp) +tommy_inline void tommy_chain_merge(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp) { - tommy_node* first_i = first->head; + /* + * comparators must not change the chains, so cache these endpoints across calls. + * also caching first_tail caused register spills with GCC and Clang on x86-64. + */ + tommy_node* first_head = first->head; + tommy_node* second_tail = second->tail; + tommy_node* first_i = first_head; tommy_node* second_i = second->head; /* merge */ while (1) { if (cmp(first_i->data, second_i->data) > 0) { tommy_node* next = second_i->next; - if (first_i == first->head) { + if (first_i == first_head) { tommy_chain_concat(second_i, first_i); - first->head = second_i; + first_head = second_i; + first->head = first_head; } else { tommy_chain_splice(first_i->prev, first_i, second_i, second_i); } - if (second_i == second->tail) + if (second_i == second_tail) break; second_i = next; } else { if (first_i == first->tail) { tommy_chain_concat(first_i, second_i); - first->tail = second->tail; + first->tail = second_tail; break; } first_i = first_i->next; @@ -98,13 +106,36 @@ tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tom } /** - * Merges two chains managing special degenerated cases. - * It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains. - * \param first First chain (will contain the result). - * \param second Second chain (will be empty after the merge). + * Merges two chains containing one node each. + * \param first First singleton chain (will contain the result). + * \param second Second singleton chain (consumed by the merge; its descriptor is not cleared + * and must not be used as an independent chain afterward). * \param cmp Comparison function. */ -tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp) +tommy_inline void tommy_chain_merge_singleton(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp) +{ + tommy_node* first_node = first->head; + tommy_node* second_node = second->head; + + /* keep equal elements in their original order for a stable sort */ + if (cmp(first_node->data, second_node->data) <= 0) { + tommy_chain_concat(first_node, second_node); + first->tail = second_node; + } else { + tommy_chain_concat(second_node, first_node); + first->head = second_node; + } +} + +/** + * Merges two chains managing special degenerate cases. + * It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains. + * \param first First chain (will contain the result). + * \param second Second chain (consumed by the merge; its descriptor is not cleared + * and must not be used as an independent chain afterward). + * \param cmp Comparison function. + */ +tommy_inline void tommy_chain_merge_degenerated(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp) { /* identify the condition first <= second */ if (cmp(first->tail->data, second->head->data) <= 0) { @@ -126,9 +157,9 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* /** * Sorts a chain. - * It's a stable merge sort using power of 2 buckets, with O(N*log(N)) complexity, + * It's a stable merge sort using power of 2 buckets, with O(n log n) complexity, * similar to the one used in the SGI STL libraries and in the Linux Kernel, - * but faster on degenerated cases like already ordered lists. + * but faster on degenerate cases like already ordered lists. * * SGI STL stl_list.h * http://www.sgi.com/tech/stl/stl_list.h @@ -136,43 +167,48 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* * Linux Kernel lib/list_sort.c * http://lxr.linux.no/#linux+v2.6.36/lib/list_sort.c * - * \param chain Chain to sort. + * \param chain The chain. * \param cmp Comparison function. */ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* cmp) { /* * Bit buckets of chains. - * Each bucket contains 2^i nodes or it's empty. + * Each bucket contains 2^(i+1) nodes or it's empty. + * The bucket containing an unpaired final node has one node less. * The chain at address TOMMY_BIT_MAX is an independent variable operating as "carry". * We keep it in the same "bit" vector to avoid reports from the valgrind tool sgcheck. */ tommy_chain bit[TOMMY_SIZE_BIT + 1]; - /** + /* * Value stored inside the bit bucket. * It's used to know which bucket is empty or full. */ - tommy_size_t counter; + tommy_size_t counter = 0; tommy_node* node = chain->head; tommy_node* tail = chain->tail; - tommy_size_t mask; - tommy_size_t i; - counter = 0; while (1) { - tommy_node* next; - tommy_chain* last; - /* carry bit to add */ - last = &bit[TOMMY_SIZE_BIT]; + tommy_chain* last = &bit[TOMMY_SIZE_BIT]; bit[TOMMY_SIZE_BIT].head = node; bit[TOMMY_SIZE_BIT].tail = node; - next = node->next; + tommy_node* next = node->next; + + /* start with a sorted pair to halve the number of bucket insertions */ + if (node != tail) { + node = next; + /* save the next input node before merging can change the links */ + next = node->next; + tommy_chain second = { node, node }; + tommy_chain_merge_singleton(last, &second, cmp); + } /* add the bit, propagating the carry */ - i = 0; - mask = counter; + tommy_size_t i = 0; + tommy_size_t mask = counter; + while ((mask & 1) != 0) { tommy_chain_merge_degenerated(&bit[i], last, cmp); mask >>= 1; @@ -192,8 +228,8 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* } /* merge the buckets */ - i = tommy_ctz(counter); - mask = counter >> i; + tommy_size_t i = tommy_ctz(counter); + tommy_size_t mask = counter >> i; while (mask != 1) { mask >>= 1; if (mask & 1) @@ -207,3 +243,4 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* } #endif + diff --git a/tommyds/tommyhash.c b/tommyds/tommyhash.c index f6b022b3..fc4fd276 100644 --- a/tommyds/tommyhash.c +++ b/tommyds/tommyhash.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-2-Clause +// SPDX-License-Identifier: BSD-2-Clause AND MIT // Copyright (C) 2010 Andrea Mazzoleni #include "tommyhash.h" @@ -13,222 +13,178 @@ * They are needed only if BigEndian. */ #if defined(__GNUC__) -#define tommy_swap32(x) __builtin_bswap32(x) +#define tommy_imp_hash_swap32(x) __builtin_bswap32(x) #else -tommy_inline tommy_uint32_t tommy_swap32(tommy_uint32_t v) +tommy_inline tommy_uint32_t tommy_imp_hash_swap32(tommy_uint32_t v) { return ((v & 0xFF000000) >> 24) | - ((v & 0x00FF0000) >> 8) | - ((v & 0x0000FF00) << 8) | - ((v & 0x000000FF) << 24); + ((v & 0x00FF0000) >> 8) | + ((v & 0x0000FF00) << 8) | + ((v & 0x000000FF) << 24); } #endif -tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr) +tommy_inline tommy_uint32_t tommy_imp_hash_le_uint32_read(const void* ptr) { tommy_uint32_t v; memcpy(&v, ptr, sizeof(v)); #if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \ (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) - v = tommy_swap32(v); + v = tommy_imp_hash_swap32(v); #endif return v; } -#define tommy_rot(x, k) \ - (((x) << (k)) | ((x) >> (32 - (k)))) +/******************************************************************************/ +/* a5hash */ -#define tommy_mix(a, b, c) \ - do { \ - a -= c; a ^= tommy_rot(c, 4); c += b; \ - b -= a; b ^= tommy_rot(a, 6); a += c; \ - c -= b; c ^= tommy_rot(b, 8); b += a; \ - a -= c; a ^= tommy_rot(c, 16); c += b; \ - b -= a; b ^= tommy_rot(a, 19); a += c; \ - c -= b; c ^= tommy_rot(b, 4); b += a; \ - } while (0) +/** + * a5hash32 and a5hash (64-bit) by Aleksey Vaneev, from https://github.com/avaneev/a5hash. + * Adapted from version 5.25 with little-endian word reads. + * The 32-bit variant additionally mixes the expanded seed asymmetrically into + * h[2] and h[3] before processing the input and produces different hash values. + * Copyright (c) 2025 Aleksey Vaneev. Released under the MIT license, see LICENSES/MIT. + */ -#define tommy_final(a, b, c) \ - do { \ - c ^= b; c -= tommy_rot(b, 14); \ - a ^= c; a -= tommy_rot(c, 11); \ - b ^= a; b -= tommy_rot(a, 25); \ - c ^= b; c -= tommy_rot(b, 16); \ - a ^= c; a -= tommy_rot(c, 4); \ - b ^= a; b -= tommy_rot(a, 14); \ - c ^= b; c -= tommy_rot(b, 24); \ - } while (0) +tommy_inline void tommy_mul(tommy_uint32_t* lo, tommy_uint32_t* hi) +{ + /* both operands are 32-bit; the full product supplies two state words */ + tommy_uint64_t v = (tommy_uint64_t)*lo * *hi; + *lo = (tommy_uint32_t)v; + *hi = v >> 32; +} TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len) { const unsigned char* key = tommy_cast(const unsigned char*, void_key); - tommy_uint32_t a, b, c; + tommy_uint32_t val01 = 0x55555555; + tommy_uint32_t val10 = 0xaaaaaaaa; + tommy_uint32_t h[4] = { 0x243f6a88, 0x85a308d3, 0xfb0bd3ea, 0x0f58fd47 }; + tommy_uint32_t a, b; + tommy_size_t len = key_len; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val; + h[0] ^= key_len; + h[1] ^= key_len; +#if TOMMY_SIZE_BIT == 64 + h[2] = (key_len >> 32) ^ 0x452821e6; + h[3] = (key_len >> 32) ^ 0x38d01377; + tommy_mul(&h[2], &h[3]); +#endif + h[0] ^= init_val & val01; + h[1] ^= init_val & val10; + tommy_mul(&h[0], &h[1]); - while (key_len > 12) { - a += tommy_le_uint32_read(key + 0); - b += tommy_le_uint32_read(key + 4); - c += tommy_le_uint32_read(key + 8); + /* + * Mix the expanded seed asymmetrically into h[2] and h[3] so that short keys + * skipping the main loop still depend on the seed, and neither h[0] nor h[1] + * cancels out when folding h[0] ^= h[2] and h[1] ^= h[3] at the end. + */ + h[2] ^= h[0] ^ h[1]; + h[3] ^= h[0]; - tommy_mix(a, b, c); + if (len <= 16) { + if (len >= 4) { + const unsigned char* tail = key + len - 4; + a = tommy_imp_hash_le_uint32_read(key); + b = tommy_imp_hash_le_uint32_read(tail); + if (len >= 9) { + tommy_size_t offset = (len / 8) * 4; + h[2] += tommy_imp_hash_le_uint32_read(key + offset); + h[3] += tommy_imp_hash_le_uint32_read(tail - offset); + tommy_mul(&h[2], &h[3]); + } + } else { + a = 0; + b = 0; + if (len != 0) { + a = key[0]; + if (len >= 2) + a |= key[1] << 8; + if (len == 3) + a |= key[2] << 16; + } + } + } else { + val01 ^= h[0]; + val10 ^= h[1]; + do { + tommy_uint32_t s1 = h[0]; + tommy_uint32_t s4 = h[3]; + h[0] += tommy_imp_hash_le_uint32_read(key); + h[1] += tommy_imp_hash_le_uint32_read(key + 4); + tommy_mul(&h[0], &h[1]); + h[2] += tommy_imp_hash_le_uint32_read(key + 8); + h[3] += tommy_imp_hash_le_uint32_read(key + 12); + tommy_mul(&h[2], &h[3]); + h[0] += val01; + h[1] += s4; + h[2] += s1; + h[3] += val10; + key += 16; + len -= 16; + } while (len > 16); - key_len -= 12; - key += 12; + /* the last words may overlap the previous block, but stay within the input */ + const unsigned char* tail = key + len; + a = tommy_imp_hash_le_uint32_read(tail - 8); + b = tommy_imp_hash_le_uint32_read(tail - 4); + if (len >= 9) { + h[2] += tommy_imp_hash_le_uint32_read(tail - 16); + h[3] += tommy_imp_hash_le_uint32_read(tail - 12); + tommy_mul(&h[2], &h[3]); + } } - switch (key_len) { - case 0 : - return c; /* used only when called with a zero length */ - case 12 : - c += tommy_le_uint32_read(key + 8); - b += tommy_le_uint32_read(key + 4); - a += tommy_le_uint32_read(key + 0); - break; - case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */ - case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */ - case 9 : c += key[8]; /* fallthrough */ - case 8 : - b += tommy_le_uint32_read(key + 4); - a += tommy_le_uint32_read(key + 0); - break; - case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */ - case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */ - case 5 : b += key[4]; /* fallthrough */ - case 4 : - a += tommy_le_uint32_read(key + 0); - break; - case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */ - case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */ - case 1 : a += key[0]; /* fallthrough */ - } - - tommy_final(a, b, c); - - return c; + h[0] ^= h[2]; + h[1] ^= h[3]; + h[0] += a; + h[1] += b; + tommy_mul(&h[0], &h[1]); + h[0] ^= val01; + tommy_mul(&h[0], &h[1]); + return h[0] ^ h[1]; } TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len) { const unsigned char* key = tommy_cast(const unsigned char*, void_key); - tommy_uint32_t a, b, c; + tommy_uint64_t val01 = 0x5555555555555555ULL; + tommy_uint64_t val10 = 0xaaaaaaaaaaaaaaaaULL; + tommy_uint64_t h[2] = { (0x452821e638d01377ULL ^ key_len) ^ (init_val & val10), (0x243f6a8885a308d3ULL ^ key_len) ^ (init_val & val01) }; + tommy_size_t len = key_len; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff); - c += init_val >> 32; - - while (key_len > 12) { - a += tommy_le_uint32_read(key + 0); - b += tommy_le_uint32_read(key + 4); - c += tommy_le_uint32_read(key + 8); - - tommy_mix(a, b, c); - - key_len -= 12; - key += 12; + tommy_mum(&h[0], &h[1]); + if (len > 16) { + val01 ^= h[0]; + val10 ^= h[1]; + do { + /* each 64-bit operand puts the first little-endian word in its high half */ + h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 ^ tommy_imp_hash_le_uint32_read(key + 4); + h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + 8) << 32 ^ tommy_imp_hash_le_uint32_read(key + 12); + tommy_mum(&h[0], &h[1]); + h[0] += val01; + h[1] += val10; + key += 16; + len -= 16; + } while (len > 16); } - switch (key_len) { - case 0 : - return c + ((tommy_uint64_t)b << 32); /* used only when called with a zero length */ - case 12 : - c += tommy_le_uint32_read(key + 8); - b += tommy_le_uint32_read(key + 4); - a += tommy_le_uint32_read(key + 0); - break; - case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */ - case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */ - case 9 : c += key[8]; /* fallthrough */ - case 8 : - b += tommy_le_uint32_read(key + 4); - a += tommy_le_uint32_read(key + 0); - break; - case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */ - case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */ - case 5 : b += key[4]; /* fallthrough */ - case 4 : - a += tommy_le_uint32_read(key + 0); - break; - case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */ - case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */ - case 1 : a += key[0]; /* fallthrough */ + if (len >= 4) { + const unsigned char* tail = key + len - 4; + tommy_size_t offset = (len / 8) * 4; + h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 | tommy_imp_hash_le_uint32_read(tail); + h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + offset) << 32 | tommy_imp_hash_le_uint32_read(tail - offset); + } else if (len != 0) { + h[0] ^= key[0]; + if (len >= 2) + h[0] ^= key[1] << 8; + if (len == 3) + h[0] ^= key[2] << 16; } - tommy_final(a, b, c); - - return c + ((tommy_uint64_t)b << 32); -} - -TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key) -{ - const unsigned char* key = tommy_cast(const unsigned char*, void_key); - tommy_uint32_t a, b, c; - tommy_uint32_t m[3] = { 0xff, 0xff00, 0xff0000 }; - - a = b = c = 0xdeadbeef + init_val; - /* this is different than original lookup3 and the result won't match */ - - while (1) { - tommy_uint32_t v = tommy_le_uint32_read(key); - - if (tommy_haszero_u32(v)) { - if (v & m[0]) { - a += v & m[0]; - if (v & m[1]) { - a += v & m[1]; - if (v & m[2]) - a += v & m[2]; - } - } - - break; - } - - a += v; - - v = tommy_le_uint32_read(key + 4); - - if (tommy_haszero_u32(v)) { - if (v & m[0]) { - b += v & m[0]; - if (v & m[1]) { - b += v & m[1]; - if (v & m[2]) - b += v & m[2]; - } - } - - break; - } - - b += v; - - v = tommy_le_uint32_read(key + 8); - - if (tommy_haszero_u32(v)) { - if (v & m[0]) { - c += v & m[0]; - if (v & m[1]) { - c += v & m[1]; - if (v & m[2]) - c += v & m[2]; - } - } - - break; - } - - c += v; - - tommy_mix(a, b, c); - - key += 12; - } - - /* for lengths that are multipliers of 12 we already have called mix */ - /* this is different than the original lookup3 and the result won't match */ - - tommy_final(a, b, c); - - return c; + tommy_mum(&h[0], &h[1]); + h[0] ^= val01; + tommy_mum(&h[0], &h[1]); + return h[0] ^ h[1]; } diff --git a/tommyds/tommyhash.h b/tommyds/tommyhash.h index 763f5a48..a0dd6043 100644 --- a/tommyds/tommyhash.h +++ b/tommyds/tommyhash.h @@ -2,79 +2,145 @@ // Copyright (C) 2010 Andrea Mazzoleni /** \file - * Hash functions for the use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin. + * Hash functions for use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin. + * + * This module provides hash functions designed for in-memory data structures. + * ::tommy_hash_u32 and ::tommy_strhash_u32 use a modified a5hash32, while + * ::tommy_hash_u64 and ::tommy_strhash_u64 use a5hash (64-bit). + * Reversible integer hash functions are also provided for 32-bit and 64-bit keys. + * + * a5hash32 is intended for 32-bit platforms and uses 32-bit operands with + * full 64-bit multiplication results. a5hash uses 64-bit operands with + * full 128-bit multiplication results. Both algorithms pass SMHasher3 tests. + * + * Prefer the 64-bit version whenever possible, as it provides better resistance + * to collisions, particularly with specially crafted inputs. However, neither + * version is designed to resist deliberate collision attacks. If resistance to + * such attacks is required, use a dedicated hash function designed for that purpose. + * + * All hash functions are intended for runtime in-memory use only and not + * for persistent storage, as hash compatibility across different versions of the + * library is not guaranteed. String and sized buffer hash functions produce + * identical results for 0-terminated strings. */ -#ifndef __TOMMYHASH_H -#define __TOMMYHASH_H +#ifndef TOMMY_HASH_H +#define TOMMY_HASH_H #include "tommytypes.h" +#include /* for strlen */ /******************************************************************************/ /* hash */ /** - * Hash function with a 32 bits result. - * Implementation of the Robert Jenkins "lookup3" hash 32 bits version, - * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle(). + * Hash function with a 32-bit result. + * Modified implementation of a5hash32 by Aleksey Vaneev, + * from https://github.com/avaneev/a5hash * - * This hash is designed to provide a good overall performance on all platforms, - * including 32 bits. If you target only 64 bits, you can use faster hashes, - * like SpookyHash or FarmHash. + * The expanded seed is mixed asymmetrically into the two additional state words + * before processing the input. This preserves seed dependence for short keys + * that skip the main loop and avoids cancellation of either seed state word + * in the final XOR fold. The resulting hash values differ from upstream a5hash32. + * + * This algorithm is compact, fast on short inputs, and passes SMHasher3 tests. * * \param init_val Initialization value. * Using a different initialization value, you can generate a completely different set of hash values. * Use 0 if not relevant. - * \param void_key Pointer to the data to hash. + * \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0. * \param key_len Size of the data to hash. + * \return The 32-bit hash value. * \note - * This function is endianness independent. - * \return The hash value of 32 bits. + * This function is endianness independent and alignment independent. + * It produces identical hash results to ::tommy_strhash_u32 for 0-terminated strings. + * Compatibility across different versions of the library is not guaranteed: + * this hash is intended for in-memory runtime use only and not for persistent storage. + * It is non-cryptographic and is not designed to protect against malicious collision attacks. */ TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len); /** - * Hash function with a 64 bits result. - * Implementation of the Robert Jenkins "lookup3" hash 64 bits version, - * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle2(). + * Hash function with a 64-bit result. + * Implementation of a5hash (64-bit) by Aleksey Vaneev, + * from https://github.com/avaneev/a5hash * - * This hash is designed to provide a good overall performance on all platforms, - * including 32 bits. If you target only 64 bits, you can use faster hashes, - * like SpookyHash or FarmHash. + * This algorithm is compact, fast on short inputs, and passes SMHasher3 tests. * * \param init_val Initialization value. * Using a different initialization value, you can generate a completely different set of hash values. * Use 0 if not relevant. - * \param void_key Pointer to the data to hash. + * \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0. * \param key_len Size of the data to hash. + * \return The 64-bit hash value. * \note - * This function is endianness independent. - * \return The hash value of 64 bits. + * This function is endianness independent and alignment independent. + * It produces identical hash results to ::tommy_strhash_u64 for 0-terminated strings. + * Compatibility across different versions of the library is not guaranteed: + * this hash is intended for in-memory runtime use only and not for persistent storage. + * It is non-cryptographic and is not designed to protect against malicious collision attacks. */ TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len); /** - * String hash function with a 32 bits result. - * Implementation is based on Robert Jenkins "lookup3" hash 32 bits version, - * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle(). + * String hash function with a 32-bit result. + * Modified implementation of a5hash32 by Aleksey Vaneev, + * from https://github.com/avaneev/a5hash * - * This hash is designed to handle strings with an unknown length. If you - * know the string length, the other hash functions are surely faster. + * This hash is designed to handle strings with an unknown length. + * It is implemented inline using strlen() followed by ::tommy_hash_u32. + * This gives better performance than scanning for the terminator while hashing. + * It uses the same modified seed mixing and differs from upstream a5hash32. * * \param init_val Initialization value. * Using a different initialization value, you can generate a completely different set of hash values. * Use 0 if not relevant. - * \param void_key Pointer to the string to hash. It has to be 0 terminated. + * \param void_key Pointer to the string to hash. It has to be 0-terminated. + * \return The 32-bit hash value. * \note - * This function is endianness independent. - * \return The hash value of 32 bits. + * This function is endianness independent and alignment independent. + * It produces identical hash results to ::tommy_hash_u32 for 0-terminated strings. + * Compatibility across different versions of the library is not guaranteed: + * this hash is intended for in-memory runtime use only and not for persistent storage. + * It is non-cryptographic and is not designed to protect against malicious collision attacks. */ -TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key); +tommy_inline tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key) +{ + return tommy_hash_u32(init_val, void_key, strlen(tommy_cast(const char*, void_key))); +} /** - * Integer reversible hash function for 32 bits. + * String hash function with a 64-bit result. + * Implementation of a5hash (64-bit) by Aleksey Vaneev, + * from https://github.com/avaneev/a5hash + * + * This hash is designed to handle strings with an unknown length. + * It is implemented inline using strlen() followed by ::tommy_hash_u64. + * This gives better performance than scanning for the terminator while hashing. + * + * \param init_val Initialization value. + * Using a different initialization value, you can generate a completely different set of hash values. + * Use 0 if not relevant. + * \param void_key Pointer to the string to hash. It has to be 0-terminated. + * \return The 64-bit hash value. + * \note + * This function is endianness independent and alignment independent. + * It produces identical hash results to ::tommy_hash_u64 for 0-terminated strings. + * Compatibility across different versions of the library is not guaranteed: + * this hash is intended for in-memory runtime use only and not for persistent storage. + * It is non-cryptographic and is not designed to protect against malicious collision attacks. + */ +tommy_inline tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key) +{ + return tommy_hash_u64(init_val, void_key, strlen(tommy_cast(const char*, void_key))); +} + +/** + * Reversible 32-bit integer hash function. * Implementation of the Robert Jenkins "4-byte Integer Hashing", * from http://burtleburtle.net/bob/hash/integer.html + * \param key The 32-bit integer to hash. + * \return The 32-bit hash value. */ tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key) { @@ -90,9 +156,11 @@ tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key) } /** - * Integer reversible hash function for 64 bits. + * Reversible 64-bit integer hash function. * Implementation of the Thomas Wang "Integer Hash Function", * from http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm + * \param key The 64-bit integer to hash. + * \return The 64-bit hash value. */ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key) { @@ -107,4 +175,146 @@ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key) return key; } +/** + * Reversible 32-bit integer hash function using multiplications. + * Implementation of the MurmurHash3 fmix32 finalizer by Austin Appleby, + * from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp + * \param key The 32-bit integer to hash. + * \return The 32-bit hash value. + * \note Requires an architecture with fast 32-bit integer multiplication + * for good performance. See tommy_inthash_u32() for an alternative. + */ +tommy_inline tommy_uint32_t tommy_mulhash_u32(tommy_uint32_t key) +{ + key ^= key >> 16; + key *= 0x85ebca6bU; + key ^= key >> 13; + key *= 0xc2b2ae35U; + key ^= key >> 16; + + return key; +} + +/** + * Reversible 64-bit integer hash function using multiplications. + * Implementation of the SplitMix64 finalizer, + * from https://prng.di.unimi.it/splitmix64.c + * \param key The 64-bit integer to hash. + * \return The 64-bit hash value. + * \note Requires an architecture with fast 64-bit integer multiplication + * for good performance. It is generally not recommended on 32-bit architectures, + * where these multiplications are expensive. Consider tommy_inthash_u64() instead. + */ +tommy_inline tommy_uint64_t tommy_mulhash_u64(tommy_uint64_t key) +{ + key ^= key >> 30; + key *= 0xbf58476d1ce4e5b9ULL; + key ^= key >> 27; + key *= 0x94d049bb133111ebULL; + key ^= key >> 31; + + return key; +} + +/******************************************************************************/ +/* multiplication */ + +/** + * Multiplies two 64-bit unsigned numbers to obtain the full 128-bit product. + * + * \param A Address of the first operand, overwritten with the low 64 bits of the product. + * \param B Address of the second operand, overwritten with the high 64 bits of the product. + */ +#if defined(_MSC_VER) && defined(_M_X64) +/* + * MSVC x64 does not support inline assembly, so use its full-product intrinsic. + */ +#pragma intrinsic(_umul128) +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + tommy_uint64_t high; + tommy_uint64_t low = _umul128(*A, *B, &high); + *A = low; + *B = high; +} +#elif defined(_MSC_VER) && defined(_M_ARM64) +#pragma intrinsic(__umulh) +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + tommy_uint64_t a = *A; + tommy_uint64_t b = *B; + *A = a * b; + *B = __umulh(a, b); +} +#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__)) +/** + * Keep this before __SIZEOF_INT128__ because the explicit mulq generates more efficient code. + */ +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + tommy_uint64_t a = *A; + tommy_uint64_t b = *B; + tommy_uint64_t low, high; + + /* require a register to avoid Clang spilling the operand before mulq */ + __asm__ ( + "mulq %3" + : "=a" (low), "=d" (high) + : "a" (a), "r" (b) + : "cc" + ); + + *A = low; + *B = high; +} +#elif defined(__SIZEOF_INT128__) +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + __uint128_t product = (__uint128_t)*A * *B; + *A = product; + *B = product >> 64; +} +#elif defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) +/* + * Keep this after __SIZEOF_INT128__ because the assembly path failed optimized + * hash tests on Apple clang 21.0.0. See https://github.com/amadvance/snapraid/issues/43. + */ +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + tommy_uint64_t a = *A; + tommy_uint64_t b = *B; + tommy_uint64_t low = a * b; + tommy_uint64_t high; + __asm__ ( + "umulh %0, %1, %2" + : "=r" (high) + : "r" (a), "r" (b) + ); + *A = low; + *B = high; +} +#else +tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B) +{ + tommy_uint64_t a = *A; + tommy_uint64_t b = *B; + tommy_uint64_t a_lo = (tommy_uint32_t)a; + tommy_uint64_t a_hi = a >> 32; + tommy_uint64_t b_lo = (tommy_uint32_t)b; + tommy_uint64_t b_hi = b >> 32; + tommy_uint64_t p0 = a_lo * b_lo; + tommy_uint64_t p1 = a_lo * b_hi; + tommy_uint64_t p2 = a_hi * b_lo; + tommy_uint64_t p3 = a_hi * b_hi; + + /* retain the carry from the middle words in the high half */ + tommy_uint64_t middle = p1 + (p0 >> 32) + (tommy_uint32_t)p2; + tommy_uint64_t low = (middle << 32) | (tommy_uint32_t)p0; + tommy_uint64_t high = p3 + (middle >> 32) + (p2 >> 32); + *A = low; + *B = high; +} #endif + +#endif + diff --git a/tommyds/tommyhashdyn.c b/tommyds/tommyhashdyn.c index 54d213f2..6775d5dd 100644 --- a/tommyds/tommyhashdyn.c +++ b/tommyds/tommyhashdyn.c @@ -4,6 +4,8 @@ #include "tommyhashdyn.h" #include "tommylist.h" +#include /* for memset */ + /******************************************************************************/ /* hashdyn */ @@ -23,10 +25,16 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn) tommy_free(hashdyn->bucket); } +TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn) +{ + memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*)); + hashdyn->count = 0; +} + /** * Resize the bucket vector. */ -static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit) +static void tommy_imp_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit) { tommy_size_t bucket_bit = hashdyn->bucket_bit; tommy_size_t bucket_max = hashdyn->bucket_max; @@ -37,57 +45,72 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket if (!hashdyn->count) { /* empty table: allocate zeroed memory with calloc without looping */ new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*))); - } else if (new_bucket_bit == bucket_bit + 1) { - tommy_size_t i; - - /* grow by 1 bit: zero the two buckets inline in loop to preserve cache locality */ - new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); - - for (i = 0; i < bucket_max; ++i) { - tommy_hashdyn_node* j; - - new_bucket[i] = 0; - new_bucket[i + bucket_max] = 0; - - j = hashdyn->bucket[i]; - while (j) { - tommy_hashdyn_node* j_next = j->next; - tommy_size_t pos = j->index & new_bucket_mask; - if (new_bucket[pos]) - tommy_list_insert_tail_not_empty(new_bucket[pos], j); - else - tommy_list_insert_first(&new_bucket[pos], j); - j = j_next; - } - } } else if (new_bucket_bit > bucket_bit) { - tommy_size_t i; + if (new_bucket_bit == bucket_bit + 1) { + /* grow by 1 bit: zero the two buckets inline in loop to preserve cache locality */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); - /* grow by multiple bits with elements: allocate zeroed memory and reinsert */ - new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*))); + for (tommy_size_t i = 0; i < bucket_max; ++i) { + new_bucket[i] = 0; + new_bucket[i + bucket_max] = 0; - for (i = 0; i < bucket_max; ++i) { - tommy_hashdyn_node* j = hashdyn->bucket[i]; + tommy_hashdyn_node* j = hashdyn->bucket[i]; + if (j && !j->next) { + /* growth keeps old buckets separate, and singleton links are already valid */ + new_bucket[j->index & new_bucket_mask] = j; + continue; + } + while (j) { + tommy_hashdyn_node* j_next = j->next; + tommy_size_t pos = j->index & new_bucket_mask; + if (new_bucket[pos]) + tommy_list_insert_tail_not_empty(new_bucket[pos], j); + else + tommy_list_insert_first(&new_bucket[pos], j); + j = j_next; + } + } + } else { + /* grow by multiple bits with elements: allocate zeroed memory and reinsert */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*))); - while (j) { - tommy_hashdyn_node* j_next = j->next; - tommy_size_t pos = j->index & new_bucket_mask; - if (new_bucket[pos]) - tommy_list_insert_tail_not_empty(new_bucket[pos], j); - else - tommy_list_insert_first(&new_bucket[pos], j); - j = j_next; + for (tommy_size_t i = 0; i < bucket_max; ++i) { + tommy_hashdyn_node* j = hashdyn->bucket[i]; + + if (j && !j->next) { + /* growth keeps old buckets separate, and singleton links are already valid */ + new_bucket[j->index & new_bucket_mask] = j; + continue; + } + while (j) { + tommy_hashdyn_node* j_next = j->next; + tommy_size_t pos = j->index & new_bucket_mask; + if (new_bucket[pos]) + tommy_list_insert_tail_not_empty(new_bucket[pos], j); + else + tommy_list_insert_first(&new_bucket[pos], j); + j = j_next; + } } } } else { - tommy_size_t i; - - /* shrink with elements: all buckets are overwritten, no pre-zeroing needed */ + /* all buckets are overwritten, no pre-zeroing needed */ new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); - for (i = 0; i < new_bucket_max; ++i) { - new_bucket[i] = hashdyn->bucket[i]; - tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]); + if (new_bucket_bit + 1 == bucket_bit) { + /* shrink by 1 bit: each new bucket joins exactly two old buckets */ + for (tommy_size_t i = 0; i < new_bucket_max; ++i) { + new_bucket[i] = hashdyn->bucket[i]; + tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]); + } + } else { + /* shrink by multiple bits */ + for (tommy_size_t i = 0; i < new_bucket_max; ++i) { + /* all old buckets with the same new modulus must be concatenated. */ + new_bucket[i] = hashdyn->bucket[i]; + for (tommy_size_t j = i + new_bucket_max; j < bucket_max; j += new_bucket_max) + tommy_list_concat(&new_bucket[i], &hashdyn->bucket[j]); + } } } @@ -103,21 +126,21 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket /** * Grow. */ -tommy_inline void hashdyn_grow_step(tommy_hashdyn* hashdyn) +tommy_inline void tommy_imp_hashdyn_grow_step(tommy_hashdyn* hashdyn) { - /* grow if more than 50% full */ + /* grow if at least 50% full */ if (hashdyn->count >= hashdyn->bucket_max / 2) - tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1); + tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1); } /** * Shrink. */ -tommy_inline void hashdyn_shrink_step(tommy_hashdyn* hashdyn) +tommy_inline void tommy_imp_hashdyn_shrink_step(tommy_hashdyn* hashdyn) { - /* shrink if less than 12.5% full */ + /* shrink if at most 12.5% full */ if (hashdyn->count <= hashdyn->bucket_max / 8 && hashdyn->bucket_bit > TOMMY_HASHDYN_BIT) - tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1); + tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1); } TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count) @@ -130,7 +153,20 @@ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count) if (bucket_bit <= hashdyn->bucket_bit) return; - tommy_hashdyn_resize(hashdyn, bucket_bit); + tommy_imp_hashdyn_resize(hashdyn, bucket_bit); +} + +TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn) +{ + tommy_uint_t bucket_bit = tommy_ilog2(hashdyn->count | 1) + 2; + + if (bucket_bit < TOMMY_HASHDYN_BIT) + bucket_bit = TOMMY_HASHDYN_BIT; + + if (bucket_bit >= hashdyn->bucket_bit) + return; + + tommy_imp_hashdyn_resize(hashdyn, bucket_bit); } TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash) @@ -143,7 +179,42 @@ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* ++hashdyn->count; - hashdyn_grow_step(hashdyn); + tommy_imp_hashdyn_grow_step(hashdyn); +} + +TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashdyn_node** bucket = &hashdyn->bucket[hash & hashdyn->bucket_mask]; + tommy_hashdyn_node* i = *bucket; + + while (i) { + /* compare keys only for matching full hashes, as the bucket may contain different hashes */ + if (i->index == hash && cmp(cmp_arg, i->data) == 0) + return i->data; + i = i->next; + } + + /* insert before resizing, while the bucket selected for the search is still valid */ + tommy_list_insert_tail(bucket, node, data); + + node->index = hash; + + ++hashdyn->count; + + tommy_imp_hashdyn_grow_step(hashdyn); + + return data; +} + +TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash) +{ + if (node->index == hash) + return; + + /* unlink using the stored hash, without invoking the resize policy. */ + tommy_list_remove_existing(&hashdyn->bucket[node->index & hashdyn->bucket_mask], node); + node->index = hash; + tommy_list_insert_tail(&hashdyn->bucket[hash & hashdyn->bucket_mask], node, node->data); } TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node) @@ -154,7 +225,7 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash --hashdyn->count; - hashdyn_shrink_step(hashdyn); + tommy_imp_hashdyn_shrink_step(hashdyn); return node->data; } @@ -171,7 +242,7 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func* --hashdyn->count; - hashdyn_shrink_step(hashdyn); + tommy_imp_hashdyn_shrink_step(hashdyn); return node->data; } @@ -185,9 +256,8 @@ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* { tommy_size_t bucket_max = hashdyn->bucket_max; tommy_hashdyn_node** bucket = hashdyn->bucket; - tommy_size_t pos; - for (pos = 0; pos < bucket_max; ++pos) { + for (tommy_size_t pos = 0; pos < bucket_max; ++pos) { tommy_hashdyn_node* node = bucket[pos]; while (node) { @@ -202,9 +272,8 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a { tommy_size_t bucket_max = hashdyn->bucket_max; tommy_hashdyn_node** bucket = hashdyn->bucket; - tommy_size_t pos; - for (pos = 0; pos < bucket_max; ++pos) { + for (tommy_size_t pos = 0; pos < bucket_max; ++pos) { tommy_hashdyn_node* node = bucket[pos]; while (node) { @@ -215,68 +284,180 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a } } -TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn) +TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn) { return hashdyn->bucket_max * (tommy_size_t)sizeof(hashdyn->bucket[0]) + tommy_hashdyn_count(hashdyn) * (tommy_size_t)sizeof(tommy_hashdyn_node); } -/** - * \brief Transfers all elements from the dynamic hashtable into a tommy_list. - * - * Removes every element from the \p hashdyn hashtable and inserts them - * into the provided \p list (at the tail), preserving the per-bucket order - * but not guaranteeing any particular global order. - * - * After the call: - * - the hashtable is left completely empty (\c count = 0, all buckets = NULL) - * - the target list contains all the elements that were previously in the hashtable - * - * This function is useful when you need to: - * - extract all elements to process/sort them outside the hash table - * - convert the hashtable into a list for sequential iteration - * - prepare for a full clear + re-insertion with different hash/ordering - * - move ownership of the nodes to a list-based container - * - * \note The operation is O(n) where n is the number of elements. - * \note No memory allocation is performed. - * \note The relative order of elements that were in the same bucket is preserved, - * but the order among different buckets is bucket-order dependent. - * - * Typical usage pattern: - * \code - * tommy_list all_elements; - * tommy_list_init(&all_elements); - * - * // move everything out of the hashtable into the list - * tommy_hashdyn_to_list(&hashdyn, &all_elements); - * - * // now you can sort, filter, process sequentially, etc. - * tommy_list_sort(&all_elements, compare_by_value); - * \endcode - * - * \param hashdyn The dynamic hashtable to drain - * \param list The list that will receive all the elements - */ TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list) { - tommy_size_t bucket_max = hashdyn->bucket_max; - tommy_hashdyn_node** bucket = hashdyn->bucket; - tommy_size_t pos; + tommy_builder builder; + tommy_node* builder_tail = tommy_builder_init(&builder); - /* move everything to the list */ - for (pos = 0; pos < bucket_max; ++pos) { - tommy_hashdyn_node* node = bucket[pos]; - - while (node) { - tommy_node* node_next = node->next; - tommy_list_insert_tail(list, node, node->data); - node = node_next; + /* clear buckets as they are transferred to avoid a second scan */ + for (tommy_size_t pos = 0; pos < hashdyn->bucket_max; ++pos) { + tommy_node* head = hashdyn->bucket[pos]; + if (head) { + tommy_node* tail = head->prev; + builder_tail = tommy_builder_concat(builder_tail, head, tail); } + hashdyn->bucket[pos] = 0; } - /* clear all */ - memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*)); + tommy_list_concat_builder(list, &builder, builder_tail); hashdyn->count = 0; } +/** + * Compares hashes in least-significant-bit-first order, with 0 before 1. + * + * The first differing bit decides the order: first precedes second when + * second has a 1 there. Equal hashes compare false. + */ +tommy_inline tommy_bool_t tommy_imp_hashdyn_before(tommy_hash_t first, tommy_hash_t second) +{ + /* mark the positions where the hashes differ using XOR */ + tommy_hash_t diff = first ^ second; + + /* isolate the lowest differing bit using two's complement */ + tommy_hash_t bit = diff & -diff; + + /* a 1 in second means first has 0 there; equal hashes yield bit == 0 */ + return (second & bit) != 0; +} + +/** + * Gets the adjacent active bucket in least-significant-bit-first order. + * Returns bucket_max when there is no adjacent bucket. + * + * Traverses the virtual binary tree of buckets in bit-reversed order. + * + * For example, with 8 buckets the forward traversal order is: + * 0, 4, 2, 6, 1, 5, 3, 7 + * + * When resizing to 16 buckets: + * 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 + */ +tommy_inline tommy_size_t tommy_imp_hashdyn_step(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse) +{ + tommy_size_t bit = (hashdyn->bucket_mask + 1) / 2; + + /* propagate binary carry in bit-reversed order from MSB downwards */ + while (bit && ((pos & bit) != 0) != reverse) { + pos ^= bit; + bit >>= 1; + } + + /* carry wrapped around the root of the tree: end of traversal */ + if (!bit) + return hashdyn->bucket_max; + pos ^= bit; + + return pos; +} + +/** + * Finds the first or last node starting at an active bucket. + * + * Scans buckets in least-significant-bit-first order until a non-empty bucket + * is found, then returns its extreme element (minimum for forward, maximum for reverse). + */ +tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_limit(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse) +{ + while (pos != hashdyn->bucket_max) { + tommy_hashdyn_node* best = 0; + for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) { + /* equal hashes retain insertion order, so the reverse limit selects the last */ + if (reverse) { + if (!best || !tommy_imp_hashdyn_before(i->index, best->index)) + best = i; + } else { + if (!best || tommy_imp_hashdyn_before(i->index, best->index)) + best = i; + } + } + if (best) + return best; + pos = tommy_imp_hashdyn_step(hashdyn, pos, reverse); + } + return 0; +} + +/** + * Finds a neighbor using the current bucket layout and a resize-independent order. + * + * First searches for the closest neighbor within the current bucket. + * If none exists, advances to the next non-empty active bucket in LSB order. + */ +tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_neighbor(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_bool_t reverse) +{ + tommy_size_t pos = node->index & hashdyn->bucket_mask; + tommy_hashdyn_node* best = 0; + tommy_bool_t passed = 0; + + /* + * Bucket links can change during grow and shrink. Compare full hashes instead + * of following those links as iteration order, so no remaining node moves + * behind the iterator. Equal hashes always stay in one bucket in insertion order. + */ + for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) { + if (i == node) { + passed = 1; + } else if (i->index == node->index) { + /* for identical hashes, follow insertion order relative to the current node */ + if (reverse && !passed) + best = i; + else if (!reverse && passed) + return i; + } else if (reverse) { + if (tommy_imp_hashdyn_before(i->index, node->index)) { + /* find the closest neighbor within the bucket in LSB order */ + if (!best || !tommy_imp_hashdyn_before(i->index, best->index)) + best = i; + } + } else { + if (tommy_imp_hashdyn_before(node->index, i->index)) { + /* find the closest neighbor within the bucket in LSB order */ + if (!best || tommy_imp_hashdyn_before(i->index, best->index)) + best = i; + } + } + } + if (best) + return best; + + /* bucket exhausted: advance to the next non-empty bucket in LSB order */ + return tommy_imp_hashdyn_limit(hashdyn, tommy_imp_hashdyn_step(hashdyn, pos, reverse), reverse); +} + +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn) +{ + /* start forward traversal at bucket 0, which has all LSB bits at zero */ + return tommy_imp_hashdyn_limit(hashdyn, 0, 0); +} + +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn) +{ + /* start reverse traversal at the all-ones bucket, which has the highest LSB value */ + return tommy_imp_hashdyn_limit(hashdyn, hashdyn->bucket_mask, 1); +} + +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node) +{ + /* adjacent equal hashes are consecutive in insertion order */ + if (node->next && node->next->index == node->index) + return node->next; + + return tommy_imp_hashdyn_neighbor(hashdyn, node, 0); +} + +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node) +{ + /* equal hashes follow insertion order, but the bucket head's prev points to the tail */ + if (node != hashdyn->bucket[node->index & hashdyn->bucket_mask] && node->prev->index == node->index) + return node->prev; + + return tommy_imp_hashdyn_neighbor(hashdyn, node, 1); +} + diff --git a/tommyds/tommyhashdyn.h b/tommyds/tommyhashdyn.h index 9bce46be..91018fe0 100644 --- a/tommyds/tommyhashdyn.h +++ b/tommyds/tommyhashdyn.h @@ -4,18 +4,18 @@ /** \file * Dynamic chained hashtable. * - * This hashtable resizes dynamically. It starts with the minimal size of 16 buckets, it doubles - * the size when it reaches a load factor greater than 0.5 and it halves the size when the load - * factor is lower than 0.125. + * This hashtable resizes dynamically. It starts with the minimal size of 16 buckets. + * It doubles the size when it reaches a load factor greater than or equal to 0.5 + * and it halves the size when the load factor is less than or equal to 0.125. * * All the elements are reallocated in a single resize operation done inside * tommy_hashdyn_insert() or tommy_hashdyn_remove(). * - * Note that the resize operation takes approximately 100 [ms] with 1 million of elements, - * and 1 [second] with 10 millions. This could be a problem in real-time applications. + * Note that the resize operation takes approximately 100 [ms] with 1 million elements, + * and 1 [second] with 10 million elements. This could be a problem in real-time applications. * - * The resize also **fragments** the heap, as it involves allocating a double-sized table, **copying** elements, - * and deallocating the older table, **leaving** a big hole in the heap. + * The resize also fragments the heap, as it involves allocating a double-sized table, copying elements, + * and deallocating the older table, leaving a big hole in the heap. * * The ::tommy_hashlin hashtable fixes both problems. * @@ -30,36 +30,36 @@ * To insert elements in the hashtable you have to call tommy_hashdyn_insert() for * each element. * In the insertion call you have to specify the address of the node, the - * address of the object, and the hash value of the key to use. - * The address of the object is used to initialize the tommy_node::data field - * of the node, and the hash to initialize the tommy_node::key field. + * address of the element, and the hash value of the key to use. + * The address of the element is used to initialize the tommy_node::data field + * of the node, and the hash to initialize the tommy_node::index field. * * \code - * struct object { + * struct element { * int value; * // other fields * tommy_node node; * }; * - * struct object* obj = malloc(sizeof(struct object)); // creates the object + * struct element* elem = malloc(sizeof(struct element)); // creates the element * - * obj->value = ...; // initializes the object + * elem->value = ...; // initializes the element * - * tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object + * tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value)); // inserts the element * \endcode * - * To find an element in the hashtable you have to call tommy_hashtable_search() + * To find an element in the hashtable you have to call tommy_hashdyn_search() * providing a comparison function, its argument, and the hash of the key to search. * * \code - * int compare(const void* arg, const void* obj) + * int compare(const void* arg, const void* elem) * { - * return *(const int*)arg != ((const struct object*)obj)->value; + * return *(const int*)arg != ((const struct element*)elem)->value; * } * * int value_to_find = 1; - * struct object* obj = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find)); - * if (!obj) { + * struct element* elem = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!elem) { * // not found * } else { * // found @@ -67,7 +67,7 @@ * \endcode * * To iterate over all the elements in the hashtable with the same key, you have to - * use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until NULL. + * use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until 0. * You have also to check explicitly for the key, as the bucket may contain * different keys. * @@ -75,10 +75,10 @@ * int value_to_find = 1; * tommy_node* i = tommy_hashdyn_bucket(&hashdyn, tommy_inthash_u32(value_to_find)); * while (i) { - * struct object* obj = i->data; // gets the object pointer + * struct element* elem = i->data; // gets the element pointer * - * if (obj->value == value_to_find) { - * printf("%d\n", obj->value); // process the object + * if (elem->value == value_to_find) { + * printf("%d\n", elem->value); // process the element * } * * i = i->next; // goes to the next element @@ -90,9 +90,9 @@ * and remove. * * \code - * struct object* obj = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove)); - * if (obj) { - * free(obj); // frees the object allocated memory + * struct element* elem = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove)); + * if (elem) { + * free(elem); // frees the element allocated memory * } * \endcode * @@ -105,15 +105,60 @@ * * If you need to iterate over all the elements in the hashtable, you can use * tommy_hashdyn_foreach() or tommy_hashdyn_foreach_arg(). - * If you need a more precise control with a real iteration, you have to insert - * all the elements also in a ::tommy_list, and use the list to iterate. - * See the \ref multiindex example for more detail. + * For traversal that permits insertions and removals, use tommy_hashdyn_head() + * and tommy_hashdyn_next(), or tommy_hashdyn_tail() and tommy_hashdyn_prev(). + * Hashes are compared from the least significant bit to the most significant + * bit, with 0 before 1. Equal hashes are visited in insertion order. + * This order is independent of the number of buckets and progressive resizing. + * It differs from the bucket order used by foreach and to_list. + * + * \anchor tommy_hashdyn_iterator_validity + * Iterators are pointers to nodes currently contained in the hashtable. + * Inserting elements or removing other nodes does not invalidate them or + * change the relative iteration order of the remaining elements. + * A forward or backward traversal visits every original element that remains + * contained, even if removals cause the table to shrink or insertions grow it. + * New elements can be visited if inserted after the current position in the + * traversal direction; elements inserted before it need not be visited. + * + * Removing the pointed node invalidates its use as an iterator, even if its + * storage has not been freed. Obtain the next or previous node before removal, + * and keep that saved node contained until continuing the traversal. + * + * \code + * tommy_hashdyn_node* i = tommy_hashdyn_head(&hashdyn); + * while (i) { + * tommy_hashdyn_node* next = tommy_hashdyn_next(&hashdyn, i); + * if (should_remove(i->data)) { + * free(tommy_hashdyn_remove_existing(&hashdyn, i)); + * } + * i = next; + * } + * \endcode + * + * Changing a hash with tommy_hashdyn_rehash_existing() can change iteration + * order, so the traversal guarantee does not cover rehashing during traversal. + * After swapping containers, use the container that now contains the node. + * Clearing or deinitializing the container invalidates all its iterators. + * Concurrent traversal and modification require external synchronization. + * + * Iterator calls scan bucket contents without allocating or modifying memory. + * With a good hash function and normal dynamic resizing, a full traversal + * runs in expected O(n) time. Under the same hash distribution assumption, + * sparse tables require expected O(n + b) time, where n is the number of + * elements and b is the number of buckets. Such tables can arise after + * tommy_hashdyn_reserve(), tommy_hashdyn_clear(), or tommy_hashdyn_to_list(). + * In pathological cases with severe hash collisions, traversal can take + * O(n^2 + b) time. + * Use foreach for guaranteed O(n + b) traversal when no insertions or removals + * are needed. */ -#ifndef __TOMMYHASHDYN_H -#define __TOMMYHASHDYN_H +#ifndef TOMMY_HASHDYN_H +#define TOMMY_HASHDYN_H #include "tommyhash.h" +#include "tommylist.h" /******************************************************************************/ /* hashdyn */ @@ -126,7 +171,7 @@ /** * Hashtable node. - * This is the node that you have to include inside your objects. + * This is the node that you have to include inside your elements. */ typedef tommy_node tommy_hashdyn_node; @@ -144,6 +189,7 @@ typedef struct tommy_hashdyn_struct { /** * Initializes the hashtable. + * \param hashdyn The hashtable. */ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn); @@ -152,30 +198,99 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn); * * You can call this function with elements still contained, * but such elements are not going to be freed by this call. + * \param hashdyn The hashtable. */ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn); +/** + * Exchanges the complete state of two initialized hashtables, including bucket allocations. + * The hashtables must not share nodes. Nodes and elements are not accessed or modified. + * Existing node pointers remain valid and belong to the other hashtable. + * Passing the same hashtable twice has no effect. Both hashtables remain usable and deinitializable. + * \param first The first hashtable. + * \param second The second hashtable. + * \note This operation is O(1) with respect to the number of elements. + */ +tommy_inline void tommy_hashdyn_swap(tommy_hashdyn* first, tommy_hashdyn* second) +{ + tommy_hashdyn tmp = *first; + *first = *second; + *second = tmp; +} + +/** + * Removes all elements, preserving the allocated buckets. + * Call tommy_hashdyn_shrink() after clearing to restore the initial capacity. + * The hashtable remains initialized and can be reused immediately. + * Elements are not freed and nodes are not accessed or modified. + * Their links must not be used to traverse the previous contents. + * You can call this function after tommy_hashdyn_foreach() has freed the elements. + * Subsequent insertions and removals retain the normal resizing policy. + * \param hashdyn The hashtable. + * \note This operation is O(b), where b is the number of buckets. + */ +TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn); + /** * Pre-allocates buckets for the specified number of elements. * * It ensures that the hashtable has enough buckets allocated to contain - * the specified number of elements without triggering a dynamic resize. + * the specified number of elements without triggering a dynamic resize on insertion. + * Subsequent removals retain the normal shrinking policy and may reduce + * the allocated capacity. + * \param hashdyn The hashtable. * \param count Number of elements to reserve space for. */ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count); +/** + * Shrinks the allocated buckets to fit the current number of elements. + * The target is the smallest power of 2, at least 16, for which + * the number of elements is strictly less than half the number of buckets. + * If the current allocation is already no larger than the target, nothing is done. + * An empty hashtable is reduced to 16 buckets. + * Elements are not freed, and the tommy_node::data and tommy_node::index fields + * are left unchanged. The order of elements with the same hash is preserved. + * \param hashdyn The hashtable. + * \note This operation is O(b), where b is the previous number of buckets. + */ +TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn); + /** * Inserts an element in the hashtable. + * \param hashdyn The hashtable. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. + * \param hash Hash of the element to insert. */ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash); +/** + * Inserts an element only if no equal element is already contained. + * If found, the first equal element is returned, + * and the hashtable and candidate node are left unchanged. + * Otherwise, the candidate is inserted using the normal insertion policy, + * and data is returned. + * Elements are not freed by this call. + * \param hashdyn The hashtable. + * \param node The candidate node. It must not belong to any container. + * \param data Pointer to the element to insert. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, non-zero for different elements. + * \param cmp_arg Compare argument describing the candidate key. + * \param hash Hash of the candidate key, consistent with the comparison function. + * \return The first equal element, or data if the candidate was inserted. + */ +TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash); + /** * Searches and removes an element from the hashtable. * You have to provide a compare function and the hash of the element you want to remove. * If the element is not found, 0 is returned. * If more equal elements are present, the first one is removed. + * \param hashdyn The hashtable. * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. - * The function should return 0 for equal elements, anything other for different elements. + * The function should return 0 for equal elements, non-zero for different elements. * \param cmp_arg Compare argument passed as first argument of the compare function. * \param hash Hash of the element to find and remove. * \return The removed element, or 0 if not found. @@ -185,8 +300,9 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func* /** * Gets the bucket of the specified hash. * The bucket is guaranteed to contain ALL the elements with the specified hash, - * but it can contain also others. - * You can access elements in the bucket following the ::next pointer until 0. + * but it can also contain others. + * You can access elements in the bucket following the tommy_node::next pointer until 0. + * \param hashdyn The hashtable. * \param hash Hash of the element to find. * \return The head of the bucket, or 0 if empty. */ @@ -196,35 +312,123 @@ tommy_inline tommy_hashdyn_node* tommy_hashdyn_bucket(tommy_hashdyn* hashdyn, to } /** - * Searches an element in the hashtable. + * Finds a node in the hashtable. * You have to provide a compare function and the hash of the element you want to find. * If more equal elements are present, the first one is returned. + * \param hashdyn The hashtable. * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. - * The function should return 0 for equal elements, anything other for different elements. + * The function should return 0 for equal elements, non-zero for different elements. * \param cmp_arg Compare argument passed as first argument of the compare function. * \param hash Hash of the element to find. - * \return The first element found, or 0 if none. + * \return The first node found, or 0 if none. */ -tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +tommy_inline tommy_hashdyn_node* tommy_hashdyn_find(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) { tommy_hashdyn_node* i = tommy_hashdyn_bucket(hashdyn, hash); while (i) { /* we first check if the hash matches, as in the same bucket we may have multiple hash values */ if (i->index == hash && cmp(cmp_arg, i->data) == 0) - return i->data; + return i; i = i->next; } return 0; } +/** + * Searches an element in the hashtable. + * You have to provide a compare function and the hash of the element you want to find. + * If more equal elements are present, the first one is returned. + * \param hashdyn The hashtable. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, non-zero for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashdyn_node* node = tommy_hashdyn_find(hashdyn, cmp, cmp_arg, hash); + + return node ? node->data : 0; +} + /** * Removes an element from the hashtable. - * You must already have the address of the element to remove. - * \return The tommy_node::data field of the node removed. + * You must already have the address of the node to remove. + * \param hashdyn The hashtable. + * \param node The node to remove. + * \return The removed element. */ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node); +/** + * Gets the first node in iteration order, or 0 if empty. + * See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity. + * \param hashdyn The hashtable. + * \return The first node in iteration order, or 0 if empty. + * \note This operation is expected O(1) under suitable hash distribution and + * normal dynamic resizing. In the worst case, scanning bucket contents and + * empty buckets takes O(n + b), where n is the number of elements and b is + * the number of buckets. + */ +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn); + +/** + * Gets the last node in iteration order, or 0 if empty. + * See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity. + * \param hashdyn The hashtable. + * \return The last node in iteration order, or 0 if empty. + * \note This operation is expected O(1) under suitable hash distribution and + * normal dynamic resizing. In the worst case, scanning bucket contents and + * empty buckets takes O(n + b), where n is the number of elements and b is + * the number of buckets. + */ +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn); + +/** + * Gets the following node in iteration order, or 0 for the tail. + * See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity. + * \param hashdyn The hashtable. + * \param node Node currently contained in the hashtable. It must not be 0. + * \return The following node in iteration order, or 0 for the tail. + * \note This operation is expected O(1) under suitable hash distribution and + * normal dynamic resizing. In the worst case, scanning bucket contents and + * empty buckets takes O(n + b), where n is the number of elements and b is + * the number of buckets. + */ +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node); + +/** + * Gets the preceding node in iteration order, or 0 for the head. + * See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity. + * \param hashdyn The hashtable. + * \param node Node currently contained in the hashtable. It must not be 0. + * \return The preceding node in iteration order, or 0 for the head. + * \note This operation is expected O(1) under suitable hash distribution and + * normal dynamic resizing. In the worst case, scanning bucket contents and + * empty buckets takes O(n + b), where n is the number of elements and b is + * the number of buckets. + */ +TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node); + +/** + * Updates the hash of an element already contained in the hashtable. + * The node must belong to this hashtable. The caller updates the element key + * and provides its new hash, without modifying tommy_node::index directly. + * If the hash is unchanged, the node and its position are left unchanged. + * Otherwise, the node is moved to the tail of the destination bucket, + * even if the old and new hashes identify the same bucket. + * The tommy_node::data field and the number of elements are left unchanged. + * No memory allocation, deallocation or resize is performed. + * Equal keys are allowed; no uniqueness check is performed. + * \param hashdyn The hashtable. + * \param node The node whose hash is updated. + * \param hash The new hash of the element. + * \note This operation is O(1). + */ +TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash); + /** * Calls the specified function for each element in the hashtable. * @@ -239,66 +443,102 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash * * ... * - * // creates an object - * struct object* obj = malloc(sizeof(struct object)); + * // creates an element + * struct element* elem = malloc(sizeof(struct element)); * * ... * * // insert it in the hashtable - * tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); + * tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value)); * * ... * - * // deallocates all the objects iterating the hashtable + * // deallocates all the elements iterating the hashtable * tommy_hashdyn_foreach(&hashdyn, free); * * // deallocates the hashtable * tommy_hashdyn_done(&hashdyn); * \endcode + * + * If the callback frees the elements, the hashtable retains dangling + * node pointers. Call tommy_hashdyn_clear() before reusing it, or + * tommy_hashdyn_done() to release its storage. + * \param hashdyn The hashtable. + * \param func Function to call with each element. */ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func); /** * Calls the specified function with an argument for each element in the hashtable. + * The iteration order and callback rules are the same as tommy_hashdyn_foreach(). + * The callback may deallocate the current element. + * Adding or removing elements from inside the callback is not allowed. + * \param hashdyn The hashtable. + * \param func Function to call with each element. + * \param arg Argument to pass to the function. */ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg); /** * Gets the number of elements. + * \param hashdyn The hashtable. + * \return The number of elements in the hashtable. + * \note This operation is O(1). */ -tommy_inline tommy_size_t tommy_hashdyn_count(tommy_hashdyn* hashdyn) +tommy_inline tommy_size_t tommy_hashdyn_count(const tommy_hashdyn* hashdyn) { return hashdyn->count; } /** - * Gets the size of allocated memory. - * It includes the size of the ::tommy_hashdyn_node of the stored elements. + * Checks whether the hashtable is empty. + * \param hashdyn The hashtable. + * \return Nonzero if the hashtable is empty, 0 otherwise. + * \note This operation is O(1). */ -TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn); +tommy_inline tommy_bool_t tommy_hashdyn_empty(const tommy_hashdyn* hashdyn) +{ + return hashdyn->count == 0; +} /** - * \brief Transfers all elements from the hashtable into a tommy_list. + * Gets the number of buckets. + * \param hashdyn The hashtable. + * \return The number of buckets. + * \note This operation is O(1). + */ +tommy_inline tommy_size_t tommy_hashdyn_bucket_count(const tommy_hashdyn* hashdyn) +{ + return hashdyn->bucket_max; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_hashdyn_node of the stored elements. + * \param hashdyn The hashtable. + * \return Total memory allocated by the hashtable, in bytes. + */ +TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn); + +/** + * Transfers all elements from the hashtable into a tommy_list. * * Removes every element from the \p hashdyn hashtable and inserts them * into the provided \p list (at the tail), preserving the per-bucket order * but not guaranteeing any particular global order. * * After the call: - * - the hashtable is left completely empty + * - the hashtable is left empty and initialized, preserving its allocated buckets * - the target list contains all the elements that were previously in the hashtable * + * The tommy_node::data and tommy_node::index fields are left unchanged. + * * This function is useful when you need to: - * - extract all elements to process/sort them outside the hash table + * - extract all elements to process/sort them outside the hashtable * - convert the hashtable into a list for sequential iteration * - prepare for a full clear + re-insertion with different hash/ordering * - move ownership of the nodes to a list-based container * - * \note The operation is O(n) where n is the number of elements. - * \note No memory allocation is performed. - * \note The relative order of elements that were in the same bucket is preserved, - * but the order among different buckets is bucket-order dependent. - * * Typical usage pattern: * \code * tommy_list all_elements; @@ -311,8 +551,13 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn); * tommy_list_sort(&all_elements, compare_by_value); * \endcode * - * \param hashdyn The dynamic hashtable to drain - * \param list The list that will receive all the elements + * \param hashdyn The hashtable to drain. + * \param list The destination list. It must be initialized and must not share + * nodes with the hashtable. Existing elements remain at the head of the list. + * \note This operation is O(b), where b is the number of buckets. + * \note No memory allocation or deallocation is performed. + * \note The relative order of elements that were in the same bucket is preserved, + * but the order among different buckets is bucket-order dependent. */ TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list); diff --git a/tommyds/tommylist.c b/tommyds/tommylist.c index 9f7b2102..a1d4d8ef 100644 --- a/tommyds/tommylist.c +++ b/tommyds/tommylist.c @@ -7,30 +7,109 @@ /** \internal * Setup a list. */ -tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node* tail) +tommy_inline void tommy_imp_list_set(tommy_list* list, tommy_node* head, tommy_node* tail) { head->prev = tail; tail->next = 0; *list = head; } +TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second) +{ + tommy_node* second_head = tommy_list_head(second); + + if (!second_head) + return; + + if (reference == tommy_list_head(list)) { + /* prepend */ + tommy_list_prepend(list, second); + } else { + /* splice in the middle */ + tommy_chain_splice(reference->prev, reference, second_head, second_head->prev); + } +} + +TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second) +{ + tommy_node* second_head = tommy_list_head(second); + + if (!second_head) + return; + + if (!reference->next) { + /* append */ + tommy_list_concat(list, second); + } else { + /* splice in the middle */ + tommy_chain_splice(reference, reference->next, second_head, second_head->prev); + } +} + +TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp) +{ + tommy_node* second_head = tommy_list_head(second); + + if (!second_head) + return; + + tommy_node* first_head = tommy_list_head(first); + + if (!first_head) { + *first = *second; + return; + } + + /* create chains from the non-empty lists */ + tommy_chain first_chain; + tommy_chain second_chain; + first_chain.head = first_head; + first_chain.tail = first_head->prev; + second_chain.head = second_head; + second_chain.tail = second_head->prev; + + tommy_chain_merge_degenerated(&first_chain, &second_chain, cmp); + + /* restore the destination list */ + tommy_imp_list_set(first, first_chain.head, first_chain.tail); +} + +TOMMY_API void tommy_list_reverse(tommy_list* list) +{ + tommy_node* head = tommy_list_head(list); + tommy_node* node = head; + tommy_node* prev = 0; + + if (!head) + return; + + while (node) { + tommy_node* next = node->next; + node->next = prev; + node->prev = next; + prev = node; + node = next; + } + + /* the old head is the new tail */ + tommy_imp_list_set(list, prev, head); +} + TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp) { - tommy_chain chain; - tommy_node* head; - if (tommy_list_empty(list)) return; - head = tommy_list_head(list); + tommy_node* head = tommy_list_head(list); /* create a chain from the list */ + tommy_chain chain; chain.head = head; chain.tail = head->prev; tommy_chain_mergesort(&chain, cmp); /* restore the list */ - tommy_list_set(list, chain.head, chain.tail); + tommy_imp_list_set(list, chain.head, chain.tail); } diff --git a/tommyds/tommylist.h b/tommyds/tommylist.h index 88423358..4de562fd 100644 --- a/tommyds/tommylist.h +++ b/tommyds/tommylist.h @@ -2,21 +2,21 @@ // Copyright (C) 2010 Andrea Mazzoleni /** \file - * Double linked list for collisions into hashtables. + * Doubly linked list for collisions into hashtables. * - * This list is a **doubly** linked list mainly targeted for handling collisions - * into an hashtable, but **usable** also as a generic list. + * This list is a doubly linked list mainly targeted for handling collisions + * into a hashtable, but usable also as a generic list. * * The main feature of this list is to require only one pointer to represent the - * list, compared to a classic implementation requiring a head **and** a tail pointers. + * list, compared to a classic implementation requiring both head and tail pointers. * This reduces the memory usage in hashtables. * - * Another feature is to support the insertion at the end of the list. This **allows** to store - * collisions in a stable order. Where for stable order we mean that equal elements keep + * Another feature is to support the insertion at the end of the list. This allows + * storing collisions in a stable order, meaning that equal elements preserve * their insertion order. * * To initialize the list, you have to call tommy_list_init(), or to simply assign - * to it NULL, as an empty list is represented by the NULL value. + * to it 0, as an empty list is represented by the 0 value. * * \code * tommy_list list; @@ -27,51 +27,113 @@ * To insert elements in the list you have to call tommy_list_insert_tail() * or tommy_list_insert_head() for each element. * In the insertion call you have to specify the address of the node and the - * address of the object. - * The address of the object is used to initialize the tommy_node::data field + * address of the element. + * The address of the element is used to initialize the tommy_node::data field * of the node. * * \code - * struct object { + * struct element { * int value; * // other fields * tommy_node node; * }; * - * struct object* obj = malloc(sizeof(struct object)); // creates the object + * struct element* elem = malloc(sizeof(struct element)); // creates the element * - * obj->value = ...; // initializes the object + * elem->value = ...; // initializes the element * - * tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object + * tommy_list_insert_tail(&list, &elem->node, elem); // inserts the element * \endcode * * To iterate over all the elements in the list you have to call * tommy_list_head() to get the head of the list and follow the - * tommy_node::next pointer until NULL. + * tommy_node::next pointer until 0. * * \code * tommy_node* i = tommy_list_head(&list); * while (i) { - * struct object* obj = i->data; // gets the object pointer + * struct element* elem = i->data; // gets the element pointer * - * printf("%d\n", obj->value); // process the object + * printf("%d\n", elem->value); // process the element * * i = i->next; // go to the next element * } * \endcode * + * To iterate backward, start at tommy_list_tail() and use tommy_list_prev(). + * Do not follow tommy_node::prev directly past the head, as it points to the tail. + * + * \code + * tommy_node* i = tommy_list_tail(&list); + * while (i) { + * struct element* elem = i->data; + * + * printf("%d\n", elem->value); + * + * i = tommy_list_prev(&list, i); + * } + * \endcode + * + * \anchor tommy_list_iterator_validity + * Iterators are pointers to nodes contained in the list. + * Inserting elements or removing other nodes between iterator calls does not + * invalidate pointers to nodes that remain in the list. + * Subsequent next/prev calls use the current contents and ordering; + * inserted elements may become visible and removed elements are skipped. + * + * Removing the pointed node invalidates its use as an iterator, even if + * its storage has not been freed. To continue traversal after removing + * the current node, obtain the next or previous node before removal. + * + * \code + * tommy_node* i = tommy_list_head(&list); + * while (i) { + * tommy_node* next = i->next; // obtain the next node before removal + * + * if (condition) { + * tommy_list_remove_existing(&list, i); + * free(i->data); // safe to deallocate + * } + * + * i = next; + * } + * \endcode + * + * Concurrent traversal and modification require external synchronization. + * + * To reorder existing elements, use tommy_list_move_head(), tommy_list_move_tail() + * or tommy_list_reverse(). To split a list or transfer elements between lists, + * use tommy_list_split(), tommy_list_prepend(), tommy_list_splice_before() + * and tommy_list_splice_after(). + * These operations preserve the element pointers and do not allocate memory. + * + * \code + * tommy_list second; + * tommy_list_init(&second); + * + * // transfers node and all following nodes to second + * tommy_list_split(&list, node, &second); + * + * // appends the transferred nodes back to list + * tommy_list_concat(&list, &second); + * \endcode + * + * Two lists sorted with the same comparison function can be merged with + * tommy_list_merge(). The merge is stable. + * * To destroy the list you have to remove all the elements, - * as the list is completely inplace and it doesn't allocate memory. + * as the list is completely in-place and it doesn't allocate memory. * This can be done with the tommy_list_foreach() function. * * \code - * // deallocates all the objects iterating the list + * // deallocates all the elements iterating the list * tommy_list_foreach(&list, free); + * tommy_list_clear(&list); // resets the list before reuse * \endcode */ -#ifndef __TOMMYLIST_H -#define __TOMMYLIST_H +#ifndef TOMMY_LIST_H +#define TOMMY_LIST_H #include "tommytypes.h" @@ -83,18 +145,72 @@ */ typedef tommy_node* tommy_list; +/** + * Temporary list builder with an embedded sentinel. + * Initialize with tommy_builder_init() and finish with tommy_list_concat_builder(). + * Keep the builder at the same address until finalization, as node links + * refer to its sentinel. + */ +typedef struct tommy_builder_struct { + tommy_node dummy; /**< Sentinel excluded from the resulting list. */ +} tommy_builder; + +/** + * Initializes an empty builder in O(1). + * \param builder Builder to initialize in its final location. + * \return Initial tail, pointing to the builder's sentinel. + */ +tommy_inline tommy_node* tommy_builder_init(tommy_builder* builder) +{ + builder->dummy.next = 0; + return &builder->dummy; +} + +/** + * Appends a non-empty chain to a builder in O(1), preserving element order. + * \param tail Current tail, initially returned by tommy_builder_init(). + * \param head Head of the non-empty source chain. + * \param last Tail of the source chain. For a single node, pass head again. + * The chains must not share nodes. Inner links must be valid; + * head's prev and last's next may be undefined. + * \return Updated tail to pass to the next append or tommy_list_concat_builder(). + */ +tommy_inline tommy_node* tommy_builder_concat(tommy_node* tail, tommy_node* head, tommy_node* last) +{ + head->prev = tail; + tail->next = head; + return last; +} + /** * Initializes the list. - * The list is completely inplace, so it doesn't need to be deinitialized. + * The list is completely in-place, so it doesn't need to be deinitialized. + * \param list The list. */ tommy_inline void tommy_list_init(tommy_list* list) { *list = 0; } +/** + * Removes all elements. + * The list remains initialized and can be reused immediately. + * Elements are not freed and nodes are not accessed or modified. + * Their links must not be used to traverse the previous contents. + * You can call this function after tommy_list_foreach() has freed the elements. + * \param list The list. + * \note This operation is O(1). + */ +tommy_inline void tommy_list_clear(tommy_list* list) +{ + *list = 0; +} + /** * Gets the head of the list. - * \return The head node. For empty lists 0 is returned. + * \param list The list. + * \return The head node, or 0 if the list is empty. + * \note This operation is O(1). */ tommy_inline tommy_node* tommy_list_head(tommy_list* list) { @@ -103,7 +219,9 @@ tommy_inline tommy_node* tommy_list_head(tommy_list* list) /** * Gets the tail of the list. - * \return The tail node. For empty lists 0 is returned. + * \param list The list. + * \return The tail node, or 0 if the list is empty. + * \note This operation is O(1). */ tommy_inline tommy_node* tommy_list_tail(tommy_list* list) { @@ -115,9 +233,27 @@ tommy_inline tommy_node* tommy_list_tail(tommy_list* list) return head->prev; } +/** + * Gets the previous node in the list. + * Unlike tommy_node::prev, this function returns 0 at the head of the list, + * allowing backward iteration starting from tommy_list_tail(). + * See \ref tommy_list_iterator_validity for iterator validity across insertions and removals. + * \param list The list. + * \param node The current node. The node must be in the list. + * \return The previous node, or 0 if the current node is the head. + * \note This operation is O(1). + */ +tommy_inline tommy_node* tommy_list_prev(tommy_list* list, tommy_node* node) +{ + if (node == tommy_list_head(list)) + return 0; + + return node->prev; +} + /** \internal * Creates a new list with a single element. - * \param list The list to initialize. + * \param list The list. * \param node The node to insert. */ tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node) @@ -134,7 +270,7 @@ tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node) /** \internal * Inserts an element at the head of a non-empty list. * The element is inserted at the head of the list. The list cannot be empty. - * \param list The list. The list cannot be empty. + * \param list The list. * \param node The node to insert. */ tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node) @@ -171,8 +307,8 @@ tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node* /** * Inserts an element at the head of a list. * \param list The list. - * \param node The node to insert. - * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. */ tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data) { @@ -189,8 +325,8 @@ tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, voi /** * Inserts an element at the tail of a list. * \param list The list. - * \param node The node to insert. - * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. */ tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data) { @@ -208,8 +344,8 @@ tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, voi * Inserts an element before the specified reference node. * \param list The list. * \param reference The reference node. The new node will be inserted before this node. The reference node must be in the list. - * \param node The node to insert. - * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. */ tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* reference, tommy_node* node, void* data) { @@ -235,8 +371,8 @@ tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* referen * Inserts an element after the specified reference node. * \param list The list. * \param reference The reference node. The new node will be inserted after this node. The reference node must be in the list. - * \param node The node to insert. - * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. */ tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* reference, tommy_node* node, void* data) { @@ -260,12 +396,12 @@ tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* referenc /** * Removes an element from the list. - * You must already have the address of the element to remove. - * \note The node content is left unchanged, including the tommy_node::next - * and tommy_node::prev fields that still contain pointers **in** the list. + * You must already have the address of the node to remove. * \param list The list. - * \param node The node to remove. The node must be in the list. - * \return The tommy_node::data field of the node removed. + * \param node The node to remove. + * \return The removed element. + * \note The node content is left unchanged, including the tommy_node::next + * and tommy_node::prev fields that still contain pointers in the list. */ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node) { @@ -287,32 +423,167 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node } /** - * Concats two lists. - * The second list is concatenated at the first list. + * Removes the element at the head of the list. + * \param list The list. + * \return The removed element, or 0 if the list is empty. + */ +tommy_inline void* tommy_list_remove_head(tommy_list* list) +{ + tommy_node* node = tommy_list_head(list); + + if (!node) + return 0; + + /* the new head must retain the tail pointer in the "circular" prev list */ + if (node->next) + node->next->prev = node->prev; + + *list = node->next; + + return node->data; +} + +/** + * Removes the element at the tail of the list. + * \param list The list. + * \return The removed element, or 0 if the list is empty. + */ +tommy_inline void* tommy_list_remove_tail(tommy_list* list) +{ + tommy_node* head = tommy_list_head(list); + + if (!head) + return 0; + + tommy_node* node = head->prev; + + /* a single node is also the head, and its links must remain unchanged */ + if (node == head) { + *list = 0; + } else { + head->prev = node->prev; + node->prev->next = 0; + } + + return node->data; +} + +/** + * Moves an existing node to the head of the list. + * If the node is already at the head, nothing is done. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The list. + * \param node The node to move. The node must be in the list. + */ +tommy_inline void tommy_list_move_head(tommy_list* list, tommy_node* node) +{ + if (node == tommy_list_head(list)) + return; + + tommy_list_remove_existing(list, node); + tommy_list_insert_head_not_empty(list, node); +} + +/** + * Moves an existing node to the tail of the list. + * If the node is already at the tail, nothing is done. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The list. + * \param node The node to move. The node must be in the list. + */ +tommy_inline void tommy_list_move_tail(tommy_list* list, tommy_node* node) +{ + if (node->next == 0) + return; + + tommy_list_remove_existing(list, node); + tommy_list_insert_tail_not_empty(tommy_list_head(list), node); +} + +/** + * Splits a list before the specified node. + * The list retains the nodes preceding the specified node. The second list + * receives the specified node and all following nodes. The order of the elements is preserved. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The list to split. + * \param node The first node to transfer. The node must be in the list, + * or 0 to leave list unchanged and set second to 0. + * Specifying the head transfers the whole list. + * \param second Output list receiving node and all following nodes. + * It must be distinct from list and need not be initialized. + * Its previous value is overwritten. Any previously referenced nodes + * are left unchanged and are not deallocated. + * \note This operation is O(1). + */ +tommy_inline void tommy_list_split(tommy_list* list, tommy_node* node, tommy_list* second) +{ + tommy_node* head = tommy_list_head(list); + tommy_node* tail; + tommy_node* before; + + if (!node) { + *second = 0; + return; + } + + if (node == head) { + *second = *list; + *list = 0; + return; + } + + tail = head->prev; + before = node->prev; + + /* terminate the retained list */ + head->prev = before; + before->next = 0; + + /* create the list of transferred nodes */ + node->prev = tail; + *second = node; +} + +/** + * Exchanges the contents of two lists. + * The lists must not share nodes. Nodes and elements are not accessed or modified. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * Passing the same list twice has no effect. + * \param first The first list. + * \param second The second list. + * \note This operation is O(1). + */ +tommy_inline void tommy_list_swap(tommy_list* first, tommy_list* second) +{ + tommy_list tmp = *first; + *first = *second; + *second = tmp; +} + +/** + * Concatenates two lists. + * The second list is concatenated to the first list. * \param first The first list. * \param second The second list. After this call the list content is undefined, * and you should not use it anymore. + * \note This operation is O(1). */ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second) { - tommy_node* first_head; - tommy_node* first_tail; - tommy_node* second_head; - /* if the second is empty, nothing to do */ - second_head = tommy_list_head(second); + tommy_node* second_head = tommy_list_head(second); if (second_head == 0) return; /* if the first is empty, copy the second */ - first_head = tommy_list_head(first); + tommy_node* first_head = tommy_list_head(first); if (first_head == 0) { *first = *second; return; } /* tail of the first list */ - first_tail = first_head->prev; + tommy_node* first_tail = first_head->prev; /* set the "circular" prev list */ first_head->prev = second_head->prev; @@ -322,32 +593,136 @@ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second) first_tail->next = second_head; } +/** + * Appends a builder's chain to a list, preserving element order and restoring list links. + * \param list Destination list, possibly empty. It must not share nodes with the builder. + * \param builder Initialized builder, possibly empty. Its contents are consumed; + * reinitialize it before reuse. + * \param tail Current tail returned by the builder helpers, ignored when empty. + * \note This operation is O(1). An empty builder has no effect. + */ +tommy_inline void tommy_list_concat_builder(tommy_list* list, const tommy_builder* builder, tommy_node* tail) +{ + tommy_node* head = builder->dummy.next; + + if (!head) + return; + + tommy_node* first = *list; + + /* replace the sentinel link so no list node refers to the temporary builder */ + if (first) { + tommy_node* first_tail = first->prev; + first_tail->next = head; + head->prev = first_tail; + first->prev = tail; + } else { + *list = head; + head->prev = tail; + } + + tail->next = 0; +} + +/** + * Prepends a list to another list. + * The second list is prepended to the first list. + * \param first The first list. + * \param second The second list. After this call the list content is undefined, + * and you should not use it anymore. + * \note This operation is O(1). + */ +tommy_inline void tommy_list_prepend(tommy_list* first, tommy_list* second) +{ + tommy_list_concat(second, first); + *first = *second; +} + +/** + * Transfers all elements of a list before the specified reference node. + * The order of the transferred elements is preserved. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The destination list. + * \param reference The node before which to insert. It must not be 0 and must be in the destination list. + * \param second The source list. After this call the list content is undefined, + * and you should not use it anymore. + * The lists must be distinct and must not share nodes. + * \note This operation is O(1). An empty source list has no effect. + */ +TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second); + +/** + * Transfers all elements of a list after the specified reference node. + * The order of the transferred elements is preserved. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The destination list. + * \param reference The node after which to insert. It must not be 0 and must be in the destination list. + * \param second The source list. After this call the list content is undefined, + * and you should not use it anymore. + * The lists must be distinct and must not share nodes. + * \note This operation is O(1). An empty source list has no effect. + */ +TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second); + +/** + * Merges two sorted lists. + * Both lists must be sorted using the same comparison function. + * The merge is stable: equal elements from the first list precede those from + * the second list, and the relative order within each list is preserved. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param first The destination list, receiving the sorted result. + * \param second The source list. After this call the list content is undefined, + * and you should not use it anymore. + * The lists must be distinct and must not share nodes. + * \param cmp Compare function called with two elements. + * The function should return <0 if the first element is less than the second, + * ==0 if equal, and >0 if greater. It must not modify the lists. + * \note This operation is O(n+m), or O(1) when one list is empty or the ranges + * can be concatenated directly. It uses O(1) auxiliary space. + */ +TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp); + +/** + * Reverses the order of the elements in a list. + * Empty lists and lists containing one element are left unchanged. + * The tommy_node::data and tommy_node::index fields are left unchanged. + * \param list The list. + * \note This operation is O(n) and uses O(1) auxiliary space. + */ +TOMMY_API void tommy_list_reverse(tommy_list* list); + /** * Sorts a list. - * It's a stable merge sort with O(N*log(N)) worst complexity. - * It's faster on degenerated cases like partially ordered lists. + * It's a stable merge sort with O(n log n) worst complexity. + * It's faster on degenerate cases like partially ordered lists. + * \param list The list. * \param cmp Compare function called with two elements. * The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greater. + * \note This operation is O(n log n) in the worst case. */ TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp); /** - * Checks if empty. - * \return If the list is empty. + * Checks whether the list is empty. + * \param list The list. + * \return Nonzero if the list is empty, 0 otherwise. + * \note This operation is O(1). */ -tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list) +tommy_inline tommy_bool_t tommy_list_empty(const tommy_list* list) { - return tommy_list_head(list) == 0; + return *list == 0; } /** * Gets the number of elements. + * \param list The list. + * \return The number of elements in the list. * \note This operation is O(n). */ -tommy_inline tommy_size_t tommy_list_count(tommy_list* list) +tommy_inline tommy_size_t tommy_list_count(const tommy_list* list) { tommy_size_t count = 0; - tommy_node* i = tommy_list_head(list); + const tommy_node* i = *list; while (i) { ++count; @@ -371,19 +746,26 @@ tommy_inline tommy_size_t tommy_list_count(tommy_list* list) * * ... * - * // creates an object - * struct object* obj = malloc(sizeof(struct object)); + * // creates an element + * struct element* elem = malloc(sizeof(struct element)); * * ... * * // insert it in the list - * tommy_list_insert_tail(&list, &obj->node, obj); + * tommy_list_insert_tail(&list, &elem->node, elem); * * ... * - * // deallocates all the objects iterating the list + * // deallocates all the elements iterating the list * tommy_list_foreach(&list, free); + * tommy_list_clear(&list); // resets the list before reuse * \endcode + * + * If the callback frees the elements, the list retains dangling node + * pointers. Call tommy_list_clear() or tommy_list_init() before reusing it. + * The list has no storage to release. + * \param list The list. + * \param func Function to call with each element. */ tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func) { @@ -398,6 +780,12 @@ tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func) /** * Calls the specified function with an argument for each element in the list. + * The iteration order and callback rules are the same as tommy_list_foreach(). + * The callback may deallocate the current element. + * Adding or removing elements from inside the callback is not allowed. + * \param list The list. + * \param func Function to call with each element. + * \param arg Argument to pass to the function. */ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg) { @@ -411,3 +799,4 @@ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_fun } #endif + diff --git a/tommyds/tommytree.c b/tommyds/tommytree.c index 7f3fcd19..069401be 100644 --- a/tommyds/tommytree.c +++ b/tommyds/tommytree.c @@ -14,7 +14,7 @@ #define TOMMY_TREE_BALANCE_LEFT ((tommy_size_t)1) #define TOMMY_TREE_BALANCE_RIGHT ((tommy_size_t)2) -tommy_inline int tommy_tree_balance_get(tommy_tree_node* node) +tommy_inline int tommy_imp_tree_balance_get(tommy_tree_node* node) { tommy_size_t balance = node->index & TOMMY_TREE_BALANCE_MASK; @@ -25,7 +25,7 @@ tommy_inline int tommy_tree_balance_get(tommy_tree_node* node) return 0; } -tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance) +tommy_inline void tommy_imp_tree_balance_set(tommy_tree_node* node, int balance) { tommy_size_t value = 0; @@ -37,12 +37,12 @@ tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance) node->index = (node->index & ~TOMMY_TREE_BALANCE_MASK) | value; } -tommy_inline void tommy_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent) +tommy_inline void tommy_imp_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent) { node->index = (tommy_size_t)(tommy_uintptr_t)parent | (node->index & TOMMY_TREE_BALANCE_MASK); } -tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node) +tommy_inline void tommy_imp_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node) { tommy_tree_node* parent = tommy_tree_parent(root); @@ -55,45 +55,45 @@ tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, to } if (node) - tommy_tree_parent_set(node, parent); + tommy_imp_tree_parent_set(node, parent); } -tommy_inline tommy_tree_node* tommy_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root) +tommy_inline tommy_tree_node* tommy_imp_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root) { tommy_tree_node* next = root->next; - tommy_tree_replace(tree, root, next); + tommy_imp_tree_replace(tree, root, next); root->next = next->prev; if (root->next) - tommy_tree_parent_set(root->next, root); + tommy_imp_tree_parent_set(root->next, root); next->prev = root; - tommy_tree_parent_set(root, next); + tommy_imp_tree_parent_set(root, next); return next; } -tommy_inline tommy_tree_node* tommy_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root) +tommy_inline tommy_tree_node* tommy_imp_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root) { tommy_tree_node* prev = root->prev; - tommy_tree_replace(tree, root, prev); + tommy_imp_tree_replace(tree, root, prev); root->prev = prev->next; if (root->prev) - tommy_tree_parent_set(root->prev, root); + tommy_imp_tree_parent_set(root->prev, root); prev->next = root; - tommy_tree_parent_set(root, prev); + tommy_imp_tree_parent_set(root, prev); return prev; } -tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node) +tommy_inline void tommy_imp_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node) { tommy_tree_node* parent = tommy_tree_parent(node); while (parent) { - int balance = tommy_tree_balance_get(parent); + int balance = tommy_imp_tree_balance_get(parent); if (node == parent->prev) --balance; @@ -102,12 +102,12 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n } if (balance == 0) { - tommy_tree_balance_set(parent, balance); + tommy_imp_tree_balance_set(parent, balance); return; } if (balance >= -1 && balance <= 1) { - tommy_tree_balance_set(parent, balance); + tommy_imp_tree_balance_set(parent, balance); node = parent; parent = tommy_tree_parent(parent); continue; @@ -115,39 +115,39 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n if (balance == -2) { tommy_tree_node* left = parent->prev; - int left_balance = tommy_tree_balance_get(left); + int left_balance = tommy_imp_tree_balance_get(left); if (left_balance < 0) { - tommy_tree_rotate_right(tree, parent); - tommy_tree_balance_set(parent, 0); - tommy_tree_balance_set(left, 0); + tommy_imp_tree_rotate_right(tree, parent); + tommy_imp_tree_balance_set(parent, 0); + tommy_imp_tree_balance_set(left, 0); } else { tommy_tree_node* middle = left->next; - int middle_balance = tommy_tree_balance_get(middle); + int middle_balance = tommy_imp_tree_balance_get(middle); - tommy_tree_rotate_left(tree, left); - tommy_tree_rotate_right(tree, parent); - tommy_tree_balance_set(parent, middle_balance < 0 ? 1 : 0); - tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0); - tommy_tree_balance_set(middle, 0); + tommy_imp_tree_rotate_left(tree, left); + tommy_imp_tree_rotate_right(tree, parent); + tommy_imp_tree_balance_set(parent, middle_balance < 0 ? 1 : 0); + tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0); + tommy_imp_tree_balance_set(middle, 0); } } else { tommy_tree_node* right = parent->next; - int right_balance = tommy_tree_balance_get(right); + int right_balance = tommy_imp_tree_balance_get(right); if (right_balance > 0) { - tommy_tree_rotate_left(tree, parent); - tommy_tree_balance_set(parent, 0); - tommy_tree_balance_set(right, 0); + tommy_imp_tree_rotate_left(tree, parent); + tommy_imp_tree_balance_set(parent, 0); + tommy_imp_tree_balance_set(right, 0); } else { tommy_tree_node* middle = right->prev; - int middle_balance = tommy_tree_balance_get(middle); + int middle_balance = tommy_imp_tree_balance_get(middle); - tommy_tree_rotate_right(tree, right); - tommy_tree_rotate_left(tree, parent); - tommy_tree_balance_set(parent, middle_balance > 0 ? -1 : 0); - tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0); - tommy_tree_balance_set(middle, 0); + tommy_imp_tree_rotate_right(tree, right); + tommy_imp_tree_rotate_left(tree, parent); + tommy_imp_tree_balance_set(parent, middle_balance > 0 ? -1 : 0); + tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0); + tommy_imp_tree_balance_set(middle, 0); } } @@ -155,41 +155,59 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n } } -TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data) +tommy_inline void* tommy_imp_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data, tommy_bool_t unique) { tommy_tree_node* parent = 0; + tommy_tree_node* existing = 0; tommy_tree_node** link = &tree->root; + /* cache the comparator to avoid reloading it after each comparison */ + tommy_compare_func* cmp_func = tree->cmp; + + while (*link) { + /* comparators must not change the tree, so keep the node across the call */ + parent = *link; + int cmp = cmp_func(data, parent->data); + + if (cmp < 0) + link = &parent->prev; + else if (cmp == 0 && unique) { + existing = parent; + link = &parent->prev; + } else { + /* placing equal keys after existing ones preserves insertion order through rotations */ + link = &parent->next; + } + } + + if (existing) + return existing->data; node->data = data; node->prev = 0; node->next = 0; - node->index = 0; - - while (*link) { - int cmp = tree->cmp(data, (*link)->data); - - parent = *link; - if (cmp < 0) - link = &parent->prev; - else if (cmp > 0) - link = &parent->next; - else - return parent->data; - } - node->index = (tommy_size_t)(tommy_uintptr_t)parent; *link = node; ++tree->count; - tommy_tree_insert_balance(tree, node); + tommy_imp_tree_insert_balance(tree, node); return node->data; } -tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk) +TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data) +{ + tommy_imp_tree_insert(tree, node, data, 0); +} + +TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data) +{ + return tommy_imp_tree_insert(tree, node, data, 1); +} + +tommy_inline void tommy_imp_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk) { while (node) { - int balance = tommy_tree_balance_get(node); + int balance = tommy_imp_tree_balance_get(node); if (left_shrunk) ++balance; @@ -197,15 +215,13 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n --balance; if (balance == -1 || balance == 1) { - tommy_tree_balance_set(node, balance); + tommy_imp_tree_balance_set(node, balance); return; } if (balance == 0) { - tommy_tree_node* parent; - - tommy_tree_balance_set(node, balance); - parent = tommy_tree_parent(node); + tommy_imp_tree_balance_set(node, balance); + tommy_tree_node* parent = tommy_tree_parent(node); if (!parent) return; left_shrunk = node == parent->prev; @@ -215,27 +231,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n if (balance == -2) { tommy_tree_node* left = node->prev; - int left_balance = tommy_tree_balance_get(left); + int left_balance = tommy_imp_tree_balance_get(left); tommy_tree_node* root; if (left_balance <= 0) { - root = tommy_tree_rotate_right(tree, node); + root = tommy_imp_tree_rotate_right(tree, node); if (left_balance == 0) { - tommy_tree_balance_set(node, -1); - tommy_tree_balance_set(left, 1); + tommy_imp_tree_balance_set(node, -1); + tommy_imp_tree_balance_set(left, 1); return; } - tommy_tree_balance_set(node, 0); - tommy_tree_balance_set(left, 0); + tommy_imp_tree_balance_set(node, 0); + tommy_imp_tree_balance_set(left, 0); } else { tommy_tree_node* middle = left->next; - int middle_balance = tommy_tree_balance_get(middle); + int middle_balance = tommy_imp_tree_balance_get(middle); - tommy_tree_rotate_left(tree, left); - root = tommy_tree_rotate_right(tree, node); - tommy_tree_balance_set(node, middle_balance < 0 ? 1 : 0); - tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0); - tommy_tree_balance_set(middle, 0); + tommy_imp_tree_rotate_left(tree, left); + root = tommy_imp_tree_rotate_right(tree, node); + tommy_imp_tree_balance_set(node, middle_balance < 0 ? 1 : 0); + tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0); + tommy_imp_tree_balance_set(middle, 0); } node = tommy_tree_parent(root); @@ -247,27 +263,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n { tommy_tree_node* right = node->next; - int right_balance = tommy_tree_balance_get(right); + int right_balance = tommy_imp_tree_balance_get(right); tommy_tree_node* root; if (right_balance >= 0) { - root = tommy_tree_rotate_left(tree, node); + root = tommy_imp_tree_rotate_left(tree, node); if (right_balance == 0) { - tommy_tree_balance_set(node, 1); - tommy_tree_balance_set(right, -1); + tommy_imp_tree_balance_set(node, 1); + tommy_imp_tree_balance_set(right, -1); return; } - tommy_tree_balance_set(node, 0); - tommy_tree_balance_set(right, 0); + tommy_imp_tree_balance_set(node, 0); + tommy_imp_tree_balance_set(right, 0); } else { tommy_tree_node* middle = right->prev; - int middle_balance = tommy_tree_balance_get(middle); + int middle_balance = tommy_imp_tree_balance_get(middle); - tommy_tree_rotate_right(tree, right); - root = tommy_tree_rotate_left(tree, node); - tommy_tree_balance_set(node, middle_balance > 0 ? -1 : 0); - tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0); - tommy_tree_balance_set(middle, 0); + tommy_imp_tree_rotate_right(tree, right); + root = tommy_imp_tree_rotate_left(tree, node); + tommy_imp_tree_balance_set(node, middle_balance > 0 ? -1 : 0); + tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0); + tommy_imp_tree_balance_set(middle, 0); } node = tommy_tree_parent(root); @@ -289,7 +305,7 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no parent = tommy_tree_parent(node); left_shrunk = parent && node == parent->prev; - tommy_tree_replace(tree, node, child); + tommy_imp_tree_replace(tree, node, child); } else { tommy_tree_node* next = node->next; @@ -298,67 +314,106 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no parent = tommy_tree_parent(next); if (parent == node) { - tommy_tree_replace(tree, node, next); + tommy_imp_tree_replace(tree, node, next); next->prev = node->prev; - tommy_tree_parent_set(next->prev, next); - tommy_tree_balance_set(next, tommy_tree_balance_get(node)); + tommy_imp_tree_parent_set(next->prev, next); + tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node)); parent = next; left_shrunk = 0; } else { parent->prev = next->next; if (parent->prev) - tommy_tree_parent_set(parent->prev, parent); + tommy_imp_tree_parent_set(parent->prev, parent); - tommy_tree_replace(tree, node, next); + tommy_imp_tree_replace(tree, node, next); next->prev = node->prev; next->next = node->next; - tommy_tree_parent_set(next->prev, next); - tommy_tree_parent_set(next->next, next); - tommy_tree_balance_set(next, tommy_tree_balance_get(node)); + tommy_imp_tree_parent_set(next->prev, next); + tommy_imp_tree_parent_set(next->next, next); + tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node)); left_shrunk = 1; } } --tree->count; if (parent) - tommy_tree_remove_balance(tree, parent, left_shrunk); + tommy_imp_tree_remove_balance(tree, parent, left_shrunk); return data; } -TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func) +TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list) { - while (root) { - tommy_tree_node* next; + /* AVL height is less than twice the bit width of its node count */ + tommy_tree_node* stack[2 * TOMMY_SIZE_BIT]; + tommy_size_t depth = 0; + tommy_tree_node* node = tree->root; + tommy_builder builder; + tommy_node* builder_tail = tommy_builder_init(&builder); - tommy_tree_foreach_node(root->prev, func); + while (node || depth) { + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } - /* make a copy in case func is free() */ - next = root->next; + node = stack[--depth]; + /* chain appending overwrites child links, so keep the right subtree reachable */ + tommy_tree_node* next = node->next; + builder_tail = tommy_builder_concat(builder_tail, node, node); + node = next; + } - func(root->data); + tommy_list_concat_builder(list, &builder, builder_tail); + tommy_tree_clear(tree); +} - root = next; +TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func) +{ + /* AVL height is less than twice the bit width of its node count */ + tommy_tree_node* stack[2 * TOMMY_SIZE_BIT]; + tommy_size_t depth = 0; + tommy_tree_node* node = tree->root; + + while (node || depth) { + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } + + node = stack[--depth]; + /* save the next subtree before the callback can free this node */ + tommy_tree_node* next = node->next; + func(node->data); + node = next; } } -TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg) +TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg) { - while (root) { - tommy_tree_node* next; + /* AVL height is less than twice the bit width of its node count */ + tommy_tree_node* stack[2 * TOMMY_SIZE_BIT]; + tommy_size_t depth = 0; + tommy_tree_node* node = tree->root; - tommy_tree_foreach_arg_node(root->prev, func, arg); + while (node || depth) { + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } - /* make a copy in case func is free() */ - next = root->next; - - func(arg, root->data); - - root = next; + node = stack[--depth]; + /* save the next subtree before the callback can free this node */ + tommy_tree_node* next = node->next; + func(arg, node->data); + node = next; } } -TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree) +TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree) { return tommy_tree_count(tree) * sizeof(tommy_tree_node); } diff --git a/tommyds/tommytree.h b/tommyds/tommytree.h index 9bae8611..f2ff9f99 100644 --- a/tommyds/tommytree.h +++ b/tommyds/tommytree.h @@ -7,7 +7,8 @@ * This tree is a standard AVL tree implementation that stores elements in the * order defined by the comparison function. * - * As a difference from other tommy containers, duplicate elements cannot be inserted. + * Duplicate elements are allowed. They are visited in insertion order. + * Use tommy_tree_insert_unique() to reject duplicate keys. * * To initialize a tree you have to call tommy_tree_init() specifying a comparison * function that will define the order in the tree. @@ -21,31 +22,31 @@ * To insert elements in the tree you have to call tommy_tree_insert() for * each element. * In the insertion call you have to specify the address of the node and the - * address of the object. - * The address of the object is used to initialize the tommy_node::data field + * address of the element. + * The address of the element is used to initialize the tommy_node::data field * of the node. * * \code - * struct object { + * struct element { * int value; * // other fields * tommy_tree_node node; * }; * - * struct object* obj = malloc(sizeof(struct object)); // creates the object + * struct element* elem = malloc(sizeof(struct element)); // creates the element * - * obj->value = ...; // initializes the object + * elem->value = ...; // initializes the element * - * tommy_tree_insert(&tree, &obj->node, obj); // inserts the object + * tommy_tree_insert(&tree, &elem->node, elem); // inserts the element * \endcode * - * To find an element in the tree you have to call tommy_tree_search() providing - * the key to search. + * To find the first element with a given key in tree order, call + * tommy_tree_search() providing the key to search. * * \code - * struct object value_to_find = { 1 }; - * struct object* obj = tommy_tree_search(&tree, &value_to_find); - * if (!obj) { + * struct element value_to_find = { 1 }; + * struct element* elem = tommy_tree_search(&tree, &value_to_find); + * if (!elem) { * // not found * } else { * // found @@ -59,22 +60,36 @@ * \code * tommy_tree_node* i = tommy_tree_head(&tree); * while (i) { - * struct object* obj = i->data; // gets the object pointer + * struct element* elem = i->data; // gets the element pointer * - * printf("%d\n", obj->value); // process the object + * printf("%d\n", elem->value); // process the element * * i = tommy_tree_next(i); // go to the next element * } * \endcode * - * To remove an element from the tree you have to call tommy_tree_remove() - * providing the key to search and remove. + * \anchor tommy_tree_iterator_validity + * Iterators are pointers to nodes contained in the tree. + * Inserting elements or removing other nodes between iterator calls does not + * invalidate pointers to nodes that remain in the tree. + * Subsequent next/prev calls use the current contents and ordering; + * inserted elements may become visible and removed elements are skipped. + * + * Removing the pointed node invalidates its use as an iterator, even if + * its storage has not been freed. To continue traversal after removing + * the current node, obtain the next or previous node before removal. + * + * Keys and fields used for ordering must not be modified while contained. + * Concurrent traversal and modification require external synchronization. + * + * To remove the first element with a given key in tree order, call + * tommy_tree_remove() providing the key to search and remove. * * \code - * struct object value_to_remove = { 1 }; - * struct object* obj = tommy_tree_remove(&tree, &value_to_remove); - * if (obj) { - * free(obj); // frees the object allocated memory + * struct element value_to_remove = { 1 }; + * struct element* elem = tommy_tree_remove(&tree, &value_to_remove); + * if (elem) { + * free(elem); // frees the element allocated memory * } * \endcode * @@ -82,18 +97,18 @@ * address using tommy_tree_remove_existing(). * * \code - * struct object* obj = ...; - * tommy_tree_remove_existing(&tree, &obj->node); - * free(obj); + * struct element* elem = ...; + * tommy_tree_remove_existing(&tree, &elem->node); + * free(elem); * \endcode * * To remove the smallest or greatest element you can use tommy_tree_remove_head() * or tommy_tree_remove_tail(). * * \code - * struct object* obj = tommy_tree_remove_head(&tree); - * if (obj) { - * free(obj); + * struct element* elem = tommy_tree_remove_head(&tree); + * if (elem) { + * free(elem); * } * \endcode * @@ -102,22 +117,23 @@ * This can be done with the tommy_tree_foreach() function. * * \code - * // deallocates all the objects iterating the tree + * // deallocates all the elements iterating the tree * tommy_tree_foreach(&tree, free); * \endcode */ -#ifndef __TOMMYTREE_H -#define __TOMMYTREE_H +#ifndef TOMMY_TREE_H +#define TOMMY_TREE_H #include "tommytypes.h" +#include "tommylist.h" /******************************************************************************/ /* tree */ /** * Tree node. - * This is the node that you have to include inside your objects. + * This is the node that you have to include inside your elements. * The node must preserve its natural alignment because the tree stores the * balance factor in the two least significant bits of the parent pointer. */ @@ -135,6 +151,7 @@ typedef struct tommy_tree_struct { /** * Initializes the tree. + * \param tree The tree. * \param cmp The comparison function that defines the order in the tree. */ tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp) @@ -145,40 +162,81 @@ tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp) } /** - * Inserts an element in the tree. - * If the element is already present, it's not inserted again. - * Check the return value to identify if the element was already present or not. - * You have to provide the pointer of the node embedded into the object and - * the pointer to the object. - * \param node Pointer to the node embedded into the object to insert. - * \param data Pointer to the object to insert. - * \return The element in the tree. Either the already existing one, or the one just inserted. + * Exchanges the contents of two initialized trees, including their comparison functions. + * Each comparison function moves with its root to preserve the tree order. + * The trees must not share nodes. Nodes are not accessed or modified. + * Passing the same tree twice has no effect. + * \param first The first tree. + * \param second The second tree. + * \note This operation is O(1). */ -TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data); +tommy_inline void tommy_tree_swap(tommy_tree* first, tommy_tree* second) +{ + tommy_tree tmp = *first; + *first = *second; + *second = tmp; +} + +/** + * Removes all elements, preserving the comparison function. + * The tree remains initialized and can be reused immediately. + * Elements are not freed and nodes are not accessed or modified. + * Their links must not be used to traverse the previous contents. + * You can call this function after tommy_tree_foreach() has freed the elements. + * \param tree The tree. + * \note This operation is O(1). + */ +tommy_inline void tommy_tree_clear(tommy_tree* tree) +{ + tree->root = 0; + tree->count = 0; +} + +/** + * Inserts an element in the tree, allowing duplicate keys. + * Equal elements are visited in insertion order. + * You have to provide the pointer of the node embedded into the element and + * the pointer to the element. + * \param tree The tree. + * \param node Pointer to the node embedded into the element to insert. + * \param data Pointer to the element to insert. + */ +TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data); + +/** + * Inserts an element only if no equal element is already contained. + * If found, the first equal element in tree order is returned and the + * candidate node is left unchanged. Otherwise the candidate is inserted + * and data is returned. + * \param tree The tree. + * \param node The candidate node. It must not belong to any container. + * \param data Pointer to the element to insert. + * \return The first equal element, or data if the candidate was inserted. + */ +TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data); + +/** + * Transfers all elements from the tree to the tail of a list in tree order. + * Existing list elements remain before the transferred elements. The tree is + * left empty and initialized with its original comparison function. + * The list must be initialized and must not share nodes with the tree. + * Elements are not freed, and the node data and index fields are left unchanged. + * The index field no longer represents a valid tree parent after the transfer. + * \param tree The tree to drain. + * \param list The destination list. + * \note This operation is O(n), using O(log n) stack space. + */ +TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list); /** * Removes an element from the tree. - * You must already have the address of the element to remove. - * \return The tommy_node::data field of the node removed. + * You must already have the address of the node to remove. + * \param tree The tree. + * \param node The node to remove. + * \return The removed element. */ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node); - -/** \internal - * Calls the specified function for each element in the tree. - * \param root Root node of the tree. - * \param func Function to call. - */ -TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func); - -/** \internal - * Calls the specified function with an argument for each element in the tree. - * \param root Root node of the tree. - * \param func Function to call. - * \param arg Custom argument for the function. - */ -TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg); - /** * Gets the parent of the specified node. * \param node Node contained in the tree. @@ -191,13 +249,13 @@ tommy_inline tommy_tree_node* tommy_tree_parent(tommy_tree_node* node) /** * Gets the node following the specified one in tree order. + * See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals. * \param node Node contained in the tree. - * \return The following node. For the tail node 0 is returned. + * \return The following node, or 0 for the tail node. + * \note This operation is O(log n), and amortized O(1) during full traversal. */ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node) { - tommy_tree_node* parent; - if (node->next) { node = node->next; while (node->prev) @@ -205,7 +263,7 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node) return node; } - parent = tommy_tree_parent(node); + tommy_tree_node* parent = tommy_tree_parent(node); while (parent && node == parent->next) { node = parent; parent = tommy_tree_parent(parent); @@ -216,13 +274,13 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node) /** * Gets the node preceding the specified one in tree order. + * See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals. * \param node Node contained in the tree. - * \return The preceding node. For the head node 0 is returned. + * \return The preceding node, or 0 for the head node. + * \note This operation is O(log n), and amortized O(1) during full traversal. */ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node) { - tommy_tree_node* parent; - if (node->prev) { node = node->prev; while (node->next) @@ -230,7 +288,7 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node) return node; } - parent = tommy_tree_parent(node); + tommy_tree_node* parent = tommy_tree_parent(node); while (parent && node == parent->prev) { node = parent; parent = tommy_tree_parent(parent); @@ -240,8 +298,10 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node) } /** - * Gets the head (smallest) element in the tree. - * \return The head node. For empty trees 0 is returned. + * Gets the head (smallest) node in the tree. + * \param tree The tree. + * \return The head node, or 0 if the tree is empty. + * \note This operation is O(log n). */ tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree) { @@ -257,8 +317,10 @@ tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree) } /** - * Gets the tail (greatest) element in the tree. - * \return The tail node. For empty trees 0 is returned. + * Gets the tail (greatest) node in the tree. + * \param tree The tree. + * \return The tail node, or 0 if the tree is empty. + * \note This operation is O(log n). */ tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree) { @@ -276,7 +338,8 @@ tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree) /** * Removes and returns the head (smallest) element. * If the tree is empty, 0 is returned. - * \return The removed element, or 0 if empty. + * \param tree The tree. + * \return The removed element, or 0 if the tree is empty. */ tommy_inline void* tommy_tree_remove_head(tommy_tree* tree) { @@ -291,7 +354,8 @@ tommy_inline void* tommy_tree_remove_head(tommy_tree* tree) /** * Removes and returns the tail (greatest) element. * If the tree is empty, 0 is returned. - * \return The removed element, or 0 if empty. + * \param tree The tree. + * \return The removed element, or 0 if the tree is empty. */ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree) { @@ -303,44 +367,53 @@ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree) return tommy_tree_remove_existing(tree, node); } -/** \internal - * Searches a node in the tree. - * If the element is not found, 0 is returned. - * \param cmp Comparison function. - * \param node Node to start the search from. - * \param data Element used for comparison. - * \return The first node found, or 0 if none. +/** + * Finds the first equal node in the tree with a specific comparison function. + * + * Like tommy_tree_find() but you can specify a different comparison function. + * Note that this function must define a suborder of the original one. + * The first equal node in tree order is returned. + * + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first equal node in tree order, or 0 if none. */ -tommy_inline tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data) +tommy_inline tommy_tree_node* tommy_tree_find_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { + tommy_tree_node* node = tree->root; + tommy_tree_node* candidate = 0; + while (node) { - int c = cmp(data, node->data); + /* comparators must not change the tree, so retain the data across the call */ + void* data = node->data; + int c = cmp(cmp_arg, data); if (c < 0) node = node->prev; else if (c > 0) node = node->next; - else - return node; + else { + candidate = node; + node = node->prev; + } } - return 0; + return candidate; } /** - * Searches an element in the tree. + * Finds the first equal node in the tree. * If the element is not found, 0 is returned. + * \param tree The tree. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The first equal node in tree order, or 0 if none. */ -tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data) +tommy_inline tommy_tree_node* tommy_tree_find(tommy_tree* tree, const void* data) { - tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data); - - if (!node) - return 0; - - return node->data; + return tommy_tree_find_compare(tree, tree->cmp, data); } /** @@ -348,42 +421,65 @@ tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data) * * Like tommy_tree_search() but you can specify a different comparison function. * Note that this function must define a suborder of the original one. + * The first equal element in tree order is returned. * - * The ::data argument will be the first argument of the comparison function, - * and it can be of a different type than the objects in the tree. + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first equal element in tree order, or 0 if none. */ -tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg) +tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { - tommy_tree_node* node = tommy_tree_search_node(cmp, tree->root, cmp_arg); + tommy_tree_node* node = tommy_tree_find_compare(tree, cmp, cmp_arg); - if (!node) - return 0; - - return node->data; + return node ? node->data : 0; } -/** \internal - * Searches a node in the tree with key greater or equal than the specified one. - * If no such element exists, 0 is returned. - * \param cmp Comparison function. - * \param node Node to start the search from. +/** + * Searches an element in the tree. + * If the element is not found, 0 is returned. + * \param tree The tree. * \param data Element used for comparison. - * \return The first node found, or 0 if none. + * \return The first equal element in tree order, or 0 if none. */ -tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data) +tommy_inline void* tommy_tree_search(tommy_tree* tree, const void* data) { + tommy_tree_node* node = tommy_tree_find(tree, data); + + return node ? node->data : 0; +} + +/** + * Finds a node in the tree with key greater or equal than the specified one with a specific comparison function. + * + * Like tommy_tree_find_greater_equal() but you can specify a different comparison function. + * Note that this function must define a suborder of the original one. + * The first qualifying node in tree order is returned. + * + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first node in tree order with key greater or equal, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tree->root; tommy_tree_node* candidate = 0; while (node) { - int c = cmp(data, node->data); + /* comparators must not change the tree, so retain the data across the call */ + void* data = node->data; + int c = cmp(cmp_arg, data); - if (c < 0) { + if (c <= 0) { candidate = node; node = node->prev; - } else if (c > 0) { - node = node->next; } else { - return node; + node = node->next; } } @@ -391,19 +487,15 @@ tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare } /** - * Searches an element in the tree with key greater or equal than the specified one. + * Finds a node in the tree with key greater or equal than the specified one. * If no such element exists, 0 is returned. + * \param tree The tree. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The first node in tree order with key greater or equal, or 0 if none. */ -tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data) +tommy_inline tommy_tree_node* tommy_tree_find_greater_equal(tommy_tree* tree, const void* data) { - tommy_tree_node* node = tommy_tree_search_greater_equal_node(tree->cmp, tree->root, data); - - if (!node) - return 0; - - return node->data; + return tommy_tree_find_greater_equal_compare(tree, tree->cmp, data); } /** @@ -411,42 +503,60 @@ tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data) * * Like tommy_tree_search_greater_equal() but you can specify a different comparison function. * Note that this function must define a suborder of the original one. + * The first qualifying element in tree order is returned. * - * The ::data argument will be the first argument of the comparison function, - * and it can be of a different type than the objects in the tree. + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first element in tree order with key greater or equal, or 0 if none. */ -tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg) +tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { - tommy_tree_node* node = tommy_tree_search_greater_equal_node(cmp, tree->root, cmp_arg); + tommy_tree_node* node = tommy_tree_find_greater_equal_compare(tree, cmp, cmp_arg); - if (!node) - return 0; - - return node->data; + return node ? node->data : 0; } -/** \internal - * Searches a node in the tree with key less or equal than the specified one. +/** + * Searches an element in the tree with key greater or equal than the specified one. * If no such element exists, 0 is returned. - * \param cmp Comparison function. - * \param node Node to start the search from. + * \param tree The tree. * \param data Element used for comparison. - * \return The first node found, or 0 if none. + * \return The first element in tree order with key greater or equal, or 0 if none. */ -tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data) +tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, const void* data) { + tommy_tree_node* node = tommy_tree_find_greater_equal(tree, data); + + return node ? node->data : 0; +} + +/** + * Finds the first node in tree order with key strictly greater than the specified one using a specific comparison function. + * The function must define a suborder of the tree comparison function. + * Equivalent elements are excluded from the result. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first strictly greater node, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tree->root; tommy_tree_node* candidate = 0; while (node) { - int c = cmp(data, node->data); + /* comparators must not change the tree, so retain the data across the call */ + void* data = node->data; + int c = cmp(cmp_arg, data); if (c < 0) { - node = node->prev; - } else if (c > 0) { candidate = node; - node = node->next; + node = node->prev; } else { - return node; + node = node->next; } } @@ -454,19 +564,92 @@ tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_fu } /** - * Searches an element in the tree with key less or equal than the specified one. + * Finds the first node in tree order with key strictly greater than the specified one. * If no such element exists, 0 is returned. + * \param tree The tree. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The first strictly greater node, or 0 if none. */ -tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data) +tommy_inline tommy_tree_node* tommy_tree_find_greater(tommy_tree* tree, const void* data) { - tommy_tree_node* node = tommy_tree_search_less_equal_node(tree->cmp, tree->root, data); + return tommy_tree_find_greater_compare(tree, tree->cmp, data); +} - if (!node) - return 0; +/** + * Searches the first element in tree order with key strictly greater than the specified one using a specific comparison function. + * The function must define a suborder of the tree comparison function. + * Equivalent elements are excluded from the result. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first strictly greater element, or 0 if none. + */ +tommy_inline void* tommy_tree_search_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tommy_tree_find_greater_compare(tree, cmp, cmp_arg); - return node->data; + return node ? node->data : 0; +} + +/** + * Searches the first element in tree order with key strictly greater than the specified one. + * If no such element exists, 0 is returned. + * \param tree The tree. + * \param data Element used for comparison. + * \return The first strictly greater element, or 0 if none. + */ +tommy_inline void* tommy_tree_search_greater(tommy_tree* tree, const void* data) +{ + tommy_tree_node* node = tommy_tree_find_greater(tree, data); + + return node ? node->data : 0; +} + +/** + * Finds a node in the tree with key less or equal than the specified one with a specific comparison function. + * + * Like tommy_tree_find_less_equal() but you can specify a different comparison function. + * Note that this function must define a suborder of the original one. + * The last qualifying node in tree order is returned. + * + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The last node in tree order with key less or equal, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tree->root; + tommy_tree_node* candidate = 0; + + while (node) { + /* comparators must not change the tree, so retain the data across the call */ + void* data = node->data; + int c = cmp(cmp_arg, data); + + if (c < 0) { + node = node->prev; + } else { + candidate = node; + node = node->next; + } + } + + return candidate; +} + +/** + * Finds a node in the tree with key less or equal than the specified one. + * If no such element exists, 0 is returned. + * \param tree The tree. + * \param data Element used for comparison. + * \return The last node in tree order with key less or equal, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_less_equal(tommy_tree* tree, const void* data) +{ + return tommy_tree_find_less_equal_compare(tree, tree->cmp, data); } /** @@ -474,34 +657,138 @@ tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data) * * Like tommy_tree_search_less_equal() but you can specify a different comparison function. * Note that this function must define a suborder of the original one. + * The last qualifying element in tree order is returned. * - * The ::data argument will be the first argument of the comparison function, - * and it can be of a different type than the objects in the tree. + * The cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The last element in tree order with key less or equal, or 0 if none. */ -tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg) +tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { - tommy_tree_node* node = tommy_tree_search_less_equal_node(cmp, tree->root, cmp_arg); + tommy_tree_node* node = tommy_tree_find_less_equal_compare(tree, cmp, cmp_arg); - if (!node) - return 0; - - return node->data; + return node ? node->data : 0; } /** - * Searches and removes an element. - * If the element is not found, 0 is returned. + * Searches an element in the tree with key less or equal than the specified one. + * If no such element exists, 0 is returned. + * \param tree The tree. * \param data Element used for comparison. - * \return The removed element, or 0 if not found. + * \return The last element in tree order with key less or equal, or 0 if none. */ -tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data) +tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, const void* data) { - tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data); + tommy_tree_node* node = tommy_tree_find_less_equal(tree, data); - if (!node) + return node ? node->data : 0; +} + +/** + * Finds the last node in tree order with key strictly less than the specified one using a specific comparison function. + * The function must define a suborder of the tree comparison function. + * Equivalent elements are excluded from the result. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The last strictly less node, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_less_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tree->root; + tommy_tree_node* candidate = 0; + + while (node) { + /* comparators must not change the tree, so retain the data across the call */ + void* data = node->data; + int c = cmp(cmp_arg, data); + + if (c > 0) { + candidate = node; + node = node->next; + } else { + node = node->prev; + } + } + + return candidate; +} + +/** + * Finds the last node in tree order with key strictly less than the specified one. + * If no such element exists, 0 is returned. + * \param tree The tree. + * \param data Element used for comparison. + * \return The last strictly less node, or 0 if none. + */ +tommy_inline tommy_tree_node* tommy_tree_find_less(tommy_tree* tree, const void* data) +{ + return tommy_tree_find_less_compare(tree, tree->cmp, data); +} + +/** + * Searches the last element in tree order with key strictly less than the specified one using a specific comparison function. + * The function must define a suborder of the tree comparison function. + * Equivalent elements are excluded from the result. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The last strictly less element, or 0 if none. + */ +tommy_inline void* tommy_tree_search_less_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tommy_tree_find_less_compare(tree, cmp, cmp_arg); + + return node ? node->data : 0; +} + +/** + * Searches the last element in tree order with key strictly less than the specified one. + * If no such element exists, 0 is returned. + * \param tree The tree. + * \param data Element used for comparison. + * \return The last strictly less element, or 0 if none. + */ +tommy_inline void* tommy_tree_search_less(tommy_tree* tree, const void* data) +{ + tommy_tree_node* node = tommy_tree_find_less(tree, data); + + return node ? node->data : 0; +} + +/** + * Searches and removes an element from the tree with a specific comparison function. + * The comparison function must define a suborder of the original one. + * The cmp_arg argument is passed as the first argument of the comparison + * function and may have a different type than the elements in the tree. + * \param tree The tree. + * \param cmp Comparison function called with cmp_arg and the element in the tree. + * \param cmp_arg Search key, which may have a different type from tree elements. + * \return The first equal element in tree order, or 0 if not found. + */ +tommy_inline void* tommy_tree_remove_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* candidate = tommy_tree_find_compare(tree, cmp, cmp_arg); + + if (!candidate) return 0; - return tommy_tree_remove_existing(tree, node); + return tommy_tree_remove_existing(tree, candidate); +} + +/** + * Searches and removes an element from the tree. + * If the element is not found, 0 is returned. + * \param tree The tree. + * \param data Element used for comparison. + * \return The first equal element in tree order, or 0 if not found. + */ +tommy_inline void* tommy_tree_remove(tommy_tree* tree, const void* data) +{ + return tommy_tree_remove_compare(tree, tree->cmp, data); } /** @@ -520,46 +807,58 @@ tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data) * * ... * - * // creates an object - * struct object* obj = malloc(sizeof(struct object)); + * // creates an element + * struct element* elem = malloc(sizeof(struct element)); * * ... * * // insert it in the tree - * tommy_tree_insert(&tree, &obj->node, obj); + * tommy_tree_insert(&tree, &elem->node, elem); * * ... * - * // deallocates all the objects iterating the tree + * // deallocates all the elements iterating the tree * tommy_tree_foreach(&tree, free); + * tommy_tree_clear(&tree); // resets the tree before reuse * \endcode + * + * If the callback frees the elements, the tree retains dangling node + * pointers. Call tommy_tree_clear() or tommy_tree_init() before reusing it. + * The tree has no storage to release. + * \param tree The tree. + * \param func Function to call with each element. */ -tommy_inline void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func) -{ - tommy_tree_foreach_node(tree->root, func); -} +TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func); /** * Calls the specified function with an argument for each element in the tree. + * The iteration order and callback rules are the same as tommy_tree_foreach(). + * The callback may deallocate the current element. + * Adding or removing elements from inside the callback is not allowed. + * \param tree The tree. + * \param func Function to call with each element. + * \param arg Argument to pass to the function. */ -tommy_inline void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg) -{ - tommy_tree_foreach_arg_node(tree->root, func, arg); -} +TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg); /** - * Checks if empty. - * \return If the tree is empty. + * Checks whether the tree is empty. + * \param tree The tree. + * \return Nonzero if the tree is empty, 0 otherwise. + * \note This operation is O(1). */ -tommy_inline tommy_bool_t tommy_tree_empty(tommy_tree* tree) +tommy_inline tommy_bool_t tommy_tree_empty(const tommy_tree* tree) { return tree->root == 0; } /** * Gets the number of elements. + * \param tree The tree. + * \return The number of elements in the tree. + * \note This operation is O(1). */ -tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree) +tommy_inline tommy_size_t tommy_tree_count(const tommy_tree* tree) { return tree->count; } @@ -567,8 +866,10 @@ tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree) /** * Gets the size of allocated memory. * It includes the size of the ::tommy_tree_node of the stored elements. + * \param tree The tree. + * \return Total memory allocated by the tree, in bytes. */ -TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree); +TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree); #endif diff --git a/tommyds/tommytypes.h b/tommyds/tommytypes.h index a63894a7..d2dd85ec 100644 --- a/tommyds/tommytypes.h +++ b/tommyds/tommytypes.h @@ -5,8 +5,8 @@ * Generic types. */ -#ifndef __TOMMYTYPES_H -#define __TOMMYTYPES_H +#ifndef TOMMY_TYPES_H +#define TOMMY_TYPES_H /******************************************************************************/ /* types */ @@ -14,15 +14,18 @@ #include #ifdef _MSC_VER +#include typedef unsigned tommy_uint32_t; /**< Generic uint32_t type. */ typedef unsigned _int64 tommy_uint64_t; /**< Generic uint64_t type. */ typedef size_t tommy_uintptr_t; /**< Generic uintptr_t type. */ #ifdef _WIN64 #define TOMMY_SIZE_BIT 64 +#define TOMMY_SIZE_MAX _UI64_MAX typedef unsigned _int64 tommy_size_t; /**< Generic size_t type. */ typedef _int64 tommy_ssize_t; /**< Generic ssize_t type. */ #else #define TOMMY_SIZE_BIT 32 +#define TOMMY_SIZE_MAX UINT_MAX typedef unsigned tommy_size_t; /**< Generic size_t type. */ typedef int tommy_ssize_t; /**< Generic ssize_t type. */ #endif @@ -33,10 +36,12 @@ typedef uint64_t tommy_uint64_t; /**< Generic uint64_t type. */ typedef uintptr_t tommy_uintptr_t; /**< Generic uintptr_t type. */ #if SIZE_MAX == UINT64_MAX #define TOMMY_SIZE_BIT 64 +#define TOMMY_SIZE_MAX SIZE_MAX typedef uint64_t tommy_size_t; /**< Generic size_t type. */ typedef int64_t tommy_ssize_t; /**< Generic ssize_t type. */ #elif SIZE_MAX == UINT32_MAX #define TOMMY_SIZE_BIT 32 +#define TOMMY_SIZE_MAX SIZE_MAX typedef uint32_t tommy_size_t; /**< Generic size_t type. */ typedef int32_t tommy_ssize_t; /**< Generic ssize_t type. */ #else @@ -51,7 +56,7 @@ typedef int tommy_bool_t; /**< Generic boolean type. */ * Generic unsigned integer type. * * It has no specific size, as it is used to store only small values. - * To make the code more efficient, a full 32 bit integer is used. + * To make the code more efficient, a full 32-bit integer is used. */ typedef tommy_uint32_t tommy_uint_t; @@ -80,7 +85,57 @@ typedef tommy_uint32_t tommy_uint_t; /** * Generic malloc(), calloc(), realloc() and free() functions. - * Redefine them to what you need. By default they map to the C malloc(), calloc(), realloc() and free(). + * + * Redefine them to what you need. By default they map to the C malloc(), + * calloc(), realloc() and free(). + * + * \par Infallible Allocation Invariant + * Tommy assumes that dynamic memory allocation never fails and that these + * functions always return a valid, non-zero pointer. The library does not + * check for allocation failures and does not return error codes for + * out-of-memory (OOM) conditions. + * + * \par Rationale + * This choice reflects the memory model of modern general-purpose operating + * systems: + * - **Virtual Memory Overcommit:** On modern systems (such as Linux with default + * overcommit heuristics), malloc() reserves virtual address space and almost + * never returns 0 on 64-bit systems. Physical memory is committed on first write, + * meaning actual exhaustion triggers the kernel OOM Killer (SIGKILL) rather + * than an allocation failure. Checking for null returns in userspace often + * provides a false sense of security. + * - **Swap Thrashing:** In systems backed by swap, severe memory pressure causes + * destructive disk thrashing, degrading throughput by orders of magnitude and + * rendering the process unusable long before an allocation call fails. + * + * \par Advantages + * - **Zero Error-Path Overhead:** No branch predictions, conditional jumps, or + * status checks inside critical insertion and resizing loops, maximizing CPU + * cache efficiency and execution speed. + * - **Clean and Lean API:** Functions (such as insertion and resizing) can remain + * simple (often returning void or direct element pointers) without cluttering + * client code with error-propagation boilerplate. + * - **Deterministic Consistency:** Avoids complex transactional rollback logic + * otherwise required if an allocation failed halfway through dynamic table + * growth or node migration. + * - **Industry Alignment:** Follows the established design of high-performance + * systems libraries (e.g., GLib's g_malloc, Git's xmalloc, Redis's zmalloc, + * and default C++ operator new). + * + * \par Trade-offs and Limitations + * - **Embedded and Bare-Metal Systems:** Systems without an MMU/virtual memory + * (e.g., RTOS, microcontrollers) or strict overcommit disabled (vm.overcommit_memory=2) + * do return 0 on heap exhaustion. + * - **Multi-Tenant Services:** Infrastructure daemons (e.g., database engines) that + * must isolate a single client's excessive memory request without crashing the + * entire process cannot rely on unchecked allocations. + * + * \par Custom Interception + * If your environment requires custom handling (such as memory tracking, structured + * aborts, or logging), redefine tommy_malloc, tommy_calloc, tommy_realloc, and + * tommy_free before including Tommy headers (or via compiler flags). + * Custom allocators must terminate the process (e.g., abort()) or initiate an + * unwinding non-local exit (e.g., longjmp or C++ exception) rather than returning 0. */ #if !defined(tommy_malloc) || !defined(tommy_calloc) || !defined(tommy_realloc) || !defined(tommy_free) #include @@ -99,7 +154,7 @@ typedef tommy_uint32_t tommy_uint_t; #endif /******************************************************************************/ -/* modificators */ +/* modifiers */ /** \internal * Definition of TOMMY_API. @@ -124,7 +179,7 @@ typedef tommy_uint32_t tommy_uint_t; * Definition of the restrict keyword if available. */ #if !defined(tommy_restrict) -#if __STDC_VERSION__ >= 199901L +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define tommy_restrict restrict #elif defined(_MSC_VER) || defined(__GNUC__) #define tommy_restrict __restrict @@ -159,12 +214,12 @@ typedef tommy_uint32_t tommy_uint_t; /* key/hash */ /** - * Type used in indexed data structures to store the key of an object. + * Type used in indexed data structures to store the key of an element. */ typedef tommy_size_t tommy_key_t; /** - * Type used in hashtables to store the hash of an object. + * Type used in hashtables to store the hash of an element. */ typedef tommy_size_t tommy_hash_t; @@ -174,11 +229,11 @@ typedef tommy_size_t tommy_hash_t; /** * Data structure node. * This node type is shared between all the data structures and used to store some - * info directly into the objects you want to store. + * info directly into the elements you want to store. * * A typical declaration is: * \code - * struct object { + * struct element { * tommy_node node; * // other fields * }; @@ -187,7 +242,7 @@ typedef tommy_size_t tommy_hash_t; typedef struct tommy_node_struct { /** * Next node. - * The tail node has it at 0, like a 0 terminated list. + * The tail node has it at 0, like a 0-terminated list. */ struct tommy_node_struct* next; @@ -198,8 +253,9 @@ typedef struct tommy_node_struct { struct tommy_node_struct* prev; /** - * Pointer to the object containing the node. + * Pointer to the element containing the node. * This field is initialized when inserting nodes into a data structure. + * The element pointer passed to insertion functions must not be 0. */ void* data; @@ -207,6 +263,7 @@ typedef struct tommy_node_struct { * Index of the node. * With tries this field is used to store the key. * With hashtables this field is used to store the hash value. + * With trees this field is used to store the parent pointer and balance factor. * With lists this field is not used. */ tommy_size_t index; @@ -217,23 +274,20 @@ typedef struct tommy_node_struct { /** * Compare function for elements. - * \param obj_a Pointer to the first object to compare. - * \param obj_b Pointer to the second object to compare. - * \return <0 if the first element is less than the second, ==0 if equal, >0 if greater. * * This function is like the C strcmp(). * * \code - * struct object { + * struct element { * tommy_node node; * int value; * }; * - * int compare(const void* obj_a, const void* obj_b) + * int compare(const void* elem_a, const void* elem_b) * { - * if (((const struct object*)obj_a)->value < ((const struct object*)obj_b)->value) + * if (((const struct element*)elem_a)->value < ((const struct element*)elem_b)->value) * return -1; - * if (((const struct object*)obj_a)->value > ((const struct object*)obj_b)->value) + * if (((const struct element*)elem_a)->value > ((const struct element*)elem_b)->value) * return 1; * return 0; * } @@ -241,64 +295,62 @@ typedef struct tommy_node_struct { * tommy_list_sort(&list, compare); * \endcode * + * \param elem_a Pointer to the first element to compare. + * \param elem_b Pointer to the second element to compare. + * \return <0 if the first element is less than the second, ==0 if equal, >0 if greater. */ -typedef int tommy_compare_func(const void* obj_a, const void* obj_b); +typedef int tommy_compare_func(const void* elem_a, const void* elem_b); /** * Search function for elements. - * \param arg Pointer to the value to search as passed at the search function. - * \param obj Pointer to the object to compare to. - * \return ==0 if the value matches the element. !=0 if different. - * - * The first argument is a pointer to the value to search exactly - * as it's passed at the search function called. - * The second argument is a pointer to the object inside the hashtable to compare. - * - * The return value has to be 0 if the values are equal. != 0 if they are different. * * \code - * struct object { + * struct element { * tommy_node node; * int value; * }; * - * int compare(const void* arg, const void* obj) + * int compare(const void* arg, const void* elem) * { * const int* value_to_find = arg; - * const struct object* object_to_compare = obj; + * const struct element* element_to_compare = elem; * - * return *value_to_find != object_to_compare->value; + * return *value_to_find != element_to_compare->value; * } * * int value_to_find = 1; - * struct object* obj = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find)); - * if (!obj) { + * struct element* elem = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!elem) { * // not found * } else { * // found * } * \endcode * + * \param arg Pointer to the value to search as passed to the search function. + * \param elem Pointer to the element to compare to. + * \return ==0 if the value matches the element. !=0 if different. */ -typedef int tommy_search_func(const void* arg, const void* obj); +typedef int tommy_search_func(const void* arg, const void* elem); /** * Foreach function. - * \param obj Pointer to the object to iterate. * - * A typical example is to use free() to deallocate all the objects in a list. + * A typical example is to use free() to deallocate all the elements in a list. * \code - * tommy_list_foreach(&list, (tommy_foreach_func*)free); + * tommy_list_foreach(&list, free); * \endcode + * + * \param elem Pointer to the element to iterate. */ -typedef void tommy_foreach_func(void* obj); +typedef void tommy_foreach_func(void* elem); /** * Foreach function with an argument. * \param arg Pointer to a generic argument. - * \param obj Pointer to the object to iterate. + * \param elem Pointer to the element to iterate. */ -typedef void tommy_foreach_arg_func(void* arg, void* obj); +typedef void tommy_foreach_arg_func(void* arg, void* elem); /******************************************************************************/ /* bit hacks */ @@ -315,7 +367,7 @@ typedef void tommy_foreach_arg_func(void* arg, void* obj); /** \internal * Integer log2 for constants. - * You can use it only for exact power of 2 up to 256. + * You can use it only for exact powers of 2 up to 256. */ #define TOMMY_ILOG2(value) ((value) == 256 ? 8 : (value) == 128 ? 7 : (value) == 64 ? 6 : (value) == 32 ? 5 : (value) == 16 ? 4 : (value) == 8 ? 3 : (value) == 4 ? 2 : (value) == 2 ? 1 : 0) @@ -355,9 +407,9 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value) */ return __builtin_clz(value) ^ 31; #else - /* Find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */ + /* find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */ /* from http://graphics.stanford.edu/~seander/bithacks.html */ - static unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = { + static const unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; @@ -372,13 +424,17 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value) #endif } -#if TOMMY_SIZE_BIT == 64 /** * Bit scan reverse or integer log2 for 64 bits. + * Return the bit index of the most significant 1 bit. + * + * If no bit is set, the result is undefined. + * \param value Value to scan. 0 is not allowed. + * \return The index of the most significant bit set. */ tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && defined(_WIN64) unsigned long count; _BitScanReverse64(&count, value); return count; @@ -393,7 +449,6 @@ tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value) return tommy_ilog2_u32(l); #endif } -#endif /** * Bit scan forward or trailing zero count. @@ -412,24 +467,28 @@ tommy_inline tommy_uint_t tommy_ctz_u32(tommy_uint32_t value) #elif defined(__GNUC__) return __builtin_ctz(value); #else - /* Count the consecutive zero bits (trailing) on the right with multiply and lookup */ + /* count the consecutive zero bits (trailing) on the right with multiply and lookup */ /* from http://graphics.stanford.edu/~seander/bithacks.html */ static const unsigned char TOMMY_DE_BRUIJN_INDEX_CTZ[32] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; - return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & - value) * 0x077CB531U)) >> 27]; + return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & -value) * 0x077CB531U)) >> 27]; #endif } -#if TOMMY_SIZE_BIT == 64 /** * Bit scan forward or trailing zero count for 64 bits. + * Return the bit index of the least significant 1 bit. + * + * If no bit is set, the result is undefined. + * \param value Value to scan. 0 is not allowed. + * \return The index of the least significant bit set. */ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && defined(_WIN64) unsigned long count; _BitScanForward64(&count, value); return count; @@ -444,16 +503,16 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value) return tommy_ctz_u32(h) + 32; #endif } -#endif /** * Rounds up to the next power of 2. * For the value 0, the result is undefined. + * \param value Value to round up. * \return The smallest power of 2 not less than the specified value. */ tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value) { - /* Round up to the next highest power of 2 */ + /* round up to the next highest power of 2 */ /* from http://graphics.stanford.edu/~seander/bithacks.html */ --value; @@ -469,6 +528,9 @@ tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value) /** * Rounds up to the next power of 2 for 64 bits. + * For the value 0, the result is undefined. + * \param value Value to round up. + * \return The smallest power of 2 not less than the specified value. */ tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value) { @@ -485,7 +547,8 @@ tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value) } /** - * Check if the specified word has a byte at 0. + * Checks if the specified word has a byte at 0. + * \param value Value to check. * \return 0 or 1. */ tommy_inline int tommy_haszero_u32(tommy_uint32_t value) @@ -493,7 +556,17 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value) return ((value - 0x01010101) & ~value & 0x80808080) != 0; } -/* +/** + * Checks if the specified 64-bit word has a byte at 0. + * \param value Value to check. + * \return 0 or 1. + */ +tommy_inline int tommy_haszero_u64(tommy_uint64_t value) +{ + return ((value - 0x0101010101010101ULL) & ~value & 0x8080808080808080ULL) != 0; +} + +/** * Bit depth mapping. */ #if TOMMY_SIZE_BIT == 64 @@ -507,3 +580,4 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value) #endif #endif +