Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions common/flatmap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#pragma once

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "common/crc32.h"
#include "common/memory.h"
#include "common/memory_address.h"

struct flatmap {
void *keys;
void *values;
uint8_t *present;
size_t capacity;
};

static inline void
flat_map_free(
struct flatmap *map,
struct memory_context *mctx,
size_t key_size,
size_t value_size
) {
if (map->keys != NULL) {
memory_bfree(
mctx, ADDR_OF(&map->keys), key_size * map->capacity
);
}
if (map->values != NULL) {
memory_bfree(
mctx, ADDR_OF(&map->values), value_size * map->capacity
);
}
if (map->present != NULL) {
memory_bfree(mctx, ADDR_OF(&map->present), map->capacity);
}
memset(map, 0, sizeof(*map));
}

static inline int
flat_map_build(
struct flatmap *map,
struct memory_context *mctx,
size_t capacity,
size_t count,
const void *keys,
size_t key_size,
const void *values,
size_t value_size
) {
if (capacity <= count) {
return -1;
}

size_t keys_memory = key_size * capacity;
size_t values_memory = value_size * capacity;
memset(map, 0, sizeof(*map));

map->capacity = capacity;

map->keys = memory_balloc(mctx, keys_memory);
SET_OFFSET_OF(&map->keys, map->keys);
if (map->keys == NULL) {
return -1;
}

map->values = memory_balloc(mctx, values_memory);
SET_OFFSET_OF(&map->values, map->values);
if (map->values == NULL) {
flat_map_free(map, mctx, key_size, value_size);
return -1;
}

uint8_t *present = memory_balloc(mctx, capacity);
if (present == NULL) {
flat_map_free(map, mctx, key_size, value_size);
return -1;
}
memset(present, 0, capacity);
SET_OFFSET_OF(&map->present, present);

for (size_t i = 0; i < count; ++i) {
size_t j = crc32(keys + i * key_size, key_size, 0) % capacity;
while (present[j]) {
++j;
if (j == capacity) {
j = 0;
}
}
present[j] = 1;
memcpy(ADDR_OF(&map->keys) + j * key_size,
keys + i * key_size,
key_size);
memcpy(ADDR_OF(&map->values) + j * value_size,
values + i * value_size,
value_size);
}

return 0;
}

static inline void *
flat_map_lookup(
struct flatmap *map, const void *key, size_t key_size, size_t value_size
) {
const void *keys = ADDR_OF(&map->keys);
void *values = ADDR_OF(&map->values);
uint8_t *present = ADDR_OF(&map->present);
size_t j = crc32(key, key_size, 0) % map->capacity;
while (present[j]) {
if (memcmp(keys + j * key_size, key, key_size) == 0) {
return values + j * value_size;
}
++j;
if (j == map->capacity) {
j = 0;
}
}
return NULL;
}
167 changes: 167 additions & 0 deletions tests/common/flatmap_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "common/flatmap.h"
#include "common/memory.h"
#include "common/memory_block.h"
#include "common/test_assert.h"
#include "lib/logging/log.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

static int
setup_allocator(struct block_allocator *ba, void **raw_mem, size_t size) {
TEST_ASSERT(
block_allocator_init(ba) == 0, "block_allocator_init failed"
);

*raw_mem = malloc(size);
TEST_ASSERT(*raw_mem != NULL, "failed to allocate raw memory");

block_allocator_put_arena(ba, *raw_mem, size);
return TEST_SUCCESS;
}

