1 /*
2 * Copyright (c) 2022 Meta
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <errno.h>
8 #include <stdbool.h>
9 #include <stddef.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13
14 #include <zephyr/sys/hash_map.h>
15 #include <zephyr/sys/hash_map_oa_lp.h>
16 #include <zephyr/sys/util.h>
17
18 enum bucket_state {
19 UNUSED,
20 USED,
21 TOMBSTONE,
22 };
23
24 struct oalp_entry {
25 uint64_t key;
26 uint64_t value;
27 enum bucket_state state;
28 };
29
30 BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, buckets) ==
31 offsetof(struct sys_hashmap_data, buckets));
32 BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, n_buckets) ==
33 offsetof(struct sys_hashmap_data, n_buckets));
34 BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, size) ==
35 offsetof(struct sys_hashmap_data, size));
36
sys_hashmap_oa_lp_find(const struct sys_hashmap * map,uint64_t key,bool used_ok,bool unused_ok,bool tombstone_ok)37 static struct oalp_entry *sys_hashmap_oa_lp_find(const struct sys_hashmap *map, uint64_t key,
38 bool used_ok, bool unused_ok, bool tombstone_ok)
39 {
40 struct oalp_entry *entry = NULL;
41 const size_t n_buckets = map->data->n_buckets;
42 uint32_t hash = map->hash_func(&key, sizeof(key));
43 struct oalp_entry *const buckets = map->data->buckets;
44
45 for (size_t i = 0, j = hash; i < n_buckets; ++i, ++j) {
46 j &= (n_buckets - 1);
47 __ASSERT_NO_MSG(j < n_buckets);
48
49 entry = &buckets[j];
50
51 switch (entry->state) {
52 case USED:
53 if (used_ok && entry->key == key) {
54 return entry;
55 }
56 break;
57 case UNUSED:
58 if (unused_ok) {
59 return entry;
60 }
61 break;
62 case TOMBSTONE:
63 if (tombstone_ok) {
64 return entry;
65 }
66 break;
67 default:
68 __ASSERT(false, "Invalid entry state. Memory has been corrupted");
69 break;
70 }
71 }
72
73 return NULL;
74 }
75
sys_hashmap_oa_lp_insert_no_rehash(struct sys_hashmap * map,uint64_t key,uint64_t value,uint64_t * old_value)76 static int sys_hashmap_oa_lp_insert_no_rehash(struct sys_hashmap *map, uint64_t key, uint64_t value,
77 uint64_t *old_value)
78 {
79 int ret;
80 struct oalp_entry *entry = NULL;
81 struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data;
82
83 entry = sys_hashmap_oa_lp_find(map, key, true, true, true);
84 __ASSERT_NO_MSG(entry != NULL);
85
86 switch (entry->state) {
87 case UNUSED:
88 ++data->size;
89 ret = 1;
90 break;
91 case TOMBSTONE:
92 --data->n_tombstones;
93 ++data->size;
94 ret = 0;
95 break;
96 case USED:
97 default:
98 ret = 0;
99 break;
100 }
101
102 if (old_value != NULL) {
103 *old_value = entry->value;
104 }
105
106 entry->state = USED;
107 entry->key = key;
108 entry->value = value;
109
110 return ret;
111 }
112
sys_hashmap_oa_lp_rehash(struct sys_hashmap * map,bool grow)113 static int sys_hashmap_oa_lp_rehash(struct sys_hashmap *map, bool grow)
114 {
115 size_t old_size;
116 size_t old_n_buckets;
117 size_t new_n_buckets = 0;
118 struct oalp_entry *entry;
119 struct oalp_entry *old_buckets;
120 struct oalp_entry *new_buckets;
121 struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data;
122
123 if (!sys_hashmap_should_rehash(map, grow, data->n_tombstones, &new_n_buckets)) {
124 return 0;
125 }
126
127 if (map->data->size != SIZE_MAX && map->data->size == map->config->max_size) {
128 return -ENOSPC;
129 }
130
131 /* extract all entries from the hashmap */
132 old_size = data->size;
133 old_n_buckets = data->n_buckets;
134 old_buckets = (struct oalp_entry *)data->buckets;
135
136 new_buckets = (struct oalp_entry *)map->alloc_func(NULL, new_n_buckets * sizeof(*entry));
137 if (new_buckets == NULL && new_n_buckets != 0) {
138 return -ENOMEM;
139 }
140
141 if (new_buckets != NULL) {
142 /* ensure all buckets are empty / initialized */
143 memset(new_buckets, 0, new_n_buckets * sizeof(*new_buckets));
144 }
145
146 data->size = 0;
147 data->buckets = new_buckets;
148 data->n_buckets = new_n_buckets;
149
150 /* re-insert all entries into the hashmap */
151 for (size_t i = 0, j = 0; i < old_n_buckets && j < old_size; ++i) {
152 entry = &old_buckets[i];
153
154 if (entry->state == USED) {
155 sys_hashmap_oa_lp_insert_no_rehash(map, entry->key, entry->value, NULL);
156 ++j;
157 }
158 }
159
160 /* free the old Hashmap */
161 map->alloc_func(old_buckets, 0);
162
163 return 0;
164 }
165
sys_hashmap_oa_lp_iter_next(struct sys_hashmap_iterator * it)166 static void sys_hashmap_oa_lp_iter_next(struct sys_hashmap_iterator *it)
167 {
168 size_t i;
169 struct oalp_entry *entry;
170 const struct sys_hashmap *map = (const struct sys_hashmap *)it->map;
171 struct oalp_entry *buckets = map->data->buckets;
172
173 __ASSERT(it->size == map->data->size, "Concurrent modification!");
174 __ASSERT(sys_hashmap_iterator_has_next(it), "Attempt to access beyond current bound!");
175
176 if (it->pos == 0) {
177 it->state = buckets;
178 }
179
180 i = (struct oalp_entry *)it->state - buckets;
181 __ASSERT(i < map->data->n_buckets, "Invalid iterator state %p", it->state);
182
183 for (; i < map->data->n_buckets; ++i) {
184 entry = &buckets[i];
185 if (entry->state == USED) {
186 it->state = &buckets[i + 1];
187 it->key = entry->key;
188 it->value = entry->value;
189 ++it->pos;
190 return;
191 }
192 }
193
194 __ASSERT(false, "Entire Hashmap traversed and no entry was found");
195 }
196
197 /*
198 * Open Addressing / Linear Probe Hashmap API
199 */
200
sys_hashmap_oa_lp_iter(const struct sys_hashmap * map,struct sys_hashmap_iterator * it)201 static void sys_hashmap_oa_lp_iter(const struct sys_hashmap *map, struct sys_hashmap_iterator *it)
202 {
203 it->map = map;
204 it->next = sys_hashmap_oa_lp_iter_next;
205 it->pos = 0;
206 *((size_t *)&it->size) = map->data->size;
207 }
208
sys_hashmap_oa_lp_clear(struct sys_hashmap * map,sys_hashmap_callback_t cb,void * cookie)209 static void sys_hashmap_oa_lp_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb,
210 void *cookie)
211 {
212 struct oalp_entry *entry;
213 struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data;
214 struct oalp_entry *buckets = data->buckets;
215
216 for (size_t i = 0, j = 0; cb != NULL && i < data->n_buckets && j < data->size; ++i) {
217 entry = &buckets[i];
218 if (entry->state == USED) {
219 cb(entry->key, entry->value, cookie);
220 ++j;
221 }
222 }
223
224 if (data->buckets != NULL) {
225 map->alloc_func(data->buckets, 0);
226 data->buckets = NULL;
227 }
228
229 data->n_buckets = 0;
230 data->size = 0;
231 data->n_tombstones = 0;
232 }
233
sys_hashmap_oa_lp_insert(struct sys_hashmap * map,uint64_t key,uint64_t value,uint64_t * old_value)234 static inline int sys_hashmap_oa_lp_insert(struct sys_hashmap *map, uint64_t key, uint64_t value,
235 uint64_t *old_value)
236 {
237 int ret;
238
239 ret = sys_hashmap_oa_lp_rehash(map, true);
240 if (ret < 0) {
241 return ret;
242 }
243
244 return sys_hashmap_oa_lp_insert_no_rehash(map, key, value, old_value);
245 }
246
sys_hashmap_oa_lp_remove(struct sys_hashmap * map,uint64_t key,uint64_t * value)247 static bool sys_hashmap_oa_lp_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value)
248 {
249 struct oalp_entry *entry;
250 struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data;
251
252 entry = sys_hashmap_oa_lp_find(map, key, true, true, false);
253 if (entry == NULL || entry->state == UNUSED) {
254 return false;
255 }
256
257 if (value != NULL) {
258 *value = entry->value;
259 }
260
261 entry->state = TOMBSTONE;
262 --data->size;
263 ++data->n_tombstones;
264
265 /* ignore a possible -ENOMEM since the table will remain intact */
266 (void)sys_hashmap_oa_lp_rehash(map, false);
267
268 return true;
269 }
270
sys_hashmap_oa_lp_get(const struct sys_hashmap * map,uint64_t key,uint64_t * value)271 static bool sys_hashmap_oa_lp_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value)
272 {
273 struct oalp_entry *entry;
274
275 entry = sys_hashmap_oa_lp_find(map, key, true, true, false);
276 if (entry == NULL || entry->state == UNUSED) {
277 return false;
278 }
279
280 if (value != NULL) {
281 *value = entry->value;
282 }
283
284 return true;
285 }
286
287 const struct sys_hashmap_api sys_hashmap_oa_lp_api = {
288 .iter = sys_hashmap_oa_lp_iter,
289 .clear = sys_hashmap_oa_lp_clear,
290 .insert = sys_hashmap_oa_lp_insert,
291 .remove = sys_hashmap_oa_lp_remove,
292 .get = sys_hashmap_oa_lp_get,
293 };
294