From 2dc349fe934ad7e577612907d5e33d81af88faf1 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 7 Oct 2026 18:44:12 +0200 Subject: [PATCH] Update to TommyDS v4.0 --- cmdline/elem.c | 8 +- cmdline/memory.c | 17 ++ cmdline/memory.h | 6 + cmdline/selftest.c | 137 +++++++------ tommyds/tommy.c | 3 +- tommyds/tommyarray.c | 93 +++++++-- tommyds/tommyarray.h | 118 +++++++++++- tommyds/tommyarrayblkof.c | 131 ++++++++++--- tommyds/tommyarrayblkof.h | 144 ++++++++++++-- tommyds/tommychain.h | 29 ++- tommyds/tommyhash.c | 395 +++++++++++++++++++++++--------------- tommyds/tommyhash.h | 63 ++++-- tommyds/tommyhashdyn.c | 192 +++++++++--------- tommyds/tommyhashdyn.h | 100 +++++++++- tommyds/tommylist.c | 87 ++++++++- tommyds/tommylist.h | 255 +++++++++++++++++++++++- tommyds/tommytree.c | 105 +++++++--- tommyds/tommytree.h | 384 +++++++++++++++++++++--------------- tommyds/tommytypes.h | 29 +-- 19 files changed, 1669 insertions(+), 627 deletions(-) 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/selftest.c b/cmdline/selftest.c index e72d35dc..ccd02188 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, 0x3ba63d24, 0xa766795d }, + { "a", 1, 0xc27589e2, 0xa766795d }, + { "abc", 3, 0x4f0589dd, 0xa766795d }, + { "message digest", 14, 0x7bd2e191, 0xa766795d }, + { "abcdefghijklmnopqrstuvwxyz", 26, 0xa1eebba8, 0xa766795d }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xc84d4c51, 0xa766795d }, + { "The quick brown fox jumps over the lazy dog", 43, 0x294e8a7b, 0xa766795d }, + { "\x00", 1, 0x3e1fbed8, 0xa766795d }, + { "\x16\x27", 2, 0x00233fc6, 0xa766795d }, + { "\xe2\x56\xb4", 3, 0xd3f61ec1, 0xa766795d }, + { "\xc9\x4d\x9c\xda", 4, 0xfa10da80, 0xa766795d }, + { "\x79\xf1\x29\x69\x5d", 5, 0xcd5e7468, 0xa766795d }, + { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x8ba76f9b, 0xa766795d }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xbf2129d0, 0xa766795d }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xc3d26370, 0xa766795d }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa6840538, 0xa766795d }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x36115feb, 0xa766795d }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x48a3b1fb, 0xa766795d }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x23412f54, 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 }, + { "", 0x3ba63d24, 0xa766795d }, + { "a", 0xc27589e2, 0xa766795d }, + { "abc", 0x4f0589dd, 0xa766795d }, + { "message digest", 0x7bd2e191, 0xa766795d }, + { "abcdefghijklmnopqrstuvwxyz", 0xa1eebba8, 0xa766795d }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xc84d4c51, 0xa766795d }, + { "The quick brown fox jumps over the lazy dog", 0x294e8a7b, 0xa766795d }, + { "\xff", 0x6ae84401, 0xa766795d }, + { "\x16\x27", 0x00233fc6, 0xa766795d }, + { "\xe2\x56\xb4", 0xd3f61ec1, 0xa766795d }, + { "\xc9\x4d\x9c\xda", 0xfa10da80, 0xa766795d }, + { "\x79\xf1\x29\x69\x5d", 0xcd5e7468, 0xa766795d }, + { "\xff\x7e\xdf\x1e\x31\x1c", 0xe135b2e2, 0xa766795d }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0xbf2129d0, 0xa766795d }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xc3d26370, 0xa766795d }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xa6840538, 0xa766795d }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x36115feb, 0xa766795d }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0xef961a79, 0xa766795d }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x23412f54, 0xa766795d }, + { "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0x04332eac, 0xa766795d }, + { "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xd3025b16, 0xa766795d }, + { "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x4ce44257, 0xa766795d }, + { "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x47d86f33, 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, 0xf1999eea212f89e6ULL, 0x2f022773a766795dULL }, + { "a", 1, 0x6af55c57ef3cc6e2ULL, 0x2f022773a766795dULL }, + { "abc", 3, 0x8a09859573f3ba72ULL, 0x2f022773a766795dULL }, + { "message digest", 14, 0x08a48193f341bef5ULL, 0x2f022773a766795dULL }, + { "abcdefghijklmnopqrstuvwxyz", 26, 0x8761b9f83f99b36dULL, 0x2f022773a766795dULL }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6f9a23bb4217ad12ULL, 0x2f022773a766795dULL }, + { "The quick brown fox jumps over the lazy dog", 43, 0x33e3651d073f9942ULL, 0x2f022773a766795dULL }, + { "\x00", 1, 0x9360785dad01a14eULL, 0x2f022773a766795dULL }, + { "\x16\x27", 2, 0x9d411c7d088f8c1eULL, 0x2f022773a766795dULL }, + { "\xe2\x56\xb4", 3, 0xa20865547db581afULL, 0x2f022773a766795dULL }, + { "\xc9\x4d\x9c\xda", 4, 0xb4eb664b81e7058dULL, 0x2f022773a766795dULL }, + { "\x79\xf1\x29\x69\x5d", 5, 0xf0b386ad052571b4ULL, 0x2f022773a766795dULL }, + { "\x00\x7e\xdf\x1e\x31\x1c", 6, 0xdd3b8851d859c7d4ULL, 0x2f022773a766795dULL }, + { "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xa8b94da524aabf49ULL, 0x2f022773a766795dULL }, + { "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xb913a803aea6d51dULL, 0x2f022773a766795dULL }, + { "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xb6961500a25f63beULL, 0x2f022773a766795dULL }, + { "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xfa6dccb9650a1d28ULL, 0x2f022773a766795dULL }, + { "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xc95d0a87ec8c2a98ULL, 0x2f022773a766795dULL }, + { "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x15901950afd60cd2ULL, 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/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..ea96782d 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,6 +44,77 @@ TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count) } } +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(tommy_array* array) { return array->bucket_max * (tommy_size_t)sizeof(void*); diff --git a/tommyds/tommyarray.h b/tommyds/tommyarray.h index f5f4d726..f4b4b225 100644 --- a/tommyds/tommyarray.h +++ b/tommyds/tommyarray.h @@ -50,11 +50,49 @@ TOMMY_API void tommy_array_init(tommy_array* array); */ TOMMY_API void tommy_array_done(tommy_array* array); +/** + * Allocates space for at least the specified number of elements. + * The initialized size and existing elements are unchanged. + * Existing element references remain valid. + */ +TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size); + /** * Grows the size up to the specified value. * All the new elements in the array are initialized with the 0 value. */ -TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t size); +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. + */ +TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size); + +/** + * Removes all elements, preserving the allocated capacity. + * Pointed-to objects are not freed. + * The array remains initialized and can be reused immediately. + */ +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. + */ +TOMMY_API void tommy_array_shrink(tommy_array* array); /** * Gets a reference of the element at the specified position. @@ -63,12 +101,10 @@ TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t size); */ 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]; } @@ -93,10 +129,20 @@ tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos) return *tommy_array_ref(array, pos); } +/** + * Gets the last element. + * The array must not be empty. + */ +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. */ -tommy_inline void tommy_array_insert(tommy_array* array, void* element) +tommy_inline void tommy_array_insert_tail(tommy_array* array, void* element) { tommy_size_t pos = array->count; @@ -105,6 +151,27 @@ tommy_inline void tommy_array_insert(tommy_array* array, void* element) tommy_array_set(array, pos, element); } +/** + * Removes and returns the last element. + * The array must not be empty. + * The removed slot is cleared, and allocated capacity is preserved. + */ +tommy_inline void* tommy_array_remove_tail(tommy_array* array) +{ + assert(array->count != 0); + void* element = tommy_array_tail(array); + tommy_array_resize(array, array->count - 1); + return element; +} + +/** + * Checks whether the array is empty. + */ +tommy_inline tommy_bool_t tommy_array_empty(tommy_array* array) +{ + return array->count == 0; +} + /** * Gets the initialized size of the array. */ @@ -113,9 +180,50 @@ tommy_inline tommy_size_t tommy_array_size(tommy_array* array) return array->count; } +/** + * Gets the number of elements that fit without further allocation. + */ +tommy_inline tommy_size_t tommy_array_capacity(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. + */ +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. + * + * \param array Array to iterate. + * \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 Array to iterate. + * \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. */ TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array); #endif + diff --git a/tommyds/tommyarrayblkof.c b/tommyds/tommyarrayblkof.c index da2d43c6..72e7ce48 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,123 @@ 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); + array->block[array->block_count] = ptr; + ++array->block_count; + } +} - ++block_mac; +TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t 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) +{ + 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 += array->element_size; } + + pos += chunk; + } +} + +TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg) +{ + 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 += array->element_size; + } + + pos += chunk; } } TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array) { - return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLKOF_SIZE * array->element_size; + 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..b77157cb 100644 --- a/tommyds/tommyarrayblkof.h +++ b/tommyds/tommyarrayblkof.h @@ -4,27 +4,34 @@ /** \file * Dynamic array based on blocks of fixed size. * - * 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 * 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. */ #ifndef __TOMMYARRAYBLKOF_H #define __TOMMYARRAYBLKOF_H #include "tommytypes.h" -#include "tommyarray.h" #include /* for assert */ /******************************************************************************/ /* 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,7 +42,9 @@ * \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; @@ -51,11 +60,48 @@ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t eleme */ TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array); +/** + * Allocates space for at least the specified number of elements. + * The initialized size and existing elements are unchanged. + * Existing element references remain valid. + */ +TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size); + /** * Grows the size up to the specified value. * All the new elements in the array are initialized with the 0 value. */ -TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size); +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. + */ +TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size); + +/** + * Removes all elements, preserving the allocated capacity. + * The array remains initialized and can be reused immediately. + */ +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. + */ +TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array); /** * Gets a reference of the element at the specified position. @@ -64,13 +110,47 @@ TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size) */ 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. + */ +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. + */ +tommy_inline void* tommy_arrayblkof_insert_tail(tommy_arrayblkof* array) +{ + tommy_size_t pos = array->count; + tommy_arrayblkof_grow(array, pos + 1); + return tommy_arrayblkof_ref(array, pos); +} + +/** + * Removes the last element without copying it. + * The array must not be empty. The removed slot is cleared. + */ +tommy_inline void tommy_arrayblkof_remove_tail(tommy_arrayblkof* array) +{ + assert(array->count != 0); + tommy_arrayblkof_resize(array, array->count - 1); +} + +/** + * Checks whether the array is empty. + */ +tommy_inline tommy_bool_t tommy_arrayblkof_empty(tommy_arrayblkof* array) +{ + return array->count == 0; } /** @@ -81,9 +161,51 @@ tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array) return array->count; } +/** + * Gets the number of elements that fit without further allocation. + */ +tommy_inline tommy_size_t tommy_arrayblkof_capacity(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. + */ +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. + * + * \param array Array to iterate. + * \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 Array to iterate. + * \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. */ TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array); #endif + diff --git a/tommyds/tommychain.h b/tommyds/tommychain.h index c864dccd..9d00ddc7 100644 --- a/tommyds/tommychain.h +++ b/tommyds/tommychain.h @@ -65,7 +65,8 @@ 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) @@ -101,7 +102,8 @@ 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). + * \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* first, tommy_chain* second, tommy_compare_func* cmp) @@ -149,30 +151,24 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* */ 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; /* 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 +188,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 +203,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..e9add03f 100644 --- a/tommyds/tommyhash.c +++ b/tommyds/tommyhash.c @@ -6,7 +6,7 @@ /******************************************************************************/ /* hash */ -#include /* for memcpy */ +#include /* for memcpy, strlen */ /** * Swap endianness. @@ -14,13 +14,26 @@ */ #if defined(__GNUC__) #define tommy_swap32(x) __builtin_bswap32(x) +#define tommy_swap64(x) __builtin_bswap64(x) #else tommy_inline tommy_uint32_t tommy_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); +} + +tommy_inline tommy_uint64_t tommy_swap64(tommy_uint64_t v) +{ + return ((v & 0xFF00000000000000ULL) >> 56) | + ((v & 0x00FF000000000000ULL) >> 40) | + ((v & 0x0000FF0000000000ULL) >> 24) | + ((v & 0x000000FF00000000ULL) >> 8) | + ((v & 0x00000000FF000000ULL) << 8) | + ((v & 0x0000000000FF0000ULL) << 24) | + ((v & 0x000000000000FF00ULL) << 40) | + ((v & 0x00000000000000FFULL) << 56); } #endif @@ -35,200 +48,278 @@ tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr) return v; } +tommy_inline tommy_uint64_t tommy_le_uint64_read(const void* ptr) +{ + tommy_uint64_t v; + memcpy(&v, ptr, sizeof(v)); +#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + v = tommy_swap64(v); +#endif + return v; +} + #define tommy_rot(x, k) \ (((x) << (k)) | ((x) >> (32 - (k)))) -#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) - -#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) +#define tommy_rot64(x, k) \ + (((x) << (k)) | ((x) >> (64 - (k)))) 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; + const tommy_uint32_t c1 = 0xcc9e2d51; + const tommy_uint32_t c2 = 0x1b873593; + tommy_uint32_t h = init_val; + tommy_size_t nblocks = key_len / 4; + const unsigned char* tail = key + nblocks * 4; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val; + while (nblocks >= 2) { + tommy_uint32_t k0 = tommy_le_uint32_read(key); + tommy_uint32_t k1 = tommy_le_uint32_read(key + 4); - 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); + k0 *= c1; + k1 *= c1; - tommy_mix(a, b, c); + k0 = tommy_rot(k0, 15); + k1 = tommy_rot(k1, 15); - key_len -= 12; - key += 12; + k0 *= c2; + k1 *= c2; + + h ^= k0; + h = tommy_rot(h, 13); + h = h * 5 + 0xe6546b64; + + h ^= k1; + h = tommy_rot(h, 13); + h = h * 5 + 0xe6546b64; + + key += 8; + nblocks -= 2; } - 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 */ + if (nblocks) { + tommy_uint32_t k = tommy_le_uint32_read(key); + k *= c1; + k = tommy_rot(k, 15); + k *= c2; + h ^= k; + h = tommy_rot(h, 13); + h = h * 5 + 0xe6546b64; } - tommy_final(a, b, c); + tommy_uint32_t k = 0; + switch (key_len & 3) { + case 3 : k ^= ((tommy_uint32_t)tail[2]) << 16; /* fallthrough */ + case 2 : k ^= ((tommy_uint32_t)tail[1]) << 8; /* fallthrough */ + case 1 : k ^= (tommy_uint32_t)tail[0]; + k *= c1; + k = tommy_rot(k, 15); + k *= c2; + h ^= k; + } - return c; + h ^= (tommy_uint32_t)key_len; + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; } 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; + const tommy_uint64_t c1 = 0x87c37b91114253d5ULL; + const tommy_uint64_t c2 = 0x4cf5ad432745937fULL; + tommy_uint64_t h = init_val; + tommy_size_t nblocks = key_len / 8; + const unsigned char* tail = key + nblocks * 8; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff); - c += init_val >> 32; + while (nblocks >= 2) { + tommy_uint64_t k0 = tommy_le_uint64_read(key); + tommy_uint64_t k1 = tommy_le_uint64_read(key + 8); - 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); + k0 *= c1; + k1 *= c1; - tommy_mix(a, b, c); + k0 = tommy_rot64(k0, 31); + k1 = tommy_rot64(k1, 31); - key_len -= 12; - key += 12; + k0 *= c2; + k1 *= c2; + + h ^= k0; + h = tommy_rot64(h, 27); + h = h * 5 + 0x52dce729ULL; + + h ^= k1; + h = tommy_rot64(h, 27); + h = h * 5 + 0x52dce729ULL; + + key += 16; + nblocks -= 2; } - 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 (nblocks) { + tommy_uint64_t k = tommy_le_uint64_read(key); + k *= c1; + k = tommy_rot64(k, 31); + k *= c2; + h ^= k; + h = tommy_rot64(h, 27); + h = h * 5 + 0x52dce729ULL; } - tommy_final(a, b, c); + tommy_uint64_t k = 0; + switch (key_len & 7) { + case 7 : k ^= ((tommy_uint64_t)tail[6]) << 48; /* fallthrough */ + case 6 : k ^= ((tommy_uint64_t)tail[5]) << 40; /* fallthrough */ + case 5 : k ^= ((tommy_uint64_t)tail[4]) << 32; /* fallthrough */ + case 4 : k ^= ((tommy_uint64_t)tail[3]) << 24; /* fallthrough */ + case 3 : k ^= ((tommy_uint64_t)tail[2]) << 16; /* fallthrough */ + case 2 : k ^= ((tommy_uint64_t)tail[1]) << 8; /* fallthrough */ + case 1 : k ^= (tommy_uint64_t)tail[0]; + k *= c1; + k = tommy_rot64(k, 31); + k *= c2; + h ^= k; + } - return c + ((tommy_uint64_t)b << 32); + h ^= (tommy_uint64_t)key_len; + h ^= h >> 33; + h *= 0xff51afd7ed558ccdULL; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53ULL; + h ^= h >> 33; + + return h; } 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 }; + const unsigned char* p = tommy_cast(const unsigned char*, void_key); + const tommy_uint32_t c1 = 0xcc9e2d51; + const tommy_uint32_t c2 = 0x1b873593; + tommy_uint32_t h = init_val; + tommy_size_t len = 0; - a = b = c = 0xdeadbeef + init_val; - /* this is different than original lookup3 and the result won't match */ + if (((tommy_uintptr_t)p & 3) != 0) + return tommy_hash_u32(init_val, void_key, strlen(tommy_cast(const char*, void_key))); + /* + * Fast path: string pointer is already aligned to 4 bytes. + * This assumes page-granularity protection and page boundaries aligned to + * 4 bytes, so each aligned load stays within a readable page. + * The final load may read up to 3 bytes past the terminator and outside + * the allocated object. Staying within the page avoids a page-boundary + * fault, but an out-of-object read is still undefined behavior in ISO C + * and can be reported by ASan. This path assumes the compiler and runtime + * tolerate these overreads. + */ while (1) { - tommy_uint32_t v = tommy_le_uint32_read(key); + tommy_uint32_t v = tommy_le_uint32_read(p); 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]; - } - } + tommy_uint32_t z = (v - 0x01010101) & ~v & 0x80808080; + tommy_uint_t zero_idx = tommy_ctz_u32(z) / 8; + if (zero_idx != 0) { + tommy_uint32_t mask = (1U << (zero_idx * 8)) - 1; + tommy_uint32_t k = v & mask; + k *= c1; + k = tommy_rot(k, 15); + k *= c2; + h ^= k; + len += zero_idx; + } break; } - a += v; + tommy_uint32_t k = v; + k *= c1; + k = tommy_rot(k, 15); + k *= c2; + h ^= k; + h = tommy_rot(h, 13); + h = h * 5 + 0xe6546b64; - 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; + p += 4; + len += 4; } - /* 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 */ + h ^= len; + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; - tommy_final(a, b, c); - - return c; + return h; +} + +TOMMY_API tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key) +{ + const unsigned char* p = tommy_cast(const unsigned char*, void_key); + const tommy_uint64_t c1 = 0x87c37b91114253d5ULL; + const tommy_uint64_t c2 = 0x4cf5ad432745937fULL; + tommy_uint64_t h = init_val; + tommy_size_t len = 0; + + if (((tommy_uintptr_t)p & 7) != 0) + return tommy_hash_u64(init_val, void_key, strlen(tommy_cast(const char*, void_key))); + + /* + * Fast path: string pointer is already aligned to 8 bytes. + * This assumes page-granularity protection and page boundaries aligned to + * 8 bytes, so each aligned load stays within a readable page. + * The final load may read up to 7 bytes past the terminator and outside + * the allocated object. Staying within the page avoids a page-boundary + * fault, but an out-of-object read is still undefined behavior in ISO C + * and can be reported by ASan. This path assumes the compiler and runtime + * tolerate these overreads. + */ + while (1) { + tommy_uint64_t v = tommy_le_uint64_read(p); + + if (tommy_haszero_u64(v)) { + tommy_uint64_t z = (v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL; + tommy_uint_t zero_idx = tommy_ctz_u64(z) / 8; + + if (zero_idx != 0) { + tommy_uint64_t mask = (1ULL << (zero_idx * 8)) - 1; + tommy_uint64_t k = v & mask; + k *= c1; + k = tommy_rot64(k, 31); + k *= c2; + h ^= k; + len += zero_idx; + } + break; + } + + tommy_uint64_t k = v; + k *= c1; + k = tommy_rot64(k, 31); + k *= c2; + h ^= k; + h = tommy_rot64(h, 27); + h = h * 5 + 0x52dce729ULL; + + p += 8; + len += 8; + } + + h ^= len; + h ^= h >> 33; + h *= 0xff51afd7ed558ccdULL; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53ULL; + h ^= h >> 33; + + return h; } diff --git a/tommyds/tommyhash.h b/tommyds/tommyhash.h index 763f5a48..13d2113c 100644 --- a/tommyds/tommyhash.h +++ b/tommyds/tommyhash.h @@ -15,12 +15,8 @@ /** * 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(). - * - * 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. + * Implementation of MurmurHash3 (x86_32) by Austin Appleby, + * from https://github.com/aappleby/smhasher * * \param init_val Initialization value. * Using a different initialization value, you can generate a completely different set of hash values. @@ -28,19 +24,19 @@ * \param void_key Pointer to the data to hash. * \param key_len Size of the data to hash. * \note - * This function is endianness independent. + * 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 not state-of-the-art against malicious collision attacks, but it is simple and fast. * \return The hash value of 32 bits. */ 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(). - * - * 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. + * Implementation of MurmurHash3 (x64_64) by Austin Appleby, + * from https://github.com/aappleby/smhasher * * \param init_val Initialization value. * Using a different initialization value, you can generate a completely different set of hash values. @@ -48,29 +44,57 @@ TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* voi * \param void_key Pointer to the data to hash. * \param key_len Size of the data to hash. * \note - * This function is endianness independent. + * 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 not state-of-the-art against malicious collision attacks, but it is simple and fast. * \return The hash value of 64 bits. */ 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(). + * Implementation is based on MurmurHash3 (x86_32) by Austin Appleby, + * from https://github.com/aappleby/smhasher * - * 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. + * If the string is aligned, it operates in a single pass without calling strlen(). * * \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. * \note - * This function is endianness independent. + * This function is endianness independent and alignment independent. + * 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 not state-of-the-art against malicious collision attacks, but it is simple and fast. * \return The hash value of 32 bits. */ TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key); +/** + * String hash function with a 64 bits result. + * Implementation is based on MurmurHash3 (x64_64) by Austin Appleby, + * from https://github.com/aappleby/smhasher + * + * This hash is designed to handle strings with an unknown length. + * If the string is aligned, it operates in a single pass without calling strlen(). + * + * \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. + * \note + * This function is endianness independent and alignment independent. + * 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 not state-of-the-art against malicious collision attacks, but it is simple and fast. + * \return The hash value of 64 bits. + */ +TOMMY_API tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key); + /** * Integer reversible hash function for 32 bits. * Implementation of the Robert Jenkins "4-byte Integer Hashing", @@ -108,3 +132,4 @@ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key) } #endif + diff --git a/tommyds/tommyhashdyn.c b/tommyds/tommyhashdyn.c index 54d213f2..2476e229 100644 --- a/tommyds/tommyhashdyn.c +++ b/tommyds/tommyhashdyn.c @@ -4,6 +4,8 @@ #include "tommyhashdyn.h" #include "tommylist.h" +#include /* for memset */ + /******************************************************************************/ /* hashdyn */ @@ -23,6 +25,12 @@ 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. */ @@ -37,57 +45,62 @@ 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]; + 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]; + + 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]); + } } } @@ -133,6 +146,19 @@ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count) tommy_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_hashdyn_resize(hashdyn, bucket_bit); +} + TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash) { tommy_size_t pos = hash & hashdyn->bucket_mask; @@ -146,6 +172,27 @@ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* 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) +{ + void* existing = tommy_hashdyn_search(hashdyn, cmp, cmp_arg, hash); + if (existing) + return existing; + + tommy_hashdyn_insert(hashdyn, node, data, hash); + 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) { tommy_size_t pos = node->index & hashdyn->bucket_mask; @@ -185,9 +232,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 +248,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) { @@ -221,62 +266,13 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn) + 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; - /* 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; - } - } + for (tommy_size_t pos = 0; pos < hashdyn->bucket_max; ++pos) + tommy_list_concat(list, &hashdyn->bucket[pos]); /* clear all */ - memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*)); - hashdyn->count = 0; + tommy_hashdyn_clear(hashdyn); } diff --git a/tommyds/tommyhashdyn.h b/tommyds/tommyhashdyn.h index 9bce46be..457e2c7b 100644 --- a/tommyds/tommyhashdyn.h +++ b/tommyds/tommyhashdyn.h @@ -4,9 +4,9 @@ /** \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(). @@ -32,7 +32,7 @@ * 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. + * of the node, and the hash to initialize the tommy_node::index field. * * \code * struct object { @@ -48,7 +48,7 @@ * tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object * \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 @@ -114,6 +114,7 @@ #define __TOMMYHASHDYN_H #include "tommyhash.h" +#include "tommylist.h" /******************************************************************************/ /* hashdyn */ @@ -155,6 +156,17 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn); */ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn); +/** + * Removes all elements, preserving the allocated buckets. + * The hashtable remains initialized and can be reused immediately. + * Objects 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 objects. + * Subsequent insertions and removals retain the normal resizing policy. + * \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. * @@ -164,11 +176,40 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn); */ 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 two, 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. + * Objects 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. + * \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. */ 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's tommy_node::data field is returned, + * and the hashtable and candidate node are left unchanged. + * Otherwise, the candidate is inserted using the normal insertion policy, + * and its data field is returned. + * Objects are not freed by this call. + * \param node The candidate node. It must not belong to any container. + * \param data The object 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, anything other 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's data field, 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. @@ -225,6 +266,22 @@ tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_fun */ TOMMY_API void* tommy_hashdyn_remove_existing(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 object 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 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. * @@ -260,6 +317,9 @@ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_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. */ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg); @@ -271,6 +331,23 @@ tommy_inline tommy_size_t tommy_hashdyn_count(tommy_hashdyn* hashdyn) return hashdyn->count; } +/** + * Checks if empty. + * \return If the hashtable is empty. + */ +tommy_inline tommy_bool_t tommy_hashdyn_empty(tommy_hashdyn* hashdyn) +{ + return hashdyn->count == 0; +} + +/** + * Gets the number of buckets. + */ +tommy_inline tommy_size_t tommy_hashdyn_bucket_count(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. @@ -285,17 +362,19 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn); * 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 * - 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 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. * @@ -311,8 +390,9 @@ 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. */ 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..c410d4ce 100644 --- a/tommyds/tommylist.c +++ b/tommyds/tommylist.c @@ -14,17 +14,96 @@ tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node* *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_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_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; diff --git a/tommyds/tommylist.h b/tommyds/tommylist.h index 88423358..4ad3d719 100644 --- a/tommyds/tommylist.h +++ b/tommyds/tommylist.h @@ -60,6 +60,40 @@ * } * \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 object* obj = i->data; + * + * printf("%d\n", obj->value); + * + * i = tommy_list_prev(&list, i); + * } + * \endcode + * + * 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 object 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. * This can be done with the tommy_list_foreach() function. @@ -67,6 +101,7 @@ * \code * // deallocates all the objects iterating the list * tommy_list_foreach(&list, free); + * tommy_list_init(&list); // resets the list before reuse * \endcode */ @@ -115,6 +150,22 @@ 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(). + * \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. + */ +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. @@ -286,6 +337,130 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node return node->data; } +/** + * Removes the element at the head of the list. + * The object is not deallocated. + * \note The removed node content is left unchanged, as in tommy_list_remove_existing(). + * \param list The list. + * \return The tommy_node::data field of the node removed, 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; + + return tommy_list_remove_existing(list, node); +} + +/** + * Removes the element at the tail of the list. + * The object is not deallocated. + * \note The removed node content is left unchanged, as in tommy_list_remove_existing(). + * \param list The list. + * \return The tommy_node::data field of the node removed, or 0 if the list is empty. + */ +tommy_inline void* tommy_list_remove_tail(tommy_list* list) +{ + tommy_node* node = tommy_list_tail(list); + + if (!node) + return 0; + + return tommy_list_remove_existing(list, node); +} + +/** + * 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(list, node, node->data); +} + +/** + * 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 == tommy_list_tail(list)) + return; + + tommy_list_remove_existing(list, node); + tommy_list_insert_tail(list, node, node->data); +} + +/** + * 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 The list receiving node and all following nodes. + * It must be distinct from list and need not be initialized. Its previous value + * is overwritten without removing or deallocating any nodes previously referenced by second. + * \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 tommy_node::data and tommy_node::index fields are left unchanged. + * \param first The first list. + * \param second The second list. The lists must not share nodes, + * unless first and second are the same pointer, in which case nothing is done. + * \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; +} + /** * Concats two lists. * The second list is concatenated at the first list. @@ -295,24 +470,20 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node */ 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,6 +493,72 @@ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second) first_tail->next = second_head; } +/** + * Prepends two lists. + * The second list is prepended at 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. + */ +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 be nonzero and 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 be nonzero and 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. @@ -398,6 +635,9 @@ 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. */ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg) { @@ -411,3 +651,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..1f9de33e 100644 --- a/tommyds/tommytree.c +++ b/tommyds/tommytree.c @@ -155,28 +155,33 @@ 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_tree_insert_impl(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; - 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) + 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; - else - return parent->data; + } } + if (existing) + return existing->data; + + node->data = data; + node->prev = 0; + node->next = 0; node->index = (tommy_size_t)(tommy_uintptr_t)parent; *link = node; ++tree->count; @@ -186,6 +191,16 @@ TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* return node->data; } +TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data) +{ + tommy_tree_insert_impl(tree, node, data, 0); +} + +TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data) +{ + return tommy_tree_insert_impl(tree, node, data, 1); +} + tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk) { while (node) { @@ -202,10 +217,8 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n } if (balance == 0) { - tommy_tree_node* parent; - tommy_tree_balance_set(node, balance); - parent = tommy_tree_parent(node); + tommy_tree_node* parent = tommy_tree_parent(node); if (!parent) return; left_shrunk = node == parent->prev; @@ -326,35 +339,65 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no return data; } -TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func) +tommy_inline void tommy_tree_to_list_node(tommy_tree_node* node, tommy_list* list) { - while (root) { - tommy_tree_node* next; + if (node) { + tommy_tree_node* right = node->next; - tommy_tree_foreach_node(root->prev, func); - - /* make a copy in case func is free() */ - next = root->next; - - func(root->data); - - root = next; + tommy_tree_to_list_node(node->prev, list); + /* list insertion overwrites child links, so keep the right subtree reachable */ + tommy_list_insert_tail(list, node, node->data); + tommy_tree_to_list_node(right, list); } } -TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg) +TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list) { - while (root) { - tommy_tree_node* next; + tommy_tree_to_list_node(tree->root, list); + tommy_tree_clear(tree); +} - tommy_tree_foreach_arg_node(root->prev, func, arg); +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; - /* make a copy in case func is free() */ - next = root->next; + while (node || depth) { + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } - func(arg, root->data); + 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; + } +} - root = next; +TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg) +{ + /* 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(arg, node->data); + node = next; } } diff --git a/tommyds/tommytree.h b/tommyds/tommytree.h index 9bae8611..9492238a 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. @@ -39,8 +40,8 @@ * tommy_tree_insert(&tree, &obj->node, obj); // inserts the object * \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 }; @@ -67,8 +68,8 @@ * } * \endcode * - * To remove an element from the tree you have to call tommy_tree_remove() - * providing the key to search and remove. + * 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 }; @@ -111,6 +112,7 @@ #define __TOMMYTREE_H #include "tommytypes.h" +#include "tommylist.h" /******************************************************************************/ /* tree */ @@ -145,16 +147,68 @@ 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. + * 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_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. + * Objects 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 objects. + * \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 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. */ -TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data); +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 its data field is returned. + * \param node Pointer to the node embedded into the object to insert. + * \param data Pointer to the object 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. + * Objects 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. @@ -163,22 +217,6 @@ TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* */ 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. @@ -196,8 +234,6 @@ tommy_inline tommy_tree_node* tommy_tree_parent(tommy_tree_node* node) */ 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 +241,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); @@ -221,8 +257,6 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node) */ 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 +264,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); @@ -303,87 +337,71 @@ 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. +/** + * Searches an element in the tree with a specific comparison function. + * + * 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 cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the objects in the tree. */ -tommy_inline tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data) +tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { + tommy_tree_node* node = tree->root; + void* candidate = 0; + while (node) { - int c = cmp(data, node->data); + int c = cmp(cmp_arg, node->data); if (c < 0) node = node->prev; else if (c > 0) node = node->next; - else - return node; + else { + candidate = node->data; + node = node->prev; + } } - return 0; + return candidate; } /** * Searches an element in the tree. * If the element is not found, 0 is returned. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The first equal element in tree order, or 0 if none. */ -tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data) +tommy_inline void* tommy_tree_search(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_search_compare(tree, tree->cmp, data); } /** - * Searches an element in the tree with a specific comparison function. + * Searches an element in the tree with key greater or equal than the specified one with a specific comparison function. * - * Like tommy_tree_search() but you can specify a different comparison function. + * 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, + * The cmp_arg argument will be the first argument of the comparison function, * and it can be of a different type than the objects in the tree. */ -tommy_inline void* tommy_tree_search_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_node(cmp, tree->root, cmp_arg); - - if (!node) - return 0; - - return node->data; -} - -/** \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. - * \param data Element used for comparison. - * \return The first node found, 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_tree_node* candidate = 0; + tommy_tree_node* node = tree->root; + void* candidate = 0; while (node) { - int c = cmp(data, node->data); + int c = cmp(cmp_arg, node->data); - if (c < 0) { - candidate = node; + if (c <= 0) { + candidate = node->data; node = node->prev; - } else if (c > 0) { - node = node->next; } else { - return node; + node = node->next; } } @@ -394,59 +412,74 @@ 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. * If no such element exists, 0 is returned. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The first element 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 void* tommy_tree_search_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_search_greater_equal_compare(tree, tree->cmp, data); } /** - * Searches an element in the tree with key greater or equal than the specified one with a specific comparison function. - * - * 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 ::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. + * 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 cmp Comparison function called with cmp_arg and the object in the tree. + * \param cmp_arg Search key, which may have a different type from tree objects. + * \return The first strictly greater element, 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_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); - - if (!node) - return 0; - - return node->data; -} - -/** \internal - * Searches a node in the tree with key less 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 data Element used for comparison. - * \return The first node found, 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_tree_node* candidate = 0; + tommy_tree_node* node = tree->root; + void* candidate = 0; while (node) { - int c = cmp(data, node->data); + int c = cmp(cmp_arg, node->data); + + if (c < 0) { + candidate = node->data; + node = node->prev; + } else { + node = node->next; + } + } + + return candidate; +} + +/** + * Searches the first element in tree order with key strictly greater than the specified one. + * If no such element exists, 0 is returned. + * \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) +{ + return tommy_tree_search_greater_compare(tree, tree->cmp, data); +} + +/** + * Searches an element in the tree with key less or equal than the specified one with a specific comparison function. + * + * 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 cmp_arg argument will be the first argument of the comparison function, + * and it can be of a different type than the objects in the tree. + */ +tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tree->root; + void* candidate = 0; + + while (node) { + int c = cmp(cmp_arg, node->data); if (c < 0) { node = node->prev; - } else if (c > 0) { - candidate = node; - node = node->next; } else { - return node; + candidate = node->data; + node = node->next; } } @@ -457,51 +490,93 @@ 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. * If no such element exists, 0 is returned. * \param data Element used for comparison. - * \return The first element found, or 0 if none. + * \return The last element in tree order with key less or equal, or 0 if none. */ -tommy_inline void* tommy_tree_search_less_equal(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_less_equal_node(tree->cmp, tree->root, data); - - if (!node) - return 0; - - return node->data; + return tommy_tree_search_less_equal_compare(tree, tree->cmp, data); } /** - * Searches an element in the tree with key less or equal than the specified one with a specific comparison function. - * - * 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 ::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. + * 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 cmp Comparison function called with cmp_arg and the object in the tree. + * \param cmp_arg Search key, which may have a different type from tree objects. + * \return The last strictly lesser element, 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_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 = tree->root; + void* candidate = 0; - if (!node) + while (node) { + int c = cmp(cmp_arg, node->data); + + if (c > 0) { + candidate = node->data; + node = node->next; + } else { + node = node->prev; + } + } + + return candidate; +} + +/** + * Searches the last element in tree order with key strictly less than the specified one. + * If no such element exists, 0 is returned. + * \param data Element used for comparison. + * \return The last strictly lesser element, or 0 if none. + */ +tommy_inline void* tommy_tree_search_less(tommy_tree* tree, const void* data) +{ + return tommy_tree_search_less_compare(tree, tree->cmp, data); +} + +/** + * Searches and removes an element 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 objects in the tree. + * \param cmp Comparison function used to find the element. + * \param cmp_arg Argument passed to the comparison function. + * \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* node = tree->root; + tommy_tree_node* candidate = 0; + + while (node) { + int c = cmp(cmp_arg, node->data); + + if (c < 0) + node = node->prev; + else if (c > 0) + node = node->next; + else { + candidate = node; + node = node->prev; + } + } + + if (!candidate) return 0; - return node->data; + return tommy_tree_remove_existing(tree, candidate); } /** * Searches and removes an element. * If the element is not found, 0 is returned. * \param data Element used for comparison. - * \return The removed element, or 0 if not found. + * \return The first equal element in tree order, or 0 if not found. */ -tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data) +tommy_inline void* tommy_tree_remove(tommy_tree* tree, const void* data) { - tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data); - - if (!node) - return 0; - - return tommy_tree_remove_existing(tree, node); + return tommy_tree_remove_compare(tree, tree->cmp, data); } /** @@ -534,18 +609,15 @@ tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data) * tommy_tree_foreach(&tree, free); * \endcode */ -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. */ -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. diff --git a/tommyds/tommytypes.h b/tommyds/tommytypes.h index a63894a7..155ffb1d 100644 --- a/tommyds/tommytypes.h +++ b/tommyds/tommytypes.h @@ -200,6 +200,7 @@ typedef struct tommy_node_struct { /** * Pointer to the object containing the node. * This field is initialized when inserting nodes into a data structure. + * The object pointer passed to insertion functions must not be 0. */ void* data; @@ -355,7 +356,7 @@ 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] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, @@ -372,13 +373,12 @@ 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. */ 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 +393,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 +411,23 @@ 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. */ 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,7 +442,6 @@ 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. @@ -453,7 +450,7 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t 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; @@ -493,7 +490,16 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value) return ((value - 0x01010101) & ~value & 0x80808080) != 0; } -/* +/** + * Check if the specified 64-bit word has a byte at 0. + * \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 +513,4 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value) #endif #endif +