static int
test_zero_count(void) {
LOG(INFO, "Test zero count...");

struct block_allocator ba;
void *raw_mem = NULL;
const size_t arena_size = 1 << 20;
TEST_ASSERT(
setup_allocator(&ba, &raw_mem, arena_size) == TEST_SUCCESS,
"setup allocator"
);

struct memory_context mctx;
TEST_ASSERT(
memory_context_init(&mctx, "flatmap", &ba) == 0,
"init memory context"
);

struct flatmap map;
int res = flat_map_build(
&map,
&mctx,
8,
0,
NULL,
sizeof(uint32_t),
NULL,
sizeof(uint32_t)
);
TEST_ASSERT(res == 0, "build flat map");

uint32_t probes[] = {0, 1, 42, 0xDEADBEEFu};
for (size_t idx = 0; idx < sizeof(probes) / sizeof(probes[0]); ++idx) {
void *found = flat_map_lookup(
&map, &probes[idx], sizeof(uint32_t), sizeof(uint32_t)
);
TEST_ASSERT_NULL(found, "lookup on empty map must return NULL");
}

flat_map_free(&map, &mctx, sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQUAL(
mctx.bfree_count,
mctx.balloc_count,
"memory context: free count != alloc count"
);
TEST_ASSERT_EQUAL(
mctx.bfree_size,
mctx.balloc_size,
"memory context: free size != alloc size"
);
free(raw_mem);
return TEST_SUCCESS;
}

static int
test_basic_lookup(void) {
LOG(INFO, "Test basic lookup...");

struct block_allocator ba;
void *raw_mem = NULL;
const size_t arena_size = 1 << 20;
TEST_ASSERT(
setup_allocator(&ba, &raw_mem, arena_size) == TEST_SUCCESS,
"setup allocator"
);

struct memory_context mctx;
TEST_ASSERT(
memory_context_init(&mctx, "flatmap", &ba) == 0,
"init memory context"
);

const uint32_t keys[] = {1, 2, 3, 100, 200};
const uint32_t values[] = {10, 20, 30, 1000, 2000};
const size_t count = sizeof(keys) / sizeof(keys[0]);

struct flatmap map;
int res = flat_map_build(
&map,
&mctx,
count * 2,
count,
keys,
sizeof(uint32_t),
values,
sizeof(uint32_t)
);
TEST_ASSERT(res == 0, "build flat map");

for (size_t idx = 0; idx < count; ++idx) {
uint32_t key = keys[idx];
uint32_t *value = (uint32_t *)flat_map_lookup(
&map, &key, sizeof(uint32_t), sizeof(uint32_t)
);
TEST_ASSERT_NOT_NULL(
value, "lookup of present key %u returned NULL", key
);
TEST_ASSERT_EQUAL(
*value, values[idx], "value mismatch for key %u", key
);
}

const uint32_t missing[] = {0, 4, 99, 500, 0xFFFFFFFFu};
for (size_t idx = 0; idx < sizeof(missing) / sizeof(missing[0]);
++idx) {
void *value = flat_map_lookup(
&map, &missing[idx], sizeof(uint32_t), sizeof(uint32_t)
);
TEST_ASSERT_NULL(
value,
"lookup of missing key %u returned non-NULL",
missing[idx]
);
}

flat_map_free(&map, &mctx, sizeof(uint32_t), sizeof(uint32_t));
TEST_ASSERT_EQUAL(
mctx.bfree_count,
mctx.balloc_count,
"memory context: free count != alloc count"
);
TEST_ASSERT_EQUAL(
mctx.bfree_size,
mctx.balloc_size,
"memory context: free size != alloc size"
);
free(raw_mem);
return TEST_SUCCESS;
}

int
main(void) {
log_enable_name("info");

if (test_zero_count() != TEST_SUCCESS) {
return -1;
}
if (test_basic_lookup() != TEST_SUCCESS) {
return -1;
}

LOG(INFO, "All flatmap tests passed");
return 0;
}
11 changes: 11 additions & 0 deletions tests/common/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,14 @@ data_pipe_test = executable(
include_directories: yanet_rootdir,
)
test('data_pipe', data_pipe_test, suite: ['common'])

flatmap_test = executable(
'flatmap_test',
['flatmap_test.c'],
c_args: yanet_test_c_args,
link_args: yanet_link_args,
dependencies: [lib_common_dep, lib_logging_dep],
include_directories: yanet_rootdir,
)
test('flatmap', flatmap_test, suite: ['common'])

Loading