Update to TommyDS v4.0

This commit is contained in:
Andrea Mazzoleni 2026-10-07 18:44:12 +02:00
parent a39fb132f8
commit 88c54f041e
21 changed files with 3032 additions and 1095 deletions

View file

@ -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 */

View file

@ -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) {

View file

@ -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.

View file

@ -150,7 +150,7 @@ static const uint64_t MUSEAIR_CONSTANT[13] = {
do { \
l = a; \
h = b; \
util_mum(&l, &h); \
tommy_mum(&l, &h); \
} while (0)
#define likely(a) tommy_likely(a)

View file

@ -51,25 +51,25 @@ struct hash_test_vector {
* Test vectors for tommy_hash32
*/
static struct hash32_test_vector TEST_HASH32[] = {
{ "", 0, 0x8614384c, 0xa766795d },
{ "a", 1, 0x12c16c36, 0xa766795d },
{ "abc", 3, 0xc58e8af5, 0xa766795d },
{ "message digest", 14, 0x006b32f1, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x7e6fcfe0, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x8604adf8, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 43, 0xdeba3d3a, 0xa766795d },
{ "\x00", 1, 0x4a7d1c33, 0xa766795d },
{ "\x16\x27", 2, 0x8b50899b, 0xa766795d },
{ "\xe2\x56\xb4", 3, 0x60406493, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 4, 0xa049144a, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 5, 0x4da2c2f1, 0xa766795d },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x59de30cf, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x219e149c, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x25067520, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa1f368d8, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x805fc63d, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x7f75dd0f, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0xb9154382, 0xa766795d },
{ "", 0, 0xaffe48d7, 0xa766795d },
{ "a", 1, 0x2ae4a38d, 0xa766795d },
{ "abc", 3, 0x0aefd0ef, 0xa766795d },
{ "message digest", 14, 0x828c4a8a, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0xc8072ea9, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xe8e24812, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 43, 0xae34960f, 0xa766795d },
{ "\x00", 1, 0xac5373be, 0xa766795d },
{ "\x16\x27", 2, 0x321c4629, 0xa766795d },
{ "\xe2\x56\xb4", 3, 0x66b8fbd8, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 4, 0xbc37eb40, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 5, 0xb96d1628, 0xa766795d },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x760cf131, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xd1d5a37a, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x995d591f, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x8c7cfa48, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xaa8fa306, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x9c1d4af8, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x1a2214ec, 0xa766795d },
{ 0, 0, 0, 0 }
};
@ -77,29 +77,29 @@ static struct hash32_test_vector TEST_HASH32[] = {
* Test vectors for tommy_strhash32
*/
struct strhash32_test_vector TEST_STRHASH32[] = {
{ "", 0x0af1416d, 0xa766795d },
{ "a", 0x68fa0f3f, 0xa766795d },
{ "abc", 0xfc68ffc5, 0xa766795d },
{ "message digest", 0x08477b63, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 0x5b9c25e5, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x1e530ce7, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 0xaf93eefe, 0xa766795d },
{ "\xff", 0xfc88801b, 0xa766795d },
{ "\x16\x27", 0xcd7216db, 0xa766795d },
{ "\xe2\x56\xb4", 0x05f98d02, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 0xf65206f8, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 0x72bd6bda, 0xa766795d },
{ "\xff\x7e\xdf\x1e\x31\x1c", 0x57dfb9b4, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0x499ff634, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xe896b7ce, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xfe3939f0, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x4351d482, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x88e92135, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x01109c16, 0xa766795d },
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xbcb050dc, 0xa766795d },
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xbe5e1fd5, 0xa766795d },
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x70d8c97f, 0xa766795d },
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x957440a9, 0xa766795d },
{ "", 0xaffe48d7, 0xa766795d },
{ "a", 0x2ae4a38d, 0xa766795d },
{ "abc", 0x0aefd0ef, 0xa766795d },
{ "message digest", 0x828c4a8a, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 0xc8072ea9, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xe8e24812, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 0xae34960f, 0xa766795d },
{ "\xff", 0x9ab96198, 0xa766795d },
{ "\x16\x27", 0x321c4629, 0xa766795d },
{ "\xe2\x56\xb4", 0x66b8fbd8, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 0xbc37eb40, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 0xb96d1628, 0xa766795d },
{ "\xff\x7e\xdf\x1e\x31\x1c", 0xaf63217b, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0xd1d5a37a, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0x995d591f, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0x8c7cfa48, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0xaa8fa306, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x59437055, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x1a2214ec, 0xa766795d },
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xb5d1be68, 0xa766795d },
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xb2475cc8, 0xa766795d },
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x3cf85197, 0xa766795d },
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x8ec35c09, 0xa766795d },
{ 0, 0, 0 }
};
@ -107,25 +107,25 @@ struct strhash32_test_vector TEST_STRHASH32[] = {
* Test vectors for tommy_hash64
*/
static struct hash64_test_vector TEST_HASH64[] = {
{ "", 0, 0x8614384cb5165fbfULL, 0x2f022773a766795dULL },
{ "a", 1, 0x1a2e0298a8e94a3dULL, 0x2f022773a766795dULL },
{ "abc", 3, 0x7555796b7a7d21ebULL, 0x2f022773a766795dULL },
{ "message digest", 14, 0x9411a57d04b92fb4ULL, 0x2f022773a766795dULL },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x3ca3f8d2b4e69832ULL, 0x2f022773a766795dULL },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6dae542ba0015a4dULL, 0x2f022773a766795dULL },
{ "The quick brown fox jumps over the lazy dog", 43, 0xe06d8cbb3d2ea1a6ULL, 0x2f022773a766795dULL },
{ "\x00", 1, 0x201e664fb5f2c021ULL, 0x2f022773a766795dULL },
{ "\x16\x27", 2, 0xef42fa8032c4b775ULL, 0x2f022773a766795dULL },
{ "\xe2\x56\xb4", 3, 0x6e6c498a6688466cULL, 0x2f022773a766795dULL },
{ "\xc9\x4d\x9c\xda", 4, 0x5195005419905423ULL, 0x2f022773a766795dULL },
{ "\x79\xf1\x29\x69\x5d", 5, 0x221235b48afee7c1ULL, 0x2f022773a766795dULL },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x1b1f18b9266f095bULL, 0x2f022773a766795dULL },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x2cbafa8e741d49caULL, 0x2f022773a766795dULL },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x4677f04c06e0758dULL, 0x2f022773a766795dULL },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x5afe09e8214e2163ULL, 0x2f022773a766795dULL },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x115b6276d209fab6ULL, 0x2f022773a766795dULL },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xd0636d2f01cf3a3eULL, 0x2f022773a766795dULL },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x6d259f5fef74f93eULL, 0x2f022773a766795dULL },
{ "", 0, 0xf64d2d2164ca4e35ULL, 0x2f022773a766795dULL },
{ "a", 1, 0xcb0b7863fd912608ULL, 0x2f022773a766795dULL },
{ "abc", 3, 0x023adbd4412dfbc2ULL, 0x2f022773a766795dULL },
{ "message digest", 14, 0x8d16f620b58a030eULL, 0x2f022773a766795dULL },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0xa615ea6b87f2b63fULL, 0x2f022773a766795dULL },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xb9f145676ebdb4fcULL, 0x2f022773a766795dULL },
{ "The quick brown fox jumps over the lazy dog", 43, 0x99b05c9548b523dcULL, 0x2f022773a766795dULL },
{ "\x00", 1, 0x432204e0ea8617fbULL, 0x2f022773a766795dULL },
{ "\x16\x27", 2, 0x5118c58166c6ab3eULL, 0x2f022773a766795dULL },
{ "\xe2\x56\xb4", 3, 0x29484604a9976acbULL, 0x2f022773a766795dULL },
{ "\xc9\x4d\x9c\xda", 4, 0xf000ac44a9f98f4aULL, 0x2f022773a766795dULL },
{ "\x79\xf1\x29\x69\x5d", 5, 0x9603fc74ec52e5aaULL, 0x2f022773a766795dULL },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0xd39f549b363c8e2bULL, 0x2f022773a766795dULL },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x1be47d9f53a2a31dULL, 0x2f022773a766795dULL },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xe3fa24e31af8c4ebULL, 0x2f022773a766795dULL },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xef8c3dcec426a25dULL, 0x2f022773a766795dULL },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xed0be01a88bde7cbULL, 0x2f022773a766795dULL },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xf9b50d9db5dfa543ULL, 0x2f022773a766795dULL },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x695329dad2094e8aULL, 0x2f022773a766795dULL },
{ 0, 0, 0, 0 }
};
@ -188,6 +188,13 @@ static void test_hash(void)
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
digest = tommy_hash_u32(TEST_STRHASH32[i].seed, buffer_aligned, strlen(TEST_STRHASH32[i].data));
if (digest != TEST_STRHASH32[i].digest) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Failed strhash32 test\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
for (i = 0; TEST_HASH64[i].data; ++i) {
@ -456,7 +463,7 @@ static void test_tommy(void)
tommy_arrayblkof_init(&arrayblkof, sizeof(unsigned));
for (i = 0; i < TOMMY_SIZE; ++i) {
tommy_array_insert(&array, &node[i]);
tommy_array_insert_tail(&array, &node[i]);
tommy_arrayblkof_grow(&arrayblkof, i + 1);
*(unsigned*)tommy_arrayblkof_ref(&arrayblkof, i) = i;
}
@ -584,7 +591,11 @@ static void test_tommy(void)
tommy_tree_insert(&tree, &node[i], (void*)(uintptr_t)(i + 1));
/* try to insert a duplicate, count should not change */
tommy_tree_insert(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1);
if (tommy_tree_insert_unique(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1) != (void*)(uintptr_t)1) {
/* LCOV_EXCL_START */
goto bail;
/* LCOV_EXCL_STOP */
}
if (tommy_tree_count(&tree) != TOMMY_SIZE) {
/* LCOV_EXCL_START */

View file

@ -7,6 +7,7 @@
#include "util.h"
#include "raid/cpu.h"
#include "raid/memory.h"
#include "tommyds/tommyhash.h"
/****************************************************************************/
/* memory */
@ -610,112 +611,6 @@ static inline void util_write64(void* ptr, uint64_t v)
memcpy(ptr, &v, sizeof(v));
}
/**
* 64*64 -> 128bit multiply function.
*
* @param A Address of 64-bit number.
* @param B Address of 64-bit number.
*
* Calculates 128-bit C = *A * *B.
*
* Overwrites A contents with C's low 64 bits.
* Overwrites B contents with C's high 64 bits.
*/
#if defined(__x86_64__) || defined(_M_X64)
/**
* This is intentionally placed before the __SIZEOF_INT128__ implementation.
*
* This implementation happens to generate more efficient code.
* While this likely depends only on the compiler, as long as it remains faster,
* we will keep it in this order.
*/
static inline void util_mum(uint64_t* A, uint64_t* B)
{
uint64_t a = *A;
uint64_t b = *B;
uint64_t low, high;
__asm__ (
"mulq %3"
: "=a" (low), "=d" (high)
: "a" (a), "rm" (b)
: "cc"
);
*A = low;
*B = high;
}
#elif defined(__SIZEOF_INT128__)
static inline void util_mum(uint64_t* A, uint64_t* B)
{
__uint128_t product = ((__uint128_t)*A) * ((__uint128_t)*B);
*A = (uint64_t)product;
*B = (uint64_t)(product >> 64);
}
#elif defined(__aarch64__) || defined(_M_ARM64)
/**
* This is intentionally placed after the __SIZEOF_INT128__ implementation.
*
* Although the code looks correct, there is a report of this
* implementation failing the self-test on macOS (Darwin 25.4.0, arm64)
* with Apple clang 21.0.0 (clang-2100.0.123.102).
*
* See: https://github.com/amadvance/snapraid/issues/43
*
* Note that the implementation is the same of OpenSSL:
*
* #elif defined(__aarch64__) && defined(SIXTY_FOUR_BIT_LONG)
* #if defined(__GNUC__) && __GNUC__ >= 2
* #define BN_UMULT_HIGH(a, b) ({ \
* register BN_ULONG ret; \
* asm ("umulh %0,%1,%2" \
* : "=r"(ret) \
* : "r"(a), "r"(b)); \
* ret; })
* #endif
* #endif
*
* See: https://github.com/openssl/openssl/blob/0463cbf185680e66a3a8960f10cd33001377d28e/crypto/bn/bn_local.h#L439
*/
static inline void util_mum(uint64_t* A, uint64_t* B)
{
uint64_t a = *A;
uint64_t b = *B;
uint64_t low = a * b;
uint64_t high;
__asm__ (
"umulh %0, %1, %2"
: "=r" (high)
: "r" (a), "r" (b)
);
*A = low;
*B = high;
}
#else
static inline void util_mum(uint64_t* A, uint64_t* B)
{
uint64_t a = *A;
uint64_t b = *B;
uint64_t a_lo = (uint32_t)a;
uint64_t a_hi = a >> 32;
uint64_t b_lo = (uint32_t)b;
uint64_t b_hi = b >> 32;
uint64_t p0 = a_lo * b_lo;
uint64_t p1 = a_lo * b_hi;
uint64_t p2 = a_hi * b_lo;
uint64_t p3 = a_hi * b_hi;
uint64_t middle = p1 + (p0 >> 32) + (uint32_t)p2;
uint64_t low = (middle << 32) | (uint32_t)p0;
uint64_t high = p3 + (middle >> 32) + (p2 >> 32);
*A = low;
*B = high;
}
#endif
int util_selftest(void)
{
/* rotate left 32-bit */
@ -748,33 +643,33 @@ int util_selftest(void)
if (util_rotr64(0x123456789ABCDEF0ULL, 0) != 0x123456789ABCDEF0ULL) return -1;
if (util_rotr64(0x123456789ABCDEF0ULL, 64) != 0x123456789ABCDEF0ULL) return -1;
/* util_mum tests */
/* tommy_mum tests */
uint64_t a, b;
/* small values */
a = 3; b = 4;
util_mum(&a, &b);
tommy_mum(&a, &b);
if (a != 12 || b != 0) return -1;
/* 1 * anything */
a = 0xFFFFFFFFFFFFFFFFULL; b = 1;
util_mum(&a, &b);
tommy_mum(&a, &b);
if (a != 0xFFFFFFFFFFFFFFFFULL || b != 0) return -1;
/* (2^32) * (2^32) = 2^64 */
a = 1ULL << 32; b = 1ULL << 32;
util_mum(&a, &b);
tommy_mum(&a, &b);
if (a != 0 || b != 1) return -1;
/* max uint64 * max uint64 */
a = 0xFFFFFFFFFFFFFFFFULL; b = 0xFFFFFFFFFFFFFFFFULL;
util_mum(&a, &b);
tommy_mum(&a, &b);
if (a != 1 || b != 0xFFFFFFFFFFFFFFFEULL) return -1;
/* larger random-ish test */
a = 0x0123456789ABCDEFULL;
b = 0xFEDCBA9876543210ULL;
util_mum(&a, &b);
tommy_mum(&a, &b);
if (a != 0x2236D88FE5618CF0ULL || b != 0x121FA00AD77D742ULL) return -1;
return 0;

View file

@ -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"

View file

@ -3,18 +3,18 @@
#include "tommyarray.h"
#include <string.h> /* for memset */
/******************************************************************************/
/* array */
TOMMY_API void tommy_array_init(tommy_array* array)
{
tommy_uint_t i;
/* fixed initial size */
array->bucket_bit = TOMMY_ARRAY_BIT;
array->bucket_max = (tommy_size_t)1 << array->bucket_bit;
array->bucket[0] = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
for (i = 1; i < TOMMY_ARRAY_BIT; ++i)
for (tommy_uint_t i = 1; i < TOMMY_ARRAY_BIT; ++i)
array->bucket[i] = array->bucket[0];
array->count = 0;
@ -22,26 +22,18 @@ TOMMY_API void tommy_array_init(tommy_array* array)
TOMMY_API void tommy_array_done(tommy_array* array)
{
tommy_uint_t i;
tommy_free(array->bucket[0]);
for (i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) {
for (tommy_uint_t i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) {
void** segment = array->bucket[i];
tommy_free(&segment[(tommy_ptrdiff_t)1 << i]);
}
}
TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count)
TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size)
{
if (array->count >= count)
return;
array->count = count;
while (count > array->bucket_max) {
void** segment;
while (size > array->bucket_max) {
/* allocate one more segment */
segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
void** segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
/* store it adjusting the offset */
/* cast to ptrdiff_t to ensure to get a negative value */
@ -52,7 +44,78 @@ TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count)
}
}
TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array)
TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size)
{
if (size >= array->count) {
tommy_array_grow(array, size);
return;
}
/* clear the unused elements to maintain the invariant that unused slots are zero */
tommy_size_t pos = size;
while (pos < array->count) {
tommy_uint_t bsr = tommy_ilog2(pos | 1);
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
memset(&array->bucket[bsr][pos], 0, (chunk_end - pos) * sizeof(void*));
pos = chunk_end;
}
array->count = size;
}
TOMMY_API void tommy_array_shrink(tommy_array* array)
{
tommy_uint_t target_bucket_bit = TOMMY_ARRAY_BIT;
if (array->count > (tommy_size_t)1 << TOMMY_ARRAY_BIT)
target_bucket_bit = tommy_ilog2(array->count - 1) + 1;
while (array->bucket_bit > target_bucket_bit) {
--array->bucket_bit;
void** segment = array->bucket[array->bucket_bit];
tommy_free(&segment[(tommy_ptrdiff_t)1 << array->bucket_bit]);
}
array->bucket_max = (tommy_size_t)1 << array->bucket_bit;
}
TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func)
{
tommy_size_t pos = 0;
while (pos < array->count) {
tommy_uint_t bsr = tommy_ilog2(pos | 1);
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
void** ptr = array->bucket[bsr];
while (pos < chunk_end) {
func(ptr[pos]);
++pos;
}
}
}
TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg)
{
tommy_size_t pos = 0;
while (pos < array->count) {
tommy_uint_t bsr = tommy_ilog2(pos | 1);
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
void** ptr = array->bucket[bsr];
while (pos < chunk_end) {
func(arg, ptr[pos]);
++pos;
}
}
}
TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array)
{
return array->bucket_max * (tommy_size_t)sizeof(void*);
}

View file

