blob: f807057d8dcbc7cc1a459f7fbe0236a7611c6029 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2013 Google, Inc. All rights reserved.
* Copyright (c) 2006-2010 VMware, 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:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 VMWARE, 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.
*/
/* Copyright (c) 2006-2007 Determina Corp. */
/*
* hashtablex.h
*
* Generic hashtable support.
*
* Implementation is an open-address hashtable with sentinel.
* Supports invalid slots for concurrent-access usage, in particular
* lockless read concurrent access (such as for indirect branch lookup tables).
* Also supports a synchronized lookup table.
*
* Usage: define the following macros, and then include this file.
* It is meant to be included multiple times.
* Define HASHTABLEX_HEADER to get the data struct and no implementation;
* else, you'll get implementation but NOT the data struct.
*
* set HASHTABLE_USE_LOOKUPTABLE if using lookuptable
* set HASHTABLE_ENTRY_STATS if want entry stats
*
* for struct:
* token NAME_KEY
* type ENTRY_TYPE
* if HASHTABLE_USE_LOOKUPTABLE is defined:
* type AUX_ENTRY_TYPE
* endif
* fields CUSTOM_FIELDS
*
* for main table:
* ptr_uint_t ENTRY_TAG(entry)
* bool ENTRY_IS_EMPTY(entry)
* bool ENTRY_IS_SENTINEL(entry)
* bool ENTRY_IS_INVALID(entry)
* assumption: invalid entries are only used with
* HASHTABLE_LOCKLESS_ACCESS tables
* bool ENTRIES_ARE_EQUAL(table, entry1, entry2)
* if using pointers, pointer equality is fine
* ENTRY_TYPE ENTRY_EMPTY
* ENTRY_TYPE ENTRY_SENTINEL
* FIXME: to support structs we'll need lhs like AUX_ENTRY_SET_TO_SENTINEL
* (up to caller to ever set an entry to invalid)
* HTLOCK_RANK
* table_rwlock will work for most users.
* Needs higher rank than memory alloc locks.
* optional for main table:
* bool TAGS_ARE_EQUAL(table, tag1, tag2)
*
* for lookuptable:
* if HASHTABLE_USE_LOOKUPTABLE is defined:
* ptr_uint_t AUX_ENTRY_TAG(aux_entry)
* bool AUX_ENTRY_IS_EMPTY(aux_entry)
* empty entry is assumed to be all zeros!
* bool AUX_ENTRY_IS_SENTINEL(aux_entry)
* bool AUX_ENTRY_IS_INVALID(aux_entry)
* bool AUX_PAYLOAD_IS_INVALID(dcontext, table, aux_entry)
* AUX_ENTRY_SET_TO_SENTINEL(aux_entry)
* takes lhs since may be struct and C only allows setting to {} in decl
* AUX_ENTRY_IS_SET_TO_ENTRY(aux_entry, entry)
* AUX_ENTRY_SET_TO_ENTRY(aux_entry, entry)
* takes lhs since may be struct and C only allows setting to {} in decl
* string AUX_ENTRY_FORMAT_STR
* printf-args AUX_ENTRY_FORMAT_ARGS(aux_entry)
* endif
* we assume value semantics, i.e., we can copy entries via the = operator
*
* for HEAP_ACCOUNTING:
* heapacct-enum HASHTABLE_WHICH_HEAP(table_flags)
*
* to obtain persistence routines, define
* HASHTABLE_SUPPORT_PERSISTENCE
*
* for custom behavior we assume that these routines exist:
*
* static void
* HTNAME(hashtable_,NAME_KEY,_init_internal_custom)
* (dcontext_t *dcontext,
* HTNAME(,NAME_KEY,_table_t) *htable
* _IFLOOKUP(bool use_lookup));
*
* static void
* HTNAME(hashtable_,NAME_KEY,_resized_custom)
* (dcontext_t *dcontext,
* HTNAME(,NAME_KEY,_table_t) *htable,
* uint old_capacity, ENTRY_TYPE *old_table,
* ENTRY_TYPE *old_table_unaligned
* _IFLOOKUP(AUX_ENTRY_TYPE* lookuptable)
* _IFLOOKUP(byte *old_lookup_table_unaligned),
* uint old_ref_count,
* uint old_table_flags);
*
* #ifdef DEBUG
* static void
* HTNAME(hashtable_,NAME_KEY,_study_custom)
* (dcontext_t *dcontext,
* HTNAME(,NAME_KEY,_table_t) *htable,
* uint entries_inc);
* #endif
*
* static void
* HTNAME(hashtable_,NAME_KEY,_free_entry)
* (dcontext_t *dcontext, HTNAME(,NAME_KEY,_table_t) *table,
* ENTRY_TYPE entry);
*
* FIXME:
* - use defines to weed out unused static routines
* - rename unlinked_entries to invalid_entries
* - remove references to "ibl"/"ibt" and make them general comments
* - same with references to "fragment_t" or "f"
* - caller uses expanded names "hashtable_fragment_init" -- should we
* provide a macro HASHTABLE_INIT(NAME_KEY, args...) or ok to mix
* the generated names here w/ hardcoded names in instantiations?
* - provide wrapper routines for lookup, etc. that lock and unlock?
* see app_pc table usage for after call and rct target tables.
* - eliminate the HASHTABLE_ENTRY_SHARED flag?
*/
#define EXPANDKEY(pre, key, post) pre##key##post
#define HTNAME(pre, key, post) EXPANDKEY(pre, key, post)
#define KEY_STRING STRINGIFY(NAME_KEY)
#define ENTRY_IS_REAL(e) \
(!ENTRY_IS_EMPTY(e) && !ENTRY_IS_SENTINEL(e) && !ENTRY_IS_INVALID(e))
#ifdef HASHTABLE_USE_LOOKUPTABLE
# define _IFLOOKUP(x) , x
# define IFLOOKUP_ELSE(x,y) (x)
#else
# define _IFLOOKUP(x) /* nothing */
# define IFLOOKUP_ELSE(x,y) (y)
#endif
#ifndef TAGS_ARE_EQUAL
# define TAGS_ARE_EQUAL(table,t1,t2) ((t1) == (t2))
#endif
/****************************************************************************/
#ifdef HASHTABLEX_HEADER
/* N.B.: if you change any fields here you must increase
* PERSISTENT_CACHE_VERSION!
*/
typedef struct HTNAME(_,NAME_KEY,_table_t) {
/* entries used from shared private IBL routines copy come first:
* used to be lookuptable, now table for case 7691 */
/* preferred location of a given tag is then at
* lookuptable[(hash_func(tag) & hash_mask) >> hash_offset]
*/
ptr_uint_t hash_mask; /* mask selects the index bits of hash value */
#ifdef HASHTABLE_USE_LOOKUPTABLE
AUX_ENTRY_TYPE *lookuptable; /* allocation aligned within lookuptable_unaligned */
#endif
ENTRY_TYPE *table; /* hash_bits-bit addressed hash table */
uint ref_count; /* when table is shared -- HASHTABLE_SHARED --
* # threads with active ptrs to the table */
uint hash_bits;
hash_function_t hash_func; /* selects hash function */
uint hash_mask_offset; /* ignores given number of LSB bits */
uint capacity; /* = 2^hash_bits + 1 sentinel */
uint entries;
/* FIXME: rename to invalid_entries to be more general */
uint unlinked_entries;
uint load_factor_percent; /* \alpha = load_factor_percent/100 */
uint resize_threshold; /* = capacity * load_factor */
uint groom_factor_percent; /* \gamma = groom_factor_percent/100 */
uint groom_threshold; /* = capacity * groom_factor_percent */
uint max_capacity_bits; /* log_2 of maximum size to grow table */
#ifdef HASHTABLE_STATISTICS
/* These refer only to accesses from DR lookups */
hashtable_statistics_t drlookup_stats;
# ifdef HASHTABLE_ENTRY_STATS
/* Accesses from ibl */
fragment_stat_entry_t *entry_stats;
/* precomputed (entry_stats - lookup_table) for easy parallel
* table access from IBL routines
*/
uint entry_stats_to_lookup_table;
uint added_since_dumped; /* clock handle - usually the same as
* delta in entries, unless we have removed
*/
# endif
#endif /* HASHTABLE_STATISTICS */
uint table_flags; /* the HASHTABLE_* values are used here */
read_write_lock_t rwlock; /* shared tables should use a read/write lock */
ENTRY_TYPE *table_unaligned; /* real alloc for table if HASHTABLE_ALIGN_TABLE */
#ifdef HASHTABLE_USE_LOOKUPTABLE
byte *lookup_table_unaligned; /* real allocation unit for lookuptable */
#endif
#ifdef DEBUG
const char *name;
bool is_local; /* no lock needed since only known to this thread */
#endif
CUSTOM_FIELDS
} HTNAME(,NAME_KEY,_table_t);
/****************************************************************************/
#else /* implementation */
/* forward decls */
static bool
HTNAME(hashtable_,NAME_KEY,_check_size)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
uint add_now, uint add_later);
static void
HTNAME(hashtable_,NAME_KEY,_init_internal_custom)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable
_IFLOOKUP(bool use_lookup));
static void
HTNAME(hashtable_,NAME_KEY,_resized_custom)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
uint old_capacity, ENTRY_TYPE *old_table,
ENTRY_TYPE *old_table_unaligned
_IFLOOKUP(AUX_ENTRY_TYPE* lookuptable)
_IFLOOKUP(byte *old_lookup_table_unaligned),
uint old_ref_count,
uint old_table_flags);
static uint
HTNAME(hashtable_,NAME_KEY,_unlinked_remove)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table);
static void
HTNAME(hashtable_,NAME_KEY,_groom_table)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table);
static inline void
HTNAME(hashtable_,NAME_KEY,_groom_helper)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table);
static void
HTNAME(hashtable_,NAME_KEY,_free_entry)
(dcontext_t *dcontext, HTNAME(,NAME_KEY,_table_t) *table, ENTRY_TYPE entry);
#ifdef DEBUG
static void
HTNAME(hashtable_,NAME_KEY,_study)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
uint entries_inc);
static void
HTNAME(hashtable_,NAME_KEY,_study_custom)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
uint entries_inc);
# ifdef HASHTABLE_ENTRY_STATS
void
HTNAME(hashtable_,NAME_KEY,_dump_entry_stats)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable);
# endif
void
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable);
#endif /* DEBUG */
#if defined(DEBUG) && defined(INTERNAL)
static void
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable);
#endif
/* get size in bytes padded for later cache alignment */
static inline
size_t
HTNAME(hashtable_,NAME_KEY,_table_aligned_size)(uint table_capacity, uint flags)
{
size_t size;
/* we assume table size allows 32-bit indices */
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(table_capacity*sizeof(ENTRY_TYPE))));
size = table_capacity*sizeof(ENTRY_TYPE);
if (TEST(HASHTABLE_ALIGN_TABLE, flags)) {
/* aligned at least at 4, and may be aligned */
size += proc_get_cache_line_size() - 4;
}
return size;
}
#ifdef HASHTABLE_USE_LOOKUPTABLE
/* get size in bytes padded for later cache alignment */
static inline
size_t
HTNAME(hashtable_,NAME_KEY,_lookuptable_aligned_size)(uint table_capacity)
{
return table_capacity*sizeof(AUX_ENTRY_TYPE)
+ proc_get_cache_line_size() - 4; /* aligned at least at 4, and may be aligned */
}
#endif
/* callers should use either hashtable_init or hashtable_resize instead */
static void
HTNAME(hashtable_,NAME_KEY,_init_internal)
(dcontext_t *dcontext, HTNAME(,NAME_KEY,_table_t) *table,
uint bits, uint load_factor_percent, hash_function_t func,
uint hash_mask_offset _IFLOOKUP(bool use_lookup))
{
uint i;
uint sentinel_index;
size_t alloc_size;
table->hash_bits = bits;
table->hash_func = func;
table->hash_mask_offset = hash_mask_offset;
table->hash_mask = HASH_MASK(table->hash_bits) << hash_mask_offset;
table->capacity = HASHTABLE_SIZE(table->hash_bits);
/*
* add an extra null_fragment at end to allow critical collision path
* not to worry about table overwrap
* FIXME: case 2147 to stay at power of 2 should use last element instead
*/
table->capacity++;
sentinel_index = table->capacity-1;
table->entries = 0;
table->unlinked_entries = 0;
/* STUDY: try different ratios for the load factor \alpha
* It may save memory and it will not necessarily hurt performance:
* better cache utilization may help us even if touch more entries
* on a cache line.
*/
table->load_factor_percent = load_factor_percent;
/* be careful with integer overflows if ever let this in non-debug versions
* ASSERT is not enough if this is a user controlled release option - needs
* sanity check always
*/
ASSERT(table->load_factor_percent > 0 && table->load_factor_percent < 100);
table->resize_threshold = table->capacity * table->load_factor_percent / 100;
table->groom_factor_percent = 0; /* grooming disabled */
table->max_capacity_bits = 0; /* unlimited */
ASSERT(table->groom_factor_percent >= 0 /* 0 == disabled */);
ASSERT(table->groom_factor_percent < 100);
ASSERT(table->groom_factor_percent <= table->load_factor_percent);
/* when groom factor < load/2 then we'd have to groom during table rehashing */
ASSERT(table->groom_factor_percent == 0
|| table->groom_factor_percent*2 > table->load_factor_percent);
table->groom_threshold = table->capacity * table->groom_factor_percent / 100;
alloc_size = HTNAME(hashtable_,NAME_KEY,_table_aligned_size)
(table->capacity, table->table_flags);
table->table_unaligned = (ENTRY_TYPE *) TABLE_MEMOP(table->table_flags, alloc)
(dcontext, alloc_size HEAPACCT(HASHTABLE_WHICH_HEAP(table->table_flags)));
if (TEST(HASHTABLE_ALIGN_TABLE, table->table_flags)) {
ASSERT(ALIGNED(table->table_unaligned, 4)); /* guaranteed by heap_alloc */
table->table = (ENTRY_TYPE *)
ALIGN_FORWARD(table->table_unaligned, proc_get_cache_line_size());
ASSERT(ALIGNED(table->table, proc_get_cache_line_size()));
ASSERT(ALIGNED(table->table, sizeof(ENTRY_TYPE)));
} else
table->table = table->table_unaligned;
for (i=0; i<table->capacity; i++)
table->table[i] = ENTRY_EMPTY;
/* overwrite last element to be a sentinel, reached only by assembly routines */
table->table[sentinel_index] = ENTRY_SENTINEL;
table->ref_count = 0;
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (use_lookup) {
/* We need to allocate aligned size, yet there is no point in
* calling heap_mmap for small sizes. Instead we use normal
* heap and guarantee alignment manually by padding. For
* larger sizes, we're already wasting a page beyond the table
* size, so this will not waste more memory.
*/
size_t lookup_table_allocation =
HTNAME(hashtable_,NAME_KEY,_lookuptable_aligned_size)(table->capacity);
table->lookup_table_unaligned =
TABLE_MEMOP(table->table_flags, alloc)
(dcontext, lookup_table_allocation
HEAPACCT(HASHTABLE_WHICH_HEAP(table->table_flags)));
ASSERT(ALIGNED(table->lookup_table_unaligned, 4)); /* guaranteed by heap_alloc */
table->lookuptable = (AUX_ENTRY_TYPE*)
ALIGN_FORWARD(table->lookup_table_unaligned,
proc_get_cache_line_size());
/* When the table is used for IBL, for correctness we need to
* make sure it's allocated at a 4 byte aligned address. This
* is required so that writes to the start_pc field during a
* flush are atomic. If the start address is 4-byte aligned
* then each 8 byte entry -- and the entry's two 4 byte fields
* -- will also be aligned. This should be guaranteed by
* heap_alloc and we even align the table start address at
* cache line.
*/
ASSERT(ALIGNED(table->lookuptable, 4));
/* make sure an entry doesn't cross a cache line boundary for performance */
ASSERT(ALIGNED(table->lookuptable, sizeof(AUX_ENTRY_TYPE)));
/* A minor point: save one extra cache line on the whole table
* by making sure first entry is aligned to a cache line
* boundary, otherwise if straddling we would need
* 1 + table_size/cache_line_size to fit the whole table in d-cache
*/
ASSERT(ALIGNED(table->lookuptable, proc_get_cache_line_size()));
LOG(THREAD, LOG_HTABLE, 2,
"hashtable_"KEY_STRING"_init %s lookup unaligned="PFX" "
"aligned="PFX" allocated=%d\n",
table->name, table->lookup_table_unaligned,
table->lookuptable, lookup_table_allocation);
/* set all to null_fragment {tag : 0, start_pc : 0} */
memset(table->lookuptable, 0, table->capacity*sizeof(AUX_ENTRY_TYPE));
ASSERT(AUX_ENTRY_IS_EMPTY(table->lookuptable[0]));
/* set last to sentinel_fragment {tag : 0, start_pc : 1} */
AUX_ENTRY_SET_TO_SENTINEL(table->lookuptable[sentinel_index]);
} else {
/* TODO: emit_utils assumes lookuptable will exist, but we can't match
* the initializations.
*/
table->lookup_table_unaligned = NULL;
table->lookuptable = NULL;
}
#endif
ASSERT(ENTRY_IS_EMPTY(ENTRY_EMPTY));
ASSERT(ENTRY_IS_SENTINEL(ENTRY_SENTINEL));
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"_init %s htable="PFX" bits=%d size=%d "
"mask="PFX" offset=%d load=%d%% resize=%d\n"
" %s %s "PFX" %s "PFX" groom=%d%% groom_at=%d\n",
table->name, table, bits, table->capacity,
table->hash_mask,
table->hash_mask_offset,
table->load_factor_percent, table->resize_threshold,
table->name,
"table", table->table,
IFLOOKUP_ELSE(use_lookup ? "lookup":"", ""),
IFLOOKUP_ELSE(table->lookuptable, NULL),
table->groom_factor_percent, table->groom_threshold);
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
if (INTERNAL_OPTION(hashtable_ibl_entry_stats)) {
if (TEST(HASHTABLE_USE_ENTRY_STATS, table->table_flags)) {
# ifdef HASHTABLE_USE_LOOKUPTABLE
ASSERT(sizeof(fragment_stat_entry_t) == sizeof(AUX_ENTRY_TYPE));
# else
ASSERT(sizeof(fragment_stat_entry_t) == sizeof(ENTRY_TYPE));
# endif
if (table->entry_stats != NULL) { /* resize */
/* assuming resize is always doubling the table */
/* FIXME: too error prone we should pass old capacity
* somewhere if case 2147 changes the table size
*/
uint old_capacity = HASHTABLE_SIZE(table->hash_bits-1) + 1 /* sentinel */;
/* make sure we've printed the old stats, now losing them */
HEAP_ARRAY_FREE(dcontext, table->entry_stats, fragment_stat_entry_t,
old_capacity,
HASHTABLE_WHICH_HEAP(table->table_flags),
UNPROTECTED);
}
/* FIXME: either put in nonpersistent heap as appropriate, or
* preserve across resets
*/
table->entry_stats =
HEAP_ARRAY_ALLOC(dcontext, fragment_stat_entry_t, table->capacity,
HASHTABLE_WHICH_HEAP(table->table_flags),
UNPROTECTED);
ASSERT_TRUNCATE(table->entry_stats_to_lookup_table, uint,
((byte*)table->entry_stats - (byte*)table->table));
table->entry_stats_to_lookup_table = (uint)
# ifdef HASHTABLE_USE_LOOKUPTABLE
((byte*)table->entry_stats - (byte*)table->lookuptable);
# else
((byte*)table->entry_stats - (byte*)table->table);
# endif
memset(table->entry_stats, 0, table->capacity*sizeof(fragment_stat_entry_t));
}
}
# endif
#endif
HTNAME(hashtable_,NAME_KEY,_init_internal_custom)(dcontext, table
_IFLOOKUP(use_lookup));
}
static void
HTNAME(hashtable_,NAME_KEY,_init)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table,
uint bits,
uint load_factor_percent, hash_function_t func,
uint hash_offset
/* FIXME: turn this bool into a HASHTABLE_ flag */
_IFLOOKUP(bool use_lookup),
uint table_flags _IF_DEBUG(const char *table_name))
{
#ifdef DEBUG
table->name = table_name;
table->is_local = false;
#endif
table->table_flags = table_flags;
#ifdef HASHTABLE_STATISTICS
/* indicate this is first time, not a resize */
# ifdef HASHTABLE_ENTRY_STATS
table->entry_stats = NULL;
table->added_since_dumped = 0;
# endif
#endif
ASSERT(dcontext != GLOBAL_DCONTEXT || TEST(HASHTABLE_SHARED, table_flags));
HTNAME(hashtable_,NAME_KEY,_init_internal)(dcontext, table, bits,
load_factor_percent, func,
hash_offset _IFLOOKUP(use_lookup));
ASSIGN_INIT_READWRITE_LOCK_FREE(table->rwlock, HTLOCK_RANK);
#ifdef HASHTABLE_STATISTICS
INIT_HASHTABLE_STATS(table->drlookup_stats);
#endif
}
/* caller is responsible for any needed synchronization */
static void
HTNAME(hashtable_,NAME_KEY,_resize)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
HTNAME(hashtable_,NAME_KEY,_init_internal)
(dcontext, table, table->hash_bits,
table->load_factor_percent, table->hash_func,
table->hash_mask_offset
/* keep using lookup if used so far */
_IFLOOKUP(table->lookuptable != 0));
}
static inline void
HTNAME(hashtable_,NAME_KEY,_free_table)(dcontext_t *alloc_dc,
ENTRY_TYPE *table_unaligned
_IFLOOKUP(byte *lookup_table_unaligned),
uint flags, uint capacity)
{
if (table_unaligned != NULL) {
TABLE_MEMOP(flags, free)
(alloc_dc, table_unaligned,
HTNAME(hashtable_,NAME_KEY,_table_aligned_size)(capacity, flags)
HEAPACCT(HASHTABLE_WHICH_HEAP(flags)));
}
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (lookup_table_unaligned != NULL) {
TABLE_MEMOP(flags, free)
(alloc_dc, lookup_table_unaligned,
HTNAME(hashtable_,NAME_KEY,_lookuptable_aligned_size)(capacity)
HEAPACCT(HASHTABLE_WHICH_HEAP(flags)));
}
#endif
}
static void
HTNAME(hashtable_,NAME_KEY,_free)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"_free %s table="PFX" bits=%d size=%d "
"load=%d%% resize=%d %s groom=%d%% groom_at=%d\n",
table->name, table->table, table->hash_bits, table->capacity,
table->load_factor_percent, table->resize_threshold,
IFLOOKUP_ELSE((table->lookuptable != NULL) ? "use lookup":"", ""),
table->groom_factor_percent, table->groom_threshold);
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
if (INTERNAL_OPTION(hashtable_ibl_entry_stats)) {
if (TEST(HASHTABLE_USE_ENTRY_STATS, table->table_flags)) {
HEAP_ARRAY_FREE(dcontext, table->entry_stats, fragment_stat_entry_t,
table->capacity,
HASHTABLE_WHICH_HEAP(table->table_flags),
UNPROTECTED);
} else
ASSERT(table->entry_stats == NULL);
}
# endif
#endif /* HASHTABLE_STATISTICS */
HTNAME(hashtable_,NAME_KEY,_free_table)(dcontext, table->table_unaligned
_IFLOOKUP(table->lookup_table_unaligned),
table->table_flags, table->capacity);
table->table = NULL;
table->table_unaligned = NULL;
#ifdef HASHTABLE_USE_LOOKUPTABLE
table->lookuptable = NULL;
table->lookup_table_unaligned = NULL;
#endif
DELETE_READWRITE_LOCK(table->rwlock);
}
/* Need to keep the cached start_pc_fragment consistent between lookuptable and
* the htable.
* Shared fragment IBTs: Unlinked lookup table entries are marked with
* unlinked_fragment and
* are expected to target a target_delete_entry.
*/
#ifdef DEBUG
static inline void
HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
uint hindex)
{
# ifdef HASHTABLE_USE_LOOKUPTABLE
if (htable->lookuptable == NULL) {
LOG(THREAD, LOG_HTABLE, 6,
"[%u] tag="PFX")\n",
hindex, ENTRY_TAG(htable->table[hindex]));
} else {
LOG(THREAD, LOG_HTABLE, 6,
"[%u] "AUX_ENTRY_FORMAT_STR" tag="PFX")\n",
hindex,
AUX_ENTRY_FORMAT_ARGS(htable->lookuptable[hindex]),
ENTRY_IS_REAL(htable->table[hindex]) ?
ENTRY_TAG(htable->table[hindex]) : 0);
# else
LOG(THREAD, LOG_HTABLE, 6,
"[%u] tag="PFX")\n", hindex,
ENTRY_IS_REAL(htable->table[hindex]) ?
ENTRY_TAG(htable->table[hindex]) : 0);
# endif
/* We can't assert that an IBL target isn't a trace head due to a race
* between trace head marking and adding to a table. See the comments
* in fragment_add_to_hashtable().
*/
if (ENTRY_IS_INVALID(htable->table[hindex])) {
ASSERT(TEST(HASHTABLE_NOT_PRIMARY_STORAGE, htable->table_flags));
ASSERT(TEST(HASHTABLE_LOCKLESS_ACCESS, htable->table_flags));
# ifdef HASHTABLE_USE_LOOKUPTABLE
ASSERT(AUX_ENTRY_IS_INVALID(htable->lookuptable[hindex]));
# endif
}
# ifdef HASHTABLE_USE_LOOKUPTABLE
/* "Inclusive hierarachy" lookup tables -- per-type tables not attached
* to a table such as the BB table -- are simpler to reason about
* since we have more latitude setting fragment_t ptrs and so can ensure
* that the entry is always sync-ed with the corresponding fragment_t*.
* For a non-"inclusive hierarachy" table, only when the entry has not
* been unlinked (and so doesn't point to target_delete) can we expect
* the lookup table and fragment_t* to be in-sync.
*/
else if (TEST(HASHTABLE_NOT_PRIMARY_STORAGE, htable->table_flags) ||
AUX_PAYLOAD_IS_INVALID(dcontext, htable,
htable->lookuptable[hindex])) {
ASSERT(AUX_ENTRY_IS_SET_TO_ENTRY(htable->lookuptable[hindex],
htable->table[hindex]));
}
/* Shouldn't be needed but could catch errors so leaving in. */
else {
ASSERT(AUX_PAYLOAD_IS_INVALID(dcontext, htable,
htable->lookuptable[hindex]));
}
}
# endif
}
#endif
/* Returns entry if tag found;
* Otherwise returns an "empty" entry -- does NOT return NULL!
* Shared tables: It can be the case that an "invalid" entry is returned,
* which also has a tag of 0. This can occur when htable has a lookup table
* and 'tag' is for a fragment that was unlinked due to shared deletion.
*/
/* CHECK this routine is partially specialized, otherwise turn it into a macro */
static /* want to inline but > limit in fragment_lookup */ ENTRY_TYPE
HTNAME(hashtable_,NAME_KEY,_lookup)(dcontext_t *dcontext, ptr_uint_t tag,
HTNAME(,NAME_KEY,_table_t) *htable)
{
ENTRY_TYPE e;
ptr_uint_t ftag;
uint hindex = HASH_FUNC(tag, htable);
#ifdef HASHTABLE_STATISTICS
uint collision_len = 0;
#endif
ASSERT_TABLE_SYNCHRONIZED(htable, READWRITE); /* requires read (or write) lock */
e = htable->table[hindex];
DODEBUG({
HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext,htable,hindex);
});
while (!ENTRY_IS_EMPTY(e)) {
DODEBUG({
HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext,htable,hindex);
});
/* If a FAKE_TAG is present in lookuptable as an unlinked marker, use the
* table entry. */
#ifdef HASHTABLE_USE_LOOKUPTABLE
ftag = (htable->lookuptable != NULL &&
!AUX_ENTRY_IS_INVALID(htable->lookuptable[hindex])) ?
AUX_ENTRY_TAG(htable->lookuptable[hindex]) : ENTRY_TAG(e);
#else
ftag = ENTRY_TAG(e);
#endif
/* FIXME future frags have a 0 tag and that's why we have
* to compare with null_fragment for end of chain in table[]
* whenever future frags go to their own table, this code should
* be reworked to touch lookuptable only -- i.e. become while
* (ftag != NULL_TAG).
*/
if (TAGS_ARE_EQUAL(htable, ftag, tag)) {
#ifdef HASHTABLE_STATISTICS
if (collision_len > 0)
HTABLE_STAT_INC(htable,collision_hit);
HTABLE_STAT_INC(htable,hit);
#endif
return e;
}
/* collision */
#ifdef HASHTABLE_STATISTICS
LOG(THREAD_GET, LOG_HTABLE, 6,
"(hashtable_"KEY_STRING"_lookup: collision sequence "PFX" "
"%u [len=%u])\n", tag, hindex, collision_len);
collision_len++;
HTABLE_STAT_INC(htable,collision);
#endif
hindex = HASH_INDEX_WRAPAROUND(hindex + 1, htable);
e = htable->table[hindex];
DODEBUG({
HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext,htable,hindex);
});
}
HTABLE_STAT_INC(htable,miss);
return e;
}
/* Convenience routine that grabs the read lock and does a lookup */
static inline ENTRY_TYPE
HTNAME(hashtable_,NAME_KEY,_rlookup)(dcontext_t *dcontext, ptr_uint_t tag,
HTNAME(,NAME_KEY,_table_t) *htable)
{
ENTRY_TYPE e;
TABLE_RWLOCK(htable, read, lock);
e = HTNAME(hashtable_,NAME_KEY,_lookup)(dcontext, tag, htable);
TABLE_RWLOCK(htable, read, unlock);
return e;
}
/* add f to a fragment table
* returns whether resized the table or not
* N.B.: this routine will recursively call itself via check_table_size if the
* table is resized
*/
static inline bool
HTNAME(hashtable_,NAME_KEY,_add)(dcontext_t *dcontext, ENTRY_TYPE e,
HTNAME(,NAME_KEY,_table_t) *table)
{
uint hindex;
bool resized;
DEBUG_DECLARE(uint cluster_len = 0;)
ASSERT_TABLE_SYNCHRONIZED(table, WRITE); /* add requires write lock */
ASSERT(!TEST(HASHTABLE_READ_ONLY, table->table_flags));
if (TEST(HASHTABLE_READ_ONLY, table->table_flags))
return false;
/* ensure higher-level synch not allowing any races between lookup and add */
/* Shared fragment IBTs: Tolerate unlinked markers. */
ASSERT(!ENTRY_IS_REAL(HTNAME(hashtable_,NAME_KEY,_lookup)
(dcontext, ENTRY_TAG(e), table)));
/* check_table_size increments table->entries for us and ensures
* we have enough space. don't grab any properties of table prior to this
* call, like hindex, as it will change if resized.
*/
resized = !HTNAME(hashtable_,NAME_KEY,_check_size)(dcontext, table, 1, 0);
hindex = HASH_FUNC(ENTRY_TAG(e), table);
/* find an empty null slot */
do {
LOG(THREAD_GET, LOG_HTABLE, 4,
"hashtable_"KEY_STRING"_add("PFX") mask="PFX" offset=%d trying "
PFX":\n", ENTRY_TAG(e), table->hash_mask, table->hash_mask_offset, hindex);
/* We could use !ENTRY_IS_REAL() here but it could make what we're doing
* more confusing since the &unlinked_fragment is handled a little
* differently. So we enumerate the cases to make things more
* apparent.
*/
if (ENTRY_IS_EMPTY(table->table[hindex]))
break;
/* Replace pending deletion entries in a private table but not
* in a shared table. */
if (ENTRY_IS_INVALID(table->table[hindex])) {
/* FIXME We cannot blindly overwrite an unlinked entry for a shared
* table. We overwrite an entry only when we know that since the
* unlink, every thread has exited the cache at least once (just as
* with shared deletion). Refcounting on a per-entry basis is
* very likely not worth the potential gain.
*
* A broader approach that may be effective enough piggybacks
* on shared deletion. When a flusher unlinks entries in a
* shared table, the table is marked FRAG_NO_CLOBBER_UNLINK --
* this prevents unlinked entries from being replaced. After a
* thread decrements a shared deletion refcount via
* check_flush_queue(), it checks if the shared deletion queuee
* is empty. If the queue is empty, then we know that since the
* last flush (and table unlinks) all threads have
* exited the cache at least once, so any shared tables marked
* as FRAG_NO_CLOBBER_UNLINK can have that flag cleared.
*/
if (!TEST(HASHTABLE_SHARED, table->table_flags)) {
ASSERT(table->unlinked_entries > 0);
ASSERT(TEST(HASHTABLE_LOCKLESS_ACCESS, table->table_flags));
#ifdef HASHTABLE_USE_LOOKUPTABLE
ASSERT(AUX_PAYLOAD_IS_INVALID(dcontext, table,
table->lookuptable[hindex]));
ASSERT(AUX_ENTRY_IS_INVALID(table->lookuptable[hindex]));
LOG(THREAD_GET, LOG_HTABLE, 4, " replace target_delete "
"entry "AUX_ENTRY_FORMAT_STR"\n",
AUX_ENTRY_FORMAT_ARGS(table->lookuptable[hindex]));
#endif
STATS_INC(num_ibt_replace_unlinked_fragments);
break;
}
}
DODEBUG({++cluster_len;});
hindex = HASH_INDEX_WRAPAROUND(hindex + 1, table);
} while (1);
/* FIXME: case 4814 we may want to flush the table if we are running into a too long
* collision cluster
*/
DOLOG(1, LOG_HTABLE, {
if (cluster_len > HASHTABLE_SIZE((1+table->hash_bits)/2))
LOG(THREAD_GET, LOG_HTABLE,
cluster_len > HASHTABLE_SIZE((1+table->hash_bits)/2 + 1) ?
1U : 2U,
"hashtable_"KEY_STRING"_add: long collision sequence len=%u"
"for "PFX" %s table[%u] capacity=%d entries=%d)\n",
cluster_len, ENTRY_TAG(e), table->name, hindex,
table->capacity, table->entries);
});
/* If we had uniformly distributed hash functions, expected max length is
* sqrt(capacity.\pi/8) see Knuth vol.3, FIXME we double below because this
* sometimes ASSERTS for the shared_future_table at the 10 to 11 bits
* transition (seems to be fine at larger sizes)
* bug2241 we add an additional 64 to handle problems in private future
* tables at small sizes, for bug2271 we disable for tables using the
* _NONE hash function (currently private bb and trace) when we have no
* shared fragments
*/
#ifdef DEBUG
if (!TEST(HASHTABLE_RELAX_CLUSTER_CHECKS, table->table_flags) &&
(table->hash_func != HASH_FUNCTION_NONE || SHARED_FRAGMENTS_ENABLED())) {
uint max_cluster_len =
HASHTABLE_SIZE((1+table->hash_bits)/2 + 1 /* double */) + 64;
if (!(cluster_len <= max_cluster_len)) {
DO_ONCE({ /* once reach this may fire many times in a row */
/* always want to know which table this is */
SYSLOG_INTERNAL_WARNING("cluster length assert: %s cluster=%d vs %d,"
" cap=%d, entries=%d",
table->name, cluster_len, max_cluster_len,
table->capacity, table->entries);
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
ASSERT_CURIOSITY(false && "table collision cluster is too large");
});
}
}
#endif
/* actually add the entry
* add to lookuptable first to avoid race spoiling ASSERT_CONSISTENCY -- better
* for other thread to miss in lookup
*/
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (table->lookuptable != NULL) {
AUX_ENTRY_SET_TO_ENTRY(table->lookuptable[hindex], e);
ASSERT(!AUX_ENTRY_IS_INVALID(table->lookuptable[hindex]));
}
#endif
if (ENTRY_IS_INVALID(table->table[hindex]))
table->unlinked_entries--;
table->table[hindex] = e;
ASSERT(!ENTRY_IS_INVALID(table->table[hindex]));
LOG(THREAD_GET, LOG_HTABLE, 4,
"hashtable_"KEY_STRING"_add: added "PFX" to %s at table[%u]\n",
ENTRY_TAG(e), table->name, hindex);
return resized;
}
/* Ensures that the table has enough room for add_now+add_later new entries.
* If not, the table is resized and rehashed into its new space.
* The table's entry count is incremented by add_now but nothing changes
* with respect to add_later other than making space.
* Returns false if it had to re-size the table.
* Caller must hold the write lock, if this is a shared table!
* N.B.: this routine will recursively be called when resizing, b/c it
* calls fragment_add_to_hashtable, which calls back, but should never
* trigger another resize.
*/
static bool
HTNAME(hashtable_,NAME_KEY,_check_size)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table,
uint add_now, uint add_later)
{
dcontext_t *alloc_dc = FRAGMENT_TABLE_ALLOC_DC(dcontext, table->table_flags);
uint entries;
bool shared_lockless = TESTALL(HASHTABLE_ENTRY_SHARED | HASHTABLE_SHARED |
HASHTABLE_LOCKLESS_ACCESS, table->table_flags);
/* FIXME: too many assumptions here that if lockless, then a lookuptable
* is always used, etc.
*/
bool lockless = TESTALL(HASHTABLE_LOCKLESS_ACCESS | HASHTABLE_ENTRY_SHARED,
table->table_flags);
/* write lock must be held */
ASSERT_TABLE_SYNCHRONIZED(table, WRITE);
ASSERT(!TEST(HASHTABLE_READ_ONLY, table->table_flags));
if (TEST(HASHTABLE_READ_ONLY, table->table_flags))
return false;
/* check flush threshold to see if we'd want to flush hashtable */
if (table->entries > table->groom_threshold &&
table->groom_threshold > 0) {
HTNAME(hashtable_,NAME_KEY,_groom_table)(dcontext, table);
/* FIXME Grooming a table in-place doesn't work for a shared IBT table.
* To make it work, we should a) realloc a same-sized table
* b) re-add all entries in the old table, c) add the old
* to the dead list, and d) then call groom_table(). (b) is
* needed because we can't assume that groom_table() will
* always flush the entire table.
*
* Or we could groom the old table -- making sure that it's done
* thread-safely -- and then copy into the new table, requiring
* fewer re-adds.
*
* This is covered by case 5838.
*/
}
/* FIXME: case 4814 we should move clock handles here
*/
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
/* dump per entry hit statistics regularly to see working set */
if (table->entry_stats != NULL) {
table->added_since_dumped += add_now;
if (table->added_since_dumped >= INTERNAL_OPTION(hashtable_ibl_study_interval)) {
LOG(THREAD, LOG_HTABLE, 2,
"dump_and_clean_entry_statistics %s added %d\n",
table->name, table->added_since_dumped);
HTNAME(hashtable_,NAME_KEY,_dump_entry_stats)(dcontext, table);
table->added_since_dumped = 0;
}
}
# endif
#endif /* HASHTABLE_STATISTICS */
/* Pretend we had full # entries; we'll lower later */
table->entries += add_now + add_later;
/* check resize threshold to see if a larger hashtable is needed
* or that we may want to reset the table.
*
* For an IBT table, the # unlinked entries needs to be checked also.
* For a shared table, they cannot be replaced so they are effectively
* real entries. For a private table, they can be replaced but those
* that remain present can lengthen collision chain lengths.
*
* NOTE: unlinked_entries is used only when shared targets are
* stored in IBT tables, since unlinking in those tables is NOT
* followed up by a full removal of the unlinked entries.
*/
entries = lockless ? table->entries + table->unlinked_entries : table->entries;
if (entries > table->resize_threshold) {
ENTRY_TYPE *old_table = table->table;
ENTRY_TYPE *old_table_unaligned = table->table_unaligned;
uint old_capacity = table->capacity;
#ifdef HASHTABLE_USE_LOOKUPTABLE
AUX_ENTRY_TYPE *old_lookuptable_to_nullify = table->lookuptable;
byte *old_lookup_table_unaligned = table->lookup_table_unaligned;
#endif
uint old_ref_count = table->ref_count - 1; /* remove this thread's
* reference to the table */
uint i;
DEBUG_DECLARE(uint old_entries = table->entries - add_later;)
DODEBUG({
/* study before resizing */
HTNAME(hashtable_,NAME_KEY,_study)(dcontext, table, add_now+add_later);
});
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
if (table->entry_stats != NULL) {
LOG(THREAD, LOG_HTABLE, 2,
"dump_and_clean_entry_statistics %s resized\n", table->name);
HTNAME(hashtable_,NAME_KEY,_dump_entry_stats)(dcontext, table);
}
# endif
#endif /* HASHTABLE_STATISTICS */
/* For a shared IBT table, the flushing/grooming is done after
* a resize -- we can't groom in-place. */
if (table->hash_bits == table->max_capacity_bits && !shared_lockless) {
STATS_INC(num_ibt_max_capacity);
HTNAME(hashtable_,NAME_KEY,_groom_helper)(dcontext, table);
return true; /* == did not resize the table */
}
/* For an IBT table, if the # unlinked entries is what kicked
* in the resize then check the # actual entries and do
* a same-size realloc to eliminate the unlinked entries. Also,
* if we've reached the max size, do a same-size realloc.
*
* Otherwise, double the table size.
*/
if (lockless &&
(table->entries <= table->resize_threshold ||
table->hash_bits == table->max_capacity_bits)) {
STATS_INC(num_same_size_ibt_table_resizes);
}
else {
DEBUG_DECLARE(uint old_bits = table->hash_bits;)
ASSERT(table->resize_threshold ==
(HASHTABLE_SIZE(table->hash_bits)+1/*sentinel*/)
* table->load_factor_percent / 100);
while (entries >
(HASHTABLE_SIZE(table->hash_bits)+1/*sentinel*/)
* table->load_factor_percent / 100
&& table->hash_bits != table->max_capacity_bits) {
table->hash_bits++; /* double the size */
}
ASSERT(table->hash_bits > old_bits);
}
HTNAME(hashtable_,NAME_KEY,_resize)(alloc_dc, table);
/* will be incremented by rehashing below -- in fact, by
* recursive calls to this routine from
* fragment_add_to_hashtable -- see warning below
*/
table->entries = 0;
table->unlinked_entries = 0;
ASSERT(table->ref_count == 0);
/* can't just memcpy, must rehash */
/* for open address table rehash should first find an empty slot and start
from there so that we make sure that entries that used to find a hit on the first lookup
continue to do so instead of creating even longer collision parking lots
FIXME: can we do better?
*/
for (i = 0; i < old_capacity; i++) {
ENTRY_TYPE e = old_table[i];
if (!ENTRY_IS_REAL(e))
continue;
/* Don't carry over frags that point to target_delete. This can
* happen in any IBT table, shared or private, that targets
* shared fragments.
*/
if (lockless && ENTRY_IS_INVALID(old_table[i])) {
LOG(GLOBAL, LOG_HTABLE, 1,
"Don't copy tag "PFX" in %s[%u] across a resize\n",
ENTRY_TAG(e), table->name, i);
DODEBUG({old_entries--;});
STATS_INC(num_ibt_unlinked_entries_not_moved);
continue;
}
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (lockless && old_lookuptable_to_nullify != NULL &&
AUX_ENTRY_IS_INVALID(old_lookuptable_to_nullify[i])) {
LOG(GLOBAL, LOG_HTABLE, 1,
"Don't copy tag "PFX" in %s[%u] across a resize\n",
ENTRY_TAG(e), table->name, i);
ASSERT(AUX_PAYLOAD_IS_INVALID(dcontext, table,
old_lookuptable_to_nullify[i]));
DODEBUG({old_entries--;});
STATS_INC(num_ibt_unlinked_entries_not_moved);
continue;
}
#endif
/* N.B.: this routine will call us again, but we assume the
* resize will NEVER be triggered, since we hold the write lock
* and set table->entries to 0.
* We could have a special add routine that doesn't call us,
* I suppose.
*/
HTNAME(hashtable_,NAME_KEY,_add)(dcontext, e, table);
}
table->entries += add_now; /* for about-to-be-added fragment(s) */
/* (add_later will be added later though calls to this same routine) */
/* For a shared IBT table, the flushing/grooming is done after a
* same-size realloc -- we can't groom the original table in-place.
* Groom when we know that the table was just bumped up in size
* earlier in the routine -- compare the old and new sizes to
* determine.
*/
if (table->hash_bits == table->max_capacity_bits &&
old_capacity == table->capacity) {
STATS_INC(num_ibt_max_capacity);
ASSERT(shared_lockless);
HTNAME(hashtable_,NAME_KEY,_groom_helper)(dcontext, table);
}
else {
/* should have rehashed all old entries into new table */
ASSERT(table->entries == old_entries);
}
LOG(THREAD, LOG_HTABLE, 2,
"%s hashtable resized at %d entries from capacity %d to %d\n",
table->name, table->entries, old_capacity, table->capacity);
/* Since readers now synchronize with writers of shared htables,
* we can now delete old htable even when sharing.
*/
DODEBUG({
LOG(THREAD, LOG_HTABLE, 1, "Rehashed %s table\n", table->name);
/* study after rehashing
* ok to become reader for study while a writer
*/
HTNAME(hashtable_,NAME_KEY,_study)(dcontext, table, add_now);
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
});
/* Shared IBT tables are resized at safe points, not here, since
* they are accessed while in-cache, unlike other shared tables
* such as the shared BB or shared trace table.
*/
if (!shared_lockless) {
HTNAME(hashtable_,NAME_KEY,_free_table)
(alloc_dc, old_table_unaligned _IFLOOKUP(old_lookup_table_unaligned),
table->table_flags, old_capacity);
}
else {
if (old_ref_count == 0) {
/* Note that a write lock is held on the table, so no danger of
* a double free. */
HTNAME(hashtable_,NAME_KEY,_free_table)
(GLOBAL_DCONTEXT, old_table_unaligned
_IFLOOKUP(old_lookup_table_unaligned),
table->table_flags, old_capacity);
STATS_INC(num_shared_ibt_tables_freed_immediately);
}
}
HTNAME(hashtable_,NAME_KEY,_resized_custom)
(dcontext, table, old_capacity, old_table, old_table_unaligned
_IFLOOKUP(old_lookuptable_to_nullify)
_IFLOOKUP(old_lookup_table_unaligned),
old_ref_count, table->table_flags);
return false; /* == resized the table */
}
/* If there are too many unlinked markers cluttering the table, remove them. */
else if (table->unlinked_entries > 0 &&
((INTERNAL_OPTION(rehash_unlinked_threshold) < 100 &&
INTERNAL_OPTION(rehash_unlinked_threshold) <
(100 * table->unlinked_entries /
(table->entries + table->unlinked_entries)))
|| INTERNAL_OPTION(rehash_unlinked_always))) {
/* Currently, such markers should be present only when shared BBs are IB
* targets or traces are shared. */
ASSERT(SHARED_IB_TARGETS());
ASSERT(TESTALL(HASHTABLE_ENTRY_SHARED | HASHTABLE_LOCKLESS_ACCESS,
table->table_flags));
STATS_INC(num_ibt_table_rehashes);
LOG(THREAD, LOG_HTABLE, 1, "Rehash table %s: linked %u, unlinked %u\n",
table->name, table->entries, table->unlinked_entries);
/* we will inc for these at a "later" time */
table->entries -= add_later;
/* entries was incremented earlier but the new entry hasn't been
* added yet. We decrement before and re-inc after so that any calls
* to study_hashtable in the remove routine succeed.
*/
DODEBUG({ table->entries -= add_now; });
HTNAME(hashtable_,NAME_KEY,_unlinked_remove)(dcontext, table);
DODEBUG({ table->entries += add_now; });
} else {
/* we will inc for these at a "later" time */
table->entries -= add_later;
}
return true; /* == did not resize the table */
}
/* Return pointer to the record for fragment in its collision chain (table or next field)
or NULL if not found */
static inline ENTRY_TYPE *
HTNAME(hashtable_,NAME_KEY,_lookup_for_removal)(ENTRY_TYPE fr,
HTNAME(,NAME_KEY,_table_t) *htable,
uint *rhindex)
{
uint hindex = HASH_FUNC(ENTRY_TAG(fr), htable);
ENTRY_TYPE *pg = &htable->table[hindex];
while (!ENTRY_IS_EMPTY(*pg)) {
if (ENTRIES_ARE_EQUAL(htable, fr, *pg)) {
*rhindex = hindex;
return pg;
}
/* collision */
LOG(THREAD_GET, LOG_HTABLE, 6, "(hashtable_"KEY_STRING"_lookup_for_"
"removal: collision sequence "PFX" %u)\n", ENTRY_TAG(fr), hindex);
hindex = HASH_INDEX_WRAPAROUND(hindex + 1, htable);
pg = &htable->table[hindex];
}
*rhindex = 0; /* to make sure always initialized */
return NULL;
}
#ifdef HASHTABLE_USE_LOOKUPTABLE
/* FIXME: figure out what weight function I tipped off so now this is too much to inline */
static INLINE_FORCED void
HTNAME(hashtable_,NAME_KEY,_update_lookup)(HTNAME(,NAME_KEY,_table_t) *htable,
uint hindex)
{
if (htable->lookuptable != NULL) {
AUX_ENTRY_SET_TO_ENTRY(htable->lookuptable[hindex], htable->table[hindex]);
LOG(THREAD_GET, LOG_HTABLE, 4, "hashtable_"KEY_STRING"_update_lookup:"
"updated "PFX" at table[%u]\n",
AUX_ENTRY_TAG(htable->lookuptable[hindex]), hindex);
}
}
#endif
/* This is based on Algorithm R from Knuth's Vol.3, section 6.4 */
/* Deletion markers tend to form clusters ("parking lot effect"), */
/* and in steady state the hashtable will always be full. */
/* This slightly more complicated deletion scheme solves these undesired effects, */
/* with the final arrangement being as if the elements were never inserted. */
/* Returns whether it copied from the start of the table to the end (wraparound).
* FIXME: if callers need more info, could return the final hindex, or the final
* hole.
*/
static uint
HTNAME(hashtable_,NAME_KEY,_remove_helper_open_address)
(HTNAME(,NAME_KEY,_table_t) *htable, uint hindex, ENTRY_TYPE *prevg)
{
bool wrapped = false;
/* Assumptions:
We have to move the htable->table and lookuptable elements.
It is OK to do so since the address of these structures is never passed back to clients,
instead, all clients can only hold onto a fragment_t* itself, not to the indirection here.
*/
LOG(THREAD_GET, LOG_HTABLE, 4,
"hashtable_"KEY_STRING"_remove_helper_open_address(table="PFX", "
"hindex=%u)\n", htable, hindex);
for (;;) {
uint preferred;
uint hole = hindex;
/* first go ahead and set entry to null */
htable->table[hole] = ENTRY_EMPTY;
#ifdef HASHTABLE_USE_LOOKUPTABLE
HTNAME(hashtable_,NAME_KEY,_update_lookup)(htable, hole);
#endif
do {
/* positive probing to get the rest in the same cache line
also gains from +1 unit stride HW prefetching
*/
hindex = HASH_INDEX_WRAPAROUND(hindex + 1, htable);
/* no orphaned elements, we're done */
/* Note that an &unlinked_fragment will get moved since it
* and its lookup table entry are designed to preserve linear
* probing. See the comment after fragment_update_lookup()
* for the implications.
*/
if (ENTRY_IS_EMPTY(htable->table[hindex]))
return wrapped;
preferred = HASH_FUNC(ENTRY_TAG(htable->table[hindex]), htable);
/* Verify if it will be lost if we leave a hole behind its preferred address */
/* [preferred] <= [hole] < [hindex] : BAD */
/* [hindex] < [preferred] <= [hole] : BAD [after wraparound] */
/* [hole] < [hindex] < [preferred] : BAD [after wraparound] */
/* Note the <='s: hole != hindex, but it is possible that preferred == hole */
} while (!(((preferred <= hole) && (hole < hindex)) ||
((hindex < preferred) && (preferred <= hole)) ||
((hole < hindex) && (hindex < preferred))));
LOG(THREAD_GET, LOG_HTABLE, 3,
"hashtable_"KEY_STRING"_remove_helper_open_address: moving "PFX" "
"from table[%u] into table[%u], preferred=%u\n",
ENTRY_TAG(htable->table[hindex]), hindex, hole, preferred);
/* need to move current entry into the hole */
htable->table[hole] = htable->table[hindex];
if (hindex < hole)
wrapped = true;
#ifdef HASHTABLE_USE_LOOKUPTABLE
HTNAME(hashtable_,NAME_KEY,_update_lookup)(htable, hole);
/* Since an &unlinked entry can be moved into a hole, we take
* special care to sync the lookup table to preserve the assert-able
* conditions that an unlinked entry has a lookup table entry
* w/a FAKE_TAG tag and a target_delete start_pc. FAKE_TAG is
* copied via the preceding update_lookup() but that also sets
* start_pc to 0. We manually set start_pc to the start_pc value of
* the old entry, to carry over the target_delete value.
*
* We also block the call to fragment_update_lookup() in the
* caller.
*/
/* FIXME We can remove this specialized code and simplify the
* ASSERT_CONSISTENCY by using a dedicated unlinked fragment per
* table. Each unlinked fragment can have its start_pc set to
* the corresponding target_delete value for the table.
*/
if (ENTRY_IS_INVALID(htable->table[hole])) {
htable->lookuptable[hole] = htable->lookuptable[hindex];
ASSERT(!AUX_ENTRY_IS_EMPTY(htable->lookuptable[hole]));
LOG(THREAD_GET, LOG_HTABLE, 4, " re-set "PFX" at table[%u]\n",
AUX_ENTRY_TAG(htable->lookuptable[hindex]), hole);
}
#endif
}
return wrapped;
}
/* Returns whether it copied from the start of the table to the end (wraparound). */
static inline bool
HTNAME(hashtable_,NAME_KEY,_remove_helper)(HTNAME(,NAME_KEY,_table_t) *htable,
uint hindex, ENTRY_TYPE *prevg)
{
/* Non-trivial for open addressed scheme */
/* just setting elements to null will make unreachable any following elements */
/* better solution is to move entries that would become unreachable */
int iwrapped =
HTNAME(hashtable_,NAME_KEY,_remove_helper_open_address)(htable, hindex, prevg);
bool wrapped = CAST_TO_bool(iwrapped);
#ifdef HASHTABLE_USE_LOOKUPTABLE
/* Don't sync the lookup table for unlinked fragments -- see the comments
* above in fragment_remove_helper_open_address(). */
if (!ENTRY_IS_INVALID(htable->table[hindex]))
HTNAME(hashtable_,NAME_KEY,_update_lookup)(htable, hindex);
#endif
htable->entries--;
return wrapped;
}
/* Removes f from htable (does not delete f)
* returns true if fragment found and removed
*/
static inline bool
HTNAME(hashtable_,NAME_KEY,_remove)(ENTRY_TYPE fr, HTNAME(,NAME_KEY,_table_t) *htable)
{
uint hindex;
ENTRY_TYPE *pg;
ASSERT_TABLE_SYNCHRONIZED(htable, WRITE); /* remove requires write lock */
ASSERT(!TEST(HASHTABLE_READ_ONLY, htable->table_flags));
if (TEST(HASHTABLE_READ_ONLY, htable->table_flags))
return false;
pg = HTNAME(hashtable_,NAME_KEY,_lookup_for_removal)(fr, htable, &hindex);
if (pg != NULL) {
HTNAME(hashtable_,NAME_KEY,_remove_helper)(htable, hindex, pg);
return true;
}
return false;
}
/* Replaces a fragment in hashtable assuming tag is preserved
* returns true if fragment found and removed
*/
static inline bool
HTNAME(hashtable_,NAME_KEY,_replace)
(ENTRY_TYPE old_e, ENTRY_TYPE new_e, HTNAME(,NAME_KEY,_table_t) *htable)
{
uint hindex;
ENTRY_TYPE *pg =
HTNAME(hashtable_,NAME_KEY,_lookup_for_removal)(old_e, htable, &hindex);
/* replace requires write lock only because we have readers who
* need global consistency and replace requires two local writes!
*/
ASSERT_TABLE_SYNCHRONIZED(htable, WRITE);
ASSERT(!TEST(HASHTABLE_READ_ONLY, htable->table_flags));
if (TEST(HASHTABLE_READ_ONLY, htable->table_flags))
return false;
if (pg != NULL) {
ASSERT(ENTRY_TAG(old_e) == ENTRY_TAG(new_e));
ASSERT(ENTRIES_ARE_EQUAL(htable, *pg, old_e));
*pg = new_e;
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (htable->lookuptable != NULL) {
/* TODO: update
* tag doesn't change, only start_pc may change */
ASSERT(AUX_ENTRY_TAG(htable->lookuptable[hindex]) ==
ENTRY_TAG(htable->table[hindex]));
AUX_ENTRY_SET_TO_ENTRY(htable->lookuptable[hindex], htable->table[hindex]);
}
#endif
return true;
}
return false;
}
/* removes all entries and resets the table but keeps the same capacity */
/* not static b/c not used by all tables */
void
HTNAME(hashtable_,NAME_KEY,_clear)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
uint i;
ENTRY_TYPE e;
ASSERT(!TEST(HASHTABLE_READ_ONLY, table->table_flags));
if (TEST(HASHTABLE_READ_ONLY, table->table_flags))
return;
LOG(THREAD, LOG_HTABLE, 2, "hashtable_"KEY_STRING"_clear\n");
DOLOG(2, LOG_HTABLE|LOG_STATS, {
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext, table);
});
for (i = 0; i < table->capacity; i++) {
e = table->table[i];
/* must check for sentinel */
if (ENTRY_IS_REAL(e)) {
HTNAME(hashtable_,NAME_KEY,_free_entry)(dcontext, table, e);
}
table->table[i] = ENTRY_EMPTY;
}
#ifdef HASHTABLE_USE_LOOKUPTABLE
memset(table->lookuptable, 0, table->capacity*sizeof(AUX_ENTRY_TYPE));
#endif
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
table->added_since_dumped = 0;
if (INTERNAL_OPTION(hashtable_ibl_entry_stats) && table->entry_stats != NULL) {
if (TEST(HASHTABLE_USE_ENTRY_STATS, table->table_flags)) {
memset(table->entry_stats, 0, table->capacity*sizeof(fragment_stat_entry_t));
}
}
# endif
#endif
table->entries = 0;
table->unlinked_entries = 0;
}
/* removes all entries within a specified range of tags
*
* should generalize hashtable_reset to do this in all cases,
* yet we haven't had an instance where this is necessary
*
* FIXME: note that we don't do a full type dispatch here, while
* hashtable_reset is not properly moving elements hence can't be used
* for removing subsets, and is inefficient!
*/
static uint
HTNAME(hashtable_,NAME_KEY,_range_remove)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table,
ptr_uint_t tag_start, ptr_uint_t tag_end,
bool (*filter)(ENTRY_TYPE e))
{
int i;
ENTRY_TYPE e;
uint entries_removed = 0;
DEBUG_DECLARE(uint entries_initial = 0;)
ASSERT(!TEST(HASHTABLE_READ_ONLY, table->table_flags));
if (TEST(HASHTABLE_READ_ONLY, table->table_flags))
return 0;
LOG(THREAD, LOG_HTABLE, 2, "hashtable_"KEY_STRING"_range_remove\n");
DOLOG(2, LOG_HTABLE|LOG_STATS, {
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext, table);
});
DODEBUG({
HTNAME(hashtable_,NAME_KEY,_study)(dcontext, table, 0/*table consistent*/);
/* ensure write lock is held if the table is shared, unless exiting */
if (!dynamo_exited)
ASSERT_TABLE_SYNCHRONIZED(table, WRITE); /* remove requires write lock */
entries_initial = table->entries;
});
/* Deletion in hashtable_NAME_KEY_remove_helper has to move entries
* in order to keep all reachable. We go in reverse order to
* efficiently delete all entries */
i = table->capacity - 1 - 1 /* sentinel */;
while (i >= 0) {
e = table->table[i];
if (!ENTRY_IS_EMPTY(e)
&& ENTRY_TAG(e) >= tag_start && ENTRY_TAG(e) < tag_end &&
(filter == NULL || filter(e))) {
if (HTNAME(hashtable_,NAME_KEY,_remove_helper)(table, i, &table->table[i])) {
/* Pulled a chain across the wraparound, so we must start over
* at the end as otherwise we will never look at that last
* element (case 10384).
*/
i = table->capacity - 1 - 1 /* sentinel */;
} else {
/* we can assume deletion doesn't move any entries ahead of i
* to smaller values, so i stays here
*/
}
entries_removed++;
/* de-allocate payload */
HTNAME(hashtable_,NAME_KEY,_free_entry)(dcontext, table, e);
} else {
i--;
}
}
ASSERT(table->entries + entries_removed == entries_initial);
return entries_removed;
}
/* removes all unlinked entries from table's lookup table */
static uint
HTNAME(hashtable_,NAME_KEY,_unlinked_remove)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
int i;
ENTRY_TYPE e;
uint entries_removed = 0;
ASSERT(!TEST(HASHTABLE_READ_ONLY, table->table_flags));
if (TEST(HASHTABLE_READ_ONLY, table->table_flags))
return 0;
LOG(THREAD, LOG_HTABLE, 2, "hashtable_"KEY_STRING"_unlinked_remove\n");
/* body based on hashtable_range_remove() */
ASSERT(TEST(HASHTABLE_LOCKLESS_ACCESS, table->table_flags));
DOLOG(2, LOG_HTABLE|LOG_STATS, {
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext, table);
});
DODEBUG({
/* ensure write lock is held if the table is shared, unless exiting */
if (!dynamo_exited)
ASSERT_TABLE_SYNCHRONIZED(table, WRITE); /* remove requires write lock */
});
/* Deletion in fragment_remove_fragment_helper has to move entries
* in order to keep all reachable. We go in reverse order to
* efficiently delete all entries
*/
i = (int) table->capacity - 1 - 1 /* sentinel */;
while (i >= 0) {
e = table->table[i];
if (ENTRY_IS_INVALID(e)) {
LOG(THREAD, LOG_HTABLE|LOG_STATS, 2,
"unlinked_remove(%s) -- %u\n", table->name, i);
if (HTNAME(hashtable_,NAME_KEY,_remove_helper)(table, i, &table->table[i])) {
/* Pulled a chain across the wraparound, so we must start over
* at the end as otherwise we will never look at that last
* element (case 10384).
*/
i = table->capacity - 1 - 1 /* sentinel */;
} else {
/* we can assume deletion doesn't move any entries ahead of i
* to smaller values, so i stays here
*/
}
entries_removed++;
} else {
i--;
}
}
/* fragment_remove_fragment_helper decrements table->entries but we
* want only unlinked_entries decremented, so adjust entries. */
table->entries += entries_removed;
LOG(THREAD, LOG_HTABLE|LOG_STATS, 1,
"unlinked_remove(%s) -- %d deletions\n", table->name, entries_removed);
ASSERT(entries_removed == table->unlinked_entries);
#ifdef HASHTABLE_USE_LOOKUPTABLE
DODEBUG({
/* Check that there are no remnants of unlinked fragments in the table. */
for (i = 0; i < (int) table->capacity; i++) {
ASSERT(!ENTRY_IS_INVALID(table->table[i]));
ASSERT(!AUX_ENTRY_IS_INVALID(table->lookuptable[i]));
ASSERT(!AUX_PAYLOAD_IS_INVALID(dcontext, table, table->lookuptable[i]));
}
});
#else
DODEBUG({
/* Check that there are no remnants of unlinked fragments in the table. */
for (i = 0; i < (int) table->capacity; i++) {
ASSERT(!ENTRY_IS_INVALID(table->table[i]));
}
});
#endif
table->unlinked_entries = 0;
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
DODEBUG({
HTNAME(hashtable_,NAME_KEY,_study)(dcontext, table, 0/*table consistent*/);
});
return entries_removed;
}
/* we should clean the table from entries that are not frequently used
*/
static void
HTNAME(hashtable_,NAME_KEY,_groom_table)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
DOLOG(1, LOG_STATS, { print_timestamp(THREAD); });
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"_groom_table %s\n", table->name);
/* flush only tables caching data persistent in another table */
ASSERT(TEST(HASHTABLE_NOT_PRIMARY_STORAGE, table->table_flags));
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
LOG(THREAD, LOG_HTABLE, 2,
"%s hashtable flushing at %d entries capacity %d\n",
table->name, table->entries, table->capacity);
/* most simple grooming technique - just flush everyone */
HTNAME(hashtable_,NAME_KEY,_range_remove)(dcontext, table, 0, UINT_MAX, NULL);
ASSERT(table->entries == 0);
/* FIXME: we should do better - we can tag fragments that have
* been readded after getting flushed, so that they are not
* flushed next time we do this Some kind of aging that can be
* used also if we do a real clock working set.
*/
/* will not flush again until table gets resized */
table->groom_threshold = 0;
/* FIXME: we may want to do this more often - so that we can catch
* phases and that we don't even have to resize if working set
* does in fact fit here. In that case we may want to
* do have a step function,
* e.g. (groom 50, resize 80, groom step 10) translating into
* 50 - groom, 60 - groom, 70 - groom, 80 - resize
*/
STATS_INC(num_ibt_groomed);
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"_groom_table %s totally groomed - "
"should be empty\n", table->name);
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
}
static inline void
HTNAME(hashtable_,NAME_KEY,_groom_helper)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
/* flush only tables caching data persistent in another table */
ASSERT(TEST(HASHTABLE_NOT_PRIMARY_STORAGE, table->table_flags));
ASSERT(table->hash_bits != 0);
/* can't double size, and there is no point in resizing
* we have to flush it.
*/
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"_check_size reached maximum size %s\n",
table->name);
/* Currently groom_table() resets the whole table, but if
* it gets smarter, we may want to invoke the reset all
* logic here.
*/
table->entries--; /* entry not added yet */
HTNAME(hashtable_,NAME_KEY,_groom_table)(dcontext, table);
table->entries++;
/* can't make forward progress if groom_table() doesn't
* remove at least one entry
*/
ASSERT(table->entries <= table->resize_threshold);
}
#ifdef DEBUG
/* The above dyn.avg.coll is a little off: we can't show average
* succesfull search time, since some collisions are for misses but
* indcalls_miss_stat includes misses both with and without
* collisions.
*/
static void
HTNAME(hashtable_,NAME_KEY,_study)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table,
uint entries_inc/*amnt table->entries was pre-inced*/)
{
/* hashtable sparseness study */
uint i;
uint max = 0;
uint num = 0, len = 0, num_collisions = 0;
uint colliding_frags = 0;
uint total_len = 0;
uint hindex;
const char *name = table->name;
uint ave_len_threshold;
uint overwraps = 0;
ENTRY_TYPE e;
bool lockless_access = TEST(HASHTABLE_LOCKLESS_ACCESS, table->table_flags);
if (!INTERNAL_OPTION(hashtable_study))
return;
/* studying needs the entire table to be in a consistent state.
* we considered having read mean local read/write and write mean global
* read/write, thus making study() a writer and add() a reader,
* but we do want add() and remove() to be exclusive w/o relying on the
* bb building lock, so we have them as writers and study() as a reader.
*/
TABLE_RWLOCK(table, read, lock);
for (i = 0; i < table->capacity; i++) {
e = table->table[i];
DODEBUG({ HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext,table,i); });
if (ENTRY_IS_EMPTY(e))
continue;
if (ENTRY_IS_SENTINEL(e)) {
ASSERT(i == table->capacity - 1);
/* don't count in collision length - not a real fragment */
continue;
}
hindex = HASH_FUNC(ENTRY_TAG(e), table);
if (i < hindex) {
len = i + (table->capacity - hindex - 1) + 1; /* counting the sentinel */
overwraps++;
LOG(THREAD, LOG_HTABLE|LOG_STATS, 2,
"WARNING: hashtable_"KEY_STRING"_study: overwrap[%d] of "
"len=%d, F tag="PFX",i=%d,hindex=%d\n",
overwraps, len, ENTRY_TAG(e), i, hindex);
}
else {
len = i - hindex + 1;
}
if (ENTRY_IS_INVALID(e)) {
ASSERT(lockless_access);
LOG(THREAD, LOG_HTABLE|LOG_STATS, 2,
"hashtable_"KEY_STRING"_study: entry not found in %s[%u]\n",
table->name, i);
len = 0;
continue;
}
/* check for unique entries - the entry found by
* fragment_lookup_in_hashtable should be us! */
else {
uint found_at_hindex;
ENTRY_TYPE *pg =
HTNAME(hashtable_,NAME_KEY,_lookup_for_removal)
(e, table, &found_at_hindex);
ASSERT(pg != NULL);
ASSERT(ENTRIES_ARE_EQUAL(table, *pg, e));
ASSERT(found_at_hindex == i && "duplicate entry found");
}
if (len > 0) {
if (len > max)
max = len;
total_len += len;
num++;
if (len > 1) {
num_collisions++;
colliding_frags += len;
}
}
}
DOLOG(1, LOG_HTABLE|LOG_STATS, {
uint st_top=0; uint st_bottom=0;
if (num != 0)
divide_uint64_print(total_len, num, false, 2, &st_top, &st_bottom);
LOG(THREAD, LOG_HTABLE|LOG_STATS, 1,
"%s %s hashtable statistics: num=%d, max=%d, #>1=%d, st.avg=%u.%.2u\n",
entries_inc == 0 ? "Total" : "Current", name,
num, max, num_collisions, st_top, st_bottom);
});
/* static average length is supposed to be under 5 even up
* to load factors of 90% see Knuth vol.3 or in CLR (p.238-9 in
* first edition) but of course only if we had uniformly
* distributed hash functions
*/
/* for bug 2271 we make more lenient for non trace tables using the
* _NONE hash function (i.e. private bb) when we are !SHARED_FRAGMENTS_ENABLED().
*/
if (!TEST(HASHTABLE_RELAX_CLUSTER_CHECKS, table->table_flags) &&
(lockless_access || table->hash_func != HASH_FUNCTION_NONE ||
SHARED_FRAGMENTS_ENABLED()))
ave_len_threshold = 5;
else
ave_len_threshold = 10;
DOLOG(1, LOG_HTABLE|LOG_STATS, {
/* This happens enough that it's good to get some info on it */
if (!((total_len <= ave_len_threshold * num
|| (TEST(HASHTABLE_RELAX_CLUSTER_CHECKS, table->table_flags) &&
table->capacity <= 513))
&& "hash table high average collision length")) {
LOG(THREAD, LOG_HTABLE|LOG_STATS, 1,
"htable %s ave len: tot=%d <= %d, cap=%d entr=%d fac=%d\n",
name, total_len, ave_len_threshold*num, table->capacity, table->entries,
table->load_factor_percent);
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
}
});
ASSERT_CURIOSITY((total_len <= ave_len_threshold * num
|| (TEST(HASHTABLE_RELAX_CLUSTER_CHECKS, table->table_flags) &&
table->capacity <= 513))
&& "hash table high average collision length");
DOLOG(3, LOG_HTABLE, {
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext, table);
});
{
uint doublecheck = num + entries_inc;
LOG(THREAD, LOG_HTABLE|LOG_STATS, 2,
"\t%s doublecheck %u (unlinked %u) == %u %d\n",
name, table->entries, table->unlinked_entries, doublecheck, entries_inc);
ASSERT(table->entries == doublecheck);
}
#ifdef HASHTABLE_STATISTICS
print_hashtable_stats(dcontext, entries_inc == 0 ? "Total" : "Current",
name,
"fragment_lookup", "", &table->drlookup_stats);
#endif
HTNAME(hashtable_,NAME_KEY,_study_custom)(dcontext, table, entries_inc);
TABLE_RWLOCK(table, read, unlock);
}
void
HTNAME(hashtable_,NAME_KEY,_dump_table)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable)
{
uint i;
bool track_cache_lines;
bool entry_size;
size_t cache_line_size = proc_get_cache_line_size();
uint cache_lines_used = 0;
bool cache_line_in_use = false;
#ifdef HASHTABLE_USE_LOOKUPTABLE
track_cache_lines = true;
entry_size = sizeof(AUX_ENTRY_TYPE);
#else
track_cache_lines = TEST(HASHTABLE_ALIGN_TABLE, htable->table_flags);
entry_size = sizeof(ENTRY_TYPE);
#endif
DOLOG(1, LOG_HTABLE|LOG_STATS, {
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext, htable);
});
LOG(THREAD, LOG_HTABLE, 1,
" i tag coll hits age %s dump\n", htable->name);
/* need read lock to traverse the table */
TABLE_RWLOCK(htable, read, lock);
for (i = 0; i < htable->capacity; i++) {
if (track_cache_lines && (i*entry_size % cache_line_size == 0)) {
if (cache_line_in_use)
cache_lines_used++;
cache_line_in_use = false;
}
if (ENTRY_IS_INVALID(htable->table[i])) {
LOG(THREAD, LOG_HTABLE, 1, "%6x *** unlinked marker ***\n", i);
}
else if (ENTRY_IS_REAL(htable->table[i])) {
uint preferred = HASH_FUNC(ENTRY_TAG(htable->table[i]), htable);
uint collision_len = (preferred <= i) ? i - preferred /* collision */
: (htable->capacity + i - preferred) /* with overwrap */;
/* overwrap count should include the sentinel to total length */
# if defined(HASHTABLE_STATISTICS) && defined(HASHTABLE_ENTRY_STATS)
LOG(THREAD, LOG_HTABLE, 1,
"%6x "PFX" %3d %c %8d %3d\n",
i, ENTRY_TAG(htable->table[i]),
collision_len + 1, (preferred <= i) ? ' ' : 'O' /* overwrap */,
htable->entry_stats ? htable->entry_stats[i].hits : 0,
htable->entry_stats ? htable->entry_stats[i].age : 0
);
# else /* !(HASHTABLE_STATISTICS && HASHTABLE_ENTRY_STATS) */
LOG(THREAD, LOG_HTABLE, 1,
"%6x "PFX" %3d %c \n",
i, ENTRY_TAG(htable->table[i]),
collision_len + 1, (preferred <= i) ? ' ' : 'O' /* overwrap */
);
# endif /* (HASHTABLE_STATISTICS && HASHTABLE_ENTRY_STATS) */
if (track_cache_lines)
cache_line_in_use = true;
} else {
/* skip null_fragment entries */
}
DOLOG(2, LOG_HTABLE, {
/* print full table */
if (ENTRY_IS_EMPTY(htable->table[i]))
LOG(THREAD, LOG_HTABLE, 2,
"%6x "PFX"\n", i, 0);
});
DOLOG(2, LOG_HTABLE, {
if (track_cache_lines &&
(i+1)*entry_size % cache_line_size == 0 && cache_line_in_use)
LOG(THREAD, LOG_HTABLE, 1,
"----cache line----\n");
});
DODEBUG({ HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext, htable, i); });
}
if (track_cache_lines) {
if (cache_line_in_use)
cache_lines_used++;
if (cache_lines_used) {
LOG(THREAD, LOG_HTABLE, 1, "%s %d%% cache density, cache_lines_used=%d "
"(%dKB), minimum needed %d (%dKB)\n",
htable->name,
100 * htable->entries * entry_size /
(cache_lines_used * cache_line_size),
cache_lines_used, cache_lines_used*cache_line_size/1024,
htable->entries*entry_size/cache_line_size,
htable->entries*entry_size/1024);
}
}
TABLE_RWLOCK(htable, read, unlock);
}
#endif /* DEBUG */
#if defined(DEBUG) && defined(INTERNAL)
static void
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *table)
{
LOG(THREAD, LOG_HTABLE|LOG_STATS, 1,
"%s hashtable: %d entries, %d unlinked entries, %d capacity,"
" %d%% load\n",
table->name,
table->entries, table->unlinked_entries, table->capacity,
(100 * table->entries) / table->capacity);
}
#endif
#ifdef HASHTABLE_STATISTICS
# ifdef HASHTABLE_ENTRY_STATS
void
HTNAME(hashtable_,NAME_KEY,_dump_entry_stats)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable)
{
/* mostly a copy of dump_table but printing only entries with non 0 stats */
uint i;
uint max_age = 0;
DOLOG(1, LOG_STATS, { print_timestamp(THREAD); });
LOG(THREAD, LOG_HTABLE, 1, "dump_and_clean_entry_statistics: %s\n", htable->name);
DOLOG(1, LOG_HTABLE|LOG_STATS, {
HTNAME(hashtable_,NAME_KEY,_load_statistics)(dcontext, htable);
/* TODO: should preserve a copy of the old hashtable_statistics_t
* so that the difference between the two can be matched with
* the per-entry hits and collisions. though a Perl script will do.
*/
});
LOG(THREAD, LOG_HTABLE, 1,
" i tag coll hits age %s\n", htable->name);
/* need read lock to traverse the table */
TABLE_RWLOCK(htable, read, lock);
for (i = 0; i < htable->capacity; i++) {
if (!ENTRY_IS_EMPTY(htable->table[i]) &&
!ENTRY_IS_INVALID(htable->table[i])) {
uint preferred = HASH_FUNC(ENTRY_TAG(htable->table[i]), htable);
uint collision_len = (preferred <= i) ? i - preferred /* collision */
: (htable->capacity + i - preferred) /* with overwrap */;
if (htable->entry_stats[i].hits == 0) {
/* no hits in a row */
htable->entry_stats[i].age++;
if (max_age < htable->entry_stats[i].age)
max_age = htable->entry_stats[i].age;
}
LOG(THREAD, LOG_HTABLE, htable->entry_stats[i].hits ? 1U: 2U, /* only hits */
"%6x "PFX" %3d %c %8d %3d\n",
i, ENTRY_TAG(htable->table[i]),
collision_len + 1, (preferred <= i) ? ' ' : 'O' /* overwrap */,
htable->entry_stats[i].hits, htable->entry_stats[i].age
);
} else {
/* skip null_fragment entries */
}
DODEBUG({ HTNAME(hashtable_,NAME_KEY,_check_consistency)(dcontext, htable, i); });
}
if (max_age > 0) {
LOG(THREAD, LOG_HTABLE, 1,
"hashtable_"KEY_STRING"dump_entry_stats: %s max_age:%d\n",
htable->name, max_age);
}
TABLE_RWLOCK(htable, read, unlock);
}
# endif
#endif /* HASHTABLE_STATISTICS */
#ifdef HASHTABLE_SUPPORT_PERSISTENCE
uint
HTNAME(hashtable_,NAME_KEY,_persist_size)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable)
{
if (htable == NULL)
return 0;
return (sizeof(*htable) + (htable->capacity * sizeof(ENTRY_TYPE)));
}
/* We need enough of the htable struct fields that we simply persist
* the entire struct to avoid defining a separate subset struct.
* The version field of the entire file suffices to version the htable struct.
* The table pointer (and stats in debug) need to be writable,
* so at load time we do not directly use the mmapped copy, but we could
* with a little work (case 10349: see comments below as well) (not worth
* making COW and having the whole page private: case 9582).
* Returns true iff all writes succeeded.
*/
bool
HTNAME(hashtable_,NAME_KEY,_persist)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *htable,
file_t fd)
{
uint size;
ASSERT(htable != NULL);
if (htable == NULL)
return false;
ASSERT(fd != INVALID_FILE);
size = sizeof(*htable);
if (os_write(fd, htable, size) != (int)size)
return false;
/* We don't bother to align */
size = htable->capacity * sizeof(ENTRY_TYPE);
if (os_write(fd, htable->table, size) != (int)size)
return false;
return true;
}
static uint
HTNAME(hashtable_,NAME_KEY,_num_unique_entries)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *src1,
HTNAME(,NAME_KEY,_table_t) *src2)
{
HTNAME(,NAME_KEY,_table_t) *big, *small;
uint unique, i;
ENTRY_TYPE e;
ASSERT(src1 != NULL && src2 != NULL);
if (src1->entries >= src2->entries) {
big = src1;
small = src2;
} else {
big = src2;
small = src1;
}
unique = big->entries;
/* Deadlock-avoidance won't let us grab both locks; for now we only
* use this when all-synched so we let that solve the problem.
* N.B.: we assume that on suspend failure for flush synchall
* (which ignores such failures) we do not come here as we abort
* coarse freezing/merging/persist. FIXME: should we export
* another variable, or not set dynamo_all_threads_synched?
*/
ASSERT(dynamo_all_threads_synched);
DODEBUG({ big->is_local = true; });
TABLE_RWLOCK(small, read, lock);
for (i = 0; i < small->capacity; i++) {
e = small->table[i];
if (ENTRY_IS_REAL(e)) {
if (ENTRY_IS_EMPTY(HTNAME(hashtable_,NAME_KEY,_lookup(dcontext,
ENTRY_TAG(e), big))))
unique++;
}
}
TABLE_RWLOCK(small, read, unlock);
DODEBUG({ big->is_local = false; });
return unique;
}
/* Adds all entries from src to dst */
static void
HTNAME(hashtable_,NAME_KEY,_add_all)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *dst,
HTNAME(,NAME_KEY,_table_t) *src,
bool check_dups)
{
ENTRY_TYPE e;
uint i;
ASSERT(dst != NULL && src != NULL);
/* assumption: dst is private to this thread and so does not need synch */
DODEBUG({ dst->is_local = true; });
TABLE_RWLOCK(src, read, lock);
for (i = 0; i < src->capacity; i++) {
e = src->table[i];
if (ENTRY_IS_REAL(e)) {
if (!check_dups ||
ENTRY_IS_EMPTY(HTNAME(hashtable_,NAME_KEY,_lookup(dcontext,
ENTRY_TAG(e), dst)))) {
/* add will also add lookuptable entry, if any */
HTNAME(hashtable_,NAME_KEY,_add)(dcontext, e, dst);
}
}
}
TABLE_RWLOCK(src, read, unlock);
DODEBUG({ dst->is_local = false; });
}
/* Creates a new hashtable that contains the union of src1 and src2 (removing
* the duplicates)
*/
HTNAME(,NAME_KEY,_table_t) *
HTNAME(hashtable_,NAME_KEY,_merge)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *src1,
HTNAME(,NAME_KEY,_table_t) *src2)
{
HTNAME(,NAME_KEY,_table_t) *dst;
uint merged_entries, init_bits;
ASSERT(src1 != NULL && src2 != NULL);
/* We only support merging the same type of table */
ASSERT((src1->table_flags & ~(HASHTABLE_COPY_IGNORE_FLAGS)) ==
(src2->table_flags & ~(HASHTABLE_COPY_IGNORE_FLAGS)));
ASSERT(src1->load_factor_percent == src2->load_factor_percent);
ASSERT(src1->hash_func == src2->hash_func);
dst = (HTNAME(,NAME_KEY,_table_t) *)
TABLE_TYPE_MEMOP(src1->table_flags, ALLOC, dcontext, HTNAME(,NAME_KEY,_table_t),
HASHTABLE_WHICH_HEAP(src1->table_flags), PROTECTED);
merged_entries =
HTNAME(hashtable_,NAME_KEY,_num_unique_entries)(dcontext, src1, src2);
LOG(THREAD, LOG_HTABLE, 2,
"hashtable_"KEY_STRING"_merge: %d + %d => %d unique entries\n",
src1->entries, src2->entries, merged_entries);
init_bits =
hashtable_bits_given_entries(merged_entries, src1->load_factor_percent);
HTNAME(hashtable_,NAME_KEY,_init)(dcontext, dst, init_bits,
src1->load_factor_percent, src1->hash_func,
src1->hash_mask_offset _IFLOOKUP(use_lookup),
src1->table_flags & ~(HASHTABLE_COPY_IGNORE_FLAGS)
_IF_DEBUG(src1->name));
HTNAME(hashtable_,NAME_KEY,_add_all)(dcontext, dst, src1, false/*don't check dups*/);
HTNAME(hashtable_,NAME_KEY,_add_all)(dcontext, dst, src2, true/*check dups*/);
return dst;
}
/* Performs a deep copy (struct plus table) of src */
HTNAME(,NAME_KEY,_table_t) *
HTNAME(hashtable_,NAME_KEY,_copy)(dcontext_t *dcontext,
HTNAME(,NAME_KEY,_table_t) *src)
{
HTNAME(,NAME_KEY,_table_t) *dst;
ASSERT(src != NULL);
dst = (HTNAME(,NAME_KEY,_table_t) *)
TABLE_TYPE_MEMOP(src->table_flags, ALLOC, dcontext, HTNAME(,NAME_KEY,_table_t),
HASHTABLE_WHICH_HEAP(src->table_flags), PROTECTED);
/* use init() rather than memcpy of header, so we get table and/or lookuptable
* allocated to proper alignment
*/
HTNAME(hashtable_,NAME_KEY,_init)(dcontext, dst, src->hash_bits,
src->load_factor_percent, src->hash_func,
src->hash_mask_offset _IFLOOKUP(use_lookup),
src->table_flags & ~(HASHTABLE_COPY_IGNORE_FLAGS)
_IF_DEBUG(src->name));
dst->entries = src->entries;
dst->unlinked_entries = src->unlinked_entries;
if (dst->table != NULL)
memcpy(dst->table, src->table, dst->capacity*sizeof(ENTRY_TYPE));
#ifdef HASHTABLE_USE_LOOKUPTABLE
if (dst->lookuptable != NULL)
memcpy(dst->lookuptable, src->lookuptable, dst->capacity*sizeof(AUX_ENTRY_TYPE));
#endif
return dst;
}
/* See comments in HTNAME(hashtable_,NAME_KEY,_persist)
* Returns a newly allocated struct on the heap.
*/
HTNAME(,NAME_KEY,_table_t) *
HTNAME(hashtable_,NAME_KEY,_resurrect)(dcontext_t *dcontext, byte *mapped_table
_IF_DEBUG(const char *table_name))
{
HTNAME(,NAME_KEY,_table_t) *htable;
uint flags;
ASSERT(mapped_table != NULL);
flags = ((HTNAME(,NAME_KEY,_table_t) *)mapped_table)->table_flags;
/* FIXME: the free, and the init alloc, are in client code: would be better
* to have all in same file using more-easily-kept-consistent alloc routines
*/
htable = (HTNAME(,NAME_KEY,_table_t) *)
TABLE_TYPE_MEMOP(flags, ALLOC, dcontext, HTNAME(,NAME_KEY,_table_t),
HASHTABLE_WHICH_HEAP(table->table_flags), PROTECTED);
/* FIXME case 10349: we could directly use the mmapped struct when
* !HASHTABLE_STATISTICS if we supported calculating where the table lies
* in all the htable routines, and set HASHTABLE_READ_ONLY when persisting
*/
memcpy(htable, mapped_table, sizeof(*htable));
htable->table_flags |= HASHTABLE_READ_ONLY;
htable->table = (ENTRY_TYPE *) (mapped_table + sizeof(*htable));
htable->table_unaligned = NULL;
ASSIGN_INIT_READWRITE_LOCK_FREE(htable->rwlock, HTLOCK_RANK);
DODEBUG({
htable->name = table_name;
});
#ifdef HASHTABLE_STATISTICS
INIT_HASHTABLE_STATS(htable->drlookup_stats);
#endif
return htable;
}
#endif /* HASHTABLE_SUPPORT_PERSISTENCE */
#endif
/****************************************************************************/
/* make it easier to have multiple instantiations in a file
* user must re-specify name and types for struct and impl, though
*/
#undef NAME_KEY
#undef ENTRY_TYPE
#undef AUX_ENTRY_TYPE
#undef CUSTOM_FIELDS
#undef ENTRY_TAG
#undef ENTRY_IS_EMPTY
#undef ENTRY_IS_SENTINEL
#undef ENTRY_IS_INVALID
#undef ENTRIES_ARE_EQUAL
#undef ENTRY_EMPTY
#undef ENTRY_SENTINEL
#undef TAGS_ARE_EQUAL
#undef AUX_ENTRY_TAG
#undef AUX_ENTRY_IS_EMPTY
#undef AUX_ENTRY_IS_SENTINEL
#undef AUX_ENTRY_IS_INVALID
#undef AUX_PAYLOAD_IS_INVALID
#undef AUX_ENTRY_SET_TO_SENTINEL
#undef AUX_ENTRY_IS_SET_TO_ENTRY
#undef AUX_ENTRY_SET_TO_ENTRY
#undef AUX_ENTRY_FORMAT_STR
#undef AUX_ENTRY_FORMAT_ARGS
#undef HASHTABLE_WHICH_HEAP
#undef HASHTABLE_USE_LOOKUPTABLE
#undef HASHTABLE_ENTRY_STATS
#undef HASHTABLE_SUPPORT_PERSISTENCE
#undef HTLOCK_RANK
#undef _IFLOOKUP
#undef IFLOOKUP_ELSE