/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 | | #include "curlx/strdup.h" |
28 | | |
29 | | /* random patterns for API verification */ |
30 | | #ifdef DEBUGBUILD |
31 | 124k | #define CURL_U8_STRSET_MAGIC 0x7117e783 |
32 | | #endif |
33 | | |
34 | | #define CURL_U8_STRSET_DEBUG 0 |
35 | | |
36 | 143k | #define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) |
37 | | |
38 | | static const uint8_t u8_smask[] = { |
39 | | 0x00U, |
40 | | 0x01U, |
41 | | 0x03U, |
42 | | 0x07U, |
43 | | 0x0FU, |
44 | | 0x1FU, |
45 | | 0x3FU, |
46 | | 0x7FU, |
47 | | 0xFFU, |
48 | | }; |
49 | | |
50 | 3.53M | #define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) |
51 | 854k | #define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) |
52 | 693k | #define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) |
53 | | |
54 | | /* A hashset for tuples (id, string) using Robin Hood Hashing. |
55 | | * <https://www.cs.cornell.edu/courses/JavaAndDS/files/hashing_RobinHood.pdf> |
56 | | * The basic idea here to handle collisions by robbing "rich" entries and |
57 | | * giving to the "poor": |
58 | | * - We have an array: (id, string) are ideally placed at index "id % size". |
59 | | * - If slot at index is already occupied, we have a collision. |
60 | | * - A simple collision strategy would look at the next index, and the |
61 | | * next until finding an empty slot. |
62 | | * - The drawback is that this may lead to many checks on lookups, as it |
63 | | * will need to also look at subsequent slots until it finds the match. |
64 | | * The amount of lookups is the "probe sequence length" (psl) and this |
65 | | * may vary greatly between entries. |
66 | | * - Robin Hood Hashing balances the 'psl's of all entries more evenly: |
67 | | * - psl == 0 means an entry is in exactly the right slot |
68 | | * - psl == 1 means it is in the slot right after. psl == 2 is the slot |
69 | | * after that, etc. |
70 | | * - when inserting a new entry, track its psl. Finding a slot where |
71 | | * the existing entry has a lower psl makes a swap. Put the new entry |
72 | | * and its psl there, take the previous entry and its psl and find |
73 | | * the next best slot for the previous entry. */ |
74 | | void Curl_u8_strset_init(struct u8_strset *set) |
75 | 124k | { |
76 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
77 | | #pragma GCC diagnostic push |
78 | | #pragma GCC diagnostic ignored "-Warray-bounds" |
79 | | #endif |
80 | 124k | memset(set, 0, sizeof(*set)); |
81 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
82 | | #pragma GCC diagnostic pop |
83 | | #endif |
84 | 124k | set->data = set->sdata; |
85 | 124k | set->ids = set->sids; |
86 | 124k | set->psl = set->spsl; |
87 | 124k | set->slotbits = CURL_U8_STRSET_START_BITS; |
88 | 124k | set->count = 0; |
89 | 124k | #ifdef DEBUGBUILD |
90 | 124k | set->init = CURL_U8_STRSET_MAGIC; |
91 | 124k | #endif |
92 | 124k | } |
93 | | |
94 | | void Curl_u8_strset_clear(struct u8_strset *set) |
95 | 41.6k | { |
96 | 41.6k | uint16_t i; |
97 | 41.6k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
98 | 503k | for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) |
99 | 461k | curlx_safefree(set->data[i]); |
100 | | |
101 | 41.6k | if(set->data != set->sdata) |
102 | 16.0k | curlx_safefree(set->data); |
103 | 41.6k | Curl_u8_strset_init(set); |
104 | 41.6k | } |
105 | | |
106 | | static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) |
107 | 318k | { |
108 | 318k | uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); |
109 | 318k | uint8_t psl = 0; |
110 | 627k | while(set->data[i]) { |
111 | 308k | if(psl > set->psl[i]) { /* SWAP */ |
112 | 71.9k | char *tmpdata; |
113 | 71.9k | tmpdata = set->data[i]; |
114 | 71.9k | set->data[i] = val; |
115 | 71.9k | val = tmpdata; |
116 | 71.9k | CURL_SWAP(set->psl[i], psl); |
117 | 71.9k | CURL_SWAP(set->ids[i], id); |
118 | 71.9k | } |
119 | 308k | i = CURL_U8_SET_SLOT_IDX(set, i + 1); |
120 | 308k | ++psl; |
121 | 308k | } |
122 | 318k | set->ids[i] = id; |
123 | 318k | set->data[i] = val; |
124 | 318k | set->psl[i] = psl; |
125 | 318k | ++set->count; |
126 | 318k | } |
127 | | |
128 | | static bool u8_strset_grow(struct u8_strset *set) |
129 | 16.0k | { |
130 | 16.0k | uint8_t i, *prev_ids, nslotbits; |
131 | 16.0k | uint16_t prev_slots; |
132 | 16.0k | char **prev_data; |
133 | 16.0k | size_t nslots; |
134 | 16.0k | void *d; |
135 | | |
136 | 16.0k | if(set->slotbits >= 8) |
137 | 0 | return FALSE; |
138 | 16.0k | nslotbits = (uint8_t)(set->slotbits + 1); |
139 | | #if CURL_U8_STRSET_DEBUG |
140 | | curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n", |
141 | | set->slotbits, nslotbits); |
142 | | #endif |
143 | 16.0k | nslots = CURL_U8_SLOT_CNT(nslotbits); |
144 | 16.0k | d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots)); |
145 | 16.0k | if(!d) |
146 | 0 | return FALSE; |
147 | | |
148 | 16.0k | prev_data = set->data; |
149 | 16.0k | prev_ids = set->ids; |
150 | 16.0k | prev_slots = set->slotbits; |
151 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
152 | | #pragma GCC diagnostic push |
153 | | #pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" |
154 | | #endif |
155 | 16.0k | set->data = (char **)d; |
156 | | #if defined(__GNUC__) && __GNUC__ >= 13 |
157 | | #pragma GCC diagnostic pop |
158 | | #endif |
159 | 16.0k | set->ids = (uint8_t *)d + (nslots * sizeof(char *)); |
160 | 16.0k | set->psl = set->ids + nslots; |
161 | 16.0k | set->slotbits = nslotbits; |
162 | 16.0k | set->count = 0; |
163 | | /* re-add previous entries */ |
164 | 144k | for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { |
165 | 128k | if(prev_data[i]) |
166 | 128k | u8_strset_addn(set, prev_ids[i], prev_data[i]); |
167 | 128k | } |
168 | 16.0k | if(prev_data != set->sdata) |
169 | 2 | curlx_free(prev_data); |
170 | 16.0k | return TRUE; |
171 | 16.0k | } |
172 | | |
173 | | static bool u8_strset_get_index(struct u8_strset *set, |
174 | | uint8_t id, uint8_t *pindex) |
175 | 1.46M | { |
176 | 1.46M | uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); |
177 | 1.46M | uint8_t psl = 0; |
178 | 2.90M | while(set->data[i] && (psl <= set->psl[i])) { |
179 | 1.66M | if(set->ids[i] == id) { |
180 | 217k | *pindex = i; |
181 | | #if CURL_U8_STRSET_DEBUG |
182 | | curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]); |
183 | | #endif |
184 | 217k | return TRUE; |
185 | 217k | } |
186 | 1.44M | i = CURL_U8_SET_SLOT_IDX(set, i + 1); |
187 | 1.44M | ++psl; |
188 | 1.44M | } |
189 | | #if CURL_U8_STRSET_DEBUG |
190 | | curl_mfprintf(stderr, "u8_strset_index %d not found\n", id); |
191 | | #endif |
192 | 1.24M | *pindex = 0; |
193 | 1.24M | return FALSE; |
194 | 1.46M | } |
195 | | |
196 | | uint16_t Curl_u8_strset_count(struct u8_strset *set) |
197 | 0 | { |
198 | 0 | return set->count; |
199 | 0 | } |
200 | | |
201 | | const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) |
202 | 1.06M | { |
203 | 1.06M | uint8_t i; |
204 | 1.06M | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
205 | 1.06M | if(u8_strset_get_index(set, id, &i)) |
206 | 215k | return set->data[i]; |
207 | 845k | return NULL; |
208 | 1.06M | } |
209 | | |
210 | | CURLcode Curl_u8_strset_setn(struct u8_strset *set, |
211 | | uint8_t id, char *str) |
212 | 193k | { |
213 | 193k | uint8_t i; |
214 | | |
215 | 193k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
216 | | #if CURL_U8_STRSET_DEBUG |
217 | | curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str); |
218 | | #endif |
219 | 193k | if(!str) { |
220 | 1.46k | Curl_u8_strset_unset(set, id); |
221 | 1.46k | return CURLE_OK; |
222 | 1.46k | } |
223 | | |
224 | 191k | if(u8_strset_get_index(set, id, &i)) { |
225 | | /* `id` is in set, replace value */ |
226 | 1.79k | curlx_free(set->data[i]); |
227 | 1.79k | set->data[i] = str; |
228 | 1.79k | return CURLE_OK; |
229 | 1.79k | } |
230 | | /* `id` not in set yet, grow if full */ |
231 | 189k | if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { |
232 | 0 | curlx_free(str); |
233 | 0 | return CURLE_OUT_OF_MEMORY; |
234 | 0 | } |
235 | | |
236 | 189k | u8_strset_addn(set, id, str); |
237 | 189k | return CURLE_OK; |
238 | 189k | } |
239 | | |
240 | | CURLcode Curl_u8_strset_setx(struct u8_strset *set, |
241 | | uint8_t id, const char *str, size_t slen) |
242 | 190k | { |
243 | 190k | char *val; |
244 | | |
245 | 190k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
246 | 190k | if(!str) { |
247 | 0 | Curl_u8_strset_unset(set, id); |
248 | 0 | return CURLE_OK; |
249 | 0 | } |
250 | | |
251 | 190k | val = curlx_memdup0(str, slen); |
252 | 190k | if(!val) |
253 | 0 | return CURLE_OUT_OF_MEMORY; |
254 | 190k | return Curl_u8_strset_setn(set, id, val); |
255 | 190k | } |
256 | | |
257 | | CURLcode Curl_u8_strset_set(struct u8_strset *set, |
258 | | uint8_t id, const char *str) |
259 | 0 | { |
260 | 0 | return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); |
261 | 0 | } |
262 | | |
263 | | static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) |
264 | 210k | { |
265 | 210k | uint8_t i, j; |
266 | | |
267 | 210k | DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); |
268 | 210k | if(u8_strset_get_index(set, id, &i)) { |
269 | | /* `id` is in set */ |
270 | 320 | if(zero) |
271 | 283 | curlx_strzero(set->data[i]); |
272 | 320 | curlx_safefree(set->data[i]); |
273 | 320 | set->ids[i] = set->psl[i] = 0; |
274 | 320 | --set->count; |
275 | 320 | j = CURL_U8_SET_SLOT_IDX(set, i + 1); |
276 | | /* shift all entries with positive psl "down" */ |
277 | 369 | while(set->data[j] && set->psl[j]) { |
278 | 49 | set->data[i] = set->data[j]; |
279 | 49 | set->ids[i] = set->ids[j]; |
280 | 49 | set->psl[i] = (uint8_t)(set->psl[j] - 1); |
281 | 49 | set->data[j] = NULL; |
282 | 49 | set->ids[j] = set->psl[j] = 0; |
283 | 49 | i = j; |
284 | 49 | j = CURL_U8_SET_SLOT_IDX(set, i + 1); |
285 | 49 | } |
286 | 320 | } |
287 | 210k | } |
288 | | |
289 | | void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id) |
290 | 1.76k | { |
291 | 1.76k | u8_strset_unset(set, id, FALSE); |
292 | 1.76k | } |
293 | | |
294 | | void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id) |
295 | 208k | { |
296 | 208k | u8_strset_unset(set, id, TRUE); |
297 | 208k | } |
298 | | |
299 | | CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) |
300 | 0 | { |
301 | 0 | CURLcode result = CURLE_OK; |
302 | 0 | uint16_t i; |
303 | |
|
304 | 0 | DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); |
305 | 0 | Curl_u8_strset_clear(dest); |
306 | 0 | for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { |
307 | 0 | if(src->data[i]) |
308 | 0 | result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); |
309 | 0 | } |
310 | 0 | return result; |
311 | 0 | } |