1use ahash::RandomState;
4use bytemuck::{Pod, Zeroable};
5use rand::{rngs::ThreadRng, Rng};
6use rostl_primitives::{
7 cmov_body, cxchg_body, impl_cmov_for_generic_pod,
8 traits::{Cmov, _Cmovbase},
9};
10
11use seq_macro::seq;
12
13use crate::{array::DynamicArray, queue::ShortQueue};
14
15const INSERTION_QUEUE_MAX_SIZE: usize = 20;
17const DEAMORTIZED_INSERTIONS: usize = 2;
19const BUCKET_SIZE: usize = 2;
21
22use std::hash::Hash;
23
24pub trait OHash: Cmov + Pod + Hash + PartialEq {}
26impl<K> OHash for K where K: Cmov + Pod + Hash + PartialEq {}
28
29#[repr(align(16))]
31#[derive(Debug, Default, Clone, Copy, Zeroable)]
32pub struct InlineElement<K, V>
33where
34 K: OHash,
35 V: Cmov + Pod,
36{
37 key: K,
38 value: V,
39}
40unsafe impl<K: OHash, V: Cmov + Pod> Pod for InlineElement<K, V> {}
41impl_cmov_for_generic_pod!(InlineElement<K,V>; where K: OHash, V: Cmov + Pod);
42
43#[derive(Debug, Default, Clone, Copy, Zeroable)]
44pub struct BucketElement<K, V>
46where
47 K: OHash,
48 V: Cmov + Pod,
49{
50 is_valid: bool,
51 element: InlineElement<K, V>,
52}
53unsafe impl<K: OHash, V: Cmov + Pod> Pod for BucketElement<K, V> {}
54impl_cmov_for_generic_pod!(BucketElement<K,V>; where K: OHash, V: Cmov + Pod);
55
56impl<K, V> BucketElement<K, V>
57where
58 K: OHash,
59 V: Cmov + Pod,
60{
61 #[inline(always)]
62 const fn is_empty(&self) -> bool {
63 !self.is_valid
64 }
65}
66
67#[repr(align(64))]
74#[derive(Debug, Default, Clone, Copy, Zeroable)]
75struct Bucket<K, V>
76where
77 K: OHash,
78 V: Cmov + Pod,
79{
80 elements: [BucketElement<K, V>; BUCKET_SIZE],
81}
82unsafe impl<K: OHash, V: Cmov + Pod> Pod for Bucket<K, V> {}
83impl_cmov_for_generic_pod!(Bucket<K,V>; where K: OHash, V: Cmov + Pod);
84
85impl<K, V> Bucket<K, V>
86where
87 K: OHash,
88 V: Cmov + Pod,
89{
90 fn update_if_exists(&mut self, real: bool, element: InlineElement<K, V>) -> bool {
97 let mut updated = false;
98 for i in 0..BUCKET_SIZE {
99 let element_i = &mut self.elements[i];
100 let choice = real & !element_i.is_empty() & (element_i.element.key == element.key);
101 element_i.element.value.cmov(&element.value, choice);
102 updated.cmov(&true, choice);
103 }
104 updated
105 }
106
107 fn read_if_exists(&self, key: K, ret: &mut V) -> bool {
108 let mut found = false;
109 for i in 0..BUCKET_SIZE {
110 let element_i = &self.elements[i];
111 let choice = !element_i.is_empty() & (element_i.element.key == key);
112 ret.cmov(&element_i.element.value, choice);
113 found.cmov(&true, choice);
114 }
115 found
116 }
117
118 fn insert_if_available(&mut self, real: bool, element: InlineElement<K, V>) -> bool {
125 let mut inserted = !real;
126 for i in 0..BUCKET_SIZE {
127 let element_i = &mut self.elements[i];
128 let choice = !inserted & element_i.is_empty();
129 element_i.is_valid.cmov(&true, choice);
130 element_i.element.cmov(&element, choice);
131 inserted.cmov(&true, choice);
132 }
133 inserted
134 }
135}
136
137#[derive(Debug)]
146pub struct UnsortedMap<K, V>
147where
148 K: OHash + Default + std::fmt::Debug,
149 V: Cmov + Pod + Default + std::fmt::Debug,
150{
151 size: usize,
153 capacity: usize,
155 table: [DynamicArray<Bucket<K, V>>; 2],
157 hash_builders: [RandomState; 2],
159 insertion_queue: ShortQueue<InlineElement<K, V>, INSERTION_QUEUE_MAX_SIZE>,
161 rng: ThreadRng,
163}
164
165impl<K, V> UnsortedMap<K, V>
166where
167 K: OHash + Default + std::fmt::Debug,
168 V: Cmov + Pod + Default + std::fmt::Debug,
169{
170 pub fn new(capacity: usize) -> Self {
172 debug_assert!(capacity > 0);
173 Self {
174 size: 0,
175 capacity,
176 table: [DynamicArray::new(capacity), DynamicArray::new(capacity)],
177 hash_builders: [RandomState::new(), RandomState::new()],
178 insertion_queue: ShortQueue::new(),
179 rng: rand::rng(),
180 }
181 }
182
183 #[inline(always)]
184 fn hash_key<const TABLE: usize>(&self, key: &K) -> usize {
185 (self.hash_builders[TABLE].hash_one(key) % self.capacity as u64) as usize
186 }
187
188 pub fn get(&mut self, key: K, ret: &mut V) -> bool {
196 let mut found = false;
197 let mut tmp: Bucket<K, V> = Default::default();
198
199 seq!(INDEX in 0..2 {
202 let hash = self.hash_key::<INDEX>(&key);
203 let table = &mut self.table[INDEX];
204 table.read(hash, &mut tmp);
205 let found_local = tmp.read_if_exists(key, ret);
206 found.cmov(&true, found_local);
207 });
208
209 for i in 0..self.insertion_queue.size {
211 let element = self.insertion_queue.elements.data[i];
212 let found_local = !element.is_empty() & (element.value.key == key);
213 ret.cmov(&element.value.value, found_local);
214 found.cmov(&true, found_local);
215 }
216
217 found
218 }
219
220 fn try_insert_entry(&mut self, real: bool, element: &mut InlineElement<K, V>) -> bool {
225 let mut done = !real;
226
227 seq!(INDEX_REV in 0..2 {{
228 #[allow(clippy::identity_op, clippy::eq_op)] const INDEX: usize = 1 - INDEX_REV;
230
231 let hash = self.hash_key::<INDEX>(&element.key);
232 let table = &mut self.table[INDEX];
233 table.update(hash, |bucket| {
234 let choice = !done;
235 let inserted = bucket.insert_if_available(choice, *element);
236 done.cmov(&true, inserted);
237 let randidx = self.rng.random_range(0..BUCKET_SIZE);
238 bucket.elements[randidx].element.cxchg(element, !done);
239 });
240 }});
241 done
242 }
243
244 pub fn deamortize_insertion_queue(&mut self) {
246 for _ in 0..DEAMORTIZED_INSERTIONS {
247 let mut element = InlineElement::default();
248 let real = self.insertion_queue.size > 0;
249
250 self.insertion_queue.maybe_pop(real, &mut element);
252 let has_pending_element = !self.try_insert_entry(real, &mut element);
253 self.insertion_queue.maybe_push(has_pending_element, element);
254 }
255 }
256
257 pub fn insert(&mut self, key: K, value: V) {
261 assert!(self.insertion_queue.size < INSERTION_QUEUE_MAX_SIZE);
263 self.insertion_queue.maybe_push(true, InlineElement { key, value });
264 self.deamortize_insertion_queue();
265 self.size.cmov(&(self.size + 1), true);
266 }
267
268 pub fn write(&mut self, key: K, value: V) {
274 let mut updated = false;
275
276 seq!(INDEX in 0..2 {
279 let hash = self.hash_key::<INDEX>(&key);
280 let table = &mut self.table[INDEX];
281 table.update(hash, |bucket| {
282 let choice = !updated;
283 let updated_local = bucket.update_if_exists(choice, InlineElement { key, value });
284 updated.cmov(&true, updated_local);
285 });
286 });
287
288 for i in 0..self.insertion_queue.size {
290 let element = &mut self.insertion_queue.elements.data[i];
291 let choice = !updated & !element.is_empty() & (element.value.key == key);
292 element.value.value.cmov(&value, choice);
293 updated.cmov(&true, choice);
294 }
295
296 assert!(updated);
297 }
298
299 }
302
303#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_unsorted_map() {
311 let mut map: UnsortedMap<u32, u32> = UnsortedMap::new(2);
312 assert_eq!(map.size, 0);
313 let mut value = 0;
314 assert!(!map.get(1, &mut value));
315 map.insert(1, 2);
316 assert_eq!(map.size, 1);
317 assert!(map.get(1, &mut value));
318 assert_eq!(value, 2);
319 map.write(1, 3);
320 assert!(map.get(1, &mut value));
321 assert_eq!(value, 3);
322 }
323
324 }