/src/nghttp2/lib/nghttp2_map.c
Line | Count | Source |
1 | | /* |
2 | | * nghttp2 - HTTP/2 C Library |
3 | | * |
4 | | * Copyright (c) 2017 ngtcp2 contributors |
5 | | * Copyright (c) 2012 nghttp2 contributors |
6 | | * |
7 | | * Permission is hereby granted, free of charge, to any person obtaining |
8 | | * a copy of this software and associated documentation files (the |
9 | | * "Software"), to deal in the Software without restriction, including |
10 | | * without limitation the rights to use, copy, modify, merge, publish, |
11 | | * distribute, sublicense, and/or sell copies of the Software, and to |
12 | | * permit persons to whom the Software is furnished to do so, subject to |
13 | | * the following conditions: |
14 | | * |
15 | | * The above copyright notice and this permission notice shall be |
16 | | * included in all copies or substantial portions of the Software. |
17 | | * |
18 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
19 | | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
20 | | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
21 | | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
22 | | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
23 | | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
24 | | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
25 | | */ |
26 | | #include "nghttp2_map.h" |
27 | | |
28 | | #include <string.h> |
29 | | #include <assert.h> |
30 | | #include <stdio.h> |
31 | | |
32 | | #include "nghttp2_helper.h" |
33 | | |
34 | 18.9k | #define NGHTTP2_INITIAL_HASHBITS 4 |
35 | | |
36 | 23.4k | void nghttp2_map_init(nghttp2_map *map, uint64_t seed, nghttp2_mem *mem) { |
37 | 23.4k | *map = (nghttp2_map){ |
38 | 23.4k | .mem = mem, |
39 | 23.4k | .seed = seed, |
40 | 23.4k | }; |
41 | 23.4k | } |
42 | | |
43 | 23.4k | void nghttp2_map_free(nghttp2_map *map) { |
44 | 23.4k | if (!map) { |
45 | 0 | return; |
46 | 0 | } |
47 | | |
48 | 23.4k | nghttp2_mem_free(map->mem, map->keys); |
49 | 23.4k | } |
50 | | |
51 | | int nghttp2_map_each(const nghttp2_map *map, int (*func)(void *data, void *ptr), |
52 | 32.8k | void *ptr) { |
53 | 32.8k | int rv; |
54 | 32.8k | size_t i; |
55 | 32.8k | size_t tablelen; |
56 | | |
57 | 32.8k | if (map->size == 0) { |
58 | 8.94k | return 0; |
59 | 8.94k | } |
60 | | |
61 | 23.8k | tablelen = (size_t)1 << map->hashbits; |
62 | | |
63 | 409k | for (i = 0; i < tablelen; ++i) { |
64 | 385k | if (map->psl[i] == 0) { |
65 | 351k | continue; |
66 | 351k | } |
67 | | |
68 | 33.8k | rv = func(map->data[i], ptr); |
69 | 33.8k | if (rv != 0) { |
70 | 5 | return rv; |
71 | 5 | } |
72 | 33.8k | } |
73 | | |
74 | 23.8k | return 0; |
75 | 23.8k | } |
76 | | |
77 | | /* Hasher from |
78 | | https://github.com/rust-lang/rustc-hash/blob/dc5c33f1283de2da64d8d7a06401d91aded03ad4/src/lib.rs |
79 | | to maximize the output's sensitivity to all input bits. */ |
80 | 180k | #define NGHTTP2_MAP_HASHER 0xf1357aea2e62a9c5ull |
81 | | /* 64-bit Fibonacci hashing constant, Golden Ratio constant, to get |
82 | | the high bits with the good distribution. */ |
83 | 180k | #define NGHTTP2_MAP_FIBO 0x9e3779b97f4a7c15ull |
84 | | |
85 | 180k | static size_t map_index(const nghttp2_map *map, nghttp2_map_key_type key32) { |
86 | 180k | uint64_t key = (uint64_t)key32; |
87 | | |
88 | 180k | key += map->seed; |
89 | 180k | key *= NGHTTP2_MAP_HASHER; |
90 | 180k | return (size_t)((key * NGHTTP2_MAP_FIBO) >> (64 - map->hashbits)); |
91 | 180k | } |
92 | | |
93 | | #ifndef WIN32 |
94 | 0 | void nghttp2_map_print_distance(const nghttp2_map *map) { |
95 | 0 | size_t i; |
96 | 0 | size_t idx; |
97 | 0 | size_t tablelen; |
98 | |
|
99 | 0 | if (map->size == 0) { |
100 | 0 | return; |
101 | 0 | } |
102 | | |
103 | 0 | tablelen = (size_t)1 << map->hashbits; |
104 | |
|
105 | 0 | for (i = 0; i < tablelen; ++i) { |
106 | 0 | if (map->psl[i] == 0) { |
107 | 0 | fprintf(stderr, "@%zu <EMPTY>\n", i); |
108 | 0 | continue; |
109 | 0 | } |
110 | | |
111 | 0 | idx = map_index(map, map->keys[i]); |
112 | 0 | fprintf(stderr, "@%zu key=%d base=%zu distance=%u\n", i, map->keys[i], idx, |
113 | 0 | map->psl[i] - 1); |
114 | 0 | } |
115 | 0 | } |
116 | | #endif /* !defined(WIN32) */ |
117 | | |
118 | | static void map_set_entry(nghttp2_map *map, size_t idx, |
119 | 28.7k | nghttp2_map_key_type key, void *data, size_t psl) { |
120 | 28.7k | map->keys[idx] = key; |
121 | 28.7k | map->data[idx] = data; |
122 | 28.7k | map->psl[idx] = (uint8_t)psl; |
123 | 28.7k | } |
124 | | |
125 | | #define NGHTTP2_SWAP(TYPE, A, B) \ |
126 | 8.40k | do { \ |
127 | 8.40k | TYPE t = (TYPE) * (A); \ |
128 | 8.40k | \ |
129 | 8.40k | *(A) = *(B); \ |
130 | 8.40k | *(B) = t; \ |
131 | 8.40k | } while (0) |
132 | | |
133 | | /* |
134 | | * map_insert inserts |key| and |data| to |map|, and returns the index |
135 | | * where the pair is stored if it succeeds. Otherwise, it returns one |
136 | | * of the following negative error codes: |
137 | | * |
138 | | * NGHTTP2_ERR_INVALID_ARGUMENT |
139 | | * The another data associated to |key| is already present. |
140 | | */ |
141 | | static nghttp2_ssize map_insert(nghttp2_map *map, nghttp2_map_key_type key, |
142 | 28.6k | void *data) { |
143 | 28.6k | size_t idx = map_index(map, key); |
144 | 28.6k | size_t mask = ((size_t)1 << map->hashbits) - 1; |
145 | 28.6k | size_t psl = 1; |
146 | 28.6k | size_t kpsl; |
147 | | |
148 | 40.0k | for (;;) { |
149 | 40.0k | kpsl = map->psl[idx]; |
150 | | |
151 | 40.0k | if (kpsl == 0) { |
152 | 28.6k | map_set_entry(map, idx, key, data, psl); |
153 | 28.6k | ++map->size; |
154 | | |
155 | 28.6k | return (nghttp2_ssize)idx; |
156 | 28.6k | } |
157 | | |
158 | 11.3k | if (psl > kpsl) { |
159 | 2.80k | NGHTTP2_SWAP(nghttp2_map_key_type, &key, &map->keys[idx]); |
160 | 2.80k | NGHTTP2_SWAP(void *, &data, &map->data[idx]); |
161 | 2.80k | NGHTTP2_SWAP(uint8_t, &psl, &map->psl[idx]); |
162 | 8.56k | } else if (map->keys[idx] == key) { |
163 | | /* This check ensures that no duplicate keys are inserted. But |
164 | | it is just a waste after first swap or if this function is |
165 | | called from map_resize. That said, there is no difference |
166 | | with or without this conditional in performance wise. */ |
167 | 0 | return NGHTTP2_ERR_INVALID_ARGUMENT; |
168 | 0 | } |
169 | | |
170 | 11.3k | ++psl; |
171 | 11.3k | idx = (idx + 1) & mask; |
172 | 11.3k | } |
173 | 28.6k | } |
174 | | |
175 | | /* NGHTTP2_MAP_MAX_HASHBITS is the maximum number of bits used for |
176 | | hash table. The theoretical limit of the maximum number of keys |
177 | | that can be stored is 1 << NGHTTP2_MAP_MAX_HASHBITS. */ |
178 | 19.1k | #define NGHTTP2_MAP_MAX_HASHBITS (sizeof(size_t) * 8 - 1) |
179 | | |
180 | 19.1k | static int map_resize(nghttp2_map *map, size_t new_hashbits) { |
181 | 19.1k | size_t i; |
182 | 19.1k | size_t tablelen; |
183 | 19.1k | nghttp2_ssize idx; |
184 | 19.1k | nghttp2_map new_map = { |
185 | 19.1k | .mem = map->mem, |
186 | 19.1k | .seed = map->seed, |
187 | 19.1k | .hashbits = new_hashbits, |
188 | 19.1k | }; |
189 | 19.1k | void *buf; |
190 | 19.1k | (void)idx; |
191 | | |
192 | 19.1k | if (new_hashbits > NGHTTP2_MAP_MAX_HASHBITS) { |
193 | 0 | return NGHTTP2_ERR_NOMEM; |
194 | 0 | } |
195 | | |
196 | 19.1k | tablelen = (size_t)1 << new_hashbits; |
197 | | |
198 | 19.1k | buf = nghttp2_mem_calloc(map->mem, tablelen, |
199 | 19.1k | sizeof(nghttp2_map_key_type) + sizeof(void *) + |
200 | 19.1k | sizeof(uint8_t)); |
201 | 19.1k | if (buf == NULL) { |
202 | 0 | return NGHTTP2_ERR_NOMEM; |
203 | 0 | } |
204 | | |
205 | 19.1k | new_map.keys = buf; |
206 | 19.1k | new_map.data = |
207 | 19.1k | (void *)((uint8_t *)new_map.keys + tablelen * sizeof(nghttp2_map_key_type)); |
208 | 19.1k | new_map.psl = (uint8_t *)new_map.data + tablelen * sizeof(void *); |
209 | | |
210 | 19.1k | if (map->size) { |
211 | 164 | tablelen = (size_t)1 << map->hashbits; |
212 | | |
213 | 3.06k | for (i = 0; i < tablelen; ++i) { |
214 | 2.89k | if (map->psl[i] == 0) { |
215 | 526 | continue; |
216 | 526 | } |
217 | | |
218 | 2.37k | idx = map_insert(&new_map, map->keys[i], map->data[i]); |
219 | | |
220 | | /* map_insert must not fail because all keys are unique during |
221 | | resize. */ |
222 | 2.37k | assert(idx >= 0); |
223 | 2.37k | } |
224 | 164 | } |
225 | | |
226 | 19.1k | nghttp2_mem_free(map->mem, map->keys); |
227 | 19.1k | map->keys = new_map.keys; |
228 | 19.1k | map->data = new_map.data; |
229 | 19.1k | map->psl = new_map.psl; |
230 | 19.1k | map->hashbits = new_hashbits; |
231 | | |
232 | 19.1k | return 0; |
233 | 19.1k | } |
234 | | |
235 | | /* NGHTTP2_MAX_PSL_RESIZE_THRESH is the maximum psl threshold. If |
236 | | reached, resize the table. */ |
237 | 7.17k | #define NGHTTP2_MAX_PSL_RESIZE_THRESH 128 |
238 | | |
239 | 26.2k | int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_key_type key, void *data) { |
240 | 26.2k | int rv; |
241 | 26.2k | size_t tablelen; |
242 | 26.2k | nghttp2_ssize idx; |
243 | | |
244 | 26.2k | assert(data); |
245 | | |
246 | | /* tablelen is incorrect if map->hashbits == 0 which leads to |
247 | | tablelen = 1, but it is only used to check the load factor, and |
248 | | it works in this special case. */ |
249 | 26.2k | tablelen = (size_t)1 << map->hashbits; |
250 | | |
251 | | /* Load factor is 7 / 8. Because tablelen is power of 2, (tablelen |
252 | | - (tablelen >> 3)) computes tablelen * 7 / 8. */ |
253 | 26.2k | if (map->size + 1 >= (tablelen - (tablelen >> 3))) { |
254 | 19.1k | rv = map_resize(map, map->hashbits ? map->hashbits + 1 |
255 | 19.1k | : NGHTTP2_INITIAL_HASHBITS); |
256 | 19.1k | if (rv != 0) { |
257 | 0 | return rv; |
258 | 0 | } |
259 | | |
260 | 19.1k | idx = map_insert(map, key, data); |
261 | 19.1k | if (idx < 0) { |
262 | 0 | return (int)idx; |
263 | 0 | } |
264 | | |
265 | 19.1k | return 0; |
266 | 19.1k | } |
267 | | |
268 | 7.17k | idx = map_insert(map, key, data); |
269 | 7.17k | if (idx < 0) { |
270 | 0 | return (int)idx; |
271 | 0 | } |
272 | | |
273 | | /* Resize if psl reaches really large value which is almost |
274 | | improbable, but just in case. */ |
275 | 7.17k | if (map->psl[idx] - 1 < NGHTTP2_MAX_PSL_RESIZE_THRESH) { |
276 | 7.17k | return 0; |
277 | 7.17k | } |
278 | | |
279 | 0 | return map_resize(map, map->hashbits + 1); |
280 | 7.17k | } |
281 | | |
282 | 295k | void *nghttp2_map_find(const nghttp2_map *map, nghttp2_map_key_type key) { |
283 | 295k | size_t idx; |
284 | 295k | size_t psl = 1; |
285 | 295k | size_t mask; |
286 | | |
287 | 295k | if (map->size == 0) { |
288 | 143k | return NULL; |
289 | 143k | } |
290 | | |
291 | 151k | idx = map_index(map, key); |
292 | 151k | mask = ((size_t)1 << map->hashbits) - 1; |
293 | | |
294 | 183k | for (;;) { |
295 | 183k | if (psl > map->psl[idx]) { |
296 | 92.2k | return NULL; |
297 | 92.2k | } |
298 | | |
299 | 91.7k | if (map->keys[idx] == key) { |
300 | 59.4k | return map->data[idx]; |
301 | 59.4k | } |
302 | | |
303 | 32.3k | ++psl; |
304 | 32.3k | idx = (idx + 1) & mask; |
305 | 32.3k | } |
306 | 151k | } |
307 | | |
308 | 226 | int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key) { |
309 | 226 | size_t idx; |
310 | 226 | size_t dest; |
311 | 226 | size_t psl = 1, kpsl; |
312 | 226 | size_t mask; |
313 | | |
314 | 226 | if (map->size == 0) { |
315 | 0 | return NGHTTP2_ERR_INVALID_ARGUMENT; |
316 | 0 | } |
317 | | |
318 | 226 | idx = map_index(map, key); |
319 | 226 | mask = ((size_t)1 << map->hashbits) - 1; |
320 | | |
321 | 372 | for (;;) { |
322 | 372 | if (psl > map->psl[idx]) { |
323 | 0 | return NGHTTP2_ERR_INVALID_ARGUMENT; |
324 | 0 | } |
325 | | |
326 | 372 | if (map->keys[idx] == key) { |
327 | 226 | dest = idx; |
328 | 226 | idx = (idx + 1) & mask; |
329 | | |
330 | 321 | for (;;) { |
331 | 321 | kpsl = map->psl[idx]; |
332 | 321 | if (kpsl <= 1) { |
333 | 226 | map->psl[dest] = 0; |
334 | 226 | break; |
335 | 226 | } |
336 | | |
337 | 95 | map_set_entry(map, dest, map->keys[idx], map->data[idx], kpsl - 1); |
338 | | |
339 | 95 | dest = idx; |
340 | | |
341 | 95 | idx = (idx + 1) & mask; |
342 | 95 | } |
343 | | |
344 | 226 | --map->size; |
345 | | |
346 | 226 | return 0; |
347 | 226 | } |
348 | | |
349 | 146 | ++psl; |
350 | 146 | idx = (idx + 1) & mask; |
351 | 146 | } |
352 | 226 | } |
353 | | |
354 | 0 | void nghttp2_map_clear(nghttp2_map *map) { |
355 | 0 | if (map->size == 0) { |
356 | 0 | return; |
357 | 0 | } |
358 | | |
359 | 0 | memset(map->psl, 0, sizeof(*map->psl) * ((size_t)1 << map->hashbits)); |
360 | 0 | map->size = 0; |
361 | 0 | } |
362 | | |
363 | 90.8k | size_t nghttp2_map_size(const nghttp2_map *map) { return map->size; } |