@ -7,14 +7,14 @@
* This array is able to grow dynamically upon request, without any reallocation.
*
* The grow operation involves an allocation of a new array segment, without reallocating
* the already used memory, and thus **not increasing** the heap fragmentation.
* This also implies that the address of the stored elements never change.
* the already used memory, and thus not increasing the heap fragmentation.
* This also implies that the address of the stored elements never changes.
*
* Allocated segments grow in size exponentially.
*/
#ifndef __TOMMYARRAY_H
#define __TOMMYARRAY_H
#ifndef TOMMY_ARRAY_H
#define TOMMY_ARRAY_H
#include "tommytypes.h"
@ -42,33 +42,83 @@ typedef struct tommy_array_struct {
/**
* Initializes the array.
* \param array The array.
*/
TOMMY_API void tommy_array_init(tommy_array* array);
/**
* Deinitializes the array.
* \param array The array.
*/
TOMMY_API void tommy_array_done(tommy_array* array);
/**
* Grows the size up to the specified value.
* All the new elements in the array are initialized with the 0 value.
* Allocates space for at least the specified number of elements.
* The initialized size and existing elements are unchanged.
* Existing element references remain valid.
* \param array The array.
* \param size Number of elements to reserve space for.
*/
TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t size);
TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size);
/**
* Gets a reference of the element at the specified position.
* Grows the size up to the specified value.
* All the new elements in the array are initialized with the 0 value.
* \param array The array.
* \param size New size of the array.
*/
tommy_inline void tommy_array_grow(tommy_array* array, tommy_size_t size)
{
if (size > array->count) {
array->count = size;
if (size > array->bucket_max)
tommy_array_reserve(array, size);
}
}
/**
* Changes the initialized size, preserving the common prefix.
* New elements are initialized to zero.
* Reducing the size does not release allocated capacity.
* \param array The array.
* \param size New size of the array.
*/
TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size);
/**
* Removes all elements, preserving the allocated capacity.
* Call tommy_array_shrink() after clearing to restore the initial capacity.
* Pointed-to elements are not freed.
* The array remains initialized and can be reused immediately.
* \param array The array.
*/
tommy_inline void tommy_array_clear(tommy_array* array)
{
tommy_array_resize(array, 0);
}
/**
* Releases unused allocated memory.
* Preserves the size, values, and addresses of existing elements.
* \param array The array.
*/
TOMMY_API void tommy_array_shrink(tommy_array* array);
/**
* Gets a reference to the element at the specified position.
* You must be sure that space for this position is already
* allocated calling tommy_array_grow().
* allocated by calling tommy_array_grow().
* \param array The array.
* \param pos Position of the element.
* \return Pointer to the slot at the specified position.
*/
tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos)
{
tommy_uint_t bsr;
assert(pos < array->count);
/* get the highest bit set, in case of all 0, return 0 */
bsr = tommy_ilog2(pos | 1);
tommy_uint_t bsr = tommy_ilog2(pos | 1);
return &array->bucket[bsr][pos];
}
@ -76,7 +126,10 @@ tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos)
/**
* Sets the element at the specified position.
* You must be sure that space for this position is already
* allocated calling tommy_array_grow().
* allocated by calling tommy_array_grow().
* \param array The array.
* \param pos Position of the element.
* \param element Element to set.
*/
tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* element)
{
@ -86,7 +139,10 @@ tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* el
/**
* Gets the element at the specified position.
* You must be sure that space for this position is already
* allocated calling tommy_array_grow().
* allocated by calling tommy_array_grow().
* \param array The array.
* \param pos Position of the element.
* \return The element at the specified position.
*/
tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos)
{
@ -94,28 +150,129 @@ tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos)
}
/**
* Grows and inserts a new element at the end of the array.
* Gets the last element.
* The array must not be empty.
* \param array The array.
* \return The last element.
*/
tommy_inline void tommy_array_insert(tommy_array* array, void* element)
tommy_inline void* tommy_array_tail(tommy_array* array)
{
assert(array->count != 0);
return tommy_array_get(array, array->count - 1);
}
/**
* Grows and inserts a new element at the end of the array.
* \param array The array.
* \param element Element to insert.
*/
tommy_inline void tommy_array_insert_tail(tommy_array* array, void* element)
{
tommy_size_t pos = array->count;
tommy_array_grow(array, pos + 1);
++array->count;
if (array->count > array->bucket_max)
tommy_array_reserve(array, array->count);
tommy_array_set(array, pos, element);
}
/**
* Gets the initialized size of the array.
* Removes and returns the last element.
* The array must not be empty.
* The removed slot is cleared, and allocated capacity is preserved.
* \param array The array.
* \return The removed element.
*/
tommy_inline tommy_size_t tommy_array_size(tommy_array* array)
tommy_inline void* tommy_array_remove_tail(tommy_array* array)
{
assert(array->count != 0);
tommy_size_t pos = array->count - 1;
void** ptr = tommy_array_ref(array, pos);
void* element = *ptr;
/* keep unused slots zero so growing within capacity needs no initialization */
*ptr = 0;
array->count = pos;
return element;
}
/**
* Checks whether the array is empty.
* \param array The array.
* \return Nonzero if the array is empty, 0 otherwise.
*/
tommy_inline tommy_bool_t tommy_array_empty(const tommy_array* array)
{
return array->count == 0;
}
/**
* Gets the initialized size of the array.
* \param array The array.
* \return The initialized size of the array.
*/
tommy_inline tommy_size_t tommy_array_size(const tommy_array* array)
{
return array->count;
}
/**
* Gets the size of allocated memory.
* Gets the number of elements that fit without further allocation.
* \param array The array.
* \return The capacity of the array.
*/
TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array);
tommy_inline tommy_size_t tommy_array_capacity(const tommy_array* array)
{
return array->bucket_max;
}
/**
* Exchanges two initialized arrays without copying their elements.
* Element references remain valid and belong to the other array.
* Passing the same array twice has no effect.
* \param first The first array.
* \param second The second array.
* \note This operation is O(1).
*/
tommy_inline void tommy_array_swap(tommy_array* first, tommy_array* second)
{
tommy_array tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Calls the specified function for each element in the array.
*
* You cannot add or remove elements, nor change the size of the array,
* from inside the callback.
*
* If the callback frees the elements, the array retains dangling
* pointers. Call tommy_array_clear() before reusing it, or
* tommy_array_done() to release its storage.
* \param array The array.
* \param func Function to call with each element.
*/
TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the array.
*
* \param array The array.
* \param func Function to call with each element.
* \param arg Argument to pass to the function.
*/
TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the size of allocated memory.
* \param array The array.
* \return Total memory allocated by the array, in bytes.
*/
TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array);
#endif

View file

@ -3,12 +3,16 @@
#include "tommyarrayblkof.h"
#include <string.h> /* for memset */
/******************************************************************************/
/* array */
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size)
{
tommy_array_init(&array->block);
array->block_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT;
array->block = tommy_cast(unsigned char**, tommy_malloc(array->block_capacity * sizeof(array->block[0])));
array->block_count = 0;
array->element_size = element_size;
array->count = 0;
@ -16,44 +20,127 @@ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t eleme
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array)
{
tommy_size_t i;
for (tommy_size_t i = 0; i < array->block_count; ++i)
tommy_free(array->block[i]);
for (i = 0; i < tommy_array_size(&array->block); ++i)
tommy_free(tommy_array_get(&array->block, i));
tommy_array_done(&array->block);
tommy_free(array->block);
}
TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t count)
TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size)
{
tommy_size_t block_max;
tommy_size_t block_mac;
if (array->count >= count)
if (size <= array->block_count * TOMMY_ARRAYBLKOF_SIZE)
return;
array->count = count;
block_max = (count + TOMMY_ARRAYBLKOF_SIZE - 1) / TOMMY_ARRAYBLKOF_SIZE;
block_mac = tommy_array_size(&array->block);
tommy_size_t block_max = (size - 1) / TOMMY_ARRAYBLKOF_SIZE + 1;
if (block_mac < block_max) {
/* grow the block array */
tommy_array_grow(&array->block, block_max);
if (array->block_capacity < block_max) {
/* only the directory moves; addresses inside existing blocks stay valid */
tommy_size_t capacity = array->block_capacity;
while (capacity < block_max)
capacity *= 2;
array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0])));
array->block_capacity = capacity;
}
/* allocate new blocks */
while (block_mac < block_max) {
void** ptr = tommy_cast(void**, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size));
while (array->block_count < block_max) {
unsigned char* ptr = tommy_cast(unsigned char*, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size));
/* set the new block */
tommy_array_set(&array->block, block_mac, ptr);
++block_mac;
}
array->block[array->block_count] = ptr;
++array->block_count;
}
}
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array)
TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size)
{
return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLKOF_SIZE * array->element_size;
if (size >= array->count) {
tommy_arrayblkof_grow(array, size);
return;
}
/* clear the unused elements to maintain the invariant that unused slots are zero */
tommy_size_t pos = size;
while (pos < array->count) {
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
tommy_size_t blk_offset = pos % TOMMY_ARRAYBLKOF_SIZE;
tommy_size_t blk_rem = TOMMY_ARRAYBLKOF_SIZE - blk_offset;
tommy_size_t count_rem = array->count - pos;
tommy_size_t chunk = blk_rem < count_rem ? blk_rem : count_rem;
memset(array->block[blk_idx] + blk_offset * array->element_size, 0, chunk * array->element_size);
pos += chunk;
}
array->count = size;
}
TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array)
{
tommy_size_t block_max = (array->count == 0) ? 0 : ((array->count - 1) / TOMMY_ARRAYBLKOF_SIZE + 1);
while (array->block_count > block_max) {
--array->block_count;
tommy_free(array->block[array->block_count]);
}
tommy_size_t min_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT;
tommy_size_t capacity = min_capacity;
while (capacity < block_max)
capacity *= 2;
if (capacity < array->block_capacity) {
array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0])));
array->block_capacity = capacity;
}
}
TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func)
{
/* cache the element size to avoid reloading it after each callback */
tommy_size_t element_size = array->element_size;
tommy_size_t pos = 0;
while (pos < array->count) {
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
tommy_size_t chunk = array->count - pos;
unsigned char* ptr = array->block[blk_idx];
if (chunk > TOMMY_ARRAYBLKOF_SIZE)
chunk = TOMMY_ARRAYBLKOF_SIZE;
for (tommy_size_t i = 0; i < chunk; ++i) {
func(ptr);
ptr += element_size;
}
pos += chunk;
}
}
TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg)
{
/* cache the element size to avoid reloading it after each callback */
tommy_size_t element_size = array->element_size;
tommy_size_t pos = 0;
while (pos < array->count) {
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
tommy_size_t chunk = array->count - pos;
unsigned char* ptr = array->block[blk_idx];
if (chunk > TOMMY_ARRAYBLKOF_SIZE)
chunk = TOMMY_ARRAYBLKOF_SIZE;
for (tommy_size_t i = 0; i < chunk; ++i) {
func(arg, ptr);
ptr += element_size;
}
pos += chunk;
}
}
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array)
{
return array->block_capacity * sizeof(array->block[0]) + array->block_count * TOMMY_ARRAYBLKOF_SIZE * array->element_size;
}

View file

@ -2,29 +2,43 @@
// Copyright (C) 2013 Andrea Mazzoleni
/** \file
* Dynamic array based on blocks of fixed size.
* Dynamic array based on fixed-size blocks.
*
* This array is able to grow dynamically upon request, without any reallocation.
* This array is able to grow dynamically without reallocating stored elements.
* The directory of block pointers may be reallocated, but element addresses
* never change.
*
* This is very similar to ::tommy_arrayblk, but it allows to store elements of any
* This is very similar to ::tommy_arrayblk, but it allows storing elements of any
* size and not just pointers.
*
* Note that in this case tommy_arrayblkof_ref() returns a pointer to the element,
* that should be used for getting and setting elements in the array,
* as generic getter and setter are not available.
* The container allocates space for elements but never copies user data into
* or out of it. tommy_arrayblkof_ref(), tommy_arrayblkof_tail(), and
* tommy_arrayblkof_insert_tail() return an element's address so the caller can
* read or write it directly.
*
* \note Only types with fundamental alignment requirements are supported.
* Storage is allocated using calloc(), which does not guarantee the alignment
* required by over-aligned types (e.g. _Alignas(32) or _Alignas(64)).
* When storing typed elements, use sizeof(type) as the element size to
* preserve alignment for every element.
*/
#ifndef __TOMMYARRAYBLKOF_H
#define __TOMMYARRAYBLKOF_H
#ifndef TOMMY_ARRAYBLKOF_H
#define TOMMY_ARRAYBLKOF_H
#include "tommytypes.h"
#include "tommyarray.h"
#include <assert.h> /* for assert */
#include <string.h> /* for memset */
/******************************************************************************/
/* array */
/**
* Initial and minimal capacity of the block directory expressed as a power of 2.
*/
#define TOMMY_ARRAYBLKOF_BIT 6
/**
* Elements for each block.
*/
@ -35,55 +49,214 @@
* \note Don't use internal fields directly, but access the container only using functions.
*/
typedef struct tommy_arrayblkof_struct {
tommy_array block; /**< Array of blocks. */
unsigned char** block; /**< Directory of blocks. */
tommy_size_t block_count; /**< Number of allocated blocks. */
tommy_size_t block_capacity; /**< Number of directory entries allocated. */
tommy_size_t element_size; /**< Size of the stored element in bytes. */
tommy_size_t count; /**< Number of initialized elements in the array. */
} tommy_arrayblkof;
/**
* Initializes the array.
* \param element_size Size in byte of the element to store in the array.
* \param array The array.
* \param element_size Size in bytes of the element to store in the array.
*/
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size);
/**
* Deinitializes the array.
* \param array The array.
*/
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array);
/**
* Grows the size up to the specified value.
* All the new elements in the array are initialized with the 0 value.
* Allocates space for at least the specified number of elements.
* The initialized size and existing elements are unchanged.
* Existing element references remain valid.
* \param array The array.
* \param size Number of elements to reserve space for.
*/
TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size);
TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size);
/**
* Gets a reference of the element at the specified position.
* Grows the size up to the specified value.
* All the new elements in the array are initialized with the 0 value.
* \param array The array.
* \param size New size of the array.
*/
tommy_inline void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size)
{
if (size > array->count) {
array->count = size;
if (size > array->block_count * TOMMY_ARRAYBLKOF_SIZE)
tommy_arrayblkof_reserve(array, size);
}
}
/**
* Changes the initialized size, preserving the common prefix.
* New elements are initialized to zero.
* Reducing the size does not release allocated capacity.
* \param array The array.
* \param size New size of the array.
*/
TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size);
/**
* Removes all elements, preserving the allocated capacity.
* Call tommy_arrayblkof_shrink() after clearing to restore the initial capacity.
* The array remains initialized and can be reused immediately.
* \param array The array.
*/
tommy_inline void tommy_arrayblkof_clear(tommy_arrayblkof* array)
{
tommy_arrayblkof_resize(array, 0);
}
/**
* Releases unused allocated memory.
* Preserves the size, values, and addresses of existing elements.
* \param array The array.
*/
TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array);
/**
* Gets a reference to the element at the specified position.
* You must be sure that space for this position is already
* allocated calling tommy_arrayblkof_grow().
* allocated by calling tommy_arrayblkof_grow().
* \param array The array.
* \param pos Position of the element.
* \return Pointer to the slot at the specified position.
*/
tommy_inline void* tommy_arrayblkof_ref(tommy_arrayblkof* array, tommy_size_t pos)
{
unsigned char* base;
assert(pos < array->count);
base = tommy_cast(unsigned char*, tommy_array_get(&array->block, pos / TOMMY_ARRAYBLKOF_SIZE));
return array->block[pos / TOMMY_ARRAYBLKOF_SIZE] + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size;
}
return base + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size;
/**
* Gets a reference to the last element.
* The array must not be empty.
* \param array The array.
* \return Pointer to the last element.
*/
tommy_inline void* tommy_arrayblkof_tail(tommy_arrayblkof* array)
{
assert(array->count != 0);
return tommy_arrayblkof_ref(array, array->count - 1);
}
/**
* Adds a zero-initialized element and returns its reference.
* \param array The array.
* \return Pointer to the new element.
*/
tommy_inline void* tommy_arrayblkof_insert_tail(tommy_arrayblkof* array)
{
tommy_size_t pos = array->count;
++array->count;
if (array->count > array->block_count * TOMMY_ARRAYBLKOF_SIZE)
tommy_arrayblkof_reserve(array, array->count);
return tommy_arrayblkof_ref(array, pos);
}
/**
* Removes the last element without copying it.
* The array must not be empty. The removed slot is cleared.
* \param array The array.
*/
tommy_inline void tommy_arrayblkof_remove_tail(tommy_arrayblkof* array)
{
assert(array->count != 0);
tommy_size_t pos = array->count - 1;
void* ptr = tommy_arrayblkof_ref(array, pos);
/* keep unused slots zero so growing within capacity needs no initialization */
memset(ptr, 0, array->element_size);
array->count = pos;
}
/**
* Checks whether the array is empty.
* \param array The array.
* \return Nonzero if the array is empty, 0 otherwise.
*/
tommy_inline tommy_bool_t tommy_arrayblkof_empty(const tommy_arrayblkof* array)
{
return array->count == 0;
}
/**
* Gets the initialized size of the array.
* \param array The array.
* \return The initialized size of the array.
*/
tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array)
tommy_inline tommy_size_t tommy_arrayblkof_size(const tommy_arrayblkof* array)
{
return array->count;
}
/**
* Gets the size of allocated memory.
* Gets the number of elements that fit without further allocation.
* \param array The array.
* \return The capacity of the array.
*/
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array);
tommy_inline tommy_size_t tommy_arrayblkof_capacity(const tommy_arrayblkof* array)
{
return array->block_count * TOMMY_ARRAYBLKOF_SIZE;
}
/**
* Exchanges two initialized arrays without copying their elements.
* Element references remain valid and belong to the other array.
* Passing the same array twice has no effect.
* \param first The first array.
* \param second The second array.
* \note This operation is O(1).
*/
tommy_inline void tommy_arrayblkof_swap(tommy_arrayblkof* first, tommy_arrayblkof* second)
{
/* keep record size with the storage it describes */
tommy_arrayblkof tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Calls the specified function for each element in the array.
*
* You cannot add or remove elements, nor change the size of the array,
* from inside the callback.
*
* Elements are stored inside the array: passing free() as the callback
* is invalid. A callback may release resources owned by each element,
* but must not free the element itself. Call tommy_arrayblkof_clear() before
* reusing it, or tommy_arrayblkof_done() to release its storage.
* \param array The array.
* \param func Function to call with each element.
*/
TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the array.
*
* \param array The array.
* \param func Function to call with each element.
* \param arg Argument to pass to the function.
*/
TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the size of allocated memory.
* \param array The array.
* \return Total memory allocated by the array, in bytes.
*/
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array);
#endif

View file

