diff --git a/api/docs/release.dox b/api/docs/release.dox index 7db4d7b1acb..e8d7faa9e9f 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -140,6 +140,10 @@ Further non-compatibility-affecting changes include: choices under the -raw_compress option. Compressing with lz4 is now the default (if built with lz4 support). - Added drmodtrack_lookup_pc_from_index(). + - Added an open-address hashtable implementation for cases where third-party + library must be avoided and open addressing is best: dr_hashtable_create(), + dr_hashtable_destroy(), dr_hashtable_clear(), dr_hashtable_lookup(), + dr_hashtable_add(), dr_hashtable_remove(). The changes between version 9.0.1 and 9.0.0 include the following compatibility changes: diff --git a/core/lib/dr_tools.h b/core/lib/dr_tools.h index 0a041be613c..67d81f8d616 100644 --- a/core/lib/dr_tools.h +++ b/core/lib/dr_tools.h @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2010-2021 Google, Inc. All rights reserved. + * Copyright (c) 2010-2022, Inc. All rights reserved. * Copyright (c) 2002-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -2620,4 +2620,86 @@ DR_API bool dr_trace_exists_at(void *drcontext, void *tag); +/************************************************** + * OPEN-ADDRESS HASHTABLE + */ + +DR_API +/** + * Allocates and initializes an open-address library-independent hashtable: + * + * @param[drcontext] This context controls whether thread-private or global + * heap is used for the table. + * @param[bits] The base-2 log of the initial capacity of the table. + * @param[load_factor_percent] The threshold of the table's occupancy at which + * it will be resized (so smaller values keep the table sparser + * and generally more performant but at the cost of more memory). + * @param[synch] Whether to use a lock around all operations. + * @param[free_payload_func] An optional function to call when removing an entry. + * + * @return a pointer to the heap-allocated table. + */ +void * +dr_hashtable_create(void *drcontext, uint bits, uint load_factor_percent, bool synch, + void (*free_payload_func)(void * /*drcontext*/, void *)); + +DR_API +/** + * Destroys a hashtable created by dr_hashtable_create(). + * + * @param[drcontext] Must be the same context passed to dr_hashtable_create(). + * @param[htable] A pointer to the table itself, returned by dr_hashtable_create(). + */ +void +dr_hashtable_destroy(void *drcontext, void *htable); + +DR_API +/** + * Removes all entries in a hashtable created by dr_hashtable_create(). + * + * @param[drcontext] Must be the same context passed to dr_hashtable_create(). + * @param[htable] A pointer to the table itself, returned by dr_hashtable_create(). + */ +void +dr_hashtable_clear(void *drcontext, void *htable); + +DR_API +/** + * Queries whether an entry for the given key exists. + * + * @param[drcontext] Must be the same context passed to dr_hashtable_create(). + * @param[htable] A pointer to the table itself, returned by dr_hashtable_create(). + * @param[key] The key to query. + * + * @return the payload value for the key that was passed to dr_hashtable_add(), + * or NULL if no such key is found. + */ +void * +dr_hashtable_lookup(void *drcontext, void *htable, ptr_uint_t key); + +DR_API +/** + * Adds a new entry to the hashtable. + * + * @param[drcontext] Must be the same context passed to dr_hashtable_create(). + * @param[htable] A pointer to the table itself, returned by dr_hashtable_create(). + * @param[key] The key to add. + * @param[payload] The payload to add. + */ +void +dr_hashtable_add(void *drcontext, void *htable, ptr_uint_t key, void *payload); + +DR_API +/** + * Removes an entry for the given key. + * + * @param[drcontext] Must be the same context passed to dr_hashtable_create(). + * @param[htable] A pointer to the table itself, returned by dr_hashtable_create(). + * @param[key] The key to remove. + * + * @return whether the key was found. + */ +bool +dr_hashtable_remove(void *drcontext, void *htable, ptr_uint_t key); + #endif /* _DR_TOOLS_H_ */ diff --git a/core/lib/instrument.c b/core/lib/instrument.c index 3cf0f9cdfb2..e5356465479 100644 --- a/core/lib/instrument.c +++ b/core/lib/instrument.c @@ -1,5 +1,5 @@ /* ****************************************************************************** - * Copyright (c) 2010-2021 Google, Inc. All rights reserved. + * Copyright (c) 2010-2022 Google, Inc. All rights reserved. * Copyright (c) 2010-2011 Massachusetts Institute of Technology All rights reserved. * Copyright (c) 2002-2010 VMware, Inc. All rights reserved. * ******************************************************************************/ @@ -7811,3 +7811,61 @@ dr_is_detaching(void) { return doing_detach; } + +/************************************************** + * OPEN-ADDRESS HASHTABLE + * + * Some uses cases need an open-address hashtable that does not use 3rd-party + * libraries. Rather than add something to drcontainers, we simply export the + * hashtablex.h-based table directly from DR. + */ + +DR_API +void * +dr_hashtable_create(void *drcontext, uint bits, uint load_factor_percent, bool synch, + void (*free_payload_func)(void * /*drcontext*/, void *)) +{ + uint flags = HASHTABLE_PERSISTENT; + if (synch) + flags |= HASHTABLE_SHARED | HASHTABLE_ENTRY_SHARED; + else + flags |= HASHTABLE_LOCKLESS_ACCESS; + return generic_hash_create( + (dcontext_t *)drcontext, bits, load_factor_percent, flags, + (void (*)(dcontext_t *, void *))free_payload_func _IF_DEBUG("client")); +} + +DR_API +void +dr_hashtable_destroy(void *drcontext, void *htable) +{ + generic_hash_destroy((dcontext_t *)drcontext, (generic_table_t *)htable); +} + +DR_API +void +dr_hashtable_clear(void *drcontext, void *htable) +{ + generic_hash_clear((dcontext_t *)drcontext, (generic_table_t *)htable); +} + +DR_API +void * +dr_hashtable_lookup(void *drcontext, void *htable, ptr_uint_t key) +{ + return generic_hash_lookup((dcontext_t *)drcontext, (generic_table_t *)htable, key); +} + +DR_API +void +dr_hashtable_add(void *drcontext, void *htable, ptr_uint_t key, void *payload) +{ + generic_hash_add((dcontext_t *)drcontext, (generic_table_t *)htable, key, payload); +} + +DR_API +bool +dr_hashtable_remove(void *drcontext, void *htable, ptr_uint_t key) +{ + return generic_hash_remove((dcontext_t *)drcontext, (generic_table_t *)htable, key); +} diff --git a/ext/drcontainers/hashtable.h b/ext/drcontainers/hashtable.h index 89ebed74032..3edcc18ffad 100644 --- a/ext/drcontainers/hashtable.h +++ b/ext/drcontainers/hashtable.h @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2011-2020 Google, Inc. All rights reserved. + * Copyright (c) 2011-2022 Google, Inc. All rights reserved. * Copyright (c) 2007-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -153,6 +153,8 @@ hashtable_init(hashtable_t *table, uint num_bits, hash_type_t hashtype, bool str * @param[in] cmp_key_func A callback for comparing two keys. * Leave it NULL if no callback is needed and the default is to be used. * For HASH_CUSTOM, a callback must be provided. + * + * For an open-address hashtable, consider dr_hashtable_create(). */ void hashtable_init_ex(hashtable_t *table, uint num_bits, hash_type_t hashtype, bool str_dup, diff --git a/suite/tests/client-interface/drcontainers-test.dll.c b/suite/tests/client-interface/drcontainers-test.dll.c index 2722e636edb..1dacffb4814 100644 --- a/suite/tests/client-interface/drcontainers-test.dll.c +++ b/suite/tests/client-interface/drcontainers-test.dll.c @@ -182,12 +182,42 @@ test_hashtable_apply_all_user_data(void) hashtable_delete(&hash_table); } +#define KEY 42 +#define PAYLOAD ((void *)12) +static bool free_func_called; + +static void +free_payload_func(void *drcontext, void *payload) +{ + CHECK(drcontext == dr_get_current_drcontext(), "context should be mine"); + CHECK(payload == PAYLOAD, "free payload arg incorrect"); + free_func_called = true; +} + +static void +test_dr_hashtable(void) +{ + void *dcxt = dr_get_current_drcontext(); + void *table = dr_hashtable_create(dcxt, 8, 50, /*synch=*/false, free_payload_func); + CHECK(dr_hashtable_lookup(dcxt, table, KEY) == NULL, "table should be empty"); + dr_hashtable_add(dcxt, table, KEY, PAYLOAD); + CHECK(dr_hashtable_lookup(dcxt, table, KEY) == PAYLOAD, "should find if just-added"); + CHECK(dr_hashtable_remove(dcxt, table, KEY), "should find if just-added"); + CHECK(free_func_called, "free_payload_func sanity check"); + CHECK(dr_hashtable_lookup(dcxt, table, KEY) == NULL, "just removed"); + dr_hashtable_add(dcxt, table, KEY, PAYLOAD); + dr_hashtable_clear(dcxt, table); + CHECK(dr_hashtable_lookup(dcxt, table, KEY) == NULL, "table should be empty"); + dr_hashtable_destroy(dcxt, table); +} + DR_EXPORT void dr_init(client_id_t id) { test_vector(); test_hashtable_apply_all(); test_hashtable_apply_all_user_data(); + test_dr_hashtable(); /* XXX: test other data structures */ }