1 /*
2  * multihash.h
3  * Copyright 2013-2017 John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #ifndef LIBAUDCORE_MULTIHASH_H
21 #define LIBAUDCORE_MULTIHASH_H
22 
23 #include <libaudcore/threads.h>
24 #include <utility>
25 
26 /* HashBase is a low-level hash table implementation.  It is used as a backend
27  * for SimpleHash as well as for a single channel of MultiHash. */
28 
29 class HashBase
30 {
31 public:
32     /* Skeleton structure containing internal members of a hash node (except for
33      * "refs", which is not used internally and included here only to fill an
34      * alignment gap).  Actual node structures should subclass Node. */
35     struct Node
36     {
37         Node * next;
38         unsigned hash;
39         unsigned refs;
40     };
41 
42     /* Represents the location of a node within the table. */
43     struct NodeLoc
44     {
45         Node ** ptr;
46         Node * next;
47     };
48 
49     /* Callback.  Returns true if <node> matches <data>, otherwise false. */
50     typedef bool (*MatchFunc)(const Node * node, const void * data);
51 
52     /* Callback.  Called when a node is found.  Returns true if <node> is to be
53      * removed, otherwise false. */
54     typedef bool (*FoundFunc)(Node * node, void * state);
55 
HashBase()56     constexpr HashBase() : buckets(nullptr), size(0), used(0) {}
57 
clear()58     void clear() // use as destructor
59     {
60         delete[] buckets;
61         *this = HashBase();
62     }
63 
n_items()64     int n_items() const { return used; }
65 
66     /* Adds a node.  Does not check for duplicates. */
67     void add(Node * node, unsigned hash);
68 
69     /* Locates the node matching <data>.  Returns null if no node is found.  If
70      * <loc> is not null, also returns the location of the node, which can be
71      * used to remove it from the table. */
72     Node * lookup(MatchFunc match, const void * data, unsigned hash,
73                   NodeLoc * loc = nullptr) const;
74 
75     /* Removes a node, given a location returned by lookup_full(). */
76     void remove(const NodeLoc & loc);
77 
78     /* Iterates over all nodes in the table, removing them as desired. */
79     void iterate(FoundFunc func, void * state);
80 
81 private:
82     static constexpr unsigned InitialSize = 16;
83 
84     void resize(unsigned new_size);
85 
86     Node ** buckets;
87     unsigned size, used;
88 };
89 
90 /* MultiHash is a generic, thread-safe hash table.  It scales well to multiple
91  * processors by the use of multiple channels, each with a separate lock.  The
92  * hash value of a given node decides what channel it is stored in.  Hence,
93  * different processors will tend to hit different channels, keeping lock
94  * contention to a minimum.  The all-purpose lookup function enables a variety
95  * of atomic operations, such as allocating and adding a node only if not
96  * already present. */
97 
98 class MultiHash
99 {
100 public:
101     static constexpr int Found = 1 << 0;
102     static constexpr int Added = 1 << 1;
103     static constexpr int Removed = 1 << 2;
104 
105     typedef HashBase::Node Node;
106     typedef HashBase::MatchFunc MatchFunc;
107     typedef HashBase::FoundFunc FoundFunc;
108     typedef void (*FinalFunc)(void * state);
109 
110     /* Callback.  May create a new node representing <data> to be added to the
111      * table.  Returns the new node or null. */
112     typedef Node * (*AddFunc)(const void * data, void * state);
113 
MultiHash(MatchFunc match)114     MultiHash(MatchFunc match) : match(match), locks(), channels() {}
115 
116     /* There is no destructor.  In some instances, such as the string pool, it
117      * is never safe to destroy the hash table, since it can be referenced from
118      * the destructors of other static objects.  It is left to the operating
119      * system to reclaim the memory used by the hash table upon process exit. */
120 
121     /* All-purpose lookup function.  The caller passes in the data to be looked
122      * up along with its hash value.  The two callbacks are optional.  <add> is
123      * called if no matching node is found, and may return a new node to add to
124      * the table.  <found> is called if a matching node is found, and may return
125      * true to remove the node from the table.  Returns the status of the lookup
126      * as a bitmask of Found, Added, and Removed. */
127     int lookup(const void * data, unsigned hash, AddFunc add, FoundFunc found,
128                void * state);
129 
130     /* All-purpose iteration function.  All channels of the table are locked
131      * simultaneously during the iteration to freeze the table in a consistent
132      * state.  <func> is called on each node in order, and may return true to
133      * remove the node from the table. */
134     void iterate(FoundFunc func, void * state);
135 
136     /* Variant of iterate() which runs a second callback after the iteration
137      * is complete, while the table is still locked.  This is useful when some
138      * operation needs to be performed with the table in a known state. */
139     void iterate(FoundFunc func, void * state, FinalFunc final, void * fstate);
140 
141 private:
142     static constexpr int Channels = 16; /* must be a power of two */
143     static constexpr int Shift = 24;    /* bit shift for channel selection */
144 
145     const MatchFunc match;
146     aud::spinlock locks[Channels];
147     HashBase channels[Channels];
148 };
149 
150 /* Type-safe version using templates. */
151 
152 template<class Node_T, class Data_T>
153 class MultiHash_T : private MultiHash
154 {
155 public:
156     // Required interfaces:
157     //
158     // class Node_T : public Node
159     // {
160     //     bool match (const Data_T * data) const;
161     // };
162     //
163     // class Operation
164     // {
165     //     Node_T * add (const Data_T * data);
166     //     bool found (Node_T * node);
167     // };
168 
MultiHash_T()169     MultiHash_T() : MultiHash(match_cb) {}
170 
clear()171     void clear() { MultiHash::iterate(remove_cb, nullptr); }
172 
173     template<class Op>
lookup(const Data_T * data,unsigned hash,Op & op)174     int lookup(const Data_T * data, unsigned hash, Op & op)
175     {
176         return MultiHash::lookup(data, hash, WrapOp<Op>::add, WrapOp<Op>::found,
177                                  &op);
178     }
179 
180     template<class F>
iterate(F func)181     void iterate(F func)
182     {
183         MultiHash::iterate(WrapIterate<F>::run, &func);
184     }
185 
186     template<class F, class Final>
iterate(F func,Final final)187     void iterate(F func, Final final)
188     {
189         MultiHash::iterate(WrapIterate<F>::run, &func, WrapFinal<Final>::run,
190                            &final);
191     }
192 
193 private:
match_cb(const Node * node,const void * data)194     static bool match_cb(const Node * node, const void * data)
195     {
196         return (static_cast<const Node_T *>(node))
197             ->match(static_cast<const Data_T *>(data));
198     }
199 
remove_cb(Node * node,void *)200     static bool remove_cb(Node * node, void *)
201     {
202         delete static_cast<Node_T *>(node);
203         return true;
204     }
205 
206     template<class Op>
207     struct WrapOp
208     {
addWrapOp209         static Node * add(const void * data, void * op)
210         {
211             return (static_cast<Op *>(op))
212                 ->add(static_cast<const Data_T *>(data));
213         }
foundWrapOp214         static bool found(Node * node, void * op)
215         {
216             return (static_cast<Op *>(op))->found(static_cast<Node_T *>(node));
217         }
218     };
219 
220     template<class F>
221     struct WrapIterate
222     {
runWrapIterate223         static bool run(Node * node, void * func)
224         {
225             return (*static_cast<F *>(func))(static_cast<Node_T *>(node));
226         }
227     };
228 
229     template<class Final>
230     struct WrapFinal
231     {
runWrapFinal232         static void run(void * func) { (*static_cast<Final *>(func))(); }
233     };
234 };
235 
236 /* Simpler single-thread hash table. */
237 
238 template<class Key, class Value>
239 class SimpleHash : private HashBase
240 {
241 public:
242     typedef void (*IterFunc)(const Key & key, Value & value, void * state);
243 
~SimpleHash()244     ~SimpleHash() { clear(); }
245 
246     using HashBase::n_items;
247 
lookup(const Key & key)248     Value * lookup(const Key & key)
249     {
250         auto node =
251             static_cast<Node *>(HashBase::lookup(match_cb, &key, key.hash()));
252         return node ? &node->value : nullptr;
253     }
254 
add(const Key & key,Value && value)255     Value * add(const Key & key, Value && value)
256     {
257         unsigned hash = key.hash();
258         auto node = static_cast<Node *>(HashBase::lookup(match_cb, &key, hash));
259 
260         if (node)
261             node->value = std::move(value);
262         else
263         {
264             node = new Node(key, std::move(value));
265             HashBase::add(node, hash);
266         }
267 
268         return &node->value;
269     }
270 
remove(const Key & key)271     void remove(const Key & key)
272     {
273         NodeLoc loc;
274         auto node = static_cast<Node *>(
275             HashBase::lookup(match_cb, &key, key.hash(), &loc));
276 
277         if (node)
278         {
279             delete node;
280             HashBase::remove(loc);
281         }
282     }
283 
clear()284     void clear()
285     {
286         HashBase::iterate(remove_cb, nullptr);
287         HashBase::clear();
288     }
289 
290     template<class F>
iterate(F func)291     void iterate(F func)
292     {
293         HashBase::iterate(WrapIterate<F>::run, &func);
294     }
295 
296 private:
297     struct Node : public HashBase::Node
298     {
NodeNode299         Node(const Key & key, Value && value)
300             : key(key), value(std::move(value))
301         {
302         }
303 
304         Key key;
305         Value value;
306     };
307 
308     struct IterData
309     {
310         IterFunc func;
311         void * state;
312     };
313 
match_cb(const HashBase::Node * node,const void * data)314     static bool match_cb(const HashBase::Node * node, const void * data)
315     {
316         return (static_cast<const Node *>(node))->key ==
317                *static_cast<const Key *>(data);
318     }
319 
remove_cb(HashBase::Node * node,void *)320     static bool remove_cb(HashBase::Node * node, void *)
321     {
322         delete static_cast<Node *>(node);
323         return true;
324     }
325 
326     // C-style callback wrapping generic iteration functor
327     template<class F>
328     struct WrapIterate
329     {
runWrapIterate330         static bool run(HashBase::Node * node_, void * func)
331         {
332             auto node = static_cast<Node *>(node_);
333             (*static_cast<F *>(func))(node->key, node->value);
334             return false;
335         }
336     };
337 };
338 
339 #endif /* LIBAUDCORE_MULTIHASH_H */
340