@ -9,8 +9,8 @@
* Do not use this directly. Use lists instead.
*/
#ifndef __TOMMYCHAIN_H
#define __TOMMYCHAIN_H
#ifndef TOMMY_CHAIN_H
#define TOMMY_CHAIN_H
#include "tommytypes.h"
@ -65,31 +65,39 @@ tommy_inline void tommy_chain_concat(tommy_node* first_tail, tommy_node* second_
/**
* Merges two chains.
* \param first First chain (will contain the result).
* \param second Second chain (will be empty after the merge).
* \param second Second chain (consumed by the merge; its descriptor is not cleared
* and must not be used as an independent chain afterward).
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
tommy_inline void tommy_chain_merge(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
{
tommy_node* first_i = first->head;
/*
* comparators must not change the chains, so cache these endpoints across calls.
* also caching first_tail caused register spills with GCC and Clang on x86-64.
*/
tommy_node* first_head = first->head;
tommy_node* second_tail = second->tail;
tommy_node* first_i = first_head;
tommy_node* second_i = second->head;
/* merge */
while (1) {
if (cmp(first_i->data, second_i->data) > 0) {
tommy_node* next = second_i->next;
if (first_i == first->head) {
if (first_i == first_head) {
tommy_chain_concat(second_i, first_i);
first->head = second_i;
first_head = second_i;
first->head = first_head;
} else {
tommy_chain_splice(first_i->prev, first_i, second_i, second_i);
}
if (second_i == second->tail)
if (second_i == second_tail)
break;
second_i = next;
} else {
if (first_i == first->tail) {
tommy_chain_concat(first_i, second_i);
first->tail = second->tail;
first->tail = second_tail;
break;
}
first_i = first_i->next;
@ -98,13 +106,36 @@ tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tom
}
/**
* Merges two chains managing special degenerated cases.
* It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains.
* \param first First chain (will contain the result).
* \param second Second chain (will be empty after the merge).
* Merges two chains containing one node each.
* \param first First singleton chain (will contain the result).
* \param second Second singleton chain (consumed by the merge; its descriptor is not cleared
* and must not be used as an independent chain afterward).
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
tommy_inline void tommy_chain_merge_singleton(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
{
tommy_node* first_node = first->head;
tommy_node* second_node = second->head;
/* keep equal elements in their original order for a stable sort */
if (cmp(first_node->data, second_node->data) <= 0) {
tommy_chain_concat(first_node, second_node);
first->tail = second_node;
} else {
tommy_chain_concat(second_node, first_node);
first->head = second_node;
}
}
/**
* Merges two chains managing special degenerate cases.
* It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains.
* \param first First chain (will contain the result).
* \param second Second chain (consumed by the merge; its descriptor is not cleared
* and must not be used as an independent chain afterward).
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_merge_degenerated(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
{
/* identify the condition first <= second */
if (cmp(first->tail->data, second->head->data) <= 0) {
@ -126,9 +157,9 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain*
/**
* Sorts a chain.
* It's a stable merge sort using power of 2 buckets, with O(N*log(N)) complexity,
* It's a stable merge sort using power of 2 buckets, with O(n log n) complexity,
* similar to the one used in the SGI STL libraries and in the Linux Kernel,
* but faster on degenerated cases like already ordered lists.
* but faster on degenerate cases like already ordered lists.
*
* SGI STL stl_list.h
* http://www.sgi.com/tech/stl/stl_list.h
@ -136,43 +167,48 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain*
* Linux Kernel lib/list_sort.c
* http://lxr.linux.no/#linux+v2.6.36/lib/list_sort.c
*
* \param chain Chain to sort.
* \param chain The chain.
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* cmp)
{
/*
* Bit buckets of chains.
* Each bucket contains 2^i nodes or it's empty.
* Each bucket contains 2^(i+1) nodes or it's empty.
* The bucket containing an unpaired final node has one node less.
* The chain at address TOMMY_BIT_MAX is an independent variable operating as "carry".
* We keep it in the same "bit" vector to avoid reports from the valgrind tool sgcheck.
*/
tommy_chain bit[TOMMY_SIZE_BIT + 1];
/**
/*
* Value stored inside the bit bucket.
* It's used to know which bucket is empty or full.
*/
tommy_size_t counter;
tommy_size_t counter = 0;
tommy_node* node = chain->head;
tommy_node* tail = chain->tail;
tommy_size_t mask;
tommy_size_t i;
counter = 0;
while (1) {
tommy_node* next;
tommy_chain* last;
/* carry bit to add */
last = &bit[TOMMY_SIZE_BIT];
tommy_chain* last = &bit[TOMMY_SIZE_BIT];
bit[TOMMY_SIZE_BIT].head = node;
bit[TOMMY_SIZE_BIT].tail = node;
tommy_node* next = node->next;
/* start with a sorted pair to halve the number of bucket insertions */
if (node != tail) {
node = next;
/* save the next input node before merging can change the links */
next = node->next;
tommy_chain second = { node, node };
tommy_chain_merge_singleton(last, &second, cmp);
}
/* add the bit, propagating the carry */
i = 0;
mask = counter;
tommy_size_t i = 0;
tommy_size_t mask = counter;
while ((mask & 1) != 0) {
tommy_chain_merge_degenerated(&bit[i], last, cmp);
mask >>= 1;
@ -192,8 +228,8 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
}
/* merge the buckets */
i = tommy_ctz(counter);
mask = counter >> i;
tommy_size_t i = tommy_ctz(counter);
tommy_size_t mask = counter >> i;
while (mask != 1) {
mask >>= 1;
if (mask & 1)
@ -207,3 +243,4 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
}
#endif

View file

@ -1,4 +1,4 @@
// SPDX-License-Identifier: BSD-2-Clause
// SPDX-License-Identifier: BSD-2-Clause AND MIT
// Copyright (C) 2010 Andrea Mazzoleni
#include "tommyhash.h"
@ -13,9 +13,9 @@
* They are needed only if BigEndian.
*/
#if defined(__GNUC__)
#define tommy_swap32(x) __builtin_bswap32(x)
#define tommy_imp_hash_swap32(x) __builtin_bswap32(x)
#else
tommy_inline tommy_uint32_t tommy_swap32(tommy_uint32_t v)
tommy_inline tommy_uint32_t tommy_imp_hash_swap32(tommy_uint32_t v)
{
return ((v & 0xFF000000) >> 24) |
((v & 0x00FF0000) >> 8) |
@ -24,211 +24,167 @@ tommy_inline tommy_uint32_t tommy_swap32(tommy_uint32_t v)
}
#endif
tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr)
tommy_inline tommy_uint32_t tommy_imp_hash_le_uint32_read(const void* ptr)
{
tommy_uint32_t v;
memcpy(&v, ptr, sizeof(v));
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
v = tommy_swap32(v);
v = tommy_imp_hash_swap32(v);
#endif
return v;
}
#define tommy_rot(x, k) \
(((x) << (k)) | ((x) >> (32 - (k))))
/******************************************************************************/
/* a5hash */
#define tommy_mix(a, b, c) \
do { \
a -= c; a ^= tommy_rot(c, 4); c += b; \
b -= a; b ^= tommy_rot(a, 6); a += c; \
c -= b; c ^= tommy_rot(b, 8); b += a; \
a -= c; a ^= tommy_rot(c, 16); c += b; \
b -= a; b ^= tommy_rot(a, 19); a += c; \
c -= b; c ^= tommy_rot(b, 4); b += a; \
} while (0)
/**
* a5hash32 and a5hash (64-bit) by Aleksey Vaneev, from https://github.com/avaneev/a5hash.
* Adapted from version 5.25 with little-endian word reads.
* The 32-bit variant additionally mixes the expanded seed asymmetrically into
* h[2] and h[3] before processing the input and produces different hash values.
* Copyright (c) 2025 Aleksey Vaneev. Released under the MIT license, see LICENSES/MIT.
*/
#define tommy_final(a, b, c) \
do { \
c ^= b; c -= tommy_rot(b, 14); \
a ^= c; a -= tommy_rot(c, 11); \
b ^= a; b -= tommy_rot(a, 25); \
c ^= b; c -= tommy_rot(b, 16); \
a ^= c; a -= tommy_rot(c, 4); \
b ^= a; b -= tommy_rot(a, 14); \
c ^= b; c -= tommy_rot(b, 24); \
} while (0)
tommy_inline void tommy_mul(tommy_uint32_t* lo, tommy_uint32_t* hi)
{
/* both operands are 32-bit; the full product supplies two state words */
tommy_uint64_t v = (tommy_uint64_t)*lo * *hi;
*lo = (tommy_uint32_t)v;
*hi = v >> 32;
}
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
tommy_uint32_t val01 = 0x55555555;
tommy_uint32_t val10 = 0xaaaaaaaa;
tommy_uint32_t h[4] = { 0x243f6a88, 0x85a308d3, 0xfb0bd3ea, 0x0f58fd47 };
tommy_uint32_t a, b;
tommy_size_t len = key_len;
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val;
h[0] ^= key_len;
h[1] ^= key_len;
#if TOMMY_SIZE_BIT == 64
h[2] = (key_len >> 32) ^ 0x452821e6;
h[3] = (key_len >> 32) ^ 0x38d01377;
tommy_mul(&h[2], &h[3]);
#endif
h[0] ^= init_val & val01;
h[1] ^= init_val & val10;
tommy_mul(&h[0], &h[1]);
while (key_len > 12) {
a += tommy_le_uint32_read(key + 0);
b += tommy_le_uint32_read(key + 4);
c += tommy_le_uint32_read(key + 8);
/*
* Mix the expanded seed asymmetrically into h[2] and h[3] so that short keys
* skipping the main loop still depend on the seed, and neither h[0] nor h[1]
* cancels out when folding h[0] ^= h[2] and h[1] ^= h[3] at the end.
*/
h[2] ^= h[0] ^ h[1];
h[3] ^= h[0];
tommy_mix(a, b, c);
if (len <= 16) {
if (len >= 4) {
const unsigned char* tail = key + len - 4;
a = tommy_imp_hash_le_uint32_read(key);
b = tommy_imp_hash_le_uint32_read(tail);
if (len >= 9) {
tommy_size_t offset = (len / 8) * 4;
h[2] += tommy_imp_hash_le_uint32_read(key + offset);
h[3] += tommy_imp_hash_le_uint32_read(tail - offset);
tommy_mul(&h[2], &h[3]);
}
} else {
a = 0;
b = 0;
if (len != 0) {
a = key[0];
if (len >= 2)
a |= key[1] << 8;
if (len == 3)
a |= key[2] << 16;
}
}
} else {
val01 ^= h[0];
val10 ^= h[1];
do {
tommy_uint32_t s1 = h[0];
tommy_uint32_t s4 = h[3];
h[0] += tommy_imp_hash_le_uint32_read(key);
h[1] += tommy_imp_hash_le_uint32_read(key + 4);
tommy_mul(&h[0], &h[1]);
h[2] += tommy_imp_hash_le_uint32_read(key + 8);
h[3] += tommy_imp_hash_le_uint32_read(key + 12);
tommy_mul(&h[2], &h[3]);
h[0] += val01;
h[1] += s4;
h[2] += s1;
h[3] += val10;
key += 16;
len -= 16;
} while (len > 16);
key_len -= 12;
key += 12;
/* the last words may overlap the previous block, but stay within the input */
const unsigned char* tail = key + len;
a = tommy_imp_hash_le_uint32_read(tail - 8);
b = tommy_imp_hash_le_uint32_read(tail - 4);
if (len >= 9) {
h[2] += tommy_imp_hash_le_uint32_read(tail - 16);
h[3] += tommy_imp_hash_le_uint32_read(tail - 12);
tommy_mul(&h[2], &h[3]);
}
}
switch (key_len) {
case 0 :
return c; /* used only when called with a zero length */
case 12 :
c += tommy_le_uint32_read(key + 8);
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */
case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */
case 9 : c += key[8]; /* fallthrough */
case 8 :
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */
case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */
case 5 : b += key[4]; /* fallthrough */
case 4 :
a += tommy_le_uint32_read(key + 0);
break;
case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */
case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */
case 1 : a += key[0]; /* fallthrough */
}
tommy_final(a, b, c);
return c;
h[0] ^= h[2];
h[1] ^= h[3];
h[0] += a;
h[1] += b;
tommy_mul(&h[0], &h[1]);
h[0] ^= val01;
tommy_mul(&h[0], &h[1]);
return h[0] ^ h[1];
}
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
tommy_uint64_t val01 = 0x5555555555555555ULL;
tommy_uint64_t val10 = 0xaaaaaaaaaaaaaaaaULL;
tommy_uint64_t h[2] = { (0x452821e638d01377ULL ^ key_len) ^ (init_val & val10), (0x243f6a8885a308d3ULL ^ key_len) ^ (init_val & val01) };
tommy_size_t len = key_len;
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff);
c += init_val >> 32;
while (key_len > 12) {
a += tommy_le_uint32_read(key + 0);
b += tommy_le_uint32_read(key + 4);
c += tommy_le_uint32_read(key + 8);
tommy_mix(a, b, c);
key_len -= 12;
key += 12;
tommy_mum(&h[0], &h[1]);
if (len > 16) {
val01 ^= h[0];
val10 ^= h[1];
do {
/* each 64-bit operand puts the first little-endian word in its high half */
h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 ^ tommy_imp_hash_le_uint32_read(key + 4);
h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + 8) << 32 ^ tommy_imp_hash_le_uint32_read(key + 12);
tommy_mum(&h[0], &h[1]);
h[0] += val01;
h[1] += val10;
key += 16;
len -= 16;
} while (len > 16);
}
switch (key_len) {
case 0 :
return c + ((tommy_uint64_t)b << 32); /* used only when called with a zero length */
case 12 :
c += tommy_le_uint32_read(key + 8);
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */
case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */
case 9 : c += key[8]; /* fallthrough */
case 8 :
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */
case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */
case 5 : b += key[4]; /* fallthrough */
case 4 :
a += tommy_le_uint32_read(key + 0);
break;
case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */
case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */
case 1 : a += key[0]; /* fallthrough */
if (len >= 4) {
const unsigned char* tail = key + len - 4;
tommy_size_t offset = (len / 8) * 4;
h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 | tommy_imp_hash_le_uint32_read(tail);
h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + offset) << 32 | tommy_imp_hash_le_uint32_read(tail - offset);
} else if (len != 0) {
h[0] ^= key[0];
if (len >= 2)
h[0] ^= key[1] << 8;
if (len == 3)
h[0] ^= key[2] << 16;
}
tommy_final(a, b, c);
return c + ((tommy_uint64_t)b << 32);
}
TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
tommy_uint32_t m[3] = { 0xff, 0xff00, 0xff0000 };
a = b = c = 0xdeadbeef + init_val;
/* this is different than original lookup3 and the result won't match */
while (1) {
tommy_uint32_t v = tommy_le_uint32_read(key);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
a += v & m[0];
if (v & m[1]) {
a += v & m[1];
if (v & m[2])
a += v & m[2];
}
}
break;
}
a += v;
v = tommy_le_uint32_read(key + 4);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
b += v & m[0];
if (v & m[1]) {
b += v & m[1];
if (v & m[2])
b += v & m[2];
}
}
break;
}
b += v;
v = tommy_le_uint32_read(key + 8);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
c += v & m[0];
if (v & m[1]) {
c += v & m[1];
if (v & m[2])
c += v & m[2];
}
}
break;
}
c += v;
tommy_mix(a, b, c);
key += 12;
}
/* for lengths that are multipliers of 12 we already have called mix */
/* this is different than the original lookup3 and the result won't match */
tommy_final(a, b, c);
return c;
tommy_mum(&h[0], &h[1]);
h[0] ^= val01;
tommy_mum(&h[0], &h[1]);
return h[0] ^ h[1];
}

View file

@ -2,79 +2,145 @@
// Copyright (C) 2010 Andrea Mazzoleni
/** \file
* Hash functions for the use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin.
* Hash functions for use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin.
*
* This module provides hash functions designed for in-memory data structures.
* ::tommy_hash_u32 and ::tommy_strhash_u32 use a modified a5hash32, while
* ::tommy_hash_u64 and ::tommy_strhash_u64 use a5hash (64-bit).
* Reversible integer hash functions are also provided for 32-bit and 64-bit keys.
*
* a5hash32 is intended for 32-bit platforms and uses 32-bit operands with
* full 64-bit multiplication results. a5hash uses 64-bit operands with
* full 128-bit multiplication results. Both algorithms pass SMHasher3 tests.
*
* Prefer the 64-bit version whenever possible, as it provides better resistance
* to collisions, particularly with specially crafted inputs. However, neither
* version is designed to resist deliberate collision attacks. If resistance to
* such attacks is required, use a dedicated hash function designed for that purpose.
*
* All hash functions are intended for runtime in-memory use only and not
* for persistent storage, as hash compatibility across different versions of the
* library is not guaranteed. String and sized buffer hash functions produce
* identical results for 0-terminated strings.
*/
#ifndef __TOMMYHASH_H
#define __TOMMYHASH_H
#ifndef TOMMY_HASH_H
#define TOMMY_HASH_H
#include "tommytypes.h"
#include <string.h> /* for strlen */
/******************************************************************************/
/* hash */
/**
* Hash function with a 32 bits result.
* Implementation of the Robert Jenkins "lookup3" hash 32 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
* Hash function with a 32-bit result.
* Modified implementation of a5hash32 by Aleksey Vaneev,
* from https://github.com/avaneev/a5hash
*
* This hash is designed to provide a good overall performance on all platforms,
* including 32 bits. If you target only 64 bits, you can use faster hashes,
* like SpookyHash or FarmHash.
* The expanded seed is mixed asymmetrically into the two additional state words
* before processing the input. This preserves seed dependence for short keys
* that skip the main loop and avoids cancellation of either seed state word
* in the final XOR fold. The resulting hash values differ from upstream a5hash32.
*
* This algorithm is compact, fast on short inputs, and passes SMHasher3 tests.
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the data to hash.
* \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0.
* \param key_len Size of the data to hash.
* \return The 32-bit hash value.
* \note
* This function is endianness independent.
* \return The hash value of 32 bits.
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_strhash_u32 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is non-cryptographic and is not designed to protect against malicious collision attacks.
*/
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len);
/**
* Hash function with a 64 bits result.
* Implementation of the Robert Jenkins "lookup3" hash 64 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle2().
* Hash function with a 64-bit result.
* Implementation of a5hash (64-bit) by Aleksey Vaneev,
* from https://github.com/avaneev/a5hash
*
* This hash is designed to provide a good overall performance on all platforms,
* including 32 bits. If you target only 64 bits, you can use faster hashes,
* like SpookyHash or FarmHash.
* This algorithm is compact, fast on short inputs, and passes SMHasher3 tests.
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the data to hash.
* \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0.
* \param key_len Size of the data to hash.
* \return The 64-bit hash value.
* \note
* This function is endianness independent.
* \return The hash value of 64 bits.
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_strhash_u64 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is non-cryptographic and is not designed to protect against malicious collision attacks.
*/
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len);
/**
* String hash function with a 32 bits result.
* Implementation is based on Robert Jenkins "lookup3" hash 32 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
* String hash function with a 32-bit result.
* Modified implementation of a5hash32 by Aleksey Vaneev,
* from https://github.com/avaneev/a5hash
*
* This hash is designed to handle strings with an unknown length. If you
* know the string length, the other hash functions are surely faster.
* This hash is designed to handle strings with an unknown length.
* It is implemented inline using strlen() followed by ::tommy_hash_u32.
* This gives better performance than scanning for the terminator while hashing.
* It uses the same modified seed mixing and differs from upstream a5hash32.
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the string to hash. It has to be 0 terminated.
* \param void_key Pointer to the string to hash. It has to be 0-terminated.
* \return The 32-bit hash value.
* \note
* This function is endianness independent.
* \return The hash value of 32 bits.
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_hash_u32 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is non-cryptographic and is not designed to protect against malicious collision attacks.
*/
TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key);
tommy_inline tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key)
{
return tommy_hash_u32(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
}
/**
* Integer reversible hash function for 32 bits.
* String hash function with a 64-bit result.
* Implementation of a5hash (64-bit) by Aleksey Vaneev,
* from https://github.com/avaneev/a5hash
*
* This hash is designed to handle strings with an unknown length.
* It is implemented inline using strlen() followed by ::tommy_hash_u64.
* This gives better performance than scanning for the terminator while hashing.
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the string to hash. It has to be 0-terminated.
* \return The 64-bit hash value.
* \note
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_hash_u64 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is non-cryptographic and is not designed to protect against malicious collision attacks.
*/
tommy_inline tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key)
{
return tommy_hash_u64(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
}
/**
* Reversible 32-bit integer hash function.
* Implementation of the Robert Jenkins "4-byte Integer Hashing",
* from http://burtleburtle.net/bob/hash/integer.html
* \param key The 32-bit integer to hash.
* \return The 32-bit hash value.
*/
tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key)
{
@ -90,9 +156,11 @@ tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key)
}
/**
* Integer reversible hash function for 64 bits.
* Reversible 64-bit integer hash function.
* Implementation of the Thomas Wang "Integer Hash Function",
* from http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm
* \param key The 64-bit integer to hash.
* \return The 64-bit hash value.
*/
tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
{
@ -107,4 +175,146 @@ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
return key;
}
/**
* Reversible 32-bit integer hash function using multiplications.
* Implementation of the MurmurHash3 fmix32 finalizer by Austin Appleby,
* from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
* \param key The 32-bit integer to hash.
* \return The 32-bit hash value.
* \note Requires an architecture with fast 32-bit integer multiplication
* for good performance. See tommy_inthash_u32() for an alternative.
*/
tommy_inline tommy_uint32_t tommy_mulhash_u32(tommy_uint32_t key)
{
key ^= key >> 16;
key *= 0x85ebca6bU;
key ^= key >> 13;
key *= 0xc2b2ae35U;
key ^= key >> 16;
return key;
}
/**
* Reversible 64-bit integer hash function using multiplications.
* Implementation of the SplitMix64 finalizer,
* from https://prng.di.unimi.it/splitmix64.c
* \param key The 64-bit integer to hash.
* \return The 64-bit hash value.
* \note Requires an architecture with fast 64-bit integer multiplication
* for good performance. It is generally not recommended on 32-bit architectures,
* where these multiplications are expensive. Consider tommy_inthash_u64() instead.
*/
tommy_inline tommy_uint64_t tommy_mulhash_u64(tommy_uint64_t key)
{
key ^= key >> 30;
key *= 0xbf58476d1ce4e5b9ULL;
key ^= key >> 27;
key *= 0x94d049bb133111ebULL;
key ^= key >> 31;
return key;
}
/******************************************************************************/
/* multiplication */
/**
* Multiplies two 64-bit unsigned numbers to obtain the full 128-bit product.
*
* \param A Address of the first operand, overwritten with the low 64 bits of the product.
* \param B Address of the second operand, overwritten with the high 64 bits of the product.
*/
#if defined(_MSC_VER) && defined(_M_X64)
/*
* MSVC x64 does not support inline assembly, so use its full-product intrinsic.
*/
#pragma intrinsic(_umul128)
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
tommy_uint64_t high;
tommy_uint64_t low = _umul128(*A, *B, &high);
*A = low;
*B = high;
}
#elif defined(_MSC_VER) && defined(_M_ARM64)
#pragma intrinsic(__umulh)
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
tommy_uint64_t a = *A;
tommy_uint64_t b = *B;
*A = a * b;
*B = __umulh(a, b);
}
#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
/**
* Keep this before __SIZEOF_INT128__ because the explicit mulq generates more efficient code.
*/
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
tommy_uint64_t a = *A;
tommy_uint64_t b = *B;
tommy_uint64_t low, high;
/* require a register to avoid Clang spilling the operand before mulq */
__asm__ (
"mulq %3"
: "=a" (low), "=d" (high)
: "a" (a), "r" (b)
: "cc"
);
*A = low;
*B = high;
}
#elif defined(__SIZEOF_INT128__)
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
__uint128_t product = (__uint128_t)*A * *B;
*A = product;
*B = product >> 64;
}
#elif defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__))
/*
* Keep this after __SIZEOF_INT128__ because the assembly path failed optimized
* hash tests on Apple clang 21.0.0. See https://github.com/amadvance/snapraid/issues/43.
*/
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
tommy_uint64_t a = *A;
tommy_uint64_t b = *B;
tommy_uint64_t low = a * b;
tommy_uint64_t high;
__asm__ (
"umulh %0, %1, %2"
: "=r" (high)
: "r" (a), "r" (b)
);
*A = low;
*B = high;
}
#else
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
{
tommy_uint64_t a = *A;
tommy_uint64_t b = *B;
tommy_uint64_t a_lo = (tommy_uint32_t)a;
tommy_uint64_t a_hi = a >> 32;
tommy_uint64_t b_lo = (tommy_uint32_t)b;
tommy_uint64_t b_hi = b >> 32;
tommy_uint64_t p0 = a_lo * b_lo;
tommy_uint64_t p1 = a_lo * b_hi;
tommy_uint64_t p2 = a_hi * b_lo;
tommy_uint64_t p3 = a_hi * b_hi;
/* retain the carry from the middle words in the high half */
tommy_uint64_t middle = p1 + (p0 >> 32) + (tommy_uint32_t)p2;
tommy_uint64_t low = (middle << 32) | (tommy_uint32_t)p0;
tommy_uint64_t high = p3 + (middle >> 32) + (p2 >> 32);
*A = low;
*B = high;
}
#endif
#endif

