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/selftest.c b/cmdline/selftest.c index e72d35dc..2df4c5f5 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) { @@ -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/tommyarray.h b/tommyds/tommyarray.h index f5f4d726..7ae5a801 100644 --- a/tommyds/tommyarray.h +++ b/tommyds/tommyarray.h @@ -119,3 +119,4 @@ tommy_inline tommy_size_t tommy_array_size(tommy_array* array) TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array); #endif + diff --git a/tommyds/tommyarrayblkof.h b/tommyds/tommyarrayblkof.h index 8c9edfa0..0e6b688c 100644 --- a/tommyds/tommyarrayblkof.h +++ b/tommyds/tommyarrayblkof.h @@ -87,3 +87,4 @@ tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array) 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..5e608a6c 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,7 +151,7 @@ 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. */ @@ -207,3 +209,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..706bcda2 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,237 @@ 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; + tommy_size_t i; + const unsigned char* tail; + tommy_uint32_t k; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val; - - while (key_len > 12) { - a += tommy_le_uint32_read(key + 0); - b += tommy_le_uint32_read(key + 4); - c += tommy_le_uint32_read(key + 8); - - tommy_mix(a, b, c); - - key_len -= 12; - key += 12; + for (i = 0; i < nblocks; ++i) { + k = tommy_le_uint32_read(key + i * 4); + k *= c1; + k = tommy_rot(k, 15); + k *= c2; + h ^= k; + h = tommy_rot(h, 13); + h = h * 5 + 0xe6546b64; } - 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 */ + /* avoid pointer arithmetic on a null buffer for empty input */ + tail = key_len ? key + nblocks * 4 : key; + + 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; } - tommy_final(a, b, c); + h ^= (tommy_uint32_t)key_len; + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; - return c; + 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; + tommy_size_t i; + const unsigned char* tail; + tommy_uint64_t k; - a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff); - c += init_val >> 32; - - while (key_len > 12) { - a += tommy_le_uint32_read(key + 0); - b += tommy_le_uint32_read(key + 4); - c += tommy_le_uint32_read(key + 8); - - tommy_mix(a, b, c); - - key_len -= 12; - key += 12; + for (i = 0; i < nblocks; ++i) { + k = tommy_le_uint64_read(key + i * 8); + k *= c1; + k = tommy_rot64(k, 31); + k *= c2; + h ^= k; + h = tommy_rot64(h, 27); + h = h * 5 + 0x52dce729ULL; } - 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 */ + /* avoid pointer arithmetic on a null buffer for empty input */ + tail = key_len ? key + nblocks * 8 : key; + + 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; } - tommy_final(a, b, c); + h ^= (tommy_uint64_t)key_len; + h ^= h >> 33; + h *= 0xff51afd7ed558ccdULL; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53ULL; + h ^= h >> 33; - return c + ((tommy_uint64_t)b << 32); + 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..2cb6bbb4 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,70 @@ 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; - /* 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*))); + 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*))); - for (i = 0; i < bucket_max; ++i) { - tommy_hashdyn_node* j = hashdyn->bucket[i]; + for (i = 0; i < bucket_max; ++i) { + tommy_hashdyn_node* j; - 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; + 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 { + /* 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 (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 (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 (i = 0; i < new_bucket_max; ++i) { + tommy_size_t j; + + /* all old buckets with the same new modulus must be concatenated. */ + new_bucket[i] = hashdyn->bucket[i]; + for (j = i + new_bucket_max; j < bucket_max; j += new_bucket_max) + tommy_list_concat(&new_bucket[i], &hashdyn->bucket[j]); + } } } @@ -133,6 +154,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 +180,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; @@ -221,62 +276,15 @@ 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 (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..31c26995 100644 --- a/tommyds/tommylist.c +++ b/tommyds/tommylist.c @@ -14,6 +14,86 @@ 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_chain first_chain; + tommy_chain second_chain; + tommy_node* first_head = tommy_list_head(first); + tommy_node* second_head = tommy_list_head(second); + + if (!second_head) + return; + + if (!first_head) { + *first = *second; + return; + } + + /* create chains from the non-empty lists */ + 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; diff --git a/tommyds/tommylist.h b/tommyds/tommylist.h index 88423358..d106bd12 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. @@ -322,6 +497,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 +639,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 +655,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..471be959 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) { @@ -326,35 +341,69 @@ 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_to_list_node(tree->root, list); + tommy_tree_clear(tree); +} + +TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func) +{ + /* AVL height is less than twice the bit width of its node count */ + tommy_tree_node* stack[2 * TOMMY_SIZE_BIT]; + tommy_size_t depth = 0; + tommy_tree_node* node = tree->root; + + while (node || depth) { tommy_tree_node* next; - tommy_tree_foreach_arg_node(root->prev, func, arg); + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } - /* make a copy in case func is free() */ - next = root->next; + node = stack[--depth]; + /* save the next subtree before the callback can free this node */ + next = node->next; + func(node->data); + node = next; + } +} - func(arg, root->data); +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; - root = next; + while (node || depth) { + tommy_tree_node* next; + + while (node) { + stack[depth] = node; + ++depth; + node = node->prev; + } + + node = stack[--depth]; + /* save the next subtree before the callback can free this node */ + next = node->next; + func(arg, node->data); + node = next; } } diff --git a/tommyds/tommytree.h b/tommyds/tommytree.h index 9bae8611..37e9ac0f 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,52 @@ 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. + * 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 +201,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. @@ -304,106 +326,86 @@ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree) } /** \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 the first equal node in tree order with a specific comparison function. + * The function must define a suborder of the tree comparison function. + * \param cmp Comparison function called with cmp_arg and the object in the tree. + * \param cmp_arg Argument passed as the first argument of the comparison function. + * \return The first equal node in tree order, or 0 if none. */ -tommy_inline tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data) +tommy_inline tommy_tree_node* tommy_tree_search_node_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { + tommy_tree_node* node = tree->root; + tommy_tree_node* candidate = 0; + while (node) { - int c = cmp(data, node->data); + int c = cmp(cmp_arg, node->data); if (c < 0) node = node->prev; else if (c > 0) node = node->next; - else - return node; - } - - return 0; -} - -/** - * 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. - */ -tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data) -{ - tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data); - - if (!node) - return 0; - - return node->data; -} - -/** - * 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 ::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. - */ -tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, 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; - - while (node) { - int c = cmp(data, node->data); - - if (c < 0) { + else { candidate = node; node = node->prev; - } else if (c > 0) { - node = node->next; - } else { - return node; } } 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 equal element in tree order, or 0 if none. + */ +tommy_inline void* tommy_tree_search(tommy_tree* tree, const void* data) +{ + tommy_tree_node* node = tommy_tree_search_node_compare(tree, tree->cmp, data); + + return node ? node->data : 0; +} + +/** + * 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 void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) +{ + tommy_tree_node* node = tommy_tree_search_node_compare(tree, cmp, cmp_arg); + + return node ? node->data : 0; +} + /** * 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); + tommy_tree_node* node = tree->root; + void* candidate = 0; - if (!node) - return 0; + while (node) { + int c = tree->cmp(data, node->data); - return node->data; + if (c <= 0) { + candidate = node->data; + node = node->prev; + } else { + node = node->next; + } + } + + return candidate; } /** @@ -411,42 +413,24 @@ tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data) * * Like tommy_tree_search_greater_equal() but you can specify a different comparison function. * Note that this function must define a suborder of the original one. + * The first qualifying element in tree order is returned. * - * The ::data argument will be the first argument of the comparison function, + * 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_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg) +tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { - tommy_tree_node* node = tommy_tree_search_greater_equal_node(cmp, tree->root, cmp_arg); - - 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) { + if (c <= 0) { + candidate = node->data; node = node->prev; - } else if (c > 0) { - candidate = node; - node = node->next; } else { - return node; + node = node->next; } } @@ -457,16 +441,25 @@ 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); + tommy_tree_node* node = tree->root; + void* candidate = 0; - if (!node) - return 0; + while (node) { + int c = tree->cmp(data, node->data); - return node->data; + if (c < 0) { + node = node->prev; + } else { + candidate = node->data; + node = node->next; + } + } + + return candidate; } /** @@ -474,29 +467,58 @@ tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data) * * Like tommy_tree_search_less_equal() but you can specify a different comparison function. * Note that this function must define a suborder of the original one. + * The last qualifying element in tree order is returned. * - * The ::data argument will be the first argument of the comparison function, + * 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, void* cmp_arg) +tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg) { - tommy_tree_node* node = tommy_tree_search_less_equal_node(cmp, tree->root, cmp_arg); + tommy_tree_node* node = tree->root; + void* candidate = 0; - if (!node) - return 0; + while (node) { + int c = cmp(cmp_arg, node->data); - return node->data; + if (c < 0) { + node = node->prev; + } else { + candidate = node->data; + node = node->next; + } + } + + return 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); + tommy_tree_node* node = tommy_tree_search_node_compare(tree, tree->cmp, data); + + if (!node) + return 0; + + return tommy_tree_remove_existing(tree, node); +} + +/** + * 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 = tommy_tree_search_node_compare(tree, cmp, cmp_arg); if (!node) return 0; @@ -534,18 +556,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 +