blob: 796b1d39d7d4b1eb326285c52a5d439c4ee37722 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- A skiplist implementation. m_skiplist.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2002-2005 Jeremy Fitzhardinge
jeremy@goop.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
/*
This file implements a generic skip-list type.
Skip-lists are described in William Pugh, Skip Lists: A
Probabilistic Alternative to Balanced Trees, CACM, 33(6):668-676,
June 1990.
http://www.cs.unc.edu/~baruah/Teaching/2002f/HandOuts/skiplist-CACM.pdf
Skip-lists are a randomized linked-list, where a node in the list
has at least one "next" pointer, but may have more. When
traversing the list, the "higher" next pointers allow you to skip
multiple list entries, allowing you to find the right place
quickly.
On average, 1/2 the entries have one next pointer, 1/4 have 2, 1/8
have 3, etc. This means that the skiplist has the same search
complexity as a balanced binary tree, but there is no need to
rebalance or do any other structural changes. The randomness also
means that it is invulnerable to pathalogical workloads.
Because the each node can be a different size, this implementation
puts the SkipNode structure at the end of the allocation, with the
node data starting at the beginning.
low address ->+---------------+ ^
| list data | |
key offset->: key : structure size
| | |
+---------------+ V
| struct |
| SkipNode |
+- - - - - - - -+
| next pointers |
: :
When you declare the list with VG_SKIPLIST_INIT, you specify the
structure for each list node, structure member you want to use as a
key, and the function for comparing keys.
Each node must be allocated with SkipNode_Alloc, which allocates
enough space for the client data, the SkipNode structure, and the
next pointers for this node.
The helper functions data_of_node and node_of_data do the correct
pointer arithmetic to sort all this out. The SkipNode also has a
magic number which is checked after each operation to make sure
that we're really operating on a SkipNode.
The first node of the list, the head node, is special. It contains
the maximum number of next pointers (SK_MAXHEIGHT). It has no data
associated with it, and it always compares less-than to all other
nodes. Because it has no data attached to it, it is always an
error to try and use data_of_node on it. To detect this problem,
it has a different magic number from all other SkipNodes so that it
won't be accidentally used.
*/
#include "pub_core_basics.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcprint.h"
#include "pub_core_mallocfree.h"
#include "pub_core_skiplist.h"
#include <stdlib.h>
#define SKIPLIST_DEBUG 0
#define SK_MAXHEIGHT 20 /* 2^20 elements */
#define SKIPLIST_MAGIC 0x5b1ff872
#define SKIPLIST_HEAD_MAGIC (~SKIPLIST_MAGIC)
#if SKIPLIST_DEBUG
#define inline
#endif /* SKIPLIST_DEBUG */
struct _SkipNode {
UInt magic;
UShort level; /* level is the max level (level == 0 means 1 next pointer) */
SkipNode *next[0];
};
/*
Compute the height of a new node. 1/2 will be 1, 1/4 2, 1/8 3,
etc.
*/
static inline Int get_height(void)
{
UInt ret = 0;
while((ret < SK_MAXHEIGHT - 1) && (random() & 1))
ret++;
return ret;
}
/*
Given a pointer to the node's data, return a pointer to the key.
*/
static inline void *key_of_data(const SkipList *l, void *d)
{
return (void *)((Char *)d + l->keyoff);
}
/*
Given a pointer to the node's data, return the pointer to the SkipNode
structure. If the node has a bad magic number, it will die with an
assertion failure.
*/
static inline SkipNode *node_of_data(const SkipList *l, const void *d)
{
SkipNode *n = (SkipNode *)((Char *)d + l->size);
if (SKIPLIST_DEBUG && n->magic != SKIPLIST_MAGIC)
VG_(printf)("bad magic on node %p = %x (not %x)\n",
n, n->magic, SKIPLIST_MAGIC);
vg_assert(n->magic == SKIPLIST_MAGIC);
return n;
}
/*
Given a SkipNode structure, return the pointer to the node's data.
*/
static inline void *data_of_node(const SkipList *l, const SkipNode *n)
{
if (SKIPLIST_DEBUG && n->magic != SKIPLIST_MAGIC)
VG_(printf)("bad magic on node %p = %x (not %x)\n",
n, n->magic, SKIPLIST_MAGIC);
vg_assert(n->magic == SKIPLIST_MAGIC);
return (void *)((Char *)n - l->size);
}
/*
Given a SkipNode structure, return the pointer to the node's key.
*/
static inline void *key_of_node(const SkipList *l, const SkipNode *n)
{
return key_of_data(l, data_of_node(l, n));
}
static inline void validate_skiplist(const SkipList *l, const Char *where)
{
#if SKIPLIST_DEBUG
const SkipNode *prev[SK_MAXHEIGHT];
Int i;
const SkipNode *n, *next;
VG_(printf)("---------------- %s ----------------\n", where);
if (l->head == NULL)
return;
for(i = 0; i <= l->head->level; i++) {
VG_(printf)("l->head->next[%d]=%p\n",
i, l->head->next[i]);
prev[i] = l->head->next[i];
}
for(n = l->head->next[0]; n != NULL; n = next) {
next = n->next[0];
VG_(printf)("n=%p next=%p, n->level=%d k=%s\n",
n, next, n->level, (*l->strkey)(key_of_node(l, n)));
for(i = 0; i <= n->level; i++) {
VG_(printf)(" n->next[%d] = %p\n",
i, n->next[i]);
VG_(printf)(" prev[%d] = %p\n",
i, prev[i]);
}
vg_assert(l->head->level >= n->level);
for(i = 0; i <= n->level; i++)
vg_assert(prev[i] == n);
for(i = 0; i <= n->level; i++)
prev[i] = n->next[i];
vg_assert(next == NULL || (l->cmp)(key_of_node(l, n), key_of_node(l, next)) < 0);
}
#endif /* SKIPLIST_DEBUG */
}
void *VG_(SkipNode_Alloc)(const SkipList *l)
{
UInt size;
Int h;
SkipNode *n;
Char *ret;
h = get_height();
size = l->size;
size += sizeof(SkipNode) + (h+1)*sizeof(SkipNode *);
if (l->arena == -1)
*(Short *)&l->arena = VG_AR_TOOL;
ret = VG_(arena_malloc)(l->arena, size);
if (ret == NULL)
return NULL;
n = (SkipNode *)(ret + l->size);
n->level = h;
n->magic = SKIPLIST_MAGIC;
VG_(memset)(n->next, 0, sizeof(n->next[0]) * (h+1));
return ret;
}
void VG_(SkipNode_Free)(const SkipList *l, void *p)
{
if (SKIPLIST_DEBUG) {
SkipNode *n = node_of_data(l, p);
VG_(printf)("SkipNode_Free: freeing %p (node %p)\n",
p, n);
n->magic = 0x55ffaabb;
}
VG_(arena_free)(l->arena, p);
}
void *VG_(SkipNode_First)(const SkipList *l)
{
SkipNode *n = l->head ? l->head->next[0] : NULL;
if (n == NULL)
return NULL;
else
return data_of_node(l, n);
}
void *VG_(SkipNode_Next)(const SkipList *l, void *data)
{
SkipNode *n = node_of_data(l, data);
n = n->next[0];
if (n == NULL)
return NULL;
return data_of_node(l, n);
}
static Int cmp(const SkipList *l, SkipNode *n, void *k2)
{
void *k1 = key_of_node(l, n);
if (k1 == k2)
return 0;
if (l->head == n)
return -1;
return (l->cmp)(k1, k2);
}
/* Search the list for k; it either returns the k if it exists, or the
one before if not. */
static SkipNode *SkipList__Find(const SkipList *l, void *k, SkipNode **prevs)
{
SkipNode *n;
Int lvl;
if (SKIPLIST_DEBUG)
VG_(printf)("SkipList__Find: finding %s\n", (*l->strkey)(k));
validate_skiplist(l, "SkipList__Find");
if (l->head == NULL)
return NULL;
for(lvl = l->head->level, n = l->head; lvl >= 0; lvl--) {
while(n->next[lvl] != NULL && cmp(l, n->next[lvl], k) < 0) {
if (SKIPLIST_DEBUG)
VG_(printf)("SkipList__Find: n=%p n->next[%d]=%p\n",
n, lvl, n->next[lvl]);
n = n->next[lvl];
}
if (prevs)
prevs[lvl] = n;
}
/* XXX Is there a cleaner way of getting this?
If we get an exact match, return it.
If we get the head, return NULL.
Otherwise return the one before where the hit would be.
*/
if (n->next[0] != NULL && cmp(l, n->next[0], k) == 0)
n = n->next[0];
if (n == l->head)
n = NULL;
if (SKIPLIST_DEBUG) {
VG_(printf)("SkipList__Find returning node %p\n", n);
if (n == NULL) {
SkipNode *nn;
for(nn = l->head->next[0]; nn != NULL; nn = nn->next[0])
vg_assert(cmp(l, nn, k) != 0);
} else
vg_assert(cmp(l, n, k) <= 0);
}
return n;
}
/* Return list element which is <= k, or NULL if there is none. */
void *VG_(SkipList_Find_Before)(const SkipList *l, void *k)
{
SkipNode *n = SkipList__Find(l, k, NULL);
if (n != NULL)
return data_of_node(l, n);
return NULL;
}
/* Return the list element which == k, or NULL if none */
void *VG_(SkipList_Find_Exact)(const SkipList *l, void *k)
{
SkipNode *n = SkipList__Find(l, k, NULL);
if (n != NULL && (l->cmp)(key_of_node(l, n), k) == 0)
return data_of_node(l, n);
return NULL;
}
/* Return the list element which is >= k, or NULL if none */
void *VG_(SkipList_Find_After)(const SkipList *l, void *k)
{
SkipNode *n = SkipList__Find(l, k, NULL);
if (n != NULL && (l->cmp)(key_of_node(l, n), k) < 0)
n = n->next[0];
if (n != NULL)
return data_of_node(l, n);
return NULL;
}
void VG_(SkipList_Insert)(SkipList *l, void *data)
{
SkipNode *update[SK_MAXHEIGHT];
SkipNode *n, *nn;
void *k = key_of_data(l, data);
Int i;
if (SKIPLIST_DEBUG)
VG_(printf)("inserting node %p, key %s, height %d\n",
data, (*l->strkey)(key_of_data(l, data)), node_of_data(l, data)->level);
validate_skiplist(l, "SkipList_Insert before");
if (l->head == NULL) {
Int size = sizeof(SkipNode) + sizeof(SkipNode *) * SK_MAXHEIGHT;
if (l->arena == -1)
*(Short *)&l->arena = VG_AR_TOOL;
l->head = VG_(arena_malloc)(l->arena, size);
VG_(memset)(l->head, 0, size);
l->head->magic = SKIPLIST_HEAD_MAGIC;
l->head->level = 0;
}
n = node_of_data(l, data);
/* update size of head's next vector to fit this new node */
vg_assert(l->head != NULL);
if (l->head->level < n->level) {
for(i = l->head->level+1; i <= n->level; i++)
l->head->next[i] = NULL;
l->head->level = n->level;
}
/* Look for the node, but we're mostly interested in setting
"update", which is the set of previous nodes with next pointers
we need to fix up. */
nn = SkipList__Find(l, k, update);
/* check the new entry is unique */
vg_assert(nn == NULL || (l->cmp)(key_of_node(l, nn), k) != 0);
/* update the previous node's next pointers */
for(i = 0; i <= n->level; i++) {
n->next[i] = update[i]->next[i];
update[i]->next[i] = n;
}
validate_skiplist(l, "SkipList_Insert after");
}
void *VG_(SkipList_Remove)(SkipList *l, void *k)
{
SkipNode *update[SK_MAXHEIGHT];
SkipNode *n;
Int i;
validate_skiplist(l, "SkipList_Remove before");
n = SkipList__Find(l, k, update);
if (n == NULL)
return NULL;
vg_assert((l->cmp)(k, key_of_node(l, n)) == 0);
for(i = 0; i <= n->level; i++) {
update[i]->next[i] = n->next[i];
n->next[i] = NULL;
}
validate_skiplist(l, "SkipList_Remove after");
return data_of_node(l, n);
}
/* --------------------------------------------------
Comparison functions
-------------------------------------------------- */
Int VG_(cmp_Int)(const void *v1, const void *v2)
{
Int a = *(const Int *)v1;
Int b = *(const Int *)v2;
if (a < b)
return -1;
if (a == b)
return 0;
return 1;
}
Int VG_(cmp_UInt)(const void *v1, const void *v2)
{
UInt a = *(const UInt *)v1;
UInt b = *(const UInt *)v2;
if (a < b)
return -1;
if (a == b)
return 0;
return 1;
}
Int VG_(cmp_Addr)(const void *v1, const void *v2)
{
Addr a = *(const Addr *)v1;
Addr b = *(const Addr *)v2;
if (a < b)
return -1;
if (a == b)
return 0;
return 1;
}
Int VG_(cmp_string)(const void *v1, const void *v2)
{
const Char *a = *(const Char **)v1;
const Char *b = *(const Char **)v2;
return VG_(strcmp)(a, b);
}
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/