View file

@ -4,6 +4,8 @@
#include "tommyhashdyn.h"
#include "tommylist.h"
#include <string.h> /* for memset */
/******************************************************************************/
/* hashdyn */
@ -23,10 +25,16 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn)
tommy_free(hashdyn->bucket);
}
TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn)
{
memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*));
hashdyn->count = 0;
}
/**
* Resize the bucket vector.
*/
static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit)
static void tommy_imp_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit)
{
tommy_size_t bucket_bit = hashdyn->bucket_bit;
tommy_size_t bucket_max = hashdyn->bucket_max;
@ -37,38 +45,21 @@ 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;
} else if (new_bucket_bit > bucket_bit) {
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;
for (tommy_size_t i = 0; i < bucket_max; ++i) {
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*)));
for (i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j = hashdyn->bucket[i];
if (j && !j->next) {
/* growth keeps old buckets separate, and singleton links are already valid */
new_bucket[j->index & new_bucket_mask] = j;
continue;
}
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
@ -80,15 +71,47 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket
}
}
} else {
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*)));
/* shrink with elements: all buckets are overwritten, no pre-zeroing needed */
for (tommy_size_t i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j = hashdyn->bucket[i];
if (j && !j->next) {
/* growth keeps old buckets separate, and singleton links are already valid */
new_bucket[j->index & new_bucket_mask] = j;
continue;
}
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
if (new_bucket[pos])
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
else
tommy_list_insert_first(&new_bucket[pos], j);
j = j_next;
}
}
}
} else {
/* 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) {
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]);
}
}
}
tommy_free(hashdyn->bucket);
@ -103,21 +126,21 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket
/**
* Grow.
*/
tommy_inline void hashdyn_grow_step(tommy_hashdyn* hashdyn)
tommy_inline void tommy_imp_hashdyn_grow_step(tommy_hashdyn* hashdyn)
{
/* grow if more than 50% full */
/* grow if at least 50% full */
if (hashdyn->count >= hashdyn->bucket_max / 2)
tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1);
tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1);
}
/**
* Shrink.
*/
tommy_inline void hashdyn_shrink_step(tommy_hashdyn* hashdyn)
tommy_inline void tommy_imp_hashdyn_shrink_step(tommy_hashdyn* hashdyn)
{
/* shrink if less than 12.5% full */
/* shrink if at most 12.5% full */
if (hashdyn->count <= hashdyn->bucket_max / 8 && hashdyn->bucket_bit > TOMMY_HASHDYN_BIT)
tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1);
tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1);
}
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
@ -130,7 +153,20 @@ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
if (bucket_bit <= hashdyn->bucket_bit)
return;
tommy_hashdyn_resize(hashdyn, bucket_bit);
tommy_imp_hashdyn_resize(hashdyn, bucket_bit);
}
TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn)
{
tommy_uint_t bucket_bit = tommy_ilog2(hashdyn->count | 1) + 2;
if (bucket_bit < TOMMY_HASHDYN_BIT)
bucket_bit = TOMMY_HASHDYN_BIT;
if (bucket_bit >= hashdyn->bucket_bit)
return;
tommy_imp_hashdyn_resize(hashdyn, bucket_bit);
}
TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash)
@ -143,7 +179,42 @@ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node*
++hashdyn->count;
hashdyn_grow_step(hashdyn);
tommy_imp_hashdyn_grow_step(hashdyn);
}
TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
{
tommy_hashdyn_node** bucket = &hashdyn->bucket[hash & hashdyn->bucket_mask];
tommy_hashdyn_node* i = *bucket;
while (i) {
/* compare keys only for matching full hashes, as the bucket may contain different hashes */
if (i->index == hash && cmp(cmp_arg, i->data) == 0)
return i->data;
i = i->next;
}
/* insert before resizing, while the bucket selected for the search is still valid */
tommy_list_insert_tail(bucket, node, data);
node->index = hash;
++hashdyn->count;
tommy_imp_hashdyn_grow_step(hashdyn);
return data;
}
TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash)
{
if (node->index == hash)
return;
/* unlink using the stored hash, without invoking the resize policy. */
tommy_list_remove_existing(&hashdyn->bucket[node->index & hashdyn->bucket_mask], node);
node->index = hash;
tommy_list_insert_tail(&hashdyn->bucket[hash & hashdyn->bucket_mask], node, node->data);
}
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
@ -154,7 +225,7 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash
--hashdyn->count;
hashdyn_shrink_step(hashdyn);
tommy_imp_hashdyn_shrink_step(hashdyn);
return node->data;
}
@ -171,7 +242,7 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func*
--hashdyn->count;
hashdyn_shrink_step(hashdyn);
tommy_imp_hashdyn_shrink_step(hashdyn);
return node->data;
}
@ -185,9 +256,8 @@ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func*
{
tommy_size_t bucket_max = hashdyn->bucket_max;
tommy_hashdyn_node** bucket = hashdyn->bucket;
tommy_size_t pos;
for (pos = 0; pos < bucket_max; ++pos) {
for (tommy_size_t pos = 0; pos < bucket_max; ++pos) {
tommy_hashdyn_node* node = bucket[pos];
while (node) {
@ -202,9 +272,8 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a
{
tommy_size_t bucket_max = hashdyn->bucket_max;
tommy_hashdyn_node** bucket = hashdyn->bucket;
tommy_size_t pos;
for (pos = 0; pos < bucket_max; ++pos) {
for (tommy_size_t pos = 0; pos < bucket_max; ++pos) {
tommy_hashdyn_node* node = bucket[pos];
while (node) {
@ -215,68 +284,180 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a
}
}
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn)
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn)
{
return hashdyn->bucket_max * (tommy_size_t)sizeof(hashdyn->bucket[0])
+ tommy_hashdyn_count(hashdyn) * (tommy_size_t)sizeof(tommy_hashdyn_node);
}
/**
* \brief Transfers all elements from the dynamic hashtable into a tommy_list.
*
* Removes every element from the \p hashdyn hashtable and inserts them
* into the provided \p list (at the tail), preserving the per-bucket order
* but not guaranteeing any particular global order.
*
* After the call:
* - the hashtable is left completely empty (\c count = 0, all buckets = NULL)
* - the target list contains all the elements that were previously in the hashtable
*
* This function is useful when you need to:
* - extract all elements to process/sort them outside the hash table
* - convert the hashtable into a list for sequential iteration
* - prepare for a full clear + re-insertion with different hash/ordering
* - move ownership of the nodes to a list-based container
*
* \note The operation is O(n) where n is the number of elements.
* \note No memory allocation is performed.
* \note The relative order of elements that were in the same bucket is preserved,
* but the order among different buckets is bucket-order dependent.
*
* Typical usage pattern:
* \code
* tommy_list all_elements;
* tommy_list_init(&all_elements);
*
* // move everything out of the hashtable into the list
* tommy_hashdyn_to_list(&hashdyn, &all_elements);
*
* // now you can sort, filter, process sequentially, etc.
* tommy_list_sort(&all_elements, compare_by_value);
* \endcode
*
* \param hashdyn The dynamic hashtable to drain
* \param list The list that will receive all the elements
*/
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list)
{
tommy_size_t bucket_max = hashdyn->bucket_max;
tommy_hashdyn_node** bucket = hashdyn->bucket;
tommy_size_t pos;
tommy_builder builder;
tommy_node* builder_tail = tommy_builder_init(&builder);
/* move everything to the list */
for (pos = 0; pos < bucket_max; ++pos) {
tommy_hashdyn_node* node = bucket[pos];
while (node) {
tommy_node* node_next = node->next;
tommy_list_insert_tail(list, node, node->data);
node = node_next;
/* clear buckets as they are transferred to avoid a second scan */
for (tommy_size_t pos = 0; pos < hashdyn->bucket_max; ++pos) {
tommy_node* head = hashdyn->bucket[pos];
if (head) {
tommy_node* tail = head->prev;
builder_tail = tommy_builder_concat(builder_tail, head, tail);
}
hashdyn->bucket[pos] = 0;
}
/* clear all */
memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*));
tommy_list_concat_builder(list, &builder, builder_tail);
hashdyn->count = 0;
}
/**
* Compares hashes in least-significant-bit-first order, with 0 before 1.
*
* The first differing bit decides the order: first precedes second when
* second has a 1 there. Equal hashes compare false.
*/
tommy_inline tommy_bool_t tommy_imp_hashdyn_before(tommy_hash_t first, tommy_hash_t second)
{
/* mark the positions where the hashes differ using XOR */
tommy_hash_t diff = first ^ second;
/* isolate the lowest differing bit using two's complement */
tommy_hash_t bit = diff & -diff;
/* a 1 in second means first has 0 there; equal hashes yield bit == 0 */
return (second & bit) != 0;
}
/**
* Gets the adjacent active bucket in least-significant-bit-first order.
* Returns bucket_max when there is no adjacent bucket.
*
* Traverses the virtual binary tree of buckets in bit-reversed order.
*
* For example, with 8 buckets the forward traversal order is:
* 0, 4, 2, 6, 1, 5, 3, 7
*
* When resizing to 16 buckets:
* 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
*/
tommy_inline tommy_size_t tommy_imp_hashdyn_step(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse)
{
tommy_size_t bit = (hashdyn->bucket_mask + 1) / 2;
/* propagate binary carry in bit-reversed order from MSB downwards */
while (bit && ((pos & bit) != 0) != reverse) {
pos ^= bit;
bit >>= 1;
}
/* carry wrapped around the root of the tree: end of traversal */
if (!bit)
return hashdyn->bucket_max;
pos ^= bit;
return pos;
}
/**
* Finds the first or last node starting at an active bucket.
*
* Scans buckets in least-significant-bit-first order until a non-empty bucket
* is found, then returns its extreme element (minimum for forward, maximum for reverse).
*/
tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_limit(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse)
{
while (pos != hashdyn->bucket_max) {
tommy_hashdyn_node* best = 0;
for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) {
/* equal hashes retain insertion order, so the reverse limit selects the last */
if (reverse) {
if (!best || !tommy_imp_hashdyn_before(i->index, best->index))
best = i;
} else {
if (!best || tommy_imp_hashdyn_before(i->index, best->index))
best = i;
}
}
if (best)
return best;
pos = tommy_imp_hashdyn_step(hashdyn, pos, reverse);
}
return 0;
}
/**
* Finds a neighbor using the current bucket layout and a resize-independent order.
*
* First searches for the closest neighbor within the current bucket.
* If none exists, advances to the next non-empty active bucket in LSB order.
*/
tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_neighbor(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_bool_t reverse)
{
tommy_size_t pos = node->index & hashdyn->bucket_mask;
tommy_hashdyn_node* best = 0;
tommy_bool_t passed = 0;
/*
* Bucket links can change during grow and shrink. Compare full hashes instead
* of following those links as iteration order, so no remaining node moves
* behind the iterator. Equal hashes always stay in one bucket in insertion order.
*/
for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) {
if (i == node) {
passed = 1;
} else if (i->index == node->index) {
/* for identical hashes, follow insertion order relative to the current node */
if (reverse && !passed)
best = i;
else if (!reverse && passed)
return i;
} else if (reverse) {
if (tommy_imp_hashdyn_before(i->index, node->index)) {
/* find the closest neighbor within the bucket in LSB order */
if (!best || !tommy_imp_hashdyn_before(i->index, best->index))
best = i;
}
} else {
if (tommy_imp_hashdyn_before(node->index, i->index)) {
/* find the closest neighbor within the bucket in LSB order */
if (!best || tommy_imp_hashdyn_before(i->index, best->index))
best = i;
}
}
}
if (best)
return best;
/* bucket exhausted: advance to the next non-empty bucket in LSB order */
return tommy_imp_hashdyn_limit(hashdyn, tommy_imp_hashdyn_step(hashdyn, pos, reverse), reverse);
}
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn)
{
/* start forward traversal at bucket 0, which has all LSB bits at zero */
return tommy_imp_hashdyn_limit(hashdyn, 0, 0);
}
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn)
{
/* start reverse traversal at the all-ones bucket, which has the highest LSB value */
return tommy_imp_hashdyn_limit(hashdyn, hashdyn->bucket_mask, 1);
}
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
{
/* adjacent equal hashes are consecutive in insertion order */
if (node->next && node->next->index == node->index)
return node->next;
return tommy_imp_hashdyn_neighbor(hashdyn, node, 0);
}
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
{
/* equal hashes follow insertion order, but the bucket head's prev points to the tail */
if (node != hashdyn->bucket[node->index & hashdyn->bucket_mask] && node->prev->index == node->index)
return node->prev;
return tommy_imp_hashdyn_neighbor(hashdyn, node, 1);
}

View file

