blob: 85c1222ad4cf21e196729d4c0d978c1c3a5e2456 [file] [log] [blame]
/*
* Copyright (c) 2019-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef PAS_LOCK_FREE_READ_HASHTABLE_H
#define PAS_LOCK_FREE_READ_HASHTABLE_H
#include "pas_hashtable.h"
PAS_BEGIN_EXTERN_C;
/* This creates a hashtable type. You can construct it by just initializing to zero. */
#define PAS_LOCK_FREE_READ_HASHTABLE_INITIALIZER { \
.table = NULL, \
.table_size = 0, \
.table_mask = 0, \
.key_count = 0, \
.deleted_count = 0 \
}
#define PAS_CREATE_LOCK_FREE_READ_HASHTABLE(name, entry_type, key_type) \
struct name; \
struct name##_table; \
struct name##_add_result; \
typedef struct name name; \
typedef struct name##_table name##_table; \
typedef struct name##_add_result name##_add_result; \
\
struct name { \
name##_table* table; \
}; \
\
struct PAS_ALIGNED(sizeof(entry_type)) name##_table { \
name##_table* previous; /* Just to make it not look like a leak. */ \
unsigned table_size; \
unsigned table_mask; \
unsigned key_count; \
unsigned deleted_count; \
entry_type table[1]; \
}; \
\
struct name##_add_result { \
entry_type* entry; \
bool is_new_entry; \
}; \
\
PAS_UNUSED static inline void name##_construct(name* table) \
{ \
pas_zero_memory(table, sizeof(name)); \
} \
\
PAS_UNUSED static inline void name##_rehash(name* hashtable, unsigned new_size) \
{ \
static const bool verbose = false; \
\
size_t new_byte_size; \
name##_table* table;
name##_table* new_table; \
unsigned index; \
unsigned old_index; \
unsigned new_table_mask; \
\
pas_heap_lock_assert_held(); \
\
/* This is a table of large allocations, so the sizes are not going to be large enough */ \
/* to induce overflow. */ \
\
PAS_ASSERT(pas_is_power_of_2(new_size)); \
\
table = hashtable->table; \
\
new_table_mask = new_size - 1; \
new_byte_size = PAS_OFFSETOF(name##_table, table) + (size_t)new_size * sizeof(entry_type); \
if (verbose) \
pas_log("Allocating a new table with new_size = %zu, new_byte_size = %zu.\n", new_size, new_byte_size); \
new_table = (name##_table*)(void*)pas_bootstrap_free_heap_allocate_with_alignment( \
new_byte_size, \
pas_alignment_create_traditional(sizeof(entry_type)), \
#name "/table",
pas_object_allocation).begin;
\
for (index = new_size; index--;) \
new_table->table[index] = entry_type##_create_empty(); \
\
for (old_index = 0; old_index < table->table_size; ++old_index) { \
entry_type* old_entry; \
unsigned hash; \
\
old_entry = table->table + old_index; \
if (entry_type##_is_empty_or_deleted(*old_entry)) \
continue; \
\
for (hash = key_type##_get_hash(entry_type##_get_key(*old_entry)); ; ++hash) { \
unsigned new_index; \
entry_type* new_entry; \
\
new_index = hash & new_table_mask; \
new_entry = new_table->table + new_index; \
if (entry_type##_is_empty_or_deleted(*new_entry)) { \
*new_entry = *old_entry; \
break; \
} \
} \
} \
\
new_table->table_size = new_size; \
new_table->table_mask = 0; \
new_table->deleted_count = 0; \
new_table->key_count = table->key_count; \
\
pas_fence(); \
\
hashtable->table = table; \
} \
\
PAS_UNUSED static inline void name##_expand(name* hashtable) \
{ \
name##_table* table; \
unsigned new_size; \
\
pas_heap_lock_assert_held(); \
\
table = hashtable->table; \
\
if (!table->table_size) \
new_size = PAS_HASHTABLE_MIN_SIZE; \
else if (table->key_count * PAS_HASHTABLE_MIN_LOAD < table->table_size * 2) \
new_size = table->table_size; \
else \
new_size = table->table_size * 2; \
name##_rehash(hashtable, new_size); \
} \
\
PAS_UNUSED static inline void name##_shrink(name* hashtable) \
{ \
name##_table* table; \
\
pas_heap_lock_assert_held(); \
\
table = hashtable->table; \
name##_rehash(hashtable, table->table_size / 2); \
} \
\
PAS_UNUSED static inline entry_type* name##_find(name* hashtable, key_type key) \
{ \
name##_table* table; \
\
table = hashtable->table; \
\
if (!table) \
return NULL; \
\
for (unsigned hash = key_type##_get_hash(key); ; ++hash) { \
unsigned index; \
entry_type* entry_ptr; \
entry_type##_loaded_key_before_value loaded_key_before_value; \
\
index = hash & table->table_mask; \
entry_ptr = table->table + index; \
\
loaded_key_before_value = entry_type##_load_key_before_value(entry_ptr); \
\
if (entry_type##_loaded_key_before_value_is_empty_or_deleted(loaded_key_before_value)) { \
if (entry_type##_loaded_key_before_value_is_deleted(loaded_key_before_value)) \
continue; \
return NULL; \
} \
if (key_type##_is_equal( \
entry_type##_loaded_key_before_value_get_key(loaded_key_before_value), key)) \
return entry_type##_loaded_key_before_value_get_entry(loaded_key_before_value); \
} \
} \
PAS_UNUSED static inline entry_type name##_get(name* hashtable, key_type key) \
{ \
entry_type* result; \
result = name##_find(hashtable, key); \
if (!result) \
return entry_type##_create_empty(); \
return *result; \
} \
\
PAS_UNUSED static inline name##_add_result name##_add(name* hashtable, key_type key) \
{ \
name##_table* table; \
entry_type* entry; \
entry_type* deleted_entry; \
unsigned hash; \
name##_add_result result; \
\
pas_heap_lock_assert_held(); \
\
table = hashtable->table; \
\
if ((table->key_count + table->deleted_count) * PAS_HASHTABLE_MAX_LOAD \
>= table->table_size) \
name##_expand(hashtable); \
\
deleted_entry = NULL; \
\
for (hash = key_type##_get_hash(key); ; ++hash) { \
unsigned index; \
index = hash & table->table_mask; \
entry = table->table + index; \
if (entry_type##_is_empty(*entry)) \
break; \
if (entry_type##_is_deleted(*entry)) { \
if (!deleted_entry) \
deleted_entry = entry; \
continue; \
} \
if (key_type##_is_equal(entry_type##_get_key(*entry), key)) { \
result.entry = entry; \
result.is_new_entry = false; \
return result; \
} \
} \
\
if (deleted_entry) { \
table->deleted_count--; \
entry = deleted_entry; \
} \
table->key_count++; \
\
result.entry = entry; \
result.is_new_entry = true; \
return result; \
} \
\
PAS_UNUSED static inline void name##_add_new(name* table, entry_type new_entry) \
{ \
name##_add_result result = name##_add(table, entry_type##_get_key(new_entry)); \
PAS_ASSERT(result.is_new_entry); \
entry_type##_store_atomic(result.entry, new_entry); \
} \
\
PAS_UNUSED static inline bool name##_set(name* table, entry_type new_entry) \
{ \
name##_add_result result = name##_add(table, entry_type##_get_key(new_entry)); \
entry_type##_store_atomic(result.entry, new_entry); \
return result.is_new_entry; \
} \
\
PAS_UNUSED static inline bool name##_take_impl(name* table, key_type key, entry_type* result) \
{ \
entry_type* entry_ptr; \
entry_type entry; \
\
entry_ptr = name##_find(table, key); \
if (!entry_ptr) { \
*result = entry_type##_create_empty(); \
return false; \
} \
\
entry = *entry_ptr; \
entry_type##_store_atomic(entry_ptr, entry_type##_create_deleted()); \
table->deleted_count++; \
table->key_count--; \
if (table->key_count * PAS_HASHTABLE_MIN_LOAD < table->table_size \
&& table->table_size > PAS_HASHTABLE_MIN_SIZE) \
name##_shrink(table); \
\
*result = entry; \
return true; \
} \
\
PAS_UNUSED static inline entry_type name##_take(name* table, key_type key) \
{ \
entry_type result; \
name##_take_impl(table, key, &result); \
return result; \
} \
\
PAS_UNUSED static inline bool name##_remove(name* table, key_type key) \
{ \
entry_type result; \
return name##_take_impl(table, key, &result); \
} \
\
PAS_UNUSED static inline void name##_delete(name* table, key_type key) \
{ \
bool result; \
result = name##_remove(table, key); \
PAS_ASSERT(result); \
} \
\
typedef bool (*name##_for_each_entry_callback)(entry_type* entry, void* arg); \
\
PAS_UNUSED static inline bool name##_for_each_entry(name* table, \
name##_for_each_entry_callback callback, \
void* arg) \
{ \
unsigned index; \
\
for (index = 0; index < table->table_size; ++index) { \
entry_type* entry; \
\
entry = table->table + index; \
if (entry_type##_is_empty_or_deleted(*entry)) \
continue; \
\
if (!callback(entry, arg)) \
return false; \
} \
\
return true; \
} \
\
PAS_UNUSED static inline size_t name##_size(name* table) \
{ \
return table->key_count; \
} \
\
PAS_UNUSED static inline size_t name##_entry_index_end(name* table) \
{ \
return table->table_size; \
} \
\
PAS_UNUSED static inline entry_type* name##_entry_at_index(name* table, \
size_t index) \
{ \
return table->table + index; \
} \
\
struct pas_dummy
PAS_END_EXTERN_C;
#endif /* PAS_LOCK_FREE_READ_HASHTABLE_H */