rustpython_vm/
intern.rs

1use crate::{
2    builtins::{PyStr, PyStrInterned, PyTypeRef},
3    common::lock::PyRwLock,
4    convert::ToPyObject,
5    AsObject, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, VirtualMachine,
6};
7use std::{
8    borrow::{Borrow, ToOwned},
9    ops::Deref,
10};
11
12#[derive(Debug)]
13pub struct StringPool {
14    inner: PyRwLock<std::collections::HashSet<CachedPyStrRef, ahash::RandomState>>,
15}
16
17impl Default for StringPool {
18    fn default() -> Self {
19        Self {
20            inner: PyRwLock::new(Default::default()),
21        }
22    }
23}
24
25impl Clone for StringPool {
26    fn clone(&self) -> Self {
27        Self {
28            inner: PyRwLock::new(self.inner.read().clone()),
29        }
30    }
31}
32
33impl StringPool {
34    #[inline]
35    pub unsafe fn intern<S: InternableString>(
36        &self,
37        s: S,
38        typ: PyTypeRef,
39    ) -> &'static PyStrInterned {
40        if let Some(found) = self.interned(s.as_ref()) {
41            return found;
42        }
43
44        #[cold]
45        fn miss(zelf: &StringPool, s: PyRefExact<PyStr>) -> &'static PyStrInterned {
46            let cache = CachedPyStrRef { inner: s };
47            let inserted = zelf.inner.write().insert(cache.clone());
48            if inserted {
49                let interned = unsafe { cache.as_interned_str() };
50                unsafe { interned.as_object().mark_intern() };
51                interned
52            } else {
53                unsafe {
54                    zelf.inner
55                        .read()
56                        .get(cache.as_ref())
57                        .expect("inserted is false")
58                        .as_interned_str()
59                }
60            }
61        }
62        let str_ref = s.into_pyref_exact(typ);
63        miss(self, str_ref)
64    }
65
66    #[inline]
67    pub fn interned<S: MaybeInternedString + ?Sized>(
68        &self,
69        s: &S,
70    ) -> Option<&'static PyStrInterned> {
71        if let Some(interned) = s.as_interned() {
72            return Some(interned);
73        }
74        self.inner
75            .read()
76            .get(s.as_ref())
77            .map(|cached| unsafe { cached.as_interned_str() })
78    }
79}
80
81#[derive(Debug, Clone)]
82#[repr(transparent)]
83pub struct CachedPyStrRef {
84    inner: PyRefExact<PyStr>,
85}
86
87impl std::hash::Hash for CachedPyStrRef {
88    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89        self.inner.as_str().hash(state)
90    }
91}
92
93impl PartialEq for CachedPyStrRef {
94    fn eq(&self, other: &Self) -> bool {
95        self.inner.as_str() == other.inner.as_str()
96    }
97}
98
99impl Eq for CachedPyStrRef {}
100
101impl std::borrow::Borrow<str> for CachedPyStrRef {
102    #[inline]
103    fn borrow(&self) -> &str {
104        self.inner.as_str()
105    }
106}
107
108impl AsRef<str> for CachedPyStrRef {
109    #[inline]
110    fn as_ref(&self) -> &str {
111        self.as_str()
112    }
113}
114
115impl CachedPyStrRef {
116    /// # Safety
117    /// the given cache must be alive while returned reference is alive
118    #[inline]
119    unsafe fn as_interned_str(&self) -> &'static PyStrInterned {
120        std::mem::transmute_copy(self)
121    }
122
123    #[inline]
124    fn as_str(&self) -> &str {
125        self.inner.as_str()
126    }
127}
128
129pub struct PyInterned<T>
130where
131    T: PyPayload,
132{
133    inner: Py<T>,
134}
135
136impl<T: PyPayload> PyInterned<T> {
137    #[inline]
138    pub fn leak(cache: PyRef<T>) -> &'static Self {
139        unsafe { std::mem::transmute(cache) }
140    }
141
142    #[inline]
143    fn as_ptr(&self) -> *const Py<T> {
144        self as *const _ as *const _
145    }
146
147    #[inline]
148    pub fn to_owned(&'static self) -> PyRef<T> {
149        unsafe { (*(&self as *const _ as *const PyRef<T>)).clone() }
150    }
151
152    #[inline]
153    pub fn to_object(&'static self) -> PyObjectRef {
154        self.to_owned().into()
155    }
156}
157
158impl<T: PyPayload> Borrow<PyObject> for PyInterned<T> {
159    #[inline(always)]
160    fn borrow(&self) -> &PyObject {
161        self.inner.borrow()
162    }
163}
164
165// NOTE: std::hash::Hash of Self and Self::Borrowed *must* be the same
166// This is ok only because PyObject doesn't implement Hash
167impl<T: PyPayload> std::hash::Hash for PyInterned<T> {
168    #[inline(always)]
169    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
170        self.get_id().hash(state)
171    }
172}
173
174impl<T: PyPayload> AsRef<Py<T>> for PyInterned<T> {
175    #[inline(always)]
176    fn as_ref(&self) -> &Py<T> {
177        &self.inner
178    }
179}
180
181impl<T: PyPayload> Deref for PyInterned<T> {
182    type Target = Py<T>;
183    #[inline(always)]
184    fn deref(&self) -> &Self::Target {
185        &self.inner
186    }
187}
188
189impl<T: PyPayload> PartialEq for PyInterned<T> {
190    #[inline(always)]
191    fn eq(&self, other: &Self) -> bool {
192        std::ptr::eq(self, other)
193    }
194}
195
196impl<T: PyPayload> Eq for PyInterned<T> {}
197
198impl<T: PyPayload + std::fmt::Debug> std::fmt::Debug for PyInterned<T> {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        std::fmt::Debug::fmt(&**self, f)?;
201        write!(f, "@{:p}", self.as_ptr())
202    }
203}
204
205impl<T: PyPayload> ToPyObject for &'static PyInterned<T> {
206    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
207        self.to_owned().into()
208    }
209}
210
211mod sealed {
212    use crate::{
213        builtins::PyStr,
214        object::{Py, PyExact, PyRefExact},
215    };
216
217    pub trait SealedInternable {}
218
219    impl SealedInternable for String {}
220    impl SealedInternable for &str {}
221    impl SealedInternable for PyRefExact<PyStr> {}
222
223    pub trait SealedMaybeInterned {}
224
225    impl SealedMaybeInterned for str {}
226    impl SealedMaybeInterned for PyExact<PyStr> {}
227    impl SealedMaybeInterned for Py<PyStr> {}
228}
229
230/// A sealed marker trait for `DictKey` types that always become an exact instance of `str`
231pub trait InternableString
232where
233    Self: sealed::SealedInternable + ToPyObject + AsRef<Self::Interned>,
234    Self::Interned: MaybeInternedString,
235{
236    type Interned: ?Sized;
237    fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr>;
238}
239
240impl InternableString for String {
241    type Interned = str;
242    #[inline]
243    fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
244        let obj = PyRef::new_ref(PyStr::from(self), str_type, None);
245        unsafe { PyRefExact::new_unchecked(obj) }
246    }
247}
248
249impl InternableString for &str {
250    type Interned = str;
251    #[inline]
252    fn into_pyref_exact(self, str_type: PyTypeRef) -> PyRefExact<PyStr> {
253        self.to_owned().into_pyref_exact(str_type)
254    }
255}
256
257impl InternableString for PyRefExact<PyStr> {
258    type Interned = Py<PyStr>;
259    #[inline]
260    fn into_pyref_exact(self, _str_type: PyTypeRef) -> PyRefExact<PyStr> {
261        self
262    }
263}
264
265pub trait MaybeInternedString:
266    AsRef<str> + crate::dictdatatype::DictKey + sealed::SealedMaybeInterned
267{
268    fn as_interned(&self) -> Option<&'static PyStrInterned>;
269}
270
271impl MaybeInternedString for str {
272    #[inline(always)]
273    fn as_interned(&self) -> Option<&'static PyStrInterned> {
274        None
275    }
276}
277
278impl MaybeInternedString for PyExact<PyStr> {
279    #[inline(always)]
280    fn as_interned(&self) -> Option<&'static PyStrInterned> {
281        None
282    }
283}
284
285impl MaybeInternedString for Py<PyStr> {
286    #[inline(always)]
287    fn as_interned(&self) -> Option<&'static PyStrInterned> {
288        if self.as_object().is_interned() {
289            Some(unsafe { std::mem::transmute::<&Py<PyStr>, &PyInterned<PyStr>>(self) })
290        } else {
291            None
292        }
293    }
294}
295
296impl PyObject {
297    #[inline]
298    pub fn as_interned_str(&self, vm: &crate::VirtualMachine) -> Option<&'static PyStrInterned> {
299        let s: Option<&Py<PyStr>> = self.downcast_ref();
300        if self.is_interned() {
301            s.unwrap().as_interned()
302        } else if let Some(s) = s {
303            vm.ctx.interned_str(s.as_str())
304        } else {
305            None
306        }
307    }
308}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.