@ -4,18 +4,18 @@
/** \file
* Dynamic chained hashtable.
*
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets, it doubles
* the size when it reaches a load factor greater than 0.5 and it halves the size when the load
* factor is lower than 0.125.
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets.
* It doubles the size when it reaches a load factor greater than or equal to 0.5
* and it halves the size when the load factor is less than or equal to 0.125.
*
* All the elements are reallocated in a single resize operation done inside
* tommy_hashdyn_insert() or tommy_hashdyn_remove().
*
* Note that the resize operation takes approximately 100 [ms] with 1 million of elements,
* and 1 [second] with 10 millions. This could be a problem in real-time applications.
* Note that the resize operation takes approximately 100 [ms] with 1 million elements,
* and 1 [second] with 10 million elements. This could be a problem in real-time applications.
*
* The resize also **fragments** the heap, as it involves allocating a double-sized table, **copying** elements,
* and deallocating the older table, **leaving** a big hole in the heap.
* The resize also fragments the heap, as it involves allocating a double-sized table, copying elements,
* and deallocating the older table, leaving a big hole in the heap.
*
* The ::tommy_hashlin hashtable fixes both problems.
*
@ -30,36 +30,36 @@
* To insert elements in the hashtable you have to call tommy_hashdyn_insert() for
* each element.
* In the insertion call you have to specify the address of the node, the
* address of the object, and the hash value of the key to use.
* The address of the object is used to initialize the tommy_node::data field
* of the node, and the hash to initialize the tommy_node::key field.
* address of the element, and the hash value of the key to use.
* The address of the element is used to initialize the tommy_node::data field
* of the node, and the hash to initialize the tommy_node::index field.
*
* \code
* struct object {
* struct element {
* int value;
* // other fields
* tommy_node node;
* };
*
* struct object* obj = malloc(sizeof(struct object)); // creates the object
* struct element* elem = malloc(sizeof(struct element)); // creates the element
*
* obj->value = ...; // initializes the object
* elem->value = ...; // initializes the element
*
* tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object
* tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value)); // inserts the element
* \endcode
*
* To find an element in the hashtable you have to call tommy_hashtable_search()
* To find an element in the hashtable you have to call tommy_hashdyn_search()
* providing a comparison function, its argument, and the hash of the key to search.
*
* \code
* int compare(const void* arg, const void* obj)
* int compare(const void* arg, const void* elem)
* {
* return *(const int*)arg != ((const struct object*)obj)->value;
* return *(const int*)arg != ((const struct element*)elem)->value;
* }
*
* int value_to_find = 1;
* struct object* obj = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find));
* if (!obj) {
* struct element* elem = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find));
* if (!elem) {
* // not found
* } else {
* // found
@ -67,7 +67,7 @@
* \endcode
*
* To iterate over all the elements in the hashtable with the same key, you have to
* use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until NULL.
* use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until 0.
* You have also to check explicitly for the key, as the bucket may contain
* different keys.
*
@ -75,10 +75,10 @@
* int value_to_find = 1;
* tommy_node* i = tommy_hashdyn_bucket(&hashdyn, tommy_inthash_u32(value_to_find));
* while (i) {
* struct object* obj = i->data; // gets the object pointer
* struct element* elem = i->data; // gets the element pointer
*
* if (obj->value == value_to_find) {
* printf("%d\n", obj->value); // process the object
* if (elem->value == value_to_find) {
* printf("%d\n", elem->value); // process the element
* }
*
* i = i->next; // goes to the next element
@ -90,9 +90,9 @@
* and remove.
*
* \code
* struct object* obj = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove));
* if (obj) {
* free(obj); // frees the object allocated memory
* struct element* elem = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove));
* if (elem) {
* free(elem); // frees the element allocated memory
* }
* \endcode
*
@ -105,15 +105,60 @@
*
* If you need to iterate over all the elements in the hashtable, you can use
* tommy_hashdyn_foreach() or tommy_hashdyn_foreach_arg().
* If you need a more precise control with a real iteration, you have to insert
* all the elements also in a ::tommy_list, and use the list to iterate.
* See the \ref multiindex example for more detail.
* For traversal that permits insertions and removals, use tommy_hashdyn_head()
* and tommy_hashdyn_next(), or tommy_hashdyn_tail() and tommy_hashdyn_prev().
* Hashes are compared from the least significant bit to the most significant
* bit, with 0 before 1. Equal hashes are visited in insertion order.
* This order is independent of the number of buckets and progressive resizing.
* It differs from the bucket order used by foreach and to_list.
*
* \anchor tommy_hashdyn_iterator_validity
* Iterators are pointers to nodes currently contained in the hashtable.
* Inserting elements or removing other nodes does not invalidate them or
* change the relative iteration order of the remaining elements.
* A forward or backward traversal visits every original element that remains
* contained, even if removals cause the table to shrink or insertions grow it.
* New elements can be visited if inserted after the current position in the
* traversal direction; elements inserted before it need not be visited.
*
* Removing the pointed node invalidates its use as an iterator, even if its
* storage has not been freed. Obtain the next or previous node before removal,
* and keep that saved node contained until continuing the traversal.
*
* \code
* tommy_hashdyn_node* i = tommy_hashdyn_head(&hashdyn);
* while (i) {
* tommy_hashdyn_node* next = tommy_hashdyn_next(&hashdyn, i);
* if (should_remove(i->data)) {
* free(tommy_hashdyn_remove_existing(&hashdyn, i));
* }
* i = next;
* }
* \endcode
*
* Changing a hash with tommy_hashdyn_rehash_existing() can change iteration
* order, so the traversal guarantee does not cover rehashing during traversal.
* After swapping containers, use the container that now contains the node.
* Clearing or deinitializing the container invalidates all its iterators.
* Concurrent traversal and modification require external synchronization.
*
* Iterator calls scan bucket contents without allocating or modifying memory.
* With a good hash function and normal dynamic resizing, a full traversal
* runs in expected O(n) time. Under the same hash distribution assumption,
* sparse tables require expected O(n + b) time, where n is the number of
* elements and b is the number of buckets. Such tables can arise after
* tommy_hashdyn_reserve(), tommy_hashdyn_clear(), or tommy_hashdyn_to_list().
* In pathological cases with severe hash collisions, traversal can take
* O(n^2 + b) time.
* Use foreach for guaranteed O(n + b) traversal when no insertions or removals
* are needed.
*/
#ifndef __TOMMYHASHDYN_H
#define __TOMMYHASHDYN_H
#ifndef TOMMY_HASHDYN_H
#define TOMMY_HASHDYN_H
#include "tommyhash.h"
#include "tommylist.h"
/******************************************************************************/
/* hashdyn */
@ -126,7 +171,7 @@
/**
* Hashtable node.
* This is the node that you have to include inside your objects.
* This is the node that you have to include inside your elements.
*/
typedef tommy_node tommy_hashdyn_node;
@ -144,6 +189,7 @@ typedef struct tommy_hashdyn_struct {
/**
* Initializes the hashtable.
* \param hashdyn The hashtable.
*/
TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
@ -152,30 +198,99 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
*
* You can call this function with elements still contained,
* but such elements are not going to be freed by this call.
* \param hashdyn The hashtable.
*/
TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn);
/**
* Exchanges the complete state of two initialized hashtables, including bucket allocations.
* The hashtables must not share nodes. Nodes and elements are not accessed or modified.
* Existing node pointers remain valid and belong to the other hashtable.
* Passing the same hashtable twice has no effect. Both hashtables remain usable and deinitializable.
* \param first The first hashtable.
* \param second The second hashtable.
* \note This operation is O(1) with respect to the number of elements.
*/
tommy_inline void tommy_hashdyn_swap(tommy_hashdyn* first, tommy_hashdyn* second)
{
tommy_hashdyn tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Removes all elements, preserving the allocated buckets.
* Call tommy_hashdyn_shrink() after clearing to restore the initial capacity.
* The hashtable remains initialized and can be reused immediately.
* Elements are not freed and nodes are not accessed or modified.
* Their links must not be used to traverse the previous contents.
* You can call this function after tommy_hashdyn_foreach() has freed the elements.
* Subsequent insertions and removals retain the normal resizing policy.
* \param hashdyn The hashtable.
* \note This operation is O(b), where b is the number of buckets.
*/
TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn);
/**
* Pre-allocates buckets for the specified number of elements.
*
* It ensures that the hashtable has enough buckets allocated to contain
* the specified number of elements without triggering a dynamic resize.
* the specified number of elements without triggering a dynamic resize on insertion.
* Subsequent removals retain the normal shrinking policy and may reduce
* the allocated capacity.
* \param hashdyn The hashtable.
* \param count Number of elements to reserve space for.
*/
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count);
/**
* Shrinks the allocated buckets to fit the current number of elements.
* The target is the smallest power of 2, at least 16, for which
* the number of elements is strictly less than half the number of buckets.
* If the current allocation is already no larger than the target, nothing is done.
* An empty hashtable is reduced to 16 buckets.
* Elements are not freed, and the tommy_node::data and tommy_node::index fields
* are left unchanged. The order of elements with the same hash is preserved.
* \param hashdyn The hashtable.
* \note This operation is O(b), where b is the previous number of buckets.
*/
TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn);
/**
* Inserts an element in the hashtable.
* \param hashdyn The hashtable.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
* \param hash Hash of the element to insert.
*/
TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash);
/**
* Inserts an element only if no equal element is already contained.
* If found, the first equal element is returned,
* and the hashtable and candidate node are left unchanged.
* Otherwise, the candidate is inserted using the normal insertion policy,
* and data is returned.
* Elements are not freed by this call.
* \param hashdyn The hashtable.
* \param node The candidate node. It must not belong to any container.
* \param data Pointer to the element to insert.
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
* The function should return 0 for equal elements, non-zero for different elements.
* \param cmp_arg Compare argument describing the candidate key.
* \param hash Hash of the candidate key, consistent with the comparison function.
* \return The first equal element, or data if the candidate was inserted.
*/
TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash);
/**
* Searches and removes an element from the hashtable.
* You have to provide a compare function and the hash of the element you want to remove.
* If the element is not found, 0 is returned.
* If more equal elements are present, the first one is removed.
* \param hashdyn The hashtable.
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
* The function should return 0 for equal elements, anything other for different elements.
* The function should return 0 for equal elements, non-zero for different elements.
* \param cmp_arg Compare argument passed as first argument of the compare function.
* \param hash Hash of the element to find and remove.
* \return The removed element, or 0 if not found.
@ -185,8 +300,9 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func*
/**
* Gets the bucket of the specified hash.
* The bucket is guaranteed to contain ALL the elements with the specified hash,
* but it can contain also others.
* You can access elements in the bucket following the ::next pointer until 0.
* but it can also contain others.
* You can access elements in the bucket following the tommy_node::next pointer until 0.
* \param hashdyn The hashtable.
* \param hash Hash of the element to find.
* \return The head of the bucket, or 0 if empty.
*/
@ -196,35 +312,123 @@ tommy_inline tommy_hashdyn_node* tommy_hashdyn_bucket(tommy_hashdyn* hashdyn, to
}
/**
* Searches an element in the hashtable.
* Finds a node in the hashtable.
* You have to provide a compare function and the hash of the element you want to find.
* If more equal elements are present, the first one is returned.
* \param hashdyn The hashtable.
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
* The function should return 0 for equal elements, anything other for different elements.
* The function should return 0 for equal elements, non-zero for different elements.
* \param cmp_arg Compare argument passed as first argument of the compare function.
* \param hash Hash of the element to find.
* \return The first element found, or 0 if none.
* \return The first node found, or 0 if none.
*/
tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
tommy_inline tommy_hashdyn_node* tommy_hashdyn_find(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
{
tommy_hashdyn_node* i = tommy_hashdyn_bucket(hashdyn, hash);
while (i) {
/* we first check if the hash matches, as in the same bucket we may have multiple hash values */
if (i->index == hash && cmp(cmp_arg, i->data) == 0)
return i->data;
return i;
i = i->next;
}
return 0;
}
/**
* Searches an element in the hashtable.
* You have to provide a compare function and the hash of the element you want to find.
* If more equal elements are present, the first one is returned.
* \param hashdyn The hashtable.
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
* The function should return 0 for equal elements, non-zero for different elements.
* \param cmp_arg Compare argument passed as first argument of the compare function.
* \param hash Hash of the element to find.
* \return The first element found, or 0 if none.
*/
tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
{
tommy_hashdyn_node* node = tommy_hashdyn_find(hashdyn, cmp, cmp_arg, hash);
return node ? node->data : 0;
}
/**
* Removes an element from the hashtable.
* You must already have the address of the element to remove.
* \return The tommy_node::data field of the node removed.
* You must already have the address of the node to remove.
* \param hashdyn The hashtable.
* \param node The node to remove.
* \return The removed element.
*/
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
/**
* Gets the first node in iteration order, or 0 if empty.
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
* \param hashdyn The hashtable.
* \return The first node in iteration order, or 0 if empty.
* \note This operation is expected O(1) under suitable hash distribution and
* normal dynamic resizing. In the worst case, scanning bucket contents and
* empty buckets takes O(n + b), where n is the number of elements and b is
* the number of buckets.
*/
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn);
/**
* Gets the last node in iteration order, or 0 if empty.
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
* \param hashdyn The hashtable.
* \return The last node in iteration order, or 0 if empty.
* \note This operation is expected O(1) under suitable hash distribution and
* normal dynamic resizing. In the worst case, scanning bucket contents and
* empty buckets takes O(n + b), where n is the number of elements and b is
* the number of buckets.
*/
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn);
/**
* Gets the following node in iteration order, or 0 for the tail.
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
* \param hashdyn The hashtable.
* \param node Node currently contained in the hashtable. It must not be 0.
* \return The following node in iteration order, or 0 for the tail.
* \note This operation is expected O(1) under suitable hash distribution and
* normal dynamic resizing. In the worst case, scanning bucket contents and
* empty buckets takes O(n + b), where n is the number of elements and b is
* the number of buckets.
*/
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
/**
* Gets the preceding node in iteration order, or 0 for the head.
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
* \param hashdyn The hashtable.
* \param node Node currently contained in the hashtable. It must not be 0.
* \return The preceding node in iteration order, or 0 for the head.
* \note This operation is expected O(1) under suitable hash distribution and
* normal dynamic resizing. In the worst case, scanning bucket contents and
* empty buckets takes O(n + b), where n is the number of elements and b is
* the number of buckets.
*/
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
/**
* Updates the hash of an element already contained in the hashtable.
* The node must belong to this hashtable. The caller updates the element key
* and provides its new hash, without modifying tommy_node::index directly.
* If the hash is unchanged, the node and its position are left unchanged.
* Otherwise, the node is moved to the tail of the destination bucket,
* even if the old and new hashes identify the same bucket.
* The tommy_node::data field and the number of elements are left unchanged.
* No memory allocation, deallocation or resize is performed.
* Equal keys are allowed; no uniqueness check is performed.
* \param hashdyn The hashtable.
* \param node The node whose hash is updated.
* \param hash The new hash of the element.
* \note This operation is O(1).
*/
TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash);
/**
* Calls the specified function for each element in the hashtable.
*
@ -239,66 +443,102 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash
*
* ...
*
* // creates an object
* struct object* obj = malloc(sizeof(struct object));
* // creates an element
* struct element* elem = malloc(sizeof(struct element));
*
* ...
*
* // insert it in the hashtable
* tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value));
* tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value));
*
* ...
*
* // deallocates all the objects iterating the hashtable
* // deallocates all the elements iterating the hashtable
* tommy_hashdyn_foreach(&hashdyn, free);
*
* // deallocates the hashtable
* tommy_hashdyn_done(&hashdyn);
* \endcode
*
* If the callback frees the elements, the hashtable retains dangling
* node pointers. Call tommy_hashdyn_clear() before reusing it, or
* tommy_hashdyn_done() to release its storage.
* \param hashdyn The hashtable.
* \param func Function to call with each element.
*/
TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the hashtable.
* The iteration order and callback rules are the same as tommy_hashdyn_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
* \param hashdyn The hashtable.
* \param func Function to call with each element.
* \param arg Argument to pass to the function.
*/
TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the number of elements.
* \param hashdyn The hashtable.
* \return The number of elements in the hashtable.
* \note This operation is O(1).
*/
tommy_inline tommy_size_t tommy_hashdyn_count(tommy_hashdyn* hashdyn)
tommy_inline tommy_size_t tommy_hashdyn_count(const tommy_hashdyn* hashdyn)
{
return hashdyn->count;
}
/**
* Gets the size of allocated memory.
* It includes the size of the ::tommy_hashdyn_node of the stored elements.
* Checks whether the hashtable is empty.
* \param hashdyn The hashtable.
* \return Nonzero if the hashtable is empty, 0 otherwise.
* \note This operation is O(1).
*/
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
tommy_inline tommy_bool_t tommy_hashdyn_empty(const tommy_hashdyn* hashdyn)
{
return hashdyn->count == 0;
}
/**
* \brief Transfers all elements from the hashtable into a tommy_list.
* Gets the number of buckets.
* \param hashdyn The hashtable.
* \return The number of buckets.
* \note This operation is O(1).
*/
tommy_inline tommy_size_t tommy_hashdyn_bucket_count(const tommy_hashdyn* hashdyn)
{
return hashdyn->bucket_max;
}
/**
* Gets the size of allocated memory.
* It includes the size of the ::tommy_hashdyn_node of the stored elements.
* \param hashdyn The hashtable.
* \return Total memory allocated by the hashtable, in bytes.
*/
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn);
/**
* Transfers all elements from the hashtable into a tommy_list.
*
* Removes every element from the \p hashdyn hashtable and inserts them
* into the provided \p list (at the tail), preserving the per-bucket order
* but not guaranteeing any particular global order.
*
* After the call:
* - the hashtable is left completely empty
* - the hashtable is left empty and initialized, preserving its allocated buckets
* - the target list contains all the elements that were previously in the hashtable
*
* The tommy_node::data and tommy_node::index fields are left unchanged.
*
* This function is useful when you need to:
* - extract all elements to process/sort them outside the hash table
* - extract all elements to process/sort them outside the hashtable
* - convert the hashtable into a list for sequential iteration
* - prepare for a full clear + re-insertion with different hash/ordering
* - move ownership of the nodes to a list-based container
*
* \note The operation is O(n) where n is the number of elements.
* \note No memory allocation is performed.
* \note The relative order of elements that were in the same bucket is preserved,
* but the order among different buckets is bucket-order dependent.
*
* Typical usage pattern:
* \code
* tommy_list all_elements;
@ -311,8 +551,13 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
* tommy_list_sort(&all_elements, compare_by_value);
* \endcode
*
* \param hashdyn The dynamic hashtable to drain
* \param list The list that will receive all the elements
* \param hashdyn The hashtable to drain.
* \param list The destination list. It must be initialized and must not share
* nodes with the hashtable. Existing elements remain at the head of the list.
* \note This operation is O(b), where b is the number of buckets.
* \note No memory allocation or deallocation is performed.
* \note The relative order of elements that were in the same bucket is preserved,
* but the order among different buckets is bucket-order dependent.
*/
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list);

View file

@ -7,30 +7,109 @@
/** \internal
* Setup a list.
*/
tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node* tail)
tommy_inline void tommy_imp_list_set(tommy_list* list, tommy_node* head, tommy_node* tail)
{
head->prev = tail;
tail->next = 0;
*list = head;
}
TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second)
{
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
if (reference == tommy_list_head(list)) {
/* prepend */
tommy_list_prepend(list, second);
} else {
/* splice in the middle */
tommy_chain_splice(reference->prev, reference, second_head, second_head->prev);
}
}
TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second)
{
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
if (!reference->next) {
/* append */
tommy_list_concat(list, second);
} else {
/* splice in the middle */
tommy_chain_splice(reference, reference->next, second_head, second_head->prev);
}
}
TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp)
{
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
tommy_node* first_head = tommy_list_head(first);
if (!first_head) {
*first = *second;
return;
}
/* create chains from the non-empty lists */
tommy_chain first_chain;
tommy_chain second_chain;
first_chain.head = first_head;
first_chain.tail = first_head->prev;
second_chain.head = second_head;
second_chain.tail = second_head->prev;
tommy_chain_merge_degenerated(&first_chain, &second_chain, cmp);
/* restore the destination list */
tommy_imp_list_set(first, first_chain.head, first_chain.tail);
}
TOMMY_API void tommy_list_reverse(tommy_list* list)
{
tommy_node* head = tommy_list_head(list);
tommy_node* node = head;
tommy_node* prev = 0;
if (!head)
return;
while (node) {
tommy_node* next = node->next;
node->next = prev;
node->prev = next;
prev = node;
node = next;
}
/* the old head is the new tail */
tommy_imp_list_set(list, prev, head);
}
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp)
{
tommy_chain chain;
tommy_node* head;
if (tommy_list_empty(list))
return;
head = tommy_list_head(list);
tommy_node* head = tommy_list_head(list);
/* create a chain from the list */
tommy_chain chain;
chain.head = head;
chain.tail = head->prev;
tommy_chain_mergesort(&chain, cmp);
/* restore the list */
tommy_list_set(list, chain.head, chain.tail);
tommy_imp_list_set(list, chain.head, chain.tail);
}

View file

