1 //! Simple string interning. 2 3 use crate::{ 4 collections::{HashMap, String, Vec}, 5 error::OutOfMemory, 6 prelude::*, 7 }; 8 use core::{fmt, mem, num::NonZeroU32}; 9 use wasmtime_core::alloc::TryClone; 10 11 /// An interned string associated with a particular string in a `StringPool`. 12 /// 13 /// Allows for $O(1)$ equality tests, $O(1)$ hashing, and $O(1)$ 14 /// arbitrary-but-stable ordering. 15 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 pub struct Atom { 17 index: NonZeroU32, 18 } 19 20 /// A pool of interned strings. 21 /// 22 /// Insert new strings with [`StringPool::insert`] to get an `Atom` that is 23 /// unique per string within the context of the associated pool. 24 /// 25 /// Once you have interned a string into the pool and have its `Atom`, you can 26 /// get the interned string slice via `&pool[atom]` or `pool.get(atom)`. 27 /// 28 /// In general, there are no correctness protections against indexing into a 29 /// different `StringPool` from the one that the `Atom` was not allocated 30 /// inside. Doing so is memory safe but may panic or otherwise return incorrect 31 /// results. 32 #[derive(Default)] 33 pub struct StringPool { 34 /// A map from each string in this pool (as an unsafe borrow from 35 /// `self.strings`) to its `Atom`. 36 map: mem::ManuallyDrop<HashMap<&'static str, Atom>>, 37 38 /// Strings in this pool. These must never be mutated or reallocated once 39 /// inserted. 40 strings: mem::ManuallyDrop<Vec<Box<str>>>, 41 } 42 43 impl Drop for StringPool { 44 fn drop(&mut self) { 45 // Ensure that `self.map` is dropped before `self.strings`, since 46 // `self.map` borrows from `self.strings`. 47 // 48 // Safety: Neither field will be used again. 49 unsafe { 50 mem::ManuallyDrop::drop(&mut self.map); 51 mem::ManuallyDrop::drop(&mut self.strings); 52 } 53 } 54 } 55 56 impl fmt::Debug for StringPool { 57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 struct Strings<'a>(&'a StringPool); 59 impl fmt::Debug for Strings<'_> { 60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 f.debug_map() 62 .entries( 63 self.0 64 .strings 65 .iter() 66 .enumerate() 67 .map(|(i, s)| (Atom::new(i), s)), 68 ) 69 .finish() 70 } 71 } 72 73 f.debug_struct("StringPool") 74 .field("strings", &Strings(self)) 75 .finish() 76 } 77 } 78 79 impl TryClone for StringPool { 80 fn try_clone(&self) -> Result<Self, OutOfMemory> { 81 Ok(StringPool { 82 map: self.map.try_clone()?, 83 strings: self.strings.try_clone()?, 84 }) 85 } 86 } 87 88 impl TryClone for Atom { 89 fn try_clone(&self) -> Result<Self, OutOfMemory> { 90 Ok(*self) 91 } 92 } 93 94 impl core::ops::Index<Atom> for StringPool { 95 type Output = str; 96 97 #[inline] 98 #[track_caller] 99 fn index(&self, atom: Atom) -> &Self::Output { 100 self.get(atom).unwrap() 101 } 102 } 103 104 impl serde::ser::Serialize for StringPool { 105 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 106 where 107 S: serde::Serializer, 108 { 109 serde::ser::Serialize::serialize(&*self.strings, serializer) 110 } 111 } 112 113 impl<'de> serde::de::Deserialize<'de> for StringPool { 114 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 115 where 116 D: serde::Deserializer<'de>, 117 { 118 struct Visitor; 119 impl<'de> serde::de::Visitor<'de> for Visitor { 120 type Value = StringPool; 121 122 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 123 f.write_str("a `StringPool` sequence of strings") 124 } 125 126 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 127 where 128 A: serde::de::SeqAccess<'de>, 129 { 130 use serde::de::Error as _; 131 132 let mut pool = StringPool::new(); 133 134 if let Some(len) = seq.size_hint() { 135 pool.map.reserve(len).map_err(|oom| A::Error::custom(oom))?; 136 pool.strings 137 .reserve(len) 138 .map_err(|oom| A::Error::custom(oom))?; 139 } 140 141 while let Some(s) = seq.next_element::<String>()? { 142 debug_assert_eq!(s.len(), s.capacity()); 143 let s = s.into_boxed_str().map_err(|oom| A::Error::custom(oom))?; 144 if !pool.map.contains_key(&*s) { 145 pool.insert_new_boxed_str(s) 146 .map_err(|oom| A::Error::custom(oom))?; 147 } 148 } 149 150 Ok(pool) 151 } 152 } 153 deserializer.deserialize_seq(Visitor) 154 } 155 } 156 157 impl StringPool { 158 /// Create a new, empty pool. 159 pub fn new() -> Self { 160 Self::default() 161 } 162 163 /// Insert a new string into this pool. 164 pub fn insert(&mut self, s: &str) -> Result<Atom, OutOfMemory> { 165 if let Some(atom) = self.map.get(s) { 166 return Ok(*atom); 167 } 168 169 self.map.reserve(1)?; 170 self.strings.reserve(1)?; 171 172 let mut owned = String::new(); 173 owned.reserve_exact(s.len())?; 174 owned.push_str(s).expect("reserved capacity"); 175 let owned = owned 176 .into_boxed_str() 177 .expect("reserved exact capacity, so shouldn't need to realloc"); 178 179 self.insert_new_boxed_str(owned) 180 } 181 182 fn insert_new_boxed_str(&mut self, owned: Box<str>) -> Result<Atom, OutOfMemory> { 183 debug_assert!(!self.map.contains_key(&*owned)); 184 185 let index = self.strings.len(); 186 let atom = Atom::new(index); 187 self.strings.push(owned)?; 188 189 // SAFETY: We never expose this borrow and never mutate or reallocate 190 // strings once inserted into the pool. 191 let s = unsafe { mem::transmute::<&str, &'static str>(&self.strings[index]) }; 192 193 let old = self.map.insert(s, atom)?; 194 debug_assert!(old.is_none()); 195 196 Ok(atom) 197 } 198 199 /// Get the `Atom` for the given string, if it has already been inserted 200 /// into this pool. 201 pub fn get_atom(&self, s: &str) -> Option<Atom> { 202 self.map.get(s).copied() 203 } 204 205 /// Does this pool contain the given `atom`? 206 #[inline] 207 pub fn contains(&self, atom: Atom) -> bool { 208 atom.index() < self.strings.len() 209 } 210 211 /// Get the string associated with the given `atom`, if the pool contains 212 /// the atom. 213 #[inline] 214 pub fn get(&self, atom: Atom) -> Option<&str> { 215 if self.contains(atom) { 216 Some(&self.strings[atom.index()]) 217 } else { 218 None 219 } 220 } 221 } 222 223 impl fmt::Debug for Atom { 224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 225 f.debug_struct("Atom") 226 .field("index", &self.index()) 227 .finish() 228 } 229 } 230 231 impl serde::ser::Serialize for Atom { 232 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 233 where 234 S: serde::Serializer, 235 { 236 serde::ser::Serialize::serialize(&self.index, serializer) 237 } 238 } 239 240 impl<'de> serde::de::Deserialize<'de> for Atom { 241 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> 242 where 243 D: serde::Deserializer<'de>, 244 { 245 let index = serde::de::Deserialize::deserialize(deserializer)?; 246 Ok(Self { index }) 247 } 248 } 249 250 impl Atom { 251 fn new(index: usize) -> Self { 252 assert!(index < usize::try_from(u32::MAX).unwrap()); 253 let index = u32::try_from(index).unwrap(); 254 let index = NonZeroU32::new(index + 1).unwrap(); 255 Self { index } 256 } 257 258 /// Get this atom's index in its pool. 259 pub fn index(&self) -> usize { 260 let index = self.index.get() - 1; 261 usize::try_from(index).unwrap() 262 } 263 } 264 265 #[cfg(test)] 266 mod tests { 267 use super::*; 268 269 #[test] 270 fn basic() -> Result<()> { 271 let mut pool = StringPool::new(); 272 273 let a = pool.insert("a")?; 274 assert_eq!(&pool[a], "a"); 275 assert_eq!(pool.get_atom("a"), Some(a)); 276 277 let a2 = pool.insert("a")?; 278 assert_eq!(a, a2); 279 assert_eq!(&pool[a2], "a"); 280 281 let b = pool.insert("b")?; 282 assert_eq!(&pool[b], "b"); 283 assert_ne!(a, b); 284 assert_eq!(pool.get_atom("b"), Some(b)); 285 286 assert!(pool.get_atom("zzz").is_none()); 287 288 let mut pool2 = StringPool::new(); 289 let c = pool2.insert("c")?; 290 assert_eq!(&pool2[c], "c"); 291 assert_eq!(a, c); 292 assert_eq!(&pool2[a], "c"); 293 assert!(!pool2.contains(b)); 294 assert!(pool2.get(b).is_none()); 295 296 Ok(()) 297 } 298 299 #[test] 300 fn stress() -> Result<()> { 301 let mut pool = StringPool::new(); 302 303 let n = if cfg!(miri) { 100 } else { 10_000 }; 304 305 for _ in 0..2 { 306 let atoms: Vec<_> = (0..n).map(|i| pool.insert(&i.to_string())).try_collect()?; 307 308 for atom in atoms { 309 assert!(pool.contains(atom)); 310 assert_eq!(&pool[atom], atom.index().to_string()); 311 } 312 } 313 314 Ok(()) 315 } 316 317 #[test] 318 fn roundtrip_serialize_deserialize() -> Result<()> { 319 let mut pool = StringPool::new(); 320 let a = pool.insert("a")?; 321 let b = pool.insert("b")?; 322 let c = pool.insert("c")?; 323 324 let bytes = postcard::to_allocvec(&(pool, a, b, c))?; 325 let (pool, a2, b2, c2) = postcard::from_bytes::<(StringPool, Atom, Atom, Atom)>(&bytes)?; 326 327 assert_eq!(&pool[a], "a"); 328 assert_eq!(&pool[b], "b"); 329 assert_eq!(&pool[c], "c"); 330 331 assert_eq!(&pool[a2], "a"); 332 assert_eq!(&pool[b2], "b"); 333 assert_eq!(&pool[c2], "c"); 334 335 Ok(()) 336 } 337 } 338