universe@549: /* universe@549: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@549: * universe@549: * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. universe@549: * universe@549: * Redistribution and use in source and binary forms, with or without universe@549: * modification, are permitted provided that the following conditions are met: universe@549: * universe@549: * 1. Redistributions of source code must retain the above copyright universe@549: * notice, this list of conditions and the following disclaimer. universe@549: * universe@549: * 2. Redistributions in binary form must reproduce the above copyright universe@549: * notice, this list of conditions and the following disclaimer in the universe@549: * documentation and/or other materials provided with the distribution. universe@549: * universe@549: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@549: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@549: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE universe@549: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE universe@549: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR universe@549: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF universe@549: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS universe@549: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN universe@549: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) universe@549: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE universe@549: * POSSIBILITY OF SUCH DAMAGE. universe@549: */ universe@549: universe@550: #include universe@549: #include universe@549: #include "cx/hash_map.h" universe@550: #include "cx/utils.h" universe@549: universe@549: /** universe@549: * Computes a murmur hash-2. universe@549: * universe@549: * @param data the data to hash universe@549: * @param len the length of the data universe@549: * @return the murmur hash-2 of the data universe@549: */ universe@549: static unsigned cx_hash_map_murmur( universe@549: unsigned char const *data, universe@549: size_t len universe@549: ) { universe@549: unsigned m = 0x5bd1e995; universe@549: unsigned r = 24; universe@549: unsigned h = 25 ^ len; universe@549: unsigned i = 0; universe@549: while (len >= 4) { universe@549: unsigned k = data[i + 0] & 0xFF; universe@549: k |= (data[i + 1] & 0xFF) << 8; universe@549: k |= (data[i + 2] & 0xFF) << 16; universe@549: k |= (data[i + 3] & 0xFF) << 24; universe@549: universe@549: k *= m; universe@549: k ^= k >> r; universe@549: k *= m; universe@549: universe@549: h *= m; universe@549: h ^= k; universe@549: universe@549: i += 4; universe@549: len -= 4; universe@549: } universe@549: universe@549: switch (len) { universe@549: case 3: universe@549: h ^= (data[i + 2] & 0xFF) << 16; universe@549: __attribute__((__fallthrough__)); universe@549: case 2: universe@549: h ^= (data[i + 1] & 0xFF) << 8; universe@549: __attribute__((__fallthrough__)); universe@549: case 1: universe@549: h ^= (data[i + 0] & 0xFF); universe@549: h *= m; universe@549: __attribute__((__fallthrough__)); universe@549: default: universe@549: /* do nothing */; universe@549: } universe@549: universe@549: h ^= h >> 13; universe@549: h *= m; universe@549: h ^= h >> 15; universe@549: universe@549: return h; universe@549: } universe@549: universe@550: static void cx_hash_map_clear(struct cx_map_s *map) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: cx_for_n(i, hash_map->bucket_count) { universe@550: struct cx_hash_map_element_s *elem = hash_map->buckets[i]; universe@550: if (elem != NULL) { universe@550: do { universe@550: struct cx_hash_map_element_s *next = elem->next; universe@550: // free the key data universe@550: cxFree(map->allocator, elem->key.data); universe@550: // free the node universe@550: cxFree(map->allocator, elem); universe@550: // proceed universe@550: elem = next; universe@550: } while (elem != NULL); universe@550: universe@550: // do not leave a dangling pointer universe@550: hash_map->buckets[i] = NULL; universe@550: } universe@550: } universe@550: map->size = 0; universe@550: } universe@550: universe@550: static void cx_hash_map_destructor(struct cx_map_s *map) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: universe@550: // free the buckets universe@550: cx_hash_map_clear(map); universe@550: cxFree(map->allocator, hash_map->buckets); universe@550: universe@550: // free the map structure universe@550: cxFree(map->allocator, map); universe@550: } universe@550: universe@550: static int cx_hash_map_put( universe@550: CxMap *map, universe@550: CxDataPtr key, universe@550: void *value universe@550: ) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: CxAllocator *allocator = map->allocator; universe@550: universe@550: unsigned hash = cx_hash_map_murmur(key.data, key.len); universe@550: universe@550: size_t slot = hash % hash_map->bucket_count; universe@550: struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; universe@550: struct cx_hash_map_element_s *prev = NULL; universe@550: universe@550: while (elm != NULL && elm->key.hash < hash) { universe@550: prev = elm; universe@550: elm = elm->next; universe@550: } universe@550: universe@550: if (elm == NULL || elm->key.hash != hash) { universe@550: struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s)); universe@550: if (e == NULL) { universe@550: return -1; universe@550: } universe@550: universe@550: // write the value universe@550: // TODO: depending on future map features, we may want to copy here universe@550: e->data = value; universe@550: universe@550: // copy the key universe@550: void *kd = cxMalloc(allocator, key.len); universe@550: if (kd == NULL) { universe@550: return -1; universe@550: } universe@550: memcpy(kd, key.data, key.len); universe@550: e->key.data = kd; universe@550: e->key.len = key.len; universe@550: e->key.hash = hash; universe@550: universe@550: // insert the element into the linked list universe@550: if (prev == NULL) { universe@550: hash_map->buckets[slot] = e; universe@550: } else { universe@550: prev->next = e; universe@550: } universe@550: e->next = elm; universe@550: universe@550: // increase the size universe@550: map->size++; universe@550: } else { universe@550: // (elem != NULL && elem->key.hash == hash) - overwrite value of existing element universe@550: elm->data = value; universe@550: } universe@550: universe@550: return 0; universe@550: } universe@549: universe@551: static void cx_hash_map_unlink( universe@551: struct cx_hash_map_s *hash_map, universe@551: size_t slot, universe@551: struct cx_hash_map_element_s *prev, universe@551: struct cx_hash_map_element_s *elm universe@551: ) { universe@551: // unlink universe@551: if (prev == NULL) { universe@551: hash_map->buckets[slot] = elm->next; universe@551: } else { universe@551: prev->next = elm->next; universe@551: } universe@551: // free element universe@551: cxFree(hash_map->base.allocator, elm->key.data); universe@551: cxFree(hash_map->base.allocator, elm); universe@551: // decrease size universe@551: hash_map->base.size--; universe@551: } universe@551: universe@549: /** universe@550: * Helper function to avoid code duplication. universe@549: * universe@550: * @param map the map universe@550: * @param key the key to look up universe@550: * @param remove flag indicating whether the looked up entry shall be removed universe@550: * @return the value corresponding to the key or \c NULL universe@549: */ universe@550: static void *cx_hash_map_get_remove( universe@550: CxMap *map, universe@550: CxDataPtr key, universe@550: bool remove universe@550: ) { universe@550: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@550: universe@550: unsigned hash = cx_hash_map_murmur(key.data, key.len); universe@550: universe@550: size_t slot = hash % hash_map->bucket_count; universe@550: struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; universe@550: struct cx_hash_map_element_s *prev = NULL; universe@550: while (elm && elm->key.hash <= hash) { universe@550: if (elm->key.hash == hash && elm->key.len == key.len) { universe@550: if (memcmp(elm->key.data, key.data, key.len) == 0) { universe@550: void *data = elm->data; universe@550: if (remove) { universe@551: cx_hash_map_unlink(hash_map, slot, prev, elm); universe@550: } universe@550: return data; universe@550: } universe@550: } universe@550: prev = elm; universe@550: elm = prev->next; universe@550: } universe@550: universe@550: return NULL; universe@549: } universe@550: universe@550: static void *cx_hash_map_get( universe@550: CxMap const *map, universe@550: CxDataPtr key universe@550: ) { universe@550: // we can safely cast, because we know when remove=false, the map stays untouched universe@550: return cx_hash_map_get_remove((CxMap *) map, key, false); universe@550: } universe@550: universe@550: static void *cx_hash_map_remove( universe@550: CxMap *map, universe@550: CxDataPtr key universe@550: ) { universe@550: return cx_hash_map_get_remove(map, key, true); universe@550: } universe@550: universe@551: static void *cx_hash_map_iter_current_entry(CxIterator const *iter) { universe@551: // struct has to have a compatible signature universe@551: struct cx_map_entry_s *entry = (struct cx_map_entry_s *) &(iter->kv_data); universe@551: return entry; universe@551: } universe@551: universe@551: static void *cx_hash_map_iter_current_key(CxIterator const *iter) { universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: return &elm->key; universe@551: } universe@551: universe@551: static void *cx_hash_map_iter_current_value(CxIterator const *iter) { universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: // TODO: return a pointer to data if this map is storing copies universe@551: return elm->data; universe@551: } universe@551: universe@551: static bool cx_hash_map_iter_valid(CxIterator const *iter) { universe@551: return iter->elem_handle != NULL; universe@551: } universe@551: universe@551: static void cx_hash_map_iter_next(CxIterator *iter) { universe@551: struct cx_hash_map_s *map = iter->src_handle; universe@551: struct cx_hash_map_element_s *elm = iter->elem_handle; universe@551: universe@551: // remove current element, if asked universe@551: if (iter->remove) { universe@551: // clear the flag universe@551: iter->remove = false; universe@551: universe@551: // determine the next element universe@551: struct cx_hash_map_element_s *next = elm->next; universe@551: universe@551: // search the previous element universe@551: struct cx_hash_map_element_s *prev = NULL; universe@551: if (map->buckets[iter->slot] != elm) { universe@551: prev = map->buckets[iter->slot]; universe@551: while (prev->next != elm) { universe@551: prev = prev->next; universe@551: } universe@551: } universe@551: universe@551: // unlink universe@551: cx_hash_map_unlink(map, iter->slot, prev, elm); universe@551: universe@551: // advance universe@551: elm = next; universe@551: } else { universe@551: // just advance universe@551: elm = elm->next; universe@551: } universe@551: universe@551: // do we leave the bucket? universe@551: if (elm == NULL) { universe@551: // search the next bucket universe@551: for (; elm == NULL && iter->slot < map->bucket_count; iter->slot++) { universe@551: elm = map->buckets[iter->slot]; universe@551: } universe@551: } universe@551: universe@551: // advance the index in any case universe@551: iter->index++; universe@551: universe@551: // fill the struct with the current element universe@551: iter->elem_handle = elm; universe@551: if (elm == NULL) { universe@551: iter->kv_data.key = NULL; universe@551: iter->kv_data.value = NULL; universe@551: } else { universe@551: iter->kv_data.key = &elm->key; universe@551: // TODO: pointer to data if this map is storing copies universe@551: iter->kv_data.value = elm->data; universe@551: } universe@551: } universe@551: universe@551: static CxIterator cx_hash_map_iterator(CxMap *map) { universe@550: CxIterator iter; universe@551: universe@551: iter.src_handle = map; universe@551: iter.valid = cx_hash_map_iter_valid; universe@551: iter.next = cx_hash_map_iter_next; universe@551: iter.current = cx_hash_map_iter_current_entry; universe@551: universe@551: iter.slot = 0; universe@551: iter.index = 0; universe@551: iter.remove = false; universe@551: universe@551: if (map->size > 0) { universe@551: struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; universe@551: struct cx_hash_map_element_s *elem = NULL; universe@551: for (; elem == NULL; iter.slot++) { universe@551: elem = hash_map->buckets[iter.slot]; universe@551: } universe@551: iter.elem_handle = elem; universe@551: iter.kv_data.key = NULL; universe@551: iter.kv_data.value = NULL; universe@551: } else { universe@551: iter.elem_handle = NULL; universe@551: iter.kv_data.key = NULL; universe@551: iter.kv_data.value = NULL; universe@551: } universe@551: universe@550: return iter; universe@550: } universe@550: universe@551: static CxIterator cx_hash_map_iterator_keys(CxMap *map) { universe@551: CxIterator iter = cx_hash_map_iterator(map); universe@551: iter.current = cx_hash_map_iter_current_key; universe@550: return iter; universe@550: } universe@550: universe@551: static CxIterator cx_hash_map_iterator_values(CxMap *map) { universe@551: CxIterator iter = cx_hash_map_iterator(map); universe@551: iter.current = cx_hash_map_iter_current_value; universe@550: return iter; universe@550: } universe@550: universe@550: static cx_map_class cx_hash_map_class = { universe@550: cx_hash_map_destructor, universe@550: cx_hash_map_clear, universe@550: cx_hash_map_put, universe@550: cx_hash_map_get, universe@550: cx_hash_map_remove, universe@550: cx_hash_map_iterator, universe@550: cx_hash_map_iterator_keys, universe@550: cx_hash_map_iterator_values, universe@550: }; universe@550: universe@550: CxMap *cxHashMapCreate( universe@550: CxAllocator *allocator, universe@550: size_t buckets universe@550: ) { universe@550: if (buckets == 0) { universe@550: // implementation defined default universe@550: buckets = 16; universe@550: } universe@550: universe@550: struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s)); universe@550: if (map == NULL) return NULL; universe@550: universe@550: // initialize hash map members universe@550: map->bucket_count = buckets; universe@550: map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *)); universe@550: if (map->buckets == NULL) { universe@550: cxFree(allocator, map); universe@550: return NULL; universe@550: } universe@550: universe@550: // initialize base members universe@550: map->base.cl = &cx_hash_map_class; universe@550: map->base.allocator = allocator; universe@550: map->base.size = 0; universe@550: universe@550: return (CxMap *) map; universe@550: }