@ -2,21 +2,21 @@
// Copyright (C) 2010 Andrea Mazzoleni
/** \file
* Double linked list for collisions into hashtables.
* Doubly linked list for collisions into hashtables.
*
* This list is a **doubly** linked list mainly targeted for handling collisions
* into an hashtable, but **usable** also as a generic list.
* This list is a doubly linked list mainly targeted for handling collisions
* into a hashtable, but usable also as a generic list.
*
* The main feature of this list is to require only one pointer to represent the
* list, compared to a classic implementation requiring a head **and** a tail pointers.
* list, compared to a classic implementation requiring both head and tail pointers.
* This reduces the memory usage in hashtables.
*
* Another feature is to support the insertion at the end of the list. This **allows** to store
* collisions in a stable order. Where for stable order we mean that equal elements keep
* Another feature is to support the insertion at the end of the list. This allows
* storing collisions in a stable order, meaning that equal elements preserve
* their insertion order.
*
* To initialize the list, you have to call tommy_list_init(), or to simply assign
* to it NULL, as an empty list is represented by the NULL value.
* to it 0, as an empty list is represented by the 0 value.
*
* \code
* tommy_list list;
@ -27,51 +27,113 @@
* To insert elements in the list you have to call tommy_list_insert_tail()
* or tommy_list_insert_head() for each element.
* In the insertion call you have to specify the address of the node and the
* address of the object.
* The address of the object is used to initialize the tommy_node::data field
* address of the element.
* The address of the element is used to initialize the tommy_node::data field
* of the node.
*
* \code
* struct object {
* struct element {
* int value;
* // other fields
* tommy_node node;
* };
*
* struct object* obj = malloc(sizeof(struct object)); // creates the object
* struct element* elem = malloc(sizeof(struct element)); // creates the element
*
* obj->value = ...; // initializes the object
* elem->value = ...; // initializes the element
*
* tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object
* tommy_list_insert_tail(&list, &elem->node, elem); // inserts the element
* \endcode
*
* To iterate over all the elements in the list you have to call
* tommy_list_head() to get the head of the list and follow the
* tommy_node::next pointer until NULL.
* tommy_node::next pointer until 0.
*
* \code
* tommy_node* i = tommy_list_head(&list);
* while (i) {
* struct object* obj = i->data; // gets the object pointer
* struct element* elem = i->data; // gets the element pointer
*
* printf("%d\n", obj->value); // process the object
* printf("%d\n", elem->value); // process the element
*
* i = i->next; // go to the next element
* }
* \endcode
*
* To iterate backward, start at tommy_list_tail() and use tommy_list_prev().
* Do not follow tommy_node::prev directly past the head, as it points to the tail.
*
* \code
* tommy_node* i = tommy_list_tail(&list);
* while (i) {
* struct element* elem = i->data;
*
* printf("%d\n", elem->value);
*
* i = tommy_list_prev(&list, i);
* }
* \endcode
*
* \anchor tommy_list_iterator_validity
* Iterators are pointers to nodes contained in the list.
* Inserting elements or removing other nodes between iterator calls does not
* invalidate pointers to nodes that remain in the list.
* Subsequent next/prev calls use the current contents and ordering;
* inserted elements may become visible and removed elements are skipped.
*
* Removing the pointed node invalidates its use as an iterator, even if
* its storage has not been freed. To continue traversal after removing
* the current node, obtain the next or previous node before removal.
*
* \code
* tommy_node* i = tommy_list_head(&list);
* while (i) {
* tommy_node* next = i->next; // obtain the next node before removal
*
* if (condition) {
* tommy_list_remove_existing(&list, i);
* free(i->data); // safe to deallocate
* }
*
* i = next;
* }
* \endcode
*
* Concurrent traversal and modification require external synchronization.
*
* To reorder existing elements, use tommy_list_move_head(), tommy_list_move_tail()
* or tommy_list_reverse(). To split a list or transfer elements between lists,
* use tommy_list_split(), tommy_list_prepend(), tommy_list_splice_before()
* and tommy_list_splice_after().
* These operations preserve the element pointers and do not allocate memory.
*
* \code
* tommy_list second;
* tommy_list_init(&second);
*
* // transfers node and all following nodes to second
* tommy_list_split(&list, node, &second);
*
* // appends the transferred nodes back to list
* tommy_list_concat(&list, &second);
* \endcode
*
* Two lists sorted with the same comparison function can be merged with
* tommy_list_merge(). The merge is stable.
*
* To destroy the list you have to remove all the elements,
* as the list is completely inplace and it doesn't allocate memory.
* as the list is completely in-place and it doesn't allocate memory.
* This can be done with the tommy_list_foreach() function.
*
* \code
* // deallocates all the objects iterating the list
* // deallocates all the elements iterating the list
* tommy_list_foreach(&list, free);
* tommy_list_clear(&list); // resets the list before reuse
* \endcode
*/
#ifndef __TOMMYLIST_H
#define __TOMMYLIST_H
#ifndef TOMMY_LIST_H
#define TOMMY_LIST_H
#include "tommytypes.h"
@ -83,18 +145,72 @@
*/
typedef tommy_node* tommy_list;
/**
* Temporary list builder with an embedded sentinel.
* Initialize with tommy_builder_init() and finish with tommy_list_concat_builder().
* Keep the builder at the same address until finalization, as node links
* refer to its sentinel.
*/
typedef struct tommy_builder_struct {
tommy_node dummy; /**< Sentinel excluded from the resulting list. */
} tommy_builder;
/**
* Initializes an empty builder in O(1).
* \param builder Builder to initialize in its final location.
* \return Initial tail, pointing to the builder's sentinel.
*/
tommy_inline tommy_node* tommy_builder_init(tommy_builder* builder)
{
builder->dummy.next = 0;
return &builder->dummy;
}
/**
* Appends a non-empty chain to a builder in O(1), preserving element order.
* \param tail Current tail, initially returned by tommy_builder_init().
* \param head Head of the non-empty source chain.
* \param last Tail of the source chain. For a single node, pass head again.
* The chains must not share nodes. Inner links must be valid;
* head's prev and last's next may be undefined.
* \return Updated tail to pass to the next append or tommy_list_concat_builder().
*/
tommy_inline tommy_node* tommy_builder_concat(tommy_node* tail, tommy_node* head, tommy_node* last)
{
head->prev = tail;
tail->next = head;
return last;
}
/**
* Initializes the list.
* The list is completely inplace, so it doesn't need to be deinitialized.
* The list is completely in-place, so it doesn't need to be deinitialized.
* \param list The list.
*/
tommy_inline void tommy_list_init(tommy_list* list)
{
*list = 0;
}
/**
* Removes all elements.
* The list remains initialized and can be reused immediately.
* Elements are not freed and nodes are not accessed or modified.
* Their links must not be used to traverse the previous contents.
* You can call this function after tommy_list_foreach() has freed the elements.
* \param list The list.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_clear(tommy_list* list)
{
*list = 0;
}
/**
* Gets the head of the list.
* \return The head node. For empty lists 0 is returned.
* \param list The list.
* \return The head node, or 0 if the list is empty.
* \note This operation is O(1).
*/
tommy_inline tommy_node* tommy_list_head(tommy_list* list)
{
@ -103,7 +219,9 @@ tommy_inline tommy_node* tommy_list_head(tommy_list* list)
/**
* Gets the tail of the list.
* \return The tail node. For empty lists 0 is returned.
* \param list The list.
* \return The tail node, or 0 if the list is empty.
* \note This operation is O(1).
*/
tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
{
@ -115,9 +233,27 @@ tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
return head->prev;
}
/**
* Gets the previous node in the list.
* Unlike tommy_node::prev, this function returns 0 at the head of the list,
* allowing backward iteration starting from tommy_list_tail().
* See \ref tommy_list_iterator_validity for iterator validity across insertions and removals.
* \param list The list.
* \param node The current node. The node must be in the list.
* \return The previous node, or 0 if the current node is the head.
* \note This operation is O(1).
*/
tommy_inline tommy_node* tommy_list_prev(tommy_list* list, tommy_node* node)
{
if (node == tommy_list_head(list))
return 0;
return node->prev;
}
/** \internal
* Creates a new list with a single element.
* \param list The list to initialize.
* \param list The list.
* \param node The node to insert.
*/
tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
@ -134,7 +270,7 @@ tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
/** \internal
* Inserts an element at the head of a non-empty list.
* The element is inserted at the head of the list. The list cannot be empty.
* \param list The list. The list cannot be empty.
* \param list The list.
* \param node The node to insert.
*/
tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node)
@ -171,8 +307,8 @@ tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node*
/**
* Inserts an element at the head of a list.
* \param list The list.
* \param node The node to insert.
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
*/
tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data)
{
@ -189,8 +325,8 @@ tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, voi
/**
* Inserts an element at the tail of a list.
* \param list The list.
* \param node The node to insert.
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
*/
tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data)
{
@ -208,8 +344,8 @@ tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, voi
* Inserts an element before the specified reference node.
* \param list The list.
* \param reference The reference node. The new node will be inserted before this node. The reference node must be in the list.
* \param node The node to insert.
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
*/
tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
{
@ -235,8 +371,8 @@ tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* referen
* Inserts an element after the specified reference node.
* \param list The list.
* \param reference The reference node. The new node will be inserted after this node. The reference node must be in the list.
* \param node The node to insert.
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
*/
tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
{
@ -260,12 +396,12 @@ tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* referenc
/**
* Removes an element from the list.
* You must already have the address of the element to remove.
* \note The node content is left unchanged, including the tommy_node::next
* and tommy_node::prev fields that still contain pointers **in** the list.
* You must already have the address of the node to remove.
* \param list The list.
* \param node The node to remove. The node must be in the list.
* \return The tommy_node::data field of the node removed.
* \param node The node to remove.
* \return The removed element.
* \note The node content is left unchanged, including the tommy_node::next
* and tommy_node::prev fields that still contain pointers in the list.
*/
tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node)
{
@ -287,32 +423,167 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node
}
/**
* Concats two lists.
* The second list is concatenated at the first list.
* Removes the element at the head of the list.
* \param list The list.
* \return The removed element, or 0 if the list is empty.
*/
tommy_inline void* tommy_list_remove_head(tommy_list* list)
{
tommy_node* node = tommy_list_head(list);
if (!node)
return 0;
/* the new head must retain the tail pointer in the "circular" prev list */
if (node->next)
node->next->prev = node->prev;
*list = node->next;
return node->data;
}
/**
* Removes the element at the tail of the list.
* \param list The list.
* \return The removed element, or 0 if the list is empty.
*/
tommy_inline void* tommy_list_remove_tail(tommy_list* list)
{
tommy_node* head = tommy_list_head(list);
if (!head)
return 0;
tommy_node* node = head->prev;
/* a single node is also the head, and its links must remain unchanged */
if (node == head) {
*list = 0;
} else {
head->prev = node->prev;
node->prev->next = 0;
}
return node->data;
}
/**
* Moves an existing node to the head of the list.
* If the node is already at the head, nothing is done.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \param node The node to move. The node must be in the list.
*/
tommy_inline void tommy_list_move_head(tommy_list* list, tommy_node* node)
{
if (node == tommy_list_head(list))
return;
tommy_list_remove_existing(list, node);
tommy_list_insert_head_not_empty(list, node);
}
/**
* Moves an existing node to the tail of the list.
* If the node is already at the tail, nothing is done.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \param node The node to move. The node must be in the list.
*/
tommy_inline void tommy_list_move_tail(tommy_list* list, tommy_node* node)
{
if (node->next == 0)
return;
tommy_list_remove_existing(list, node);
tommy_list_insert_tail_not_empty(tommy_list_head(list), node);
}
/**
* Splits a list before the specified node.
* The list retains the nodes preceding the specified node. The second list
* receives the specified node and all following nodes. The order of the elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list to split.
* \param node The first node to transfer. The node must be in the list,
* or 0 to leave list unchanged and set second to 0.
* Specifying the head transfers the whole list.
* \param second Output list receiving node and all following nodes.
* It must be distinct from list and need not be initialized.
* Its previous value is overwritten. Any previously referenced nodes
* are left unchanged and are not deallocated.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_split(tommy_list* list, tommy_node* node, tommy_list* second)
{
tommy_node* head = tommy_list_head(list);
tommy_node* tail;
tommy_node* before;
if (!node) {
*second = 0;
return;
}
if (node == head) {
*second = *list;
*list = 0;
return;
}
tail = head->prev;
before = node->prev;
/* terminate the retained list */
head->prev = before;
before->next = 0;
/* create the list of transferred nodes */
node->prev = tail;
*second = node;
}
/**
* Exchanges the contents of two lists.
* The lists must not share nodes. Nodes and elements are not accessed or modified.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* Passing the same list twice has no effect.
* \param first The first list.
* \param second The second list.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_swap(tommy_list* first, tommy_list* second)
{
tommy_list tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Concatenates two lists.
* The second list is concatenated to the first list.
* \param first The first list.
* \param second The second list. After this call the list content is undefined,
* and you should not use it anymore.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
{
tommy_node* first_head;
tommy_node* first_tail;
tommy_node* second_head;
/* if the second is empty, nothing to do */
second_head = tommy_list_head(second);
tommy_node* second_head = tommy_list_head(second);
if (second_head == 0)
return;
/* if the first is empty, copy the second */
first_head = tommy_list_head(first);
tommy_node* first_head = tommy_list_head(first);
if (first_head == 0) {
*first = *second;
return;
}
/* tail of the first list */
first_tail = first_head->prev;
tommy_node* first_tail = first_head->prev;
/* set the "circular" prev list */
first_head->prev = second_head->prev;
@ -322,32 +593,136 @@ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
first_tail->next = second_head;
}
/**
* Appends a builder's chain to a list, preserving element order and restoring list links.
* \param list Destination list, possibly empty. It must not share nodes with the builder.
* \param builder Initialized builder, possibly empty. Its contents are consumed;
* reinitialize it before reuse.
* \param tail Current tail returned by the builder helpers, ignored when empty.
* \note This operation is O(1). An empty builder has no effect.
*/
tommy_inline void tommy_list_concat_builder(tommy_list* list, const tommy_builder* builder, tommy_node* tail)
{
tommy_node* head = builder->dummy.next;
if (!head)
return;
tommy_node* first = *list;
/* replace the sentinel link so no list node refers to the temporary builder */
if (first) {
tommy_node* first_tail = first->prev;
first_tail->next = head;
head->prev = first_tail;
first->prev = tail;
} else {
*list = head;
head->prev = tail;
}
tail->next = 0;
}
/**
* Prepends a list to another list.
* The second list is prepended to the first list.
* \param first The first list.
* \param second The second list. After this call the list content is undefined,
* and you should not use it anymore.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_prepend(tommy_list* first, tommy_list* second)
{
tommy_list_concat(second, first);
*first = *second;
}
/**
* Transfers all elements of a list before the specified reference node.
* The order of the transferred elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The destination list.
* \param reference The node before which to insert. It must not be 0 and must be in the destination list.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \note This operation is O(1). An empty source list has no effect.
*/
TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second);
/**
* Transfers all elements of a list after the specified reference node.
* The order of the transferred elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The destination list.
* \param reference The node after which to insert. It must not be 0 and must be in the destination list.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \note This operation is O(1). An empty source list has no effect.
*/
TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second);
/**
* Merges two sorted lists.
* Both lists must be sorted using the same comparison function.
* The merge is stable: equal elements from the first list precede those from
* the second list, and the relative order within each list is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param first The destination list, receiving the sorted result.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \param cmp Compare function called with two elements.
* The function should return <0 if the first element is less than the second,
* ==0 if equal, and >0 if greater. It must not modify the lists.
* \note This operation is O(n+m), or O(1) when one list is empty or the ranges
* can be concatenated directly. It uses O(1) auxiliary space.
*/
TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp);
/**
* Reverses the order of the elements in a list.
* Empty lists and lists containing one element are left unchanged.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \note This operation is O(n) and uses O(1) auxiliary space.
*/
TOMMY_API void tommy_list_reverse(tommy_list* list);
/**
* Sorts a list.
* It's a stable merge sort with O(N*log(N)) worst complexity.
* It's faster on degenerated cases like partially ordered lists.
* It's a stable merge sort with O(n log n) worst complexity.
* It's faster on degenerate cases like partially ordered lists.
* \param list The list.
* \param cmp Compare function called with two elements.
* The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greater.
* \note This operation is O(n log n) in the worst case.
*/
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp);
/**
* Checks if empty.
* \return If the list is empty.
* Checks whether the list is empty.
* \param list The list.
* \return Nonzero if the list is empty, 0 otherwise.
* \note This operation is O(1).
*/
tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list)
tommy_inline tommy_bool_t tommy_list_empty(const tommy_list* list)
{
return tommy_list_head(list) == 0;
return *list == 0;
}
/**
* Gets the number of elements.
* \param list The list.
* \return The number of elements in the list.
* \note This operation is O(n).
*/
tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
tommy_inline tommy_size_t tommy_list_count(const tommy_list* list)
{
tommy_size_t count = 0;
tommy_node* i = tommy_list_head(list);
const tommy_node* i = *list;
while (i) {
++count;
@ -371,19 +746,26 @@ tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
*
* ...
*
* // creates an object
* struct object* obj = malloc(sizeof(struct object));
* // creates an element
* struct element* elem = malloc(sizeof(struct element));
*
* ...
*
* // insert it in the list
* tommy_list_insert_tail(&list, &obj->node, obj);
* tommy_list_insert_tail(&list, &elem->node, elem);
*
* ...
*
* // deallocates all the objects iterating the list
* // deallocates all the elements iterating the list
* tommy_list_foreach(&list, free);
* tommy_list_clear(&list); // resets the list before reuse
* \endcode
*
* If the callback frees the elements, the list retains dangling node
* pointers. Call tommy_list_clear() or tommy_list_init() before reusing it.
* The list has no storage to release.
* \param list The list.
* \param func Function to call with each element.
*/
tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
{
@ -398,6 +780,12 @@ tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
/**
* Calls the specified function with an argument for each element in the list.
* The iteration order and callback rules are the same as tommy_list_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
* \param list The list.
* \param func Function to call with each element.
* \param arg Argument to pass to the function.
*/
tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
{
@ -411,3 +799,4 @@ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_fun
}
#endif

View file

@ -14,7 +14,7 @@
#define TOMMY_TREE_BALANCE_LEFT ((tommy_size_t)1)
#define TOMMY_TREE_BALANCE_RIGHT ((tommy_size_t)2)
tommy_inline int tommy_tree_balance_get(tommy_tree_node* node)
tommy_inline int tommy_imp_tree_balance_get(tommy_tree_node* node)
{
tommy_size_t balance = node->index & TOMMY_TREE_BALANCE_MASK;
@ -25,7 +25,7 @@ tommy_inline int tommy_tree_balance_get(tommy_tree_node* node)
return 0;
}
tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance)
tommy_inline void tommy_imp_tree_balance_set(tommy_tree_node* node, int balance)
{
tommy_size_t value = 0;
@ -37,12 +37,12 @@ tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance)
node->index = (node->index & ~TOMMY_TREE_BALANCE_MASK) | value;
}
tommy_inline void tommy_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent)
tommy_inline void tommy_imp_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent)
{
node->index = (tommy_size_t)(tommy_uintptr_t)parent | (node->index & TOMMY_TREE_BALANCE_MASK);
}
tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node)
tommy_inline void tommy_imp_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node)
{
tommy_tree_node* parent = tommy_tree_parent(root);
@ -55,45 +55,45 @@ tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, to
}
if (node)
tommy_tree_parent_set(node, parent);
tommy_imp_tree_parent_set(node, parent);
}
tommy_inline tommy_tree_node* tommy_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root)
tommy_inline tommy_tree_node* tommy_imp_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root)
{
tommy_tree_node* next = root->next;
tommy_tree_replace(tree, root, next);
tommy_imp_tree_replace(tree, root, next);
root->next = next->prev;
if (root->next)
tommy_tree_parent_set(root->next, root);
tommy_imp_tree_parent_set(root->next, root);
next->prev = root;
tommy_tree_parent_set(root, next);
tommy_imp_tree_parent_set(root, next);
return next;
}
tommy_inline tommy_tree_node* tommy_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root)
tommy_inline tommy_tree_node* tommy_imp_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root)
{
tommy_tree_node* prev = root->prev;
tommy_tree_replace(tree, root, prev);
tommy_imp_tree_replace(tree, root, prev);
root->prev = prev->next;
if (root->prev)
tommy_tree_parent_set(root->prev, root);
tommy_imp_tree_parent_set(root->prev, root);
prev->next = root;
tommy_tree_parent_set(root, prev);
tommy_imp_tree_parent_set(root, prev);
return prev;
}
tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node)
tommy_inline void tommy_imp_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node)
{
tommy_tree_node* parent = tommy_tree_parent(node);
while (parent) {
int balance = tommy_tree_balance_get(parent);
int balance = tommy_imp_tree_balance_get(parent);
if (node == parent->prev)
--balance;
@ -102,12 +102,12 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
}
if (balance == 0) {
tommy_tree_balance_set(parent, balance);
tommy_imp_tree_balance_set(parent, balance);
return;
}
if (balance >= -1 && balance <= 1) {
tommy_tree_balance_set(parent, balance);
tommy_imp_tree_balance_set(parent, balance);
node = parent;
parent = tommy_tree_parent(parent);
continue;
@ -115,39 +115,39 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
if (balance == -2) {
tommy_tree_node* left = parent->prev;
int left_balance = tommy_tree_balance_get(left);
int left_balance = tommy_imp_tree_balance_get(left);
if (left_balance < 0) {
tommy_tree_rotate_right(tree, parent);
tommy_tree_balance_set(parent, 0);
tommy_tree_balance_set(left, 0);
tommy_imp_tree_rotate_right(tree, parent);
tommy_imp_tree_balance_set(parent, 0);
tommy_imp_tree_balance_set(left, 0);
} else {
tommy_tree_node* middle = left->next;
int middle_balance = tommy_tree_balance_get(middle);
int middle_balance = tommy_imp_tree_balance_get(middle);
tommy_tree_rotate_left(tree, left);
tommy_tree_rotate_right(tree, parent);
tommy_tree_balance_set(parent, middle_balance < 0 ? 1 : 0);
tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
tommy_tree_balance_set(middle, 0);
tommy_imp_tree_rotate_left(tree, left);
tommy_imp_tree_rotate_right(tree, parent);
tommy_imp_tree_balance_set(parent, middle_balance < 0 ? 1 : 0);
tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
tommy_imp_tree_balance_set(middle, 0);
}
} else {
tommy_tree_node* right = parent->next;
int right_balance = tommy_tree_balance_get(right);
int right_balance = tommy_imp_tree_balance_get(right);
if (right_balance > 0) {
tommy_tree_rotate_left(tree, parent);
tommy_tree_balance_set(parent, 0);
tommy_tree_balance_set(right, 0);
tommy_imp_tree_rotate_left(tree, parent);
tommy_imp_tree_balance_set(parent, 0);
tommy_imp_tree_balance_set(right, 0);
} else {
tommy_tree_node* middle = right->prev;
int middle_balance = tommy_tree_balance_get(middle);
int middle_balance = tommy_imp_tree_balance_get(middle);
tommy_tree_rotate_right(tree, right);
tommy_tree_rotate_left(tree, parent);
tommy_tree_balance_set(parent, middle_balance > 0 ? -1 : 0);
tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
tommy_tree_balance_set(middle, 0);
tommy_imp_tree_rotate_right(tree, right);
tommy_imp_tree_rotate_left(tree, parent);
tommy_imp_tree_balance_set(parent, middle_balance > 0 ? -1 : 0);
tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
tommy_imp_tree_balance_set(middle, 0);
}
}
@ -155,41 +155,59 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
}
}
TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
tommy_inline void* tommy_imp_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data, tommy_bool_t unique)
{
tommy_tree_node* parent = 0;
tommy_tree_node* existing = 0;
tommy_tree_node** link = &tree->root;
/* cache the comparator to avoid reloading it after each comparison */
tommy_compare_func* cmp_func = tree->cmp;
while (*link) {
/* comparators must not change the tree, so keep the node across the call */
parent = *link;
int cmp = cmp_func(data, parent->data);
if (cmp < 0)
link = &parent->prev;
else if (cmp == 0 && unique) {
existing = parent;
link = &parent->prev;
} else {
/* placing equal keys after existing ones preserves insertion order through rotations */
link = &parent->next;
}
}
if (existing)
return existing->data;
node->data = data;
node->prev = 0;
node->next = 0;
node->index = 0;
while (*link) {
int cmp = tree->cmp(data, (*link)->data);
parent = *link;
if (cmp < 0)
link = &parent->prev;
else if (cmp > 0)
link = &parent->next;
else
return parent->data;
}
node->index = (tommy_size_t)(tommy_uintptr_t)parent;
*link = node;
++tree->count;
tommy_tree_insert_balance(tree, node);
tommy_imp_tree_insert_balance(tree, node);
return node->data;
}
tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk)
TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
{
tommy_imp_tree_insert(tree, node, data, 0);
}
TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data)
{
return tommy_imp_tree_insert(tree, node, data, 1);
}
tommy_inline void tommy_imp_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk)
{
while (node) {
int balance = tommy_tree_balance_get(node);
int balance = tommy_imp_tree_balance_get(node);
if (left_shrunk)
++balance;
@ -197,15 +215,13 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
--balance;
if (balance == -1 || balance == 1) {
tommy_tree_balance_set(node, balance);
tommy_imp_tree_balance_set(node, balance);
return;
}
if (balance == 0) {
tommy_tree_node* parent;
tommy_tree_balance_set(node, balance);
parent = tommy_tree_parent(node);
tommy_imp_tree_balance_set(node, balance);
tommy_tree_node* parent = tommy_tree_parent(node);
if (!parent)
return;
left_shrunk = node == parent->prev;
@ -215,27 +231,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
if (balance == -2) {
tommy_tree_node* left = node->prev;
int left_balance = tommy_tree_balance_get(left);
int left_balance = tommy_imp_tree_balance_get(left);
tommy_tree_node* root;
if (left_balance <= 0) {
root = tommy_tree_rotate_right(tree, node);
root = tommy_imp_tree_rotate_right(tree, node);
if (left_balance == 0) {
tommy_tree_balance_set(node, -1);
tommy_tree_balance_set(left, 1);
tommy_imp_tree_balance_set(node, -1);
tommy_imp_tree_balance_set(left, 1);
return;
}
tommy_tree_balance_set(node, 0);
tommy_tree_balance_set(left, 0);
tommy_imp_tree_balance_set(node, 0);
tommy_imp_tree_balance_set(left, 0);
} else {
tommy_tree_node* middle = left->next;
int middle_balance = tommy_tree_balance_get(middle);
int middle_balance = tommy_imp_tree_balance_get(middle);
tommy_tree_rotate_left(tree, left);
root = tommy_tree_rotate_right(tree, node);
tommy_tree_balance_set(node, middle_balance < 0 ? 1 : 0);
tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
tommy_tree_balance_set(middle, 0);
tommy_imp_tree_rotate_left(tree, left);
root = tommy_imp_tree_rotate_right(tree, node);
tommy_imp_tree_balance_set(node, middle_balance < 0 ? 1 : 0);
tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
tommy_imp_tree_balance_set(middle, 0);
}
node = tommy_tree_parent(root);
@ -247,27 +263,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
{
tommy_tree_node* right = node->next;
int right_balance = tommy_tree_balance_get(right);
int right_balance = tommy_imp_tree_balance_get(right);
tommy_tree_node* root;
if (right_balance >= 0) {
root = tommy_tree_rotate_left(tree, node);
root = tommy_imp_tree_rotate_left(tree, node);
if (right_balance == 0) {
tommy_tree_balance_set(node, 1);
tommy_tree_balance_set(right, -1);
tommy_imp_tree_balance_set(node, 1);
tommy_imp_tree_balance_set(right, -1);
return;
}
tommy_tree_balance_set(node, 0);
tommy_tree_balance_set(right, 0);
tommy_imp_tree_balance_set(node, 0);
tommy_imp_tree_balance_set(right, 0);
} else {
tommy_tree_node* middle = right->prev;
int middle_balance = tommy_tree_balance_get(middle);
int middle_balance = tommy_imp_tree_balance_get(middle);
tommy_tree_rotate_right(tree, right);
root = tommy_tree_rotate_left(tree, node);
tommy_tree_balance_set(node, middle_balance > 0 ? -1 : 0);
tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
tommy_tree_balance_set(middle, 0);
tommy_imp_tree_rotate_right(tree, right);
root = tommy_imp_tree_rotate_left(tree, node);
tommy_imp_tree_balance_set(node, middle_balance > 0 ? -1 : 0);
tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
tommy_imp_tree_balance_set(middle, 0);
}
node = tommy_tree_parent(root);
@ -289,7 +305,7 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no
parent = tommy_tree_parent(node);
left_shrunk = parent && node == parent->prev;
tommy_tree_replace(tree, node, child);
tommy_imp_tree_replace(tree, node, child);
} else {
tommy_tree_node* next = node->next;
@ -298,67 +314,106 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no
parent = tommy_tree_parent(next);
if (parent == node) {
tommy_tree_replace(tree, node, next);
tommy_imp_tree_replace(tree, node, next);
next->prev = node->prev;
tommy_tree_parent_set(next->prev, next);
tommy_tree_balance_set(next, tommy_tree_balance_get(node));
tommy_imp_tree_parent_set(next->prev, next);
tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node));
parent = next;
left_shrunk = 0;
} else {
parent->prev = next->next;
if (parent->prev)
tommy_tree_parent_set(parent->prev, parent);
tommy_imp_tree_parent_set(parent->prev, parent);
tommy_tree_replace(tree, node, next);
tommy_imp_tree_replace(tree, node, next);
next->prev = node->prev;
next->next = node->next;
tommy_tree_parent_set(next->prev, next);
tommy_tree_parent_set(next->next, next);
tommy_tree_balance_set(next, tommy_tree_balance_get(node));
tommy_imp_tree_parent_set(next->prev, next);
tommy_imp_tree_parent_set(next->next, next);
tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node));
left_shrunk = 1;
}
}
--tree->count;
if (parent)
tommy_tree_remove_balance(tree, parent, left_shrunk);
tommy_imp_tree_remove_balance(tree, parent, left_shrunk);
return data;
}
TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func)
TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list)
{
while (root) {
tommy_tree_node* next;
/* AVL height is less than twice the bit width of its node count */
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
tommy_size_t depth = 0;
tommy_tree_node* node = tree->root;
tommy_builder builder;
tommy_node* builder_tail = tommy_builder_init(&builder);
tommy_tree_foreach_node(root->prev, func);
while (node || depth) {
while (node) {
stack[depth] = node;
++depth;
node = node->prev;
}
/* make a copy in case func is free() */
next = root->next;
node = stack[--depth];
/* chain appending overwrites child links, so keep the right subtree reachable */
tommy_tree_node* next = node->next;
builder_tail = tommy_builder_concat(builder_tail, node, node);
node = next;
}
func(root->data);
tommy_list_concat_builder(list, &builder, builder_tail);
tommy_tree_clear(tree);
}
root = next;
TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
{
/* AVL height is less than twice the bit width of its node count */
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
tommy_size_t depth = 0;
tommy_tree_node* node = tree->root;
while (node || depth) {
while (node) {
stack[depth] = node;
++depth;
node = node->prev;
}
node = stack[--depth];
/* save the next subtree before the callback can free this node */
tommy_tree_node* next = node->next;
func(node->data);
node = next;
}
}
TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg)
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
{
while (root) {
tommy_tree_node* next;
/* AVL height is less than twice the bit width of its node count */
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
tommy_size_t depth = 0;
tommy_tree_node* node = tree->root;
tommy_tree_foreach_arg_node(root->prev, func, arg);
while (node || depth) {
while (node) {
stack[depth] = node;
++depth;
node = node->prev;
}
/* make a copy in case func is free() */
next = root->next;
func(arg, root->data);
root = next;
node = stack[--depth];
/* save the next subtree before the callback can free this node */
tommy_tree_node* next = node->next;
func(arg, node->data);
node = next;
}
}
TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree)
TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree)
{
return tommy_tree_count(tree) * sizeof(tommy_tree_node);
}

