| /* |
| * Copyright (c) 2012 Google |
| * 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 the copyright holders 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 THE COPYRIGHT |
| * OWNER 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. |
| */ |
| |
| #ifndef __BASE_TRIE_HH__ |
| #define __BASE_TRIE_HH__ |
| |
| #include <cassert> |
| #include <iostream> |
| #include <type_traits> |
| |
| #include "base/cprintf.hh" |
| #include "base/logging.hh" |
| #include "base/types.hh" |
| |
| namespace gem5 |
| { |
| |
| /** |
| * A trie is a tree-based data structure used for data retrieval. It uses |
| * bits masked from the msb of the key to to determine a value's location, |
| * so its lookups have their worst case time dictated by the key's size. |
| * |
| * @tparam Key Type of the key of the tree nodes. Must be an integral type. |
| * @tparam Value Type of the values associated to the keys. |
| * |
| * @ingroup api_base_utils |
| */ |
| template <class Key, class Value> |
| class Trie |
| { |
| protected: |
| static_assert(std::is_integral<Key>::value, |
| "Key has to be an integral type"); |
| |
| struct Node |
| { |
| Key key; |
| Key mask; |
| |
| bool |
| matches(Key test) |
| { |
| return (test & mask) == key; |
| } |
| |
| Value *value; |
| |
| Node *parent; |
| Node *kids[2]; |
| |
| Node(Key _key, Key _mask, Value *_val) : |
| key(_key & _mask), mask(_mask), value(_val), |
| parent(NULL) |
| { |
| kids[0] = NULL; |
| kids[1] = NULL; |
| } |
| |
| void |
| clear() |
| { |
| if (kids[1]) { |
| kids[1]->clear(); |
| delete kids[1]; |
| kids[1] = NULL; |
| } |
| if (kids[0]) { |
| kids[0]->clear(); |
| delete kids[0]; |
| kids[0] = NULL; |
| } |
| } |
| |
| void |
| dump(std::ostream &os, int level) |
| { |
| for (int i = 1; i < level; i++) { |
| ccprintf(os, "|"); |
| } |
| if (level == 0) |
| ccprintf(os, "Root "); |
| else |
| ccprintf(os, "+ "); |
| ccprintf(os, "(%p, %p, %#X, %#X, %p)\n", |
| parent, this, key, mask, value); |
| if (kids[0]) |
| kids[0]->dump(os, level + 1); |
| if (kids[1]) |
| kids[1]->dump(os, level + 1); |
| } |
| }; |
| |
| protected: |
| Node head; |
| |
| public: |
| /** |
| * @ingroup api_base_utils |
| */ |
| typedef Node *Handle; |
| |
| /** |
| * @ingroup api_base_utils |
| */ |
| Trie() : head(0, 0, NULL) |
| {} |
| |
| /** |
| * @ingroup api_base_utils |
| */ |
| static const unsigned MaxBits = sizeof(Key) * 8; |
| |
| private: |
| /** |
| * A utility method which checks whether the key being looked up lies |
| * beyond the Node being examined. If so, it returns true and advances the |
| * node being examined. |
| * @param parent The node we're currently "at", which can be updated. |
| * @param kid The node we may want to move to. |
| * @param key The key we're looking for. |
| * @param new_mask The mask to use when matching against the key. |
| * @return Whether the current Node was advanced. |
| */ |
| bool |
| goesAfter(Node **parent, Node *kid, Key key, Key new_mask) |
| { |
| if (kid && kid->matches(key) && (kid->mask & new_mask) == kid->mask) { |
| *parent = kid; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * A utility method which extends a mask value one more bit towards the |
| * lsb. This is almost just a signed right shift, except that the shifted |
| * in bits are technically undefined. This is also slightly complicated by |
| * the zero case. |
| * @param orig The original mask to extend. |
| * @return The extended mask. |
| */ |
| Key |
| extendMask(Key orig) |
| { |
| // Just in case orig was 0. |
| const Key msb = 1ULL << (MaxBits - 1); |
| return orig | (orig >> 1) | msb; |
| } |
| |
| /** |
| * Method which looks up the Handle corresponding to a particular key. This |
| * is useful if you want to delete the Handle corresponding to a key since |
| * the "remove" function takes a Handle as its argument. |
| * @param key The key to look up. |
| * @return The first Handle matching this key, or NULL if none was found. |
| */ |
| Handle |
| lookupHandle(Key key) |
| { |
| Node *node = &head; |
| while (node) { |
| if (node->value) |
| return node; |
| |
| if (node->kids[0] && node->kids[0]->matches(key)) |
| node = node->kids[0]; |
| else if (node->kids[1] && node->kids[1]->matches(key)) |
| node = node->kids[1]; |
| else |
| node = NULL; |
| } |
| |
| return NULL; |
| } |
| |
| public: |
| /** |
| * Method which inserts a key/value pair into the trie. |
| * @param key The key which can later be used to look up this value. |
| * @param width How many bits of the key (from msb) should be used. |
| * @param val A pointer to the value to store in the trie. |
| * @return A Handle corresponding to this value. |
| * |
| * @ingroup api_base_utils |
| */ |
| Handle |
| insert(Key key, unsigned width, Value *val) |
| { |
| // We use NULL value pointers to mark internal nodes of the trie, so |
| // we don't allow inserting them as real values. |
| assert(val); |
| |
| // Build a mask which masks off all the bits we don't care about. |
| Key new_mask = ~(Key)0; |
| if (width < MaxBits) |
| new_mask <<= (MaxBits - width); |
| // Use it to tidy up the key. |
| key &= new_mask; |
| |
| // Walk past all the nodes this new node will be inserted after. They |
| // can be ignored for the purposes of this function. |
| Node *node = &head; |
| while (goesAfter(&node, node->kids[0], key, new_mask) || |
| goesAfter(&node, node->kids[1], key, new_mask)) |
| {} |
| assert(node); |
| |
| Key cur_mask = node->mask; |
| // If we're already where the value needs to be... |
| if (cur_mask == new_mask) { |
| assert(!node->value); |
| node->value = val; |
| return node; |
| } |
| |
| for (unsigned int i = 0; i < 2; i++) { |
| Node *&kid = node->kids[i]; |
| Node *new_node; |
| if (!kid) { |
| // No kid. Add a new one. |
| new_node = new Node(key, new_mask, val); |
| new_node->parent = node; |
| kid = new_node; |
| return new_node; |
| } |
| |
| // Walk down the leg until something doesn't match or we run out |
| // of bits. |
| Key last_mask; |
| bool done; |
| do { |
| last_mask = cur_mask; |
| cur_mask = extendMask(cur_mask); |
| done = ((key & cur_mask) != (kid->key & cur_mask)) || |
| last_mask == new_mask; |
| } while (!done); |
| cur_mask = last_mask; |
| |
| // If this isn't the right leg to go down at all, skip it. |
| if (cur_mask == node->mask) |
| continue; |
| |
| // At the point we walked to above, add a new node. |
| new_node = new Node(key, cur_mask, NULL); |
| new_node->parent = node; |
| kid->parent = new_node; |
| new_node->kids[0] = kid; |
| kid = new_node; |
| |
| // If we ran out of bits, the value goes right here. |
| if (cur_mask == new_mask) { |
| new_node->value = val; |
| return new_node; |
| } |
| |
| // Still more bits to deal with, so add a new node for that path. |
| new_node = new Node(key, new_mask, val); |
| new_node->parent = kid; |
| kid->kids[1] = new_node; |
| return new_node; |
| } |
| |
| panic("Reached the end of the Trie insert function!\n"); |
| return NULL; |
| } |
| |
| /** |
| * Method which looks up the Value corresponding to a particular key. |
| * @param key The key to look up. |
| * @return The first Value matching this key, or NULL if none was found. |
| * |
| * @ingroup api_base_utils |
| */ |
| Value * |
| lookup(Key key) |
| { |
| Node *node = lookupHandle(key); |
| if (node) |
| return node->value; |
| else |
| return NULL; |
| } |
| |
| /** |
| * Method to delete a value from the trie. |
| * @param node A Handle to remove. |
| * @return The Value pointer from the removed entry. |
| * |
| * @ingroup api_base_utils |
| */ |
| Value * |
| remove(Handle handle) |
| { |
| Node *node = handle; |
| Value *val = node->value; |
| if (node->kids[1]) { |
| assert(node->value); |
| node->value = NULL; |
| return val; |
| } |
| if (!node->parent) |
| panic("Trie: Can't remove root node.\n"); |
| |
| Node *parent = node->parent; |
| |
| // If there's a kid, fix up it's parent pointer. |
| if (node->kids[0]) |
| node->kids[0]->parent = parent; |
| // Figure out which kid we are, and update our parent's pointers. |
| if (parent->kids[0] == node) |
| parent->kids[0] = node->kids[0]; |
| else if (parent->kids[1] == node) |
| parent->kids[1] = node->kids[0]; |
| else |
| panic("Trie: Inconsistent parent/kid relationship.\n"); |
| // Make sure if the parent only has one kid, it's kid[0]. |
| if (parent->kids[1] && !parent->kids[0]) { |
| parent->kids[0] = parent->kids[1]; |
| parent->kids[1] = NULL; |
| } |
| |
| // If the parent has less than two kids and no cargo and isn't the |
| // root, delete it too. |
| if (!parent->kids[1] && !parent->value && parent->parent) |
| remove(parent); |
| delete node; |
| return val; |
| } |
| |
| /** |
| * Method to lookup a value from the trie and then delete it. |
| * @param key The key to look up and then remove. |
| * @return The Value pointer from the removed entry, if any. |
| * |
| * @ingroup api_base_utils |
| */ |
| Value * |
| remove(Key key) |
| { |
| Handle handle = lookupHandle(key); |
| if (!handle) |
| return NULL; |
| return remove(handle); |
| } |
| |
| /** |
| * A method which removes all key/value pairs from the trie. This is more |
| * efficient than trying to remove elements individually. |
| * |
| * @ingroup api_base_utils |
| */ |
| void |
| clear() |
| { |
| head.clear(); |
| } |
| |
| /** |
| * A debugging method which prints the contents of this trie. |
| * @param title An identifying title to put in the dump header. |
| */ |
| void |
| dump(const char *title, std::ostream &os=std::cout) |
| { |
| ccprintf(os, "**************************************************\n"); |
| ccprintf(os, "*** Start of Trie: %s\n", title); |
| ccprintf(os, "*** (parent, me, key, mask, value pointer)\n"); |
| ccprintf(os, "**************************************************\n"); |
| head.dump(os, 0); |
| } |
| }; |
| |
| } // namespace gem5 |
| |
| #endif |