| /* Copyright (C) 2012-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC 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 3, or (at your option) |
| any later version. |
| |
| GCC 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. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #ifndef _VTV_MAP_H |
| #define _VTV_MAP_H 1 |
| |
| #include <string.h> |
| |
| #ifdef __MINGW32__ |
| #include <stdint.h> |
| #include "vtv_utils.h" |
| #else |
| #include <vtv_utils.h> |
| #endif |
| |
| inline uint64_t |
| load8bytes (const void *p) |
| { |
| uint64_t result; |
| memcpy (&result, p, 8); |
| return result; |
| } |
| |
| /* Insert_only_hash_map maps keys to values. The implementation is a |
| basic hash table with open addressing. The keys are not "owned" by |
| the table; it only stores pointers to keys. The key type is |
| specified below (see insert_only_hash_map::key_type) and is, |
| roughly speaking, a string of any length with the string length and |
| a hash code stored at the front. The code here does not compute |
| any hash codes, but rather uses what's given. */ |
| |
| template<typename T, typename Alloc> |
| class insert_only_hash_map |
| { |
| public: |
| typedef size_t size_type; |
| typedef T value_type; |
| typedef Alloc alloc_type; |
| enum { min_capacity = 4 }; |
| #if HASHMAP_STATS |
| enum { stats = true }; |
| #else |
| enum { stats = false }; |
| #endif |
| |
| /* Keys are a byte string (up to 2^32 - 1 long) plus a uint32_t |
| that's used as a hash code. The latter can encode arbitrary |
| information at the client's discretion, so, e.g., multiple keys |
| that are the same string still "differ" if the hash codes differ. |
| Keys are equal if the first 8 bytes are equal and the next n |
| bytes are equal. */ |
| struct key_type |
| { |
| uint32_t n; |
| uint32_t hash; |
| char bytes[0]; |
| |
| bool |
| equals (const key_type *k) const; |
| }; |
| |
| /* Create an empty map with a reasonable number of buckets for the |
| expected size. Returns NULL if the allocator fails. */ |
| |
| static insert_only_hash_map * |
| create (size_type expected_size); |
| |
| /* The opposite of create(). Free the memory for the given map. */ |
| |
| static void |
| destroy (insert_only_hash_map *m) |
| { Alloc().dealloc (m, m->size_in_bytes_); } |
| |
| /* Return a map identical to this except that *k is mapped to v. |
| Typcially it's done by modifying this in place, but if a resize |
| is necessary then this is deallocated and a new map is returned. |
| Requires k to be non-NULL. Does nothing and returns NULL if the |
| allocator fails. */ |
| |
| insert_only_hash_map* |
| put (const key_type *k, const value_type &v) |
| { return this->put_internal (k, v, false); } |
| |
| /* If *k is a key in this then set *v to point to the corresponding |
| value. Otherwise, do the equivalent of insert(k, value_type()) |
| and, if that succeeds, set *v to point to the inserted value. |
| Requires k to be non-NULL. Does nothing and returns NULL if the |
| allocator fails. Typically returns this, but will return a new |
| insert_only_hash_map if a resize occurs. If the return value is |
| non-NULL, *v is set and it's valid until a resize of the map that |
| is the return value. */ |
| |
| insert_only_hash_map * |
| find_or_add_key (const key_type *k, value_type **v); |
| |
| /* Get the value corresponding to *k. Returns NULL if there is |
| none. Requires k to be non-NULL. The return value is valid |
| until any resize. */ |
| const value_type *get (const key_type *k) const; |
| |
| size_type |
| size () const |
| { return num_entries_; } |
| |
| bool |
| empty () const |
| { return this->size () == 0; } |
| |
| size_type |
| bucket_count () const |
| { return num_buckets_; } |
| |
| private: |
| typedef std::pair <const key_type *, value_type> bucket_type; |
| |
| insert_only_hash_map *put_internal (const key_type *, const value_type &, |
| bool); |
| |
| /* This function determines when to resize the table. */ |
| bool |
| is_too_full (size_type entries) const |
| { return entries > (this->bucket_count () * 0.7); } |
| |
| /* Return a copy with double the number of buckets. Returns NULL if |
| the allocator fails. Otherwise, calls destroy (this). */ |
| insert_only_hash_map *destructive_copy (); |
| |
| /* Must be a power of 2 not less than min_capacity. */ |
| size_type num_buckets_; |
| size_type num_entries_; |
| size_type size_in_bytes_; |
| bucket_type buckets[0]; /* Actual array size is num_buckets. */ |
| }; |
| |
| template <typename T, typename Alloc> |
| insert_only_hash_map <T, Alloc> * |
| insert_only_hash_map <T, Alloc>::create (size_type expected_size) |
| { |
| size_t cap = min_capacity; |
| while (expected_size >= cap) |
| { |
| cap *= 2; |
| } |
| size_t size_in_bytes = sizeof (insert_only_hash_map <T, Alloc>) |
| + cap * sizeof (bucket_type); |
| insert_only_hash_map <T, Alloc>* result = |
| static_cast <insert_only_hash_map <T, Alloc>*> (Alloc () |
| .alloc (size_in_bytes)); |
| if (result != NULL) |
| { |
| result->size_in_bytes_ = size_in_bytes; |
| result->num_buckets_ = cap; |
| result->num_entries_ = 0; |
| memset (result->buckets, 0, cap * sizeof (bucket_type)); |
| } |
| return result; |
| } |
| |
| template <typename T, typename Alloc> |
| insert_only_hash_map <T, Alloc>* |
| insert_only_hash_map <T, Alloc>::destructive_copy () |
| { |
| insert_only_hash_map* copy = create (this->bucket_count ()); |
| if (copy == NULL) |
| return NULL; |
| VTV_DEBUG_ASSERT (copy->bucket_count () == 2 * this->bucket_count ()); |
| for (size_type i = 0; i < this->bucket_count (); i++) |
| if (this->buckets[i].first != NULL) |
| copy->put_internal (this->buckets[i].first, this->buckets[i].second, |
| true); |
| VTV_DEBUG_ASSERT (copy->size () == this->size ()); |
| destroy (this); |
| return copy; |
| } |
| |
| template <typename T, typename Alloc> |
| insert_only_hash_map <T, Alloc>* |
| insert_only_hash_map <T, Alloc>::find_or_add_key (const key_type *k, |
| value_type **v) |
| { |
| /* Table size is always a power of 2. */ |
| const size_type mask = this->bucket_count () - 1; |
| size_type bucket_index = k->hash & mask; |
| size_type step = 1; |
| for (;;) |
| { |
| bucket_type &bucket = this->buckets[bucket_index]; |
| if (bucket.first == NULL) |
| { |
| /* Key was not present. */ |
| if (this->is_too_full (this->size () + 1)) |
| { |
| insert_only_hash_map <T, Alloc>* result = |
| this->destructive_copy (); |
| return result == NULL |
| ? NULL |
| : result->find_or_add_key (k, v); |
| } |
| else |
| { |
| bucket.first = k; |
| bucket.second = T (); |
| this->num_entries_++; |
| *v = &bucket.second; |
| return this; |
| } |
| } |
| else if (bucket.first->equals (k)) |
| { |
| /* Key was present. */ |
| *v = &bucket.second; |
| return this; |
| } |
| else |
| bucket_index = (bucket_index + step++) & mask; |
| } |
| } |
| |
| template <typename T, typename Alloc> |
| insert_only_hash_map <T, Alloc>* |
| insert_only_hash_map <T, Alloc>::put_internal ( |
| const insert_only_hash_map::key_type *k, |
| const insert_only_hash_map::value_type &v, |
| bool unique_key_and_resize_not_needed) |
| { |
| /* Table size is always a power of 2. */ |
| const size_type mask = this->bucket_count () - 1; |
| size_type bucket_index = k->hash & mask; |
| size_type step = 1; |
| for (;;) |
| { |
| bucket_type &bucket = this->buckets[bucket_index]; |
| if (bucket.first == NULL) |
| { |
| /* Key was not present. */ |
| if (!unique_key_and_resize_not_needed |
| && this->is_too_full (this->size () + 1)) |
| { |
| insert_only_hash_map <T, Alloc>* result = |
| this->destructive_copy (); |
| return result == NULL |
| ? NULL |
| : result->put_internal (k, v, true); |
| } |
| else |
| { |
| bucket.first = k; |
| bucket.second = v; |
| this->num_entries_++; |
| return this; |
| } |
| } |
| else if (!unique_key_and_resize_not_needed && bucket.first->equals (k)) |
| { |
| /* Key was present. Just change the value. */ |
| bucket.second = v; |
| return this; |
| } |
| else |
| bucket_index = (bucket_index + step++) & mask; |
| } |
| } |
| |
| template <typename T, typename Alloc> |
| inline const typename insert_only_hash_map <T, Alloc>::value_type* |
| insert_only_hash_map <T, Alloc>::get (const insert_only_hash_map::key_type *k) |
| const |
| { |
| /* Table size is always a power of 2. */ |
| const size_type mask = this->bucket_count () - 1; |
| size_type bucket_index = k->hash & mask; |
| size_type step = 1; |
| for (;;) |
| { |
| const bucket_type &bucket = this->buckets[bucket_index]; |
| if (bucket.first == NULL) |
| return NULL; |
| else if (bucket.first->equals (k)) |
| return &bucket.second; |
| else |
| bucket_index = (bucket_index + step++) & mask; |
| } |
| } |
| |
| template <typename T, typename Alloc> |
| inline bool |
| insert_only_hash_map <T, Alloc>::key_type::equals ( |
| const typename insert_only_hash_map <T, Alloc>::key_type *k) const |
| { |
| const char* x = reinterpret_cast <const char *> (k); |
| const char* y = reinterpret_cast <const char *> (this); |
| return (load8bytes (x) == load8bytes (y) |
| && memcmp (x + 8, y + 8, this->n) == 0); |
| } |
| |
| #endif /* _VTV_MAP_H */ |