View file

@ -7,7 +7,8 @@
* This tree is a standard AVL tree implementation that stores elements in the
* order defined by the comparison function.
*
* As a difference from other tommy containers, duplicate elements cannot be inserted.
* Duplicate elements are allowed. They are visited in insertion order.
* Use tommy_tree_insert_unique() to reject duplicate keys.
*
* To initialize a tree you have to call tommy_tree_init() specifying a comparison
* function that will define the order in the tree.
@ -21,31 +22,31 @@
* To insert elements in the tree you have to call tommy_tree_insert() for
* each element.
* In the insertion call you have to specify the address of the node and the
* address of the object.
* The address of the object is used to initialize the tommy_node::data field
* address of the element.
* The address of the element is used to initialize the tommy_node::data field
* of the node.
*
* \code
* struct object {
* struct element {
* int value;
* // other fields
* tommy_tree_node node;
* };
*
* struct object* obj = malloc(sizeof(struct object)); // creates the object
* struct element* elem = malloc(sizeof(struct element)); // creates the element
*
* obj->value = ...; // initializes the object
* elem->value = ...; // initializes the element
*
* tommy_tree_insert(&tree, &obj->node, obj); // inserts the object
* tommy_tree_insert(&tree, &elem->node, elem); // inserts the element
* \endcode
*
* To find an element in the tree you have to call tommy_tree_search() providing
* the key to search.
* To find the first element with a given key in tree order, call
* tommy_tree_search() providing the key to search.
*
* \code
* struct object value_to_find = { 1 };
* struct object* obj = tommy_tree_search(&tree, &value_to_find);
* if (!obj) {
* struct element value_to_find = { 1 };
* struct element* elem = tommy_tree_search(&tree, &value_to_find);
* if (!elem) {
* // not found
* } else {
* // found
@ -59,22 +60,36 @@
* \code
* tommy_tree_node* i = tommy_tree_head(&tree);
* while (i) {
* struct object* obj = i->data; // gets the object pointer
* struct element* elem = i->data; // gets the element pointer
*
* printf("%d\n", obj->value); // process the object
* printf("%d\n", elem->value); // process the element
*
* i = tommy_tree_next(i); // go to the next element
* }
* \endcode
*
* To remove an element from the tree you have to call tommy_tree_remove()
* providing the key to search and remove.
* \anchor tommy_tree_iterator_validity
* Iterators are pointers to nodes contained in the tree.
* Inserting elements or removing other nodes between iterator calls does not
* invalidate pointers to nodes that remain in the tree.
* Subsequent next/prev calls use the current contents and ordering;
* inserted elements may become visible and removed elements are skipped.
*
* Removing the pointed node invalidates its use as an iterator, even if
* its storage has not been freed. To continue traversal after removing
* the current node, obtain the next or previous node before removal.
*
* Keys and fields used for ordering must not be modified while contained.
* Concurrent traversal and modification require external synchronization.
*
* To remove the first element with a given key in tree order, call
* tommy_tree_remove() providing the key to search and remove.
*
* \code
* struct object value_to_remove = { 1 };
* struct object* obj = tommy_tree_remove(&tree, &value_to_remove);
* if (obj) {
* free(obj); // frees the object allocated memory
* struct element value_to_remove = { 1 };
* struct element* elem = tommy_tree_remove(&tree, &value_to_remove);
* if (elem) {
* free(elem); // frees the element allocated memory
* }
* \endcode
*
@ -82,18 +97,18 @@
* address using tommy_tree_remove_existing().
*
* \code
* struct object* obj = ...;
* tommy_tree_remove_existing(&tree, &obj->node);
* free(obj);
* struct element* elem = ...;
* tommy_tree_remove_existing(&tree, &elem->node);
* free(elem);
* \endcode
*
* To remove the smallest or greatest element you can use tommy_tree_remove_head()
* or tommy_tree_remove_tail().
*
* \code
* struct object* obj = tommy_tree_remove_head(&tree);
* if (obj) {
* free(obj);
* struct element* elem = tommy_tree_remove_head(&tree);
* if (elem) {
* free(elem);
* }
* \endcode
*
@ -102,22 +117,23 @@
* This can be done with the tommy_tree_foreach() function.
*
* \code
* // deallocates all the objects iterating the tree
* // deallocates all the elements iterating the tree
* tommy_tree_foreach(&tree, free);
* \endcode
*/
#ifndef __TOMMYTREE_H
#define __TOMMYTREE_H
#ifndef TOMMY_TREE_H
#define TOMMY_TREE_H
#include "tommytypes.h"
#include "tommylist.h"
/******************************************************************************/
/* tree */
/**
* Tree node.
* This is the node that you have to include inside your objects.
* This is the node that you have to include inside your elements.
* The node must preserve its natural alignment because the tree stores the
* balance factor in the two least significant bits of the parent pointer.
*/
@ -135,6 +151,7 @@ typedef struct tommy_tree_struct {
/**
* Initializes the tree.
* \param tree The tree.
* \param cmp The comparison function that defines the order in the tree.
*/
tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
@ -145,40 +162,81 @@ tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
}
/**
* Inserts an element in the tree.
* If the element is already present, it's not inserted again.
* Check the return value to identify if the element was already present or not.
* You have to provide the pointer of the node embedded into the object and
* the pointer to the object.
* \param node Pointer to the node embedded into the object to insert.
* \param data Pointer to the object to insert.
* \return The element in the tree. Either the already existing one, or the one just inserted.
* Exchanges the contents of two initialized trees, including their comparison functions.
* Each comparison function moves with its root to preserve the tree order.
* The trees must not share nodes. Nodes are not accessed or modified.
* Passing the same tree twice has no effect.
* \param first The first tree.
* \param second The second tree.
* \note This operation is O(1).
*/
TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data);
tommy_inline void tommy_tree_swap(tommy_tree* first, tommy_tree* second)
{
tommy_tree tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Removes all elements, preserving the comparison function.
* The tree remains initialized and can be reused immediately.
* Elements are not freed and nodes are not accessed or modified.
* Their links must not be used to traverse the previous contents.
* You can call this function after tommy_tree_foreach() has freed the elements.
* \param tree The tree.
* \note This operation is O(1).
*/
tommy_inline void tommy_tree_clear(tommy_tree* tree)
{
tree->root = 0;
tree->count = 0;
}
/**
* Inserts an element in the tree, allowing duplicate keys.
* Equal elements are visited in insertion order.
* You have to provide the pointer of the node embedded into the element and
* the pointer to the element.
* \param tree The tree.
* \param node Pointer to the node embedded into the element to insert.
* \param data Pointer to the element to insert.
*/
TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data);
/**
* Inserts an element only if no equal element is already contained.
* If found, the first equal element in tree order is returned and the
* candidate node is left unchanged. Otherwise the candidate is inserted
* and data is returned.
* \param tree The tree.
* \param node The candidate node. It must not belong to any container.
* \param data Pointer to the element to insert.
* \return The first equal element, or data if the candidate was inserted.
*/
TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data);
/**
* Transfers all elements from the tree to the tail of a list in tree order.
* Existing list elements remain before the transferred elements. The tree is
* left empty and initialized with its original comparison function.
* The list must be initialized and must not share nodes with the tree.
* Elements are not freed, and the node data and index fields are left unchanged.
* The index field no longer represents a valid tree parent after the transfer.
* \param tree The tree to drain.
* \param list The destination list.
* \note This operation is O(n), using O(log n) stack space.
*/
TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list);
/**
* Removes an element from the tree.
* You must already have the address of the element to remove.
* \return The tommy_node::data field of the node removed.
* You must already have the address of the node to remove.
* \param tree The tree.
* \param node The node to remove.
* \return The removed element.
*/
TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node);
/** \internal
* Calls the specified function for each element in the tree.
* \param root Root node of the tree.
* \param func Function to call.
*/
TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func);
/** \internal
* Calls the specified function with an argument for each element in the tree.
* \param root Root node of the tree.
* \param func Function to call.
* \param arg Custom argument for the function.
*/
TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the parent of the specified node.
* \param node Node contained in the tree.
@ -191,13 +249,13 @@ tommy_inline tommy_tree_node* tommy_tree_parent(tommy_tree_node* node)
/**
* Gets the node following the specified one in tree order.
* See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals.
* \param node Node contained in the tree.
* \return The following node. For the tail node 0 is returned.
* \return The following node, or 0 for the tail node.
* \note This operation is O(log n), and amortized O(1) during full traversal.
*/
tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
{
tommy_tree_node* parent;
if (node->next) {
node = node->next;
while (node->prev)
@ -205,7 +263,7 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
return node;
}
parent = tommy_tree_parent(node);
tommy_tree_node* parent = tommy_tree_parent(node);
while (parent && node == parent->next) {
node = parent;
parent = tommy_tree_parent(parent);
@ -216,13 +274,13 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
/**
* Gets the node preceding the specified one in tree order.
* See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals.
* \param node Node contained in the tree.
* \return The preceding node. For the head node 0 is returned.
* \return The preceding node, or 0 for the head node.
* \note This operation is O(log n), and amortized O(1) during full traversal.
*/
tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
{
tommy_tree_node* parent;
if (node->prev) {
node = node->prev;
while (node->next)
@ -230,7 +288,7 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
return node;
}
parent = tommy_tree_parent(node);
tommy_tree_node* parent = tommy_tree_parent(node);
while (parent && node == parent->prev) {
node = parent;
parent = tommy_tree_parent(parent);
@ -240,8 +298,10 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
}
/**
* Gets the head (smallest) element in the tree.
* \return The head node. For empty trees 0 is returned.
* Gets the head (smallest) node in the tree.
* \param tree The tree.
* \return The head node, or 0 if the tree is empty.
* \note This operation is O(log n).
*/
tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree)
{
@ -257,8 +317,10 @@ tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree)
}
/**
* Gets the tail (greatest) element in the tree.
* \return The tail node. For empty trees 0 is returned.
* Gets the tail (greatest) node in the tree.
* \param tree The tree.
* \return The tail node, or 0 if the tree is empty.
* \note This operation is O(log n).
*/
tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree)
{
@ -276,7 +338,8 @@ tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree)
/**
* Removes and returns the head (smallest) element.
* If the tree is empty, 0 is returned.
* \return The removed element, or 0 if empty.
* \param tree The tree.
* \return The removed element, or 0 if the tree is empty.
*/
tommy_inline void* tommy_tree_remove_head(tommy_tree* tree)
{
@ -291,7 +354,8 @@ tommy_inline void* tommy_tree_remove_head(tommy_tree* tree)
/**
* Removes and returns the tail (greatest) element.
* If the tree is empty, 0 is returned.
* \return The removed element, or 0 if empty.
* \param tree The tree.
* \return The removed element, or 0 if the tree is empty.
*/
tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
{
@ -303,44 +367,53 @@ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
return tommy_tree_remove_existing(tree, node);
}
/** \internal
* Searches a node in the tree.
* If the element is not found, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
/**
* Finds the first equal node in the tree with a specific comparison function.
*
* Like tommy_tree_find() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first equal node in tree order is returned.
*
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first equal node in tree order, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
tommy_inline tommy_tree_node* tommy_tree_find_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
int c = cmp(data, node->data);
/* comparators must not change the tree, so retain the data across the call */
void* data = node->data;
int c = cmp(cmp_arg, data);
if (c < 0)
node = node->prev;
else if (c > 0)
node = node->next;
else
return node;
else {
candidate = node;
node = node->prev;
}
}
return 0;
return candidate;
}
/**
* Searches an element in the tree.
* Finds the first equal node in the tree.
* If the element is not found, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
* \return The first equal node in tree order, or 0 if none.
*/
tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data)
tommy_inline tommy_tree_node* tommy_tree_find(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
if (!node)
return 0;
return node->data;
return tommy_tree_find_compare(tree, tree->cmp, data);
}
/**
@ -348,42 +421,65 @@ tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data)
*
* Like tommy_tree_search() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first equal element in tree order is returned.
*
* The ::data argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first equal element in tree order, or 0 if none.
*/
tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_node(cmp, tree->root, cmp_arg);
tommy_tree_node* node = tommy_tree_find_compare(tree, cmp, cmp_arg);
if (!node)
return 0;
return node->data;
return node ? node->data : 0;
}
/** \internal
* Searches a node in the tree with key greater or equal than the specified one.
* If no such element exists, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
/**
* Searches an element in the tree.
* If the element is not found, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
* \return The first equal element in tree order, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
tommy_inline void* tommy_tree_search(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_find(tree, data);
return node ? node->data : 0;
}
/**
* Finds a node in the tree with key greater or equal than the specified one with a specific comparison function.
*
* Like tommy_tree_find_greater_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first qualifying node in tree order is returned.
*
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first node in tree order with key greater or equal, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
int c = cmp(data, node->data);
/* comparators must not change the tree, so retain the data across the call */
void* data = node->data;
int c = cmp(cmp_arg, data);
if (c < 0) {
if (c <= 0) {
candidate = node;
node = node->prev;
} else if (c > 0) {
node = node->next;
} else {
return node;
node = node->next;
}
}
@ -391,19 +487,15 @@ tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare
}
/**
* Searches an element in the tree with key greater or equal than the specified one.
* Finds a node in the tree with key greater or equal than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
* \return The first node in tree order with key greater or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data)
tommy_inline tommy_tree_node* tommy_tree_find_greater_equal(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_greater_equal_node(tree->cmp, tree->root, data);
if (!node)
return 0;
return node->data;
return tommy_tree_find_greater_equal_compare(tree, tree->cmp, data);
}
/**
@ -411,42 +503,60 @@ tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data)
*
* Like tommy_tree_search_greater_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first qualifying element in tree order is returned.
*
* The ::data argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first element in tree order with key greater or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_greater_equal_node(cmp, tree->root, cmp_arg);
tommy_tree_node* node = tommy_tree_find_greater_equal_compare(tree, cmp, cmp_arg);
if (!node)
return 0;
return node->data;
return node ? node->data : 0;
}
/** \internal
* Searches a node in the tree with key less or equal than the specified one.
/**
* Searches an element in the tree with key greater or equal than the specified one.
* If no such element exists, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
* \return The first element in tree order with key greater or equal, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_find_greater_equal(tree, data);
return node ? node->data : 0;
}
/**
* Finds the first node in tree order with key strictly greater than the specified one using a specific comparison function.
* The function must define a suborder of the tree comparison function.
* Equivalent elements are excluded from the result.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first strictly greater node, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
int c = cmp(data, node->data);
/* comparators must not change the tree, so retain the data across the call */
void* data = node->data;
int c = cmp(cmp_arg, data);
if (c < 0) {
node = node->prev;
} else if (c > 0) {
candidate = node;
node = node->next;
node = node->prev;
} else {
return node;
node = node->next;
}
}
@ -454,19 +564,92 @@ tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_fu
}
/**
* Searches an element in the tree with key less or equal than the specified one.
* Finds the first node in tree order with key strictly greater than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
* \return The first strictly greater node, or 0 if none.
*/
tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data)
tommy_inline tommy_tree_node* tommy_tree_find_greater(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_less_equal_node(tree->cmp, tree->root, data);
return tommy_tree_find_greater_compare(tree, tree->cmp, data);
}
if (!node)
return 0;
/**
* Searches the first element in tree order with key strictly greater than the specified one using a specific comparison function.
* The function must define a suborder of the tree comparison function.
* Equivalent elements are excluded from the result.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first strictly greater element, or 0 if none.
*/
tommy_inline void* tommy_tree_search_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_find_greater_compare(tree, cmp, cmp_arg);
return node->data;
return node ? node->data : 0;
}
/**
* Searches the first element in tree order with key strictly greater than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first strictly greater element, or 0 if none.
*/
tommy_inline void* tommy_tree_search_greater(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_find_greater(tree, data);
return node ? node->data : 0;
}
/**
* Finds a node in the tree with key less or equal than the specified one with a specific comparison function.
*
* Like tommy_tree_find_less_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The last qualifying node in tree order is returned.
*
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The last node in tree order with key less or equal, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
/* comparators must not change the tree, so retain the data across the call */
void* data = node->data;
int c = cmp(cmp_arg, data);
if (c < 0) {
node = node->prev;
} else {
candidate = node;
node = node->next;
}
}
return candidate;
}
/**
* Finds a node in the tree with key less or equal than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The last node in tree order with key less or equal, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_less_equal(tommy_tree* tree, const void* data)
{
return tommy_tree_find_less_equal_compare(tree, tree->cmp, data);
}
/**
@ -474,34 +657,138 @@ tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data)
*
* Like tommy_tree_search_less_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The last qualifying element in tree order is returned.
*
* The ::data argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The last element in tree order with key less or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_less_equal_node(cmp, tree->root, cmp_arg);
tommy_tree_node* node = tommy_tree_find_less_equal_compare(tree, cmp, cmp_arg);
if (!node)
return 0;
return node->data;
return node ? node->data : 0;
}
/**
* Searches and removes an element.
* If the element is not found, 0 is returned.
* Searches an element in the tree with key less or equal than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The removed element, or 0 if not found.
* \return The last element in tree order with key less or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data)
tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
tommy_tree_node* node = tommy_tree_find_less_equal(tree, data);
if (!node)
return node ? node->data : 0;
}
/**
* Finds the last node in tree order with key strictly less than the specified one using a specific comparison function.
* The function must define a suborder of the tree comparison function.
* Equivalent elements are excluded from the result.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The last strictly less node, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_less_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
/* comparators must not change the tree, so retain the data across the call */
void* data = node->data;
int c = cmp(cmp_arg, data);
if (c > 0) {
candidate = node;
node = node->next;
} else {
node = node->prev;
}
}
return candidate;
}
/**
* Finds the last node in tree order with key strictly less than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The last strictly less node, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_find_less(tommy_tree* tree, const void* data)
{
return tommy_tree_find_less_compare(tree, tree->cmp, data);
}
/**
* Searches the last element in tree order with key strictly less than the specified one using a specific comparison function.
* The function must define a suborder of the tree comparison function.
* Equivalent elements are excluded from the result.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The last strictly less element, or 0 if none.
*/
tommy_inline void* tommy_tree_search_less_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_find_less_compare(tree, cmp, cmp_arg);
return node ? node->data : 0;
}
/**
* Searches the last element in tree order with key strictly less than the specified one.
* If no such element exists, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The last strictly less element, or 0 if none.
*/
tommy_inline void* tommy_tree_search_less(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_find_less(tree, data);
return node ? node->data : 0;
}
/**
* Searches and removes an element from the tree with a specific comparison function.
* The comparison function must define a suborder of the original one.
* The cmp_arg argument is passed as the first argument of the comparison
* function and may have a different type than the elements in the tree.
* \param tree The tree.
* \param cmp Comparison function called with cmp_arg and the element in the tree.
* \param cmp_arg Search key, which may have a different type from tree elements.
* \return The first equal element in tree order, or 0 if not found.
*/
tommy_inline void* tommy_tree_remove_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* candidate = tommy_tree_find_compare(tree, cmp, cmp_arg);
if (!candidate)
return 0;
return tommy_tree_remove_existing(tree, node);
return tommy_tree_remove_existing(tree, candidate);
}
/**
* Searches and removes an element from the tree.
* If the element is not found, 0 is returned.
* \param tree The tree.
* \param data Element used for comparison.
* \return The first equal element in tree order, or 0 if not found.
*/
tommy_inline void* tommy_tree_remove(tommy_tree* tree, const void* data)
{
return tommy_tree_remove_compare(tree, tree->cmp, data);
}
/**
@ -520,46 +807,58 @@ tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data)
*
* ...
*
* // creates an object
* struct object* obj = malloc(sizeof(struct object));
* // creates an element
* struct element* elem = malloc(sizeof(struct element));
*
* ...
*
* // insert it in the tree
* tommy_tree_insert(&tree, &obj->node, obj);
* tommy_tree_insert(&tree, &elem->node, elem);
*
* ...
*
* // deallocates all the objects iterating the tree
* // deallocates all the elements iterating the tree
* tommy_tree_foreach(&tree, free);
* tommy_tree_clear(&tree); // resets the tree before reuse
* \endcode
*
* If the callback frees the elements, the tree retains dangling node
* pointers. Call tommy_tree_clear() or tommy_tree_init() before reusing it.
* The tree has no storage to release.
* \param tree The tree.
* \param func Function to call with each element.
*/
tommy_inline void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
{
tommy_tree_foreach_node(tree->root, func);
}
TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the tree.
* The iteration order and callback rules are the same as tommy_tree_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
* \param tree The tree.
* \param func Function to call with each element.
* \param arg Argument to pass to the function.
*/
tommy_inline void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
{
tommy_tree_foreach_arg_node(tree->root, func, arg);
}
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg);
/**
* Checks if empty.
* \return If the tree is empty.
* Checks whether the tree is empty.
* \param tree The tree.
* \return Nonzero if the tree is empty, 0 otherwise.
* \note This operation is O(1).
*/
tommy_inline tommy_bool_t tommy_tree_empty(tommy_tree* tree)
tommy_inline tommy_bool_t tommy_tree_empty(const tommy_tree* tree)
{
return tree->root == 0;
}
/**
* Gets the number of elements.
* \param tree The tree.
* \return The number of elements in the tree.
* \note This operation is O(1).
*/
tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree)
tommy_inline tommy_size_t tommy_tree_count(const tommy_tree* tree)
{
return tree->count;
}
@ -567,8 +866,10 @@ tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree)
/**
* Gets the size of allocated memory.
* It includes the size of the ::tommy_tree_node of the stored elements.
* \param tree The tree.
* \return Total memory allocated by the tree, in bytes.
*/
TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree);
TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree);
#endif

