blob: 7317d55ec1822401f939fe096d52b0328cb574f1 [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. */
#include "globals.h"
#include "hashtable.h"
#include <string.h> /* memset */
/* Returns the proper number of hash bits to have a capacity with the
* given load for the given number of entries
*/
uint
hashtable_bits_given_entries(uint entries, uint load)
{
/* Add 1 for the sentinel */
return hashtable_num_bits(((entries + 1) * 100) / load);
}
/*******************************************************************************
* GENERIC HASHTABLE INSTANTIATION
*
* We save code space by having hashtables that don't need special inlining
* use the same code. We also provide a more "normal" hashtable interface,
* with key and payload separation and payload freeing.
*
* We only support caller synchronization currently (the caller should
* use TABLE_RWLOCK) but we could provide a flag specifying whether
* synch is intra-routine or not.
*/
/* 2 macros w/ name and types are duplicated in fragment.h -- keep in sync */
#define NAME_KEY generic
#define ENTRY_TYPE generic_entry_t *
/* not defining HASHTABLE_USE_LOOKUPTABLE */
#define ENTRY_TAG(f) ((f)->key)
#define ENTRY_EMPTY NULL
/* we assume that 1 is not a valid value: that keys are in fact pointers */
#define ENTRY_SENTINEL ((generic_entry_t*)(ptr_uint_t)1)
#define ENTRY_IS_EMPTY(f) ((f) == NULL)
#define ENTRY_IS_SENTINEL(f) ((f) == ENTRY_SENTINEL)
#define ENTRY_IS_INVALID(f) (false) /* no invalid entries */
#define ENTRIES_ARE_EQUAL(t,f,g) (ENTRY_TAG(f) == ENTRY_TAG(g))
/* if usage becomes widespread we may need to parameterize this */
#define HASHTABLE_WHICH_HEAP(flags) (ACCT_OTHER)
#define HTLOCK_RANK table_rwlock
#define HASHTABLE_SUPPORT_PERSISTENCE 0
#include "hashtablex.h"
/* all defines are undef-ed at end of hashtablex.h */
#define GENERIC_ENTRY_IS_REAL(e) ((e) != NULL && (e) != (generic_entry_t*)(ptr_uint_t)1)
/* required routines for hashtable interface */
static void
hashtable_generic_init_internal_custom(dcontext_t *dcontext, generic_table_t *htable)
{ /* nothing */
}
static void
hashtable_generic_resized_custom(dcontext_t *dcontext, generic_table_t *htable,
uint old_capacity, generic_entry_t **old_table,
generic_entry_t **old_table_unaligned,
uint old_ref_count, uint old_table_flags)
{ /* nothing */
}
# ifdef DEBUG
static void
hashtable_generic_study_custom(dcontext_t *dcontext, generic_table_t *htable,
uint entries_inc/*amnt table->entries was pre-inced*/)
{ /* nothing */
}
# endif
static void
hashtable_generic_free_entry(dcontext_t *dcontext, generic_table_t *htable,
generic_entry_t *entry)
{
if (htable->free_payload_func != NULL)
(*htable->free_payload_func)(entry->payload);
HEAP_TYPE_FREE(dcontext, entry, generic_entry_t, ACCT_OTHER, PROTECTED);
}
/* Wrapper routines to implement our generic_entry_t and free-func layer */
generic_table_t *
generic_hash_create(dcontext_t *dcontext, uint bits, uint load_factor_percent,
uint table_flags, void (*free_payload_func)(void*)
_IF_DEBUG(const char *table_name))
{
generic_table_t *table = HEAP_TYPE_ALLOC(dcontext, generic_table_t,
ACCT_OTHER, PROTECTED);
hashtable_generic_init(dcontext, table, bits, load_factor_percent,
(hash_function_t)INTERNAL_OPTION(alt_hash_func),
0 /* hash_mask_offset */, table_flags
_IF_DEBUG(table_name));
table->free_payload_func = free_payload_func;
return table;
}
void
generic_hash_clear(dcontext_t *dcontext, generic_table_t *htable)
{
hashtable_generic_clear(dcontext, htable);
}
void
generic_hash_destroy(dcontext_t *dcontext, generic_table_t *htable)
{
/* FIXME: why doesn't hashtablex.h walk the table and call the free routine
* in free() or in remove()? It only seems to do it for range_remove().
*/
uint i;
generic_entry_t *e;
for (i = 0; i < htable->capacity; i++) {
e = htable->table[i];
/* must check for sentinel */
if (GENERIC_ENTRY_IS_REAL(e)) {
hashtable_generic_free_entry(dcontext, htable, e);
}
}
hashtable_generic_free(dcontext, htable);
HEAP_TYPE_FREE(dcontext, htable, generic_table_t, ACCT_OTHER, PROTECTED);
}
void *
generic_hash_lookup(dcontext_t *dcontext, generic_table_t *htable, ptr_uint_t key)
{
generic_entry_t *e = hashtable_generic_lookup(dcontext, key, htable);
if (e != NULL)
return e->payload;
return NULL;
}
void
generic_hash_add(dcontext_t *dcontext, generic_table_t *htable, ptr_uint_t key,
void *payload)
{
generic_entry_t *e =
HEAP_TYPE_ALLOC(dcontext, generic_entry_t, ACCT_OTHER, PROTECTED);
e->key = key;
e->payload = payload;
hashtable_generic_add(dcontext, e, htable);
}
bool
generic_hash_remove(dcontext_t *dcontext, generic_table_t *htable, ptr_uint_t key)
{
/* There is no remove routine that takes in a tag, nor one that frees the
* payload, so we construct it
*/
generic_entry_t *e = hashtable_generic_lookup(dcontext, key, htable);
if (e != NULL && hashtable_generic_remove(e, htable)) {
hashtable_generic_free_entry(dcontext, htable, e);
return true;
}
return false;
}
/* pass 0 to start. returns -1 when there are no more entries. */
int
generic_hash_iterate_next(dcontext_t *dcontext, generic_table_t *htable, int iter,
OUT ptr_uint_t *key, OUT void **payload)
{
int i;
generic_entry_t *e = NULL;
for (i = iter; i < (int) htable->capacity; i++) {
e = htable->table[i];
if (!GENERIC_ENTRY_IS_REAL(e))
continue;
else
break;
}
if (i >= (int) htable->capacity)
return -1;
ASSERT(e != NULL);
if (key != NULL)
*key = e->key;
if (payload != NULL)
*payload = e->payload;
return i+1;
}
/* removes from the hashtable in a safe way during iteration. returns an
* updated iteration index to pass to generic_hash_iterate_next().
*/
int
generic_hash_iterate_remove(dcontext_t *dcontext, generic_table_t *htable, int iter,
ptr_uint_t key)
{
generic_entry_t *e = hashtable_generic_lookup(dcontext, key, htable);
uint hindex;
generic_entry_t **rm = hashtable_generic_lookup_for_removal(e, htable, &hindex);
int res = iter;
if (rm != NULL) {
if (hashtable_generic_remove_helper(htable, hindex, rm)) {
/* pulled entry from start to here so skip it as we've already seen it */
} else {
/* pulled entry from below us, so step back */
res--;
}
hashtable_generic_free_entry(dcontext, htable, e);
}
return res;
}
/*******************************************************************************
* STRING KEY HASHTABLE INSTANTIATION
*
* We only support caller synchronization currently (the caller should
* use TABLE_RWLOCK).
*/
/* 2 macros w/ name and types are duplicated in fragment.h -- keep in sync */
#define NAME_KEY strhash
#define ENTRY_TYPE strhash_entry_t *
/* not defining HASHTABLE_USE_LOOKUPTABLE */
#define ENTRY_TAG(f) (ptr_uint_t)((f)->key)
#define ENTRY_EMPTY NULL
/* we assume that 1 is not a valid value: that keys are in fact pointers */
#define STRHASH_SENTINEL ((strhash_entry_t*)(ptr_uint_t)1)
#define ENTRY_SENTINEL STRHASH_SENTINEL
#define ENTRY_IS_EMPTY(f) ((f) == NULL)
#define ENTRY_IS_SENTINEL(f) ((f) == STRHASH_SENTINEL)
#define ENTRY_IS_INVALID(f) (false) /* no invalid entries */
#define TAGS_ARE_EQUAL(table,s1,s2) (strhash_key_cmp(table, (const char *)(s1),\
(const char *)(s2)))
#define ENTRIES_ARE_EQUAL(table,f,g) (TAGS_ARE_EQUAL(table, (f)->key, (g)->key))
/* if usage becomes widespread we may need to parameterize this */
#define HASHTABLE_WHICH_HEAP(flags) (ACCT_OTHER)
#define HTLOCK_RANK table_rwlock
#define HASHTABLE_SUPPORT_PERSISTENCE 0
/* case sensitive by default */
#define STRHASH_CASE_INSENSITIVE HASHTABLE_CUSTOM_FLAGS_START
static inline bool
strhash_key_cmp(strhash_table_t *htable, const char *s1, const char *s2)
{
if (TEST(STRHASH_CASE_INSENSITIVE, htable->table_flags))
return strcasecmp(s1, s2) == 0;
else
return strcmp(s1, s2) == 0;
}
#include "hashtablex.h"
/* all defines are undef-ed at end of hashtablex.h */
#define STRHASH_ENTRY_IS_REAL(e) ((e) != NULL && (e) != STRHASH_SENTINEL)
/* required routines for hashtable interface */
static void
hashtable_strhash_init_internal_custom(dcontext_t *dcontext, strhash_table_t *htable)
{ /* nothing */
}
static void
hashtable_strhash_resized_custom(dcontext_t *dcontext, strhash_table_t *htable,
uint old_capacity, strhash_entry_t **old_table,
strhash_entry_t **old_table_unaligned,
uint old_ref_count, uint old_table_flags)
{ /* nothing */
}
# ifdef DEBUG
static void
hashtable_strhash_study_custom(dcontext_t *dcontext, strhash_table_t *htable,
uint entries_inc/*amnt table->entries was pre-inced*/)
{ /* nothing */
}
# endif
static void
hashtable_strhash_free_entry(dcontext_t *dcontext, strhash_table_t *htable,
strhash_entry_t *entry)
{
if (htable->free_payload_func != NULL)
(*htable->free_payload_func)(entry->payload);
HEAP_TYPE_FREE(dcontext, entry, strhash_entry_t, ACCT_OTHER, PROTECTED);
}
/* Wrapper routines to implement our strhash_entry_t and free-func layer */
strhash_table_t *
strhash_hash_create(dcontext_t *dcontext, uint bits, uint load_factor_percent,
uint table_flags, void (*free_payload_func)(void*)
_IF_DEBUG(const char *table_name))
{
strhash_table_t *table = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, strhash_table_t,
ACCT_OTHER, PROTECTED);
hashtable_strhash_init(GLOBAL_DCONTEXT, table, bits, load_factor_percent,
TEST(STRHASH_CASE_INSENSITIVE, table_flags) ?
HASH_FUNCTION_STRING_NOCASE : HASH_FUNCTION_STRING,
0 /* hash_mask_offset */, table_flags
_IF_DEBUG(table_name));
table->free_payload_func = free_payload_func;
return table;
}
void
strhash_hash_clear(dcontext_t *dcontext, strhash_table_t *htable)
{
hashtable_strhash_clear(dcontext, htable);
}
void
strhash_hash_destroy(dcontext_t *dcontext, strhash_table_t *htable)
{
/* FIXME: why doesn't hashtablex.h walk the table and call the free routine
* in free() or in remove()? It only seems to do it for range_remove().
*/
uint i;
strhash_entry_t *e;
for (i = 0; i < htable->capacity; i++) {
e = htable->table[i];
/* must check for sentinel */
if (STRHASH_ENTRY_IS_REAL(e)) {
hashtable_strhash_free_entry(dcontext, htable, e);
}
}
hashtable_strhash_free(dcontext, htable);
HEAP_TYPE_FREE(dcontext, htable, strhash_table_t, ACCT_OTHER, PROTECTED);
}
void *
strhash_hash_lookup(dcontext_t *dcontext, strhash_table_t *htable, const char *key)
{
strhash_entry_t *e = hashtable_strhash_lookup(dcontext, (ptr_uint_t)key, htable);
if (e != NULL)
return e->payload;
return NULL;
}
void
strhash_hash_add(dcontext_t *dcontext, strhash_table_t *htable, const char *key,
void *payload)
{
strhash_entry_t *e =
HEAP_TYPE_ALLOC(dcontext, strhash_entry_t, ACCT_OTHER, PROTECTED);
e->key = key;
e->payload = payload;
hashtable_strhash_add(dcontext, e, htable);
}
bool
strhash_hash_remove(dcontext_t *dcontext, strhash_table_t *htable, const char *key)
{
/* There is no remove routine that takes in a tag, nor one that frees the
* payload, so we construct it
*/
strhash_entry_t *e = hashtable_strhash_lookup(dcontext, (ptr_uint_t)key, htable);
if (e != NULL && hashtable_strhash_remove(e, htable)) {
hashtable_strhash_free_entry(dcontext, htable, e);
return true;
}
return false;
}
/*******************************************************************************/
#ifdef HASHTABLE_STATISTICS
/* caller is responsible for any needed synchronization */
void
print_hashtable_stats(dcontext_t *dcontext,
const char *is_final_str, const char *is_trace_str,
const char *lookup_routine_str,
const char *brtype_str,
hashtable_statistics_t *lookup_stats)
{
uint64 hits_stat = lookup_stats->hit_stat;
uint64 total_lookups;
if (lookup_stats->hit_stat < lookup_stats->collision_hit_stat) {
/* HACK OVERFLOW: we special case here the case of a single overflow */
/* assuming only one overflow, which is the only case on spec (GAP) */
hits_stat = (uint64)lookup_stats->hit_stat + UINT_MAX + 1;
}
total_lookups= hits_stat + lookup_stats->miss_stat + lookup_stats->collision_hit_stat;
DOLOG(1, LOG_FRAGMENT|LOG_STATS, {
uint miss_top=0; uint miss_bottom=0;
uint hit_top=0; uint hit_bottom=0;
uint col_top=0; uint col_bottom=0;
if (total_lookups > 0) {
divide_uint64_print(lookup_stats->miss_stat, total_lookups, false,
4, &miss_top, &miss_bottom);
}
if (hits_stat > 0) {
divide_uint64_print(lookup_stats->collision_hit_stat, hits_stat,
false, 4, &hit_top, &hit_bottom);
divide_uint64_print(hits_stat + lookup_stats->collision_stat,
hits_stat, false, 4, &col_top, &col_bottom);
}
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s lookup hits%s: "UINT64_FORMAT_STRING", "
"misses: %u, total: "UINT64_FORMAT_STRING", miss%%=%u.%.4u\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
(lookup_stats->hit_stat < lookup_stats->collision_hit_stat) ?
"[OVFL]" : "",
hits_stat,
lookup_stats->miss_stat,
total_lookups,
miss_top, miss_bottom);
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s "
"collisions: %u, collision hits: %u, "
">2_or_miss: %u, overwrap: %u\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
lookup_stats->collision_stat,
/* FIXME: collision hit stats are updated only when inlining IBL head */
lookup_stats->collision_hit_stat,
lookup_stats->collision_stat - lookup_stats->collision_hit_stat,
lookup_stats->overwrap_stat);
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s lookup "
" coll%%=%u.%.4u, dyn.avgcoll=%u.%.4u\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
hit_top, hit_bottom,
col_top, col_bottom);
if (lookup_stats->race_condition_stat || lookup_stats->unlinked_count_stat) {
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s inlined ibl unlinking races: %d, unlinked: %d\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
lookup_stats->race_condition_stat,
lookup_stats->unlinked_count_stat);
}
if (lookup_stats->ib_stay_on_trace_stat) {
uint ontrace_top=0; uint ontrace_bottom=0;
uint lastexit_top=0; uint lastexit_bottom=0;
uint speculate_lastexit_top=0; uint speculate_lastexit_bottom=0;
/* indirect branch lookups */
uint64 total_dynamic_ibs =
total_lookups + lookup_stats->ib_stay_on_trace_stat +
(DYNAMO_OPTION(speculate_last_exit) ? lookup_stats->ib_trace_last_ibl_speculate_success : 0);
/* FIXME: add ib_stay_on_trace_stat_ovfl here */
if (total_dynamic_ibs > 0) {
divide_uint64_print(lookup_stats->ib_stay_on_trace_stat,
total_dynamic_ibs, false,
4, &ontrace_top, &ontrace_bottom);
divide_uint64_print(lookup_stats->ib_trace_last_ibl_exit,
total_dynamic_ibs, false,
4, &lastexit_top, &lastexit_bottom);
divide_uint64_print(lookup_stats->ib_trace_last_ibl_speculate_success,
total_dynamic_ibs, false,
4, &speculate_lastexit_top, &speculate_lastexit_bottom);
}
/* all percentages here relative to IB lookups */
/* ontrace is how often we stay on trace with respect to all indirect branches */
/* ontrace_fail is assuming IBL's that are not the last exit are due to ontrace failures */
/* lastexit is how many are on last IBL when it is not speculated */
/* lastexit_speculate is how many are speculatively hit (as of all IBs) */
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s stay on trace hit:%s %u, last_ibl: %u, ontrace%%=%u.%.4u, lastexit%%=%u.%.4u\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
lookup_stats->ib_stay_on_trace_stat_ovfl ? " OVFL" : "",
lookup_stats->ib_stay_on_trace_stat,
lookup_stats->ib_trace_last_ibl_exit,
ontrace_top, ontrace_bottom,
lastexit_top, lastexit_bottom
);
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s last trace exit speculation hit: %u, lastexit_ontrace%%=%u.%.4u(%%IB)\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
lookup_stats->ib_trace_last_ibl_speculate_success,
speculate_lastexit_top, speculate_lastexit_bottom);
}
if (lookup_stats->ib_trace_last_ibl_exit > 0) {
/* ignoring indirect branches that stayed on trace */
/* FIXME: add ib_stay_on_trace_stat_ovfl here */
uint speculate_only_lastexit_top=0; uint speculate_only_lastexit_bottom=0;
uint lastexit_ibl_top=0; uint lastexit_ibl_bottom=0;
uint speculate_lastexit_ibl_top=0; uint speculate_lastexit_ibl_bottom=0;
uint64 total_dynamic_ibl_no_trace = total_lookups + lookup_stats->ib_trace_last_ibl_exit;
if (total_dynamic_ibl_no_trace > 0) {
divide_uint64_print(lookup_stats->ib_trace_last_ibl_exit,
total_dynamic_ibl_no_trace, false,
4, &lastexit_ibl_top, &lastexit_ibl_bottom);
divide_uint64_print(lookup_stats->ib_trace_last_ibl_speculate_success,
total_dynamic_ibl_no_trace, false,
4, &speculate_lastexit_ibl_top, &speculate_lastexit_ibl_bottom);
}
/* ib_trace_last_ibl_exit includes all ib_trace_last_ibl_speculate_success */
divide_uint64_print(lookup_stats->ib_trace_last_ibl_speculate_success,
lookup_stats->ib_trace_last_ibl_exit, false,
4, &speculate_only_lastexit_top, &speculate_only_lastexit_bottom);
LOG(THREAD, LOG_FRAGMENT|LOG_STATS, 1,
"%s %s table %s%s last trace exit speculation hit: %u, speculation miss: %u, lastexit%%=%u.%.4u(%%IBL), lastexit_succ%%=%u.%.4u(%%IBL), spec hit%%=%u.%.4u(%%last exit)\n",
is_final_str, is_trace_str, lookup_routine_str, brtype_str,
lookup_stats->ib_trace_last_ibl_speculate_success,
lookup_stats->ib_trace_last_ibl_exit - lookup_stats->ib_trace_last_ibl_speculate_success,
lastexit_ibl_top, lastexit_ibl_bottom,
speculate_lastexit_ibl_top, speculate_lastexit_ibl_bottom,
speculate_only_lastexit_top, speculate_only_lastexit_bottom
);
}
});
}
#endif /* HASHTABLE_STATISTICS */