/src/curl/lib/uint-hashset.c
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | #include "curl_setup.h" |
25 | | |
26 | | #include "uint-hashset.h" |
27 | | |
28 | | /* random patterns for API verification */ |
29 | | #ifdef DEBUGBUILD |
30 | 23.3k | #define CURL_U8_STRSET_MAGIC 0x7117e783 |
31 | | #endif |
32 | | |
33 | | #define CURL_U8_STRSET_DEBUG 0 |
34 | | |
35 | 2.49k | #define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) |
36 | | |
37 | | static const uint8_t u8_smask[] = { |
38 | | 0x00U, |
39 | | 0x01U, |
40 | | 0x03U, |
41 | | 0x07U, |
42 | | 0x0FU, |
43 | | 0x1FU, |
44 | | 0x3FU, |
45 | | 0x7FU, |
46 | | 0xFFU, |
47 | | }; |
48 | | |
49 | 146k | #define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) |
50 | 98.9k | #define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) |
51 | 98.1k | #define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) |
52 | | |
53 | | /* A hashset for tuples (id, string) using Robin Hood Hashing. |
54 | | * <https://www.cs.cornell.edu/courses/JavaAndDS/files/hashing_RobinHood.pdf> |
55 | | * The basic idea here to handle collisions by robbing "rich" entries and |
56 | | * giving to the "poor": |
57 | | * - We have an array: (id, string) are ideally placed at index "id % size". |
58 | | * - If slot at index is already occupied, we have a collision. |
59 | | * - A simple collision strategy would look at the next index, and the |
60 | | * next until finding an empty slot. |
61 | | * - The drawback is that this may lead to many checks on lookups, as it |
62 | | * will need to also look at subsequent slots until it finds the match. |
63 | | * The amount of lookups is the "probe sequence length" (psl) and this |
64 | | * may vary greatly between entries. |
65 | | * - Robin Hood Hashing balances the 'psl's of all entries more evenly: |
66 | | * - psl == 0 means an entry is in exactly the right slot |
67 | | * - psl == 1 means it is in the slot right after. psl == 2 is the slot |
68 | | * after that, etc. |
69 | | * - when inserting a new entry, track its psl. Finding a slot where |
70 | | * the existing entry has a lower psl makes a swap. Put the new entry |
71 | | * and its psl there, take the previous entry and its psl and find |
72 | | * the next best slot for the previous entry. */ |
73 | | void Curl_u8_strset_init(struct u8_strset *set) |
74 | 23.3k | { |
75 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
76 | | #pragma GCC diagnostic push |
77 | | #pragma GCC diagnostic ignored "-Warray-bounds" |
78 | | #endif |
79 | 23.3k | memset(set, 0, sizeof(*set)); |
80 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
81 | | #pragma GCC diagnostic pop |
82 | | #endif |
83 | 23.3k | set->data = set->sdata; |
84 | 23.3k | set->ids = set->sids; |
85 | 23.3k | set->psl = set->spsl; |
86 | 23.3k | set->slotbits = CURL_U8_STRSET_START_BITS; |
87 | 23.3k | set->count = 0; |
88 | 23.3k | #ifdef DEBUGBUILD |
89 | 23.3k | set->init = CURL_U8_STRSET_MAGIC; |
90 | 23.3k | #endif |
91 | 23.3k | } |
92 | | |
93 | | void Curl_u8_strset_clear(struct u8_strset *set) |
94 | 7.77k | { |
95 | 7.77k | uint16_t i; |
96 | 7.77k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
97 | 70.6k | for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) |
98 | 62.8k | curlx_safefree(set->data[i]); |
99 | | |
100 | 7.77k | if(set->data != set->sdata) |
101 | 58 | curlx_safefree(set->data); |
102 | 7.77k | Curl_u8_strset_init(set); |
103 | 7.77k | } |
104 | | |
105 | | static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) |
106 | 28.1k | { |
107 | 28.1k | uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); |
108 | 28.1k | uint8_t psl = 0; |
109 | 37.0k | while(set->data[i]) { |
110 | 8.91k | if(psl > set->psl[i]) { /* SWAP */ |
111 | 1.24k | char *tmpdata; |
112 | 1.24k | tmpdata = set->data[i]; |
113 | 1.24k | set->data[i] = val; |
114 | 1.24k | val = tmpdata; |
115 | 1.24k | CURL_SWAP(set->psl[i], psl); |
116 | 1.24k | CURL_SWAP(set->ids[i], id); |
117 | 1.24k | } |
118 | 8.91k | i = CURL_U8_SET_SLOT_IDX(set, i + 1); |
119 | 8.91k | ++psl; |
120 | 8.91k | } |
121 | 28.1k | set->ids[i] = id; |
122 | 28.1k | set->data[i] = val; |
123 | 28.1k | set->psl[i] = psl; |
124 | 28.1k | ++set->count; |
125 | 28.1k | } |
126 | | |
127 | | static bool u8_strset_grow(struct u8_strset *set) |
128 | 72 | { |
129 | 72 | uint8_t i, *prev_ids, nslotbits; |
130 | 72 | uint16_t prev_slots; |
131 | 72 | char **prev_data; |
132 | 72 | size_t nslots; |
133 | 72 | void *d; |
134 | | |
135 | 72 | if(set->slotbits >= 8) |
136 | 0 | return FALSE; |
137 | 72 | nslotbits = (uint8_t)(set->slotbits + 1); |
138 | | #if CURL_U8_STRSET_DEBUG |
139 | | curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n", |
140 | | set->slotbits, nslotbits); |
141 | | #endif |
142 | 72 | nslots = CURL_U8_SLOT_CNT(nslotbits); |
143 | 72 | d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots)); |
144 | 72 | if(!d) |
145 | 0 | return FALSE; |
146 | | |
147 | 72 | prev_data = set->data; |
148 | 72 | prev_ids = set->ids; |
149 | 72 | prev_slots = set->slotbits; |
150 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
151 | | #pragma GCC diagnostic push |
152 | | #pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" |
153 | | #endif |
154 | 72 | set->data = (char **)d; |
155 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
156 | | #pragma GCC diagnostic pop |
157 | | #endif |
158 | 72 | set->ids = (uint8_t *)d + (nslots * sizeof(char *)); |
159 | 72 | set->psl = set->ids + nslots; |
160 | 72 | set->slotbits = nslotbits; |
161 | 72 | set->count = 0; |
162 | | /* re-add previous entries */ |
163 | 760 | for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { |
164 | 688 | if(prev_data[i]) |
165 | 688 | u8_strset_addn(set, prev_ids[i], prev_data[i]); |
166 | 688 | } |
167 | 72 | if(prev_data != set->sdata) |
168 | 14 | curlx_free(prev_data); |
169 | 72 | return TRUE; |
170 | 72 | } |
171 | | |
172 | | static bool u8_strset_get_index(struct u8_strset *set, |
173 | | uint8_t id, uint8_t *pindex) |
174 | 88.4k | { |
175 | 88.4k | uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); |
176 | 88.4k | uint8_t psl = 0; |
177 | 109k | while(set->data[i] && (psl <= set->psl[i])) { |
178 | 36.6k | if(set->ids[i] == id) { |
179 | 16.1k | *pindex = i; |
180 | | #if CURL_U8_STRSET_DEBUG |
181 | | curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]); |
182 | | #endif |
183 | 16.1k | return TRUE; |
184 | 16.1k | } |
185 | 20.5k | i = CURL_U8_SET_SLOT_IDX(set, i + 1); |
186 | 20.5k | ++psl; |
187 | 20.5k | } |
188 | | #if CURL_U8_STRSET_DEBUG |
189 | | curl_mfprintf(stderr, "u8_strset_index %d not found\n", id); |
190 | | #endif |
191 | 72.3k | *pindex = 0; |
192 | 72.3k | return FALSE; |
193 | 88.4k | } |
194 | | |
195 | | uint16_t Curl_u8_strset_count(struct u8_strset *set) |
196 | 0 | { |
197 | 0 | return set->count; |
198 | 0 | } |
199 | | |
200 | | const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) |
201 | 21.0k | { |
202 | 21.0k | uint8_t i; |
203 | 21.0k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
204 | 21.0k | if(u8_strset_get_index(set, id, &i)) |
205 | 15.7k | return set->data[i]; |
206 | 5.29k | return NULL; |
207 | 21.0k | } |
208 | | |
209 | | CURLcode Curl_u8_strset_setn(struct u8_strset *set, |
210 | | uint8_t id, char *str) |
211 | 28.1k | { |
212 | 28.1k | uint8_t i; |
213 | | |
214 | 28.1k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
215 | | #if CURL_U8_STRSET_DEBUG |
216 | | curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str); |
217 | | #endif |
218 | 28.1k | if(!str) { |
219 | 526 | Curl_u8_strset_unset(set, id); |
220 | 526 | return CURLE_OK; |
221 | 526 | } |
222 | | |
223 | 27.6k | if(u8_strset_get_index(set, id, &i)) { |
224 | | /* `id` is in set, replace value */ |
225 | 174 | curlx_free(set->data[i]); |
226 | 174 | set->data[i] = str; |
227 | 174 | return CURLE_OK; |
228 | 174 | } |
229 | | /* `id` not in set yet, grow if full */ |
230 | 27.4k | if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { |
231 | 0 | curlx_free(str); |
232 | 0 | return CURLE_OUT_OF_MEMORY; |
233 | 0 | } |
234 | | |
235 | 27.4k | u8_strset_addn(set, id, str); |
236 | 27.4k | return CURLE_OK; |
237 | 27.4k | } |
238 | | |
239 | | |
240 | | CURLcode Curl_u8_strset_set(struct u8_strset *set, |
241 | | uint8_t id, const char *str) |
242 | 27.0k | { |
243 | 27.0k | char *val; |
244 | | |
245 | 27.0k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
246 | 27.0k | if(!str) { |
247 | 0 | Curl_u8_strset_unset(set, id); |
248 | 0 | return CURLE_OK; |
249 | 0 | } |
250 | | |
251 | 27.0k | val = curlx_strdup(str); |
252 | 27.0k | if(!val) |
253 | 0 | return CURLE_OUT_OF_MEMORY; |
254 | 27.0k | return Curl_u8_strset_setn(set, id, val); |
255 | 27.0k | } |
256 | | |
257 | | static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) |
258 | 39.8k | { |
259 | 39.8k | uint8_t i, j; |
260 | | |
261 | 39.8k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
262 | 39.8k | if(u8_strset_get_index(set, id, &i)) { |
263 | | /* `id` is in set */ |
264 | 216 | if(zero) |
265 | 190 | curlx_strzero(set->data[i]); |
266 | 216 | curlx_safefree(set->data[i]); |
267 | 216 | set->ids[i] = set->psl[i] = 0; |
268 | 216 | --set->count; |
269 | 216 | j = CURL_U8_SET_SLOT_IDX(set, i + 1); |
270 | | /* shift all entries with positive psl "down" */ |
271 | 524 | while(set->data[j] && set->psl[j]) { |
272 | 308 | set->data[i] = set->data[j]; |
273 | 308 | set->ids[i] = set->ids[j]; |
274 | 308 | set->psl[i] = (uint8_t)(set->psl[j] - 1); |
275 | 308 | set->data[j] = NULL; |
276 | 308 | set->ids[j] = set->psl[j] = 0; |
277 | 308 | i = j; |
278 | 308 | j = CURL_U8_SET_SLOT_IDX(set, i + 1); |
279 | 308 | } |
280 | 216 | } |
281 | 39.8k | } |
282 | | |
283 | | void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id) |
284 | 944 | { |
285 | 944 | u8_strset_unset(set, id, FALSE); |
286 | 944 | } |
287 | | |
288 | | void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id) |
289 | 38.8k | { |
290 | 38.8k | u8_strset_unset(set, id, TRUE); |
291 | 38.8k | } |
292 | | |
293 | | CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) |
294 | 0 | { |
295 | 0 | CURLcode result = CURLE_OK; |
296 | 0 | uint16_t i; |
297 | |
|
298 | 0 | DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); |
299 | 0 | Curl_u8_strset_clear(dest); |
300 | 0 | for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { |
301 | 0 | if(src->data[i]) |
302 | 0 | result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); |
303 | 0 | } |
304 | 0 | return result; |
305 | 0 | } |