View file

@ -5,8 +5,8 @@
* Generic types.
*/
#ifndef __TOMMYTYPES_H
#define __TOMMYTYPES_H
#ifndef TOMMY_TYPES_H
#define TOMMY_TYPES_H
/******************************************************************************/
/* types */
@ -14,15 +14,18 @@
#include <stddef.h>
#ifdef _MSC_VER
#include <limits.h>
typedef unsigned tommy_uint32_t; /**< Generic uint32_t type. */
typedef unsigned _int64 tommy_uint64_t; /**< Generic uint64_t type. */
typedef size_t tommy_uintptr_t; /**< Generic uintptr_t type. */
#ifdef _WIN64
#define TOMMY_SIZE_BIT 64
#define TOMMY_SIZE_MAX _UI64_MAX
typedef unsigned _int64 tommy_size_t; /**< Generic size_t type. */
typedef _int64 tommy_ssize_t; /**< Generic ssize_t type. */
#else
#define TOMMY_SIZE_BIT 32
#define TOMMY_SIZE_MAX UINT_MAX
typedef unsigned tommy_size_t; /**< Generic size_t type. */
typedef int tommy_ssize_t; /**< Generic ssize_t type. */
#endif
@ -33,10 +36,12 @@ typedef uint64_t tommy_uint64_t; /**< Generic uint64_t type. */
typedef uintptr_t tommy_uintptr_t; /**< Generic uintptr_t type. */
#if SIZE_MAX == UINT64_MAX
#define TOMMY_SIZE_BIT 64
#define TOMMY_SIZE_MAX SIZE_MAX
typedef uint64_t tommy_size_t; /**< Generic size_t type. */
typedef int64_t tommy_ssize_t; /**< Generic ssize_t type. */
#elif SIZE_MAX == UINT32_MAX
#define TOMMY_SIZE_BIT 32
#define TOMMY_SIZE_MAX SIZE_MAX
typedef uint32_t tommy_size_t; /**< Generic size_t type. */
typedef int32_t tommy_ssize_t; /**< Generic ssize_t type. */
#else
@ -51,7 +56,7 @@ typedef int tommy_bool_t; /**< Generic boolean type. */
* Generic unsigned integer type.
*
* It has no specific size, as it is used to store only small values.
* To make the code more efficient, a full 32 bit integer is used.
* To make the code more efficient, a full 32-bit integer is used.
*/
typedef tommy_uint32_t tommy_uint_t;
@ -80,7 +85,57 @@ typedef tommy_uint32_t tommy_uint_t;
/**
* Generic malloc(), calloc(), realloc() and free() functions.
* Redefine them to what you need. By default they map to the C malloc(), calloc(), realloc() and free().
*
* Redefine them to what you need. By default they map to the C malloc(),
* calloc(), realloc() and free().
*
* \par Infallible Allocation Invariant
* Tommy assumes that dynamic memory allocation never fails and that these
* functions always return a valid, non-zero pointer. The library does not
* check for allocation failures and does not return error codes for
* out-of-memory (OOM) conditions.
*
* \par Rationale
* This choice reflects the memory model of modern general-purpose operating
* systems:
* - **Virtual Memory Overcommit:** On modern systems (such as Linux with default
* overcommit heuristics), malloc() reserves virtual address space and almost
* never returns 0 on 64-bit systems. Physical memory is committed on first write,
* meaning actual exhaustion triggers the kernel OOM Killer (SIGKILL) rather
* than an allocation failure. Checking for null returns in userspace often
* provides a false sense of security.
* - **Swap Thrashing:** In systems backed by swap, severe memory pressure causes
* destructive disk thrashing, degrading throughput by orders of magnitude and
* rendering the process unusable long before an allocation call fails.
*
* \par Advantages
* - **Zero Error-Path Overhead:** No branch predictions, conditional jumps, or
* status checks inside critical insertion and resizing loops, maximizing CPU
* cache efficiency and execution speed.
* - **Clean and Lean API:** Functions (such as insertion and resizing) can remain
* simple (often returning void or direct element pointers) without cluttering
* client code with error-propagation boilerplate.
* - **Deterministic Consistency:** Avoids complex transactional rollback logic
* otherwise required if an allocation failed halfway through dynamic table
* growth or node migration.
* - **Industry Alignment:** Follows the established design of high-performance
* systems libraries (e.g., GLib's g_malloc, Git's xmalloc, Redis's zmalloc,
* and default C++ operator new).
*
* \par Trade-offs and Limitations
* - **Embedded and Bare-Metal Systems:** Systems without an MMU/virtual memory
* (e.g., RTOS, microcontrollers) or strict overcommit disabled (vm.overcommit_memory=2)
* do return 0 on heap exhaustion.
* - **Multi-Tenant Services:** Infrastructure daemons (e.g., database engines) that
* must isolate a single client's excessive memory request without crashing the
* entire process cannot rely on unchecked allocations.
*
* \par Custom Interception
* If your environment requires custom handling (such as memory tracking, structured
* aborts, or logging), redefine tommy_malloc, tommy_calloc, tommy_realloc, and
* tommy_free before including Tommy headers (or via compiler flags).
* Custom allocators must terminate the process (e.g., abort()) or initiate an
* unwinding non-local exit (e.g., longjmp or C++ exception) rather than returning 0.
*/
#if !defined(tommy_malloc) || !defined(tommy_calloc) || !defined(tommy_realloc) || !defined(tommy_free)
#include <stdlib.h>
@ -99,7 +154,7 @@ typedef tommy_uint32_t tommy_uint_t;
#endif
/******************************************************************************/
/* modificators */
/* modifiers */
/** \internal
* Definition of TOMMY_API.
@ -124,7 +179,7 @@ typedef tommy_uint32_t tommy_uint_t;
* Definition of the restrict keyword if available.
*/
#if !defined(tommy_restrict)
#if __STDC_VERSION__ >= 199901L
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define tommy_restrict restrict
#elif defined(_MSC_VER) || defined(__GNUC__)
#define tommy_restrict __restrict
@ -159,12 +214,12 @@ typedef tommy_uint32_t tommy_uint_t;
/* key/hash */
/**
* Type used in indexed data structures to store the key of an object.
* Type used in indexed data structures to store the key of an element.
*/
typedef tommy_size_t tommy_key_t;
/**
* Type used in hashtables to store the hash of an object.
* Type used in hashtables to store the hash of an element.
*/
typedef tommy_size_t tommy_hash_t;
@ -174,11 +229,11 @@ typedef tommy_size_t tommy_hash_t;
/**
* Data structure node.
* This node type is shared between all the data structures and used to store some
* info directly into the objects you want to store.
* info directly into the elements you want to store.
*
* A typical declaration is:
* \code
* struct object {
* struct element {
* tommy_node node;
* // other fields
* };
@ -187,7 +242,7 @@ typedef tommy_size_t tommy_hash_t;
typedef struct tommy_node_struct {
/**
* Next node.
* The tail node has it at 0, like a 0 terminated list.
* The tail node has it at 0, like a 0-terminated list.
*/
struct tommy_node_struct* next;
@ -198,8 +253,9 @@ typedef struct tommy_node_struct {
struct tommy_node_struct* prev;
/**
* Pointer to the object containing the node.
* Pointer to the element containing the node.
* This field is initialized when inserting nodes into a data structure.
* The element pointer passed to insertion functions must not be 0.
*/
void* data;
@ -207,6 +263,7 @@ typedef struct tommy_node_struct {
* Index of the node.
* With tries this field is used to store the key.
* With hashtables this field is used to store the hash value.
* With trees this field is used to store the parent pointer and balance factor.
* With lists this field is not used.
*/
tommy_size_t index;
@ -217,23 +274,20 @@ typedef struct tommy_node_struct {
/**
* Compare function for elements.
* \param obj_a Pointer to the first object to compare.
* \param obj_b Pointer to the second object to compare.
* \return <0 if the first element is less than the second, ==0 if equal, >0 if greater.
*
* This function is like the C strcmp().
*
* \code
* struct object {
* struct element {
* tommy_node node;
* int value;
* };
*
* int compare(const void* obj_a, const void* obj_b)
* int compare(const void* elem_a, const void* elem_b)
* {
* if (((const struct object*)obj_a)->value < ((const struct object*)obj_b)->value)
* if (((const struct element*)elem_a)->value < ((const struct element*)elem_b)->value)
* return -1;
* if (((const struct object*)obj_a)->value > ((const struct object*)obj_b)->value)
* if (((const struct element*)elem_a)->value > ((const struct element*)elem_b)->value)
* return 1;
* return 0;
* }
@ -241,64 +295,62 @@ typedef struct tommy_node_struct {
* tommy_list_sort(&list, compare);
* \endcode
*
* \param elem_a Pointer to the first element to compare.
* \param elem_b Pointer to the second element to compare.
* \return <0 if the first element is less than the second, ==0 if equal, >0 if greater.
*/
typedef int tommy_compare_func(const void* obj_a, const void* obj_b);
typedef int tommy_compare_func(const void* elem_a, const void* elem_b);
/**
* Search function for elements.
* \param arg Pointer to the value to search as passed at the search function.
* \param obj Pointer to the object to compare to.
* \return ==0 if the value matches the element. !=0 if different.
*
* The first argument is a pointer to the value to search exactly
* as it's passed at the search function called.
* The second argument is a pointer to the object inside the hashtable to compare.
*
* The return value has to be 0 if the values are equal. != 0 if they are different.
*
* \code
* struct object {
* struct element {
* tommy_node node;
* int value;
* };
*
* int compare(const void* arg, const void* obj)
* int compare(const void* arg, const void* elem)
* {
* const int* value_to_find = arg;
* const struct object* object_to_compare = obj;
* const struct element* element_to_compare = elem;
*
* return *value_to_find != object_to_compare->value;
* return *value_to_find != element_to_compare->value;
* }
*
* int value_to_find = 1;
* struct object* obj = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find));
* if (!obj) {
* struct element* elem = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find));
* if (!elem) {
* // not found
* } else {
* // found
* }
* \endcode
*
* \param arg Pointer to the value to search as passed to the search function.
* \param elem Pointer to the element to compare to.
* \return ==0 if the value matches the element. !=0 if different.
*/
typedef int tommy_search_func(const void* arg, const void* obj);
typedef int tommy_search_func(const void* arg, const void* elem);
/**
* Foreach function.
* \param obj Pointer to the object to iterate.
*
* A typical example is to use free() to deallocate all the objects in a list.
* A typical example is to use free() to deallocate all the elements in a list.
* \code
* tommy_list_foreach(&list, (tommy_foreach_func*)free);
* tommy_list_foreach(&list, free);
* \endcode
*
* \param elem Pointer to the element to iterate.
*/
typedef void tommy_foreach_func(void* obj);
typedef void tommy_foreach_func(void* elem);
/**
* Foreach function with an argument.
* \param arg Pointer to a generic argument.
* \param obj Pointer to the object to iterate.
* \param elem Pointer to the element to iterate.
*/
typedef void tommy_foreach_arg_func(void* arg, void* obj);
typedef void tommy_foreach_arg_func(void* arg, void* elem);
/******************************************************************************/
/* bit hacks */
@ -315,7 +367,7 @@ typedef void tommy_foreach_arg_func(void* arg, void* obj);
/** \internal
* Integer log2 for constants.
* You can use it only for exact power of 2 up to 256.
* You can use it only for exact powers of 2 up to 256.
*/
#define TOMMY_ILOG2(value) ((value) == 256 ? 8 : (value) == 128 ? 7 : (value) == 64 ? 6 : (value) == 32 ? 5 : (value) == 16 ? 4 : (value) == 8 ? 3 : (value) == 4 ? 2 : (value) == 2 ? 1 : 0)
@ -355,9 +407,9 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
*/
return __builtin_clz(value) ^ 31;
#else
/* Find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */
/* find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
static unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = {
static const unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = {
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};
@ -372,13 +424,17 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
#endif
}
#if TOMMY_SIZE_BIT == 64
/**
* Bit scan reverse or integer log2 for 64 bits.
* Return the bit index of the most significant 1 bit.
*
* If no bit is set, the result is undefined.
* \param value Value to scan. 0 is not allowed.
* \return The index of the most significant bit set.
*/
tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
{
#if defined(_MSC_VER)
#if defined(_MSC_VER) && defined(_WIN64)
unsigned long count;
_BitScanReverse64(&count, value);
return count;
@ -393,7 +449,6 @@ tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
return tommy_ilog2_u32(l);
#endif
}
#endif
/**
* Bit scan forward or trailing zero count.
@ -412,24 +467,28 @@ tommy_inline tommy_uint_t tommy_ctz_u32(tommy_uint32_t value)
#elif defined(__GNUC__)
return __builtin_ctz(value);
#else
/* Count the consecutive zero bits (trailing) on the right with multiply and lookup */
/* count the consecutive zero bits (trailing) on the right with multiply and lookup */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
static const unsigned char TOMMY_DE_BRUIJN_INDEX_CTZ[32] = {
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & - value) * 0x077CB531U)) >> 27];
return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & -value) * 0x077CB531U)) >> 27];
#endif
}
#if TOMMY_SIZE_BIT == 64
/**
* Bit scan forward or trailing zero count for 64 bits.
* Return the bit index of the least significant 1 bit.
*
* If no bit is set, the result is undefined.
* \param value Value to scan. 0 is not allowed.
* \return The index of the least significant bit set.
*/
tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
{
#if defined(_MSC_VER)
#if defined(_MSC_VER) && defined(_WIN64)
unsigned long count;
_BitScanForward64(&count, value);
return count;
@ -444,16 +503,16 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
return tommy_ctz_u32(h) + 32;
#endif
}
#endif
/**
* Rounds up to the next power of 2.
* For the value 0, the result is undefined.
* \param value Value to round up.
* \return The smallest power of 2 not less than the specified value.
*/
tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value)
{
/* Round up to the next highest power of 2 */
/* round up to the next highest power of 2 */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
--value;
@ -469,6 +528,9 @@ tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value)
/**
* Rounds up to the next power of 2 for 64 bits.
* For the value 0, the result is undefined.
* \param value Value to round up.
* \return The smallest power of 2 not less than the specified value.
*/
tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value)
{
@ -485,7 +547,8 @@ tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value)
}
/**
* Check if the specified word has a byte at 0.
* Checks if the specified word has a byte at 0.
* \param value Value to check.
* \return 0 or 1.
*/
tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
@ -493,7 +556,17 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
return ((value - 0x01010101) & ~value & 0x80808080) != 0;
}
/*
/**
* Checks if the specified 64-bit word has a byte at 0.
* \param value Value to check.
* \return 0 or 1.
*/
tommy_inline int tommy_haszero_u64(tommy_uint64_t value)
{
return ((value - 0x0101010101010101ULL) & ~value & 0x8080808080808080ULL) != 0;
}
/**
* Bit depth mapping.
*/
#if TOMMY_SIZE_BIT == 64
@ -507,3 +580,4 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
#endif
#endif