|
1 | 1 | #![allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety
|
2 | 2 |
|
3 | 3 | use std::any::TypeId;
|
| 4 | +use std::cell::Cell; |
4 | 5 | use std::fmt;
|
5 | 6 | use std::hash::{BuildHasher, Hash, Hasher};
|
6 | 7 | use std::marker::PhantomData;
|
@@ -60,10 +61,14 @@ pub struct IngredientImpl<C: Configuration> {
|
60 | 61 |
|
61 | 62 | /// Maps from data to the existing interned id for that data.
|
62 | 63 | ///
|
| 64 | + /// This doesn't hold the fields themselves to save memory, instead it points to the slot ID. |
| 65 | + /// |
63 | 66 | /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
64 |
| - key_map: FxDashMap<C::Fields<'static>, Id>, |
| 67 | + key_map: FxDashMap<Id, ()>, |
65 | 68 |
|
66 | 69 | memo_table_types: Arc<MemoTableTypes>,
|
| 70 | + |
| 71 | + _marker: PhantomData<fn() -> C>, |
67 | 72 | }
|
68 | 73 |
|
69 | 74 | /// Struct storing the interned fields.
|
@@ -135,6 +140,7 @@ where
|
135 | 140 | ingredient_index,
|
136 | 141 | key_map: Default::default(),
|
137 | 142 | memo_table_types: Arc::new(MemoTableTypes::default()),
|
| 143 | + _marker: PhantomData, |
138 | 144 | }
|
139 | 145 | }
|
140 | 146 |
|
@@ -193,24 +199,32 @@ where
|
193 | 199 | {
|
194 | 200 | let (zalsa, zalsa_local) = db.zalsas();
|
195 | 201 | let current_revision = zalsa.current_revision();
|
| 202 | + let table = zalsa.table(); |
196 | 203 |
|
197 | 204 | // Optimization to only get read lock on the map if the data has already been interned.
|
198 | 205 | let data_hash = self.key_map.hasher().hash_one(&key);
|
199 | 206 | let shard = &self.key_map.shards()[self.key_map.determine_shard(data_hash as _)];
|
200 |
| - let eq = |(data, _): &_| { |
| 207 | + let found_value = Cell::new(None); |
| 208 | + let eq = |(id, _): &_| { |
| 209 | + let data = table.get::<Value<C>>(*id); |
| 210 | + found_value.set(Some(data)); |
201 | 211 | // SAFETY: it's safe to go from Data<'static> to Data<'db>
|
202 | 212 | // shrink lifetime here to use a single lifetime in Lookup::eq(&StructKey<'db>, &C::Data<'db>)
|
203 |
| - let data: &C::Fields<'db> = unsafe { std::mem::transmute(data) }; |
| 213 | + let data = unsafe { |
| 214 | + std::mem::transmute::<&C::Fields<'static>, &C::Fields<'db>>(&data.fields) |
| 215 | + }; |
204 | 216 | HashEqLike::eq(data, &key)
|
205 | 217 | };
|
206 | 218 |
|
207 | 219 | {
|
208 | 220 | let lock = shard.read();
|
209 | 221 | if let Some(bucket) = lock.find(data_hash, eq) {
|
210 | 222 | // SAFETY: Read lock on map is held during this block
|
211 |
| - let id = unsafe { *bucket.as_ref().1.get() }; |
| 223 | + let id = unsafe { bucket.as_ref().0 }; |
212 | 224 |
|
213 |
| - let value = zalsa.table().get::<Value<C>>(id); |
| 225 | + let value = found_value |
| 226 | + .get() |
| 227 | + .expect("found the interned, so `found_value` should be set"); |
214 | 228 |
|
215 | 229 | // Sync the value's revision.
|
216 | 230 | if value.last_interned_at.load() < current_revision {
|
@@ -243,12 +257,16 @@ where
|
243 | 257 | }
|
244 | 258 |
|
245 | 259 | let mut lock = shard.write();
|
246 |
| - match lock.find_or_find_insert_slot(data_hash, eq, |(element, _)| { |
247 |
| - self.key_map.hasher().hash_one(element) |
| 260 | + match lock.find_or_find_insert_slot(data_hash, eq, |(id, _)| { |
| 261 | + // This closure is only called if the table is resized. So while it's expensive to lookup all values, |
| 262 | + // it will only happen rarely. |
| 263 | + self.key_map |
| 264 | + .hasher() |
| 265 | + .hash_one(&table.get::<Value<C>>(*id).fields) |
248 | 266 | }) {
|
249 | 267 | // Data has been interned by a racing call, use that ID instead
|
250 | 268 | Ok(slot) => {
|
251 |
| - let id = unsafe { *slot.as_ref().1.get() }; |
| 269 | + let id = unsafe { slot.as_ref().0 }; |
252 | 270 | let value = zalsa.table().get::<Value<C>>(id);
|
253 | 271 |
|
254 | 272 | // Sync the value's revision.
|
@@ -302,8 +320,7 @@ where
|
302 | 320 |
|
303 | 321 | let value = zalsa.table().get::<Value<C>>(id);
|
304 | 322 |
|
305 |
| - let slot_value = (value.fields.clone(), SharedValue::new(id)); |
306 |
| - unsafe { lock.insert_in_slot(data_hash, slot, slot_value) }; |
| 323 | + unsafe { lock.insert_in_slot(data_hash, slot, (id, SharedValue::new(()))) }; |
307 | 324 |
|
308 | 325 | debug_assert_eq!(
|
309 | 326 | data_hash,
|
|
0 commit comments