rune/core/object/
symbol.rs

1use crate::core::env::sym::BUILTIN_SYMBOLS;
2use crate::core::gc::{Block, Context, GcHeap, GcMoveable, GcState, Trace, TracePtr};
3use crate::core::object::{CloneIn, Function, FunctionType, Gc, IntoObject, TagType, WithLifetime};
4use anyhow::{Result, bail};
5use std::cell::Cell;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::marker::PhantomData;
9use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
10
11/// The allocation of a global symbol. This is shared between threads, so the
12/// interned value of a symbol will be the same location no matter which thread
13/// interned it. Functions are safe to share between threads because they are
14/// marked immutable by
15/// [`SymbolMap::set_func`](`crate::core::env::SymbolMap::set_func`) and they
16/// can only be replaced atomically. In order to garbage collect the function we
17/// need to halt all running threads. This has not been implemented yet.
18pub(crate) struct SymbolCell(GcHeap<SymbolCellData>);
19
20struct SymbolCellData {
21    name: SymbolName,
22    // We can't use AtomicCell due to this issue:
23    // https://github.com/crossbeam-rs/crossbeam/issues/748
24    func: Option<AtomicPtr<u8>>,
25    special: AtomicBool,
26}
27
28#[derive(Debug)]
29enum SymbolName {
30    Interned(&'static str),
31    Uninterned(Cell<&'static str>),
32}
33
34impl SymbolName {
35    fn as_bytes(&self) -> &[u8] {
36        match self {
37            SymbolName::Interned(x) => x.as_bytes(),
38            SymbolName::Uninterned(x) => x.get().as_bytes(),
39        }
40    }
41}
42
43unsafe impl Sync for SymbolName {}
44
45#[derive(PartialEq, Eq, Copy, Clone)]
46pub(crate) struct Symbol<'a> {
47    // This is *NOT* a pointer but an offset from the start of the symbol table
48    data: *const u8,
49    marker: PhantomData<&'a SymbolCell>,
50}
51
52impl std::ops::Deref for Symbol<'_> {
53    type Target = SymbolCell;
54
55    fn deref(&self) -> &Self::Target {
56        self.get()
57    }
58}
59
60impl<'a> Symbol<'a> {
61    pub(crate) fn get(self) -> &'a SymbolCell {
62        unsafe {
63            let base = BUILTIN_SYMBOLS.as_ptr().addr();
64            let ptr = self.data.map_addr(|x| x.wrapping_add(base)).cast::<SymbolCell>();
65            // If type was a static symbol then we need to give it provenance
66            if BUILTIN_SYMBOLS.as_ptr_range().contains(&ptr) {
67                &*BUILTIN_SYMBOLS.as_ptr().with_addr(ptr.addr())
68            } else {
69                &*ptr
70            }
71        }
72    }
73
74    pub(in crate::core) fn as_ptr(self) -> *const u8 {
75        self.data.cast()
76    }
77
78    pub(in crate::core) unsafe fn from_offset_ptr(ptr: *const u8) -> Self {
79        Self { data: ptr.cast(), marker: PhantomData }
80    }
81
82    pub(in crate::core) unsafe fn from_ptr(ptr: *const SymbolCell) -> Self {
83        let base = BUILTIN_SYMBOLS.as_ptr().addr();
84        let ptr = ptr.map_addr(|x| (x.wrapping_sub(base)));
85        Self { data: ptr.cast::<u8>(), marker: PhantomData }
86    }
87
88    pub(in crate::core) const fn new_builtin(idx: usize) -> Self {
89        let ptr = core::ptr::without_provenance(idx * size_of::<SymbolCell>());
90        Self { data: ptr, marker: PhantomData }
91    }
92
93    pub(crate) fn make_special(self) {
94        self.0.special.store(true, Ordering::Release);
95    }
96
97    pub(crate) fn is_special(self) -> bool {
98        self.0.special.load(Ordering::Acquire)
99    }
100}
101
102unsafe impl Send for Symbol<'_> {}
103
104// implement withlifetime for symbol
105impl<'new> WithLifetime<'new> for Symbol<'_> {
106    type Out = Symbol<'new>;
107
108    unsafe fn with_lifetime(self) -> Self::Out {
109        unsafe { std::mem::transmute(self) }
110    }
111}
112
113impl TracePtr for Symbol<'_> {
114    fn trace_ptr(&self, state: &mut GcState) {
115        self.get().trace(state);
116    }
117}
118
119impl<'a> GcMoveable for Symbol<'a> {
120    type Value = Symbol<'a>;
121
122    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
123        let val = self.get().0.move_value(to_space);
124        val.map(|(ptr, moved)| {
125            let symbol = unsafe {
126                // SAFETY: They share the same representation
127                let ptr = ptr.cast::<SymbolCell>();
128                Self::from_ptr(ptr.as_ptr())
129            };
130            (symbol, moved)
131        })
132    }
133}
134
135impl Trace for SymbolCell {
136    fn trace(&self, state: &mut GcState) {
137        if let SymbolName::Uninterned(name) = &self.0.name {
138            let new = state.to_space.alloc_str(name.get());
139            let new = unsafe { std::mem::transmute::<&str, &'static str>(new) };
140            name.set(new);
141        }
142        // The function cell of the symbol is always cloned in the global symbol
143        // map
144    }
145}
146
147impl fmt::Display for Symbol<'_> {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        write!(f, "{}", self.name())
150    }
151}
152
153impl fmt::Debug for Symbol<'_> {
154    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155        write!(f, "{}", self.name())
156    }
157}
158
159impl Hash for Symbol<'_> {
160    fn hash<H: Hasher>(&self, state: &mut H) {
161        self.data.hash(state);
162    }
163}
164
165impl<'new> Symbol<'_> {
166    pub(in crate::core) fn clone_in<const C: bool>(
167        self,
168        bk: &'new crate::core::gc::Block<C>,
169    ) -> Gc<Symbol<'new>> {
170        if let SymbolName::Uninterned(name) = &self.0.name {
171            match bk.uninterned_symbol_map.get(self) {
172                Some(new) => new.tag(),
173                None => {
174                    let sym = Symbol::new_uninterned(name.get(), bk);
175                    if let Some(old_func) = self.get().get() {
176                        let new_func = old_func.clone_in(bk);
177                        unsafe {
178                            sym.set_func(new_func).unwrap();
179                        }
180                    }
181                    let new = sym.into_obj(bk);
182                    bk.uninterned_symbol_map.insert(self, new.untag());
183                    new
184                }
185            }
186        } else {
187            unsafe { self.with_lifetime().tag() }
188        }
189    }
190}
191
192// Since symbols are globally unique we can
193// compare them with a pointer equal test.
194impl PartialEq for SymbolCell {
195    fn eq(&self, other: &Self) -> bool {
196        std::ptr::eq(self, other)
197    }
198}
199
200impl<'ob> Symbol<'ob> {
201    pub(in crate::core) fn new(name: &'static str, block: &'ob Block<true>) -> Self {
202        SymbolCell::new_normal(name, block).into_obj(block).untag()
203    }
204
205    pub(crate) fn new_uninterned<const C: bool>(name: &str, block: &'ob Block<C>) -> Self {
206        SymbolCell::new_uninterned(name, block).into_obj(block).untag()
207    }
208}
209
210impl SymbolCell {
211    const NULL: *mut u8 = std::ptr::null_mut();
212    #[expect(clippy::declare_interior_mutable_const)]
213    const EMTPTY: AtomicPtr<u8> = AtomicPtr::new(Self::NULL);
214
215    pub(crate) fn as_bytes(&self) -> &[u8] {
216        self.0.name.as_bytes()
217    }
218
219    fn new_normal(name: &'static str, block: &Block<true>) -> Self {
220        // We have to do this workaround because starts_with is not const
221        if name.as_bytes()[0] == b':' {
222            Self::new_const(name, block)
223        } else {
224            Self(GcHeap::new(
225                SymbolCellData {
226                    name: SymbolName::Interned(name),
227                    func: Some(Self::EMTPTY),
228                    special: AtomicBool::new(false),
229                },
230                true,
231            ))
232        }
233    }
234
235    pub(in crate::core) const fn new_static(name: &'static str) -> Self {
236        // We have to do this workaround because starts_with is not const
237        if name.as_bytes()[0] == b':' {
238            Self::new_static_const(name)
239        } else {
240            Self(GcHeap::new_pure(SymbolCellData {
241                name: SymbolName::Interned(name),
242                func: Some(Self::EMTPTY),
243                special: AtomicBool::new(false),
244            }))
245        }
246    }
247
248    pub(in crate::core) const fn new_static_special(name: &'static str) -> Self {
249        Self(GcHeap::new_pure(SymbolCellData {
250            name: SymbolName::Interned(name),
251            func: Some(Self::EMTPTY),
252            special: AtomicBool::new(true),
253        }))
254    }
255
256    fn new_const(name: &'static str, _block: &Block<true>) -> Self {
257        Self(GcHeap::new(
258            SymbolCellData {
259                name: SymbolName::Interned(name),
260                func: None,
261                special: AtomicBool::new(true),
262            },
263            true,
264        ))
265    }
266
267    pub(in crate::core) const fn new_static_const(name: &'static str) -> Self {
268        Self(GcHeap::new_pure(SymbolCellData {
269            name: SymbolName::Interned(name),
270            func: None,
271            special: AtomicBool::new(true),
272        }))
273    }
274
275    fn new_uninterned<const C: bool>(name: &str, bk: &Block<C>) -> Self {
276        let mut owned_name = bk.string_with_capacity(name.len());
277        owned_name.push_str(name);
278        let name = unsafe { std::mem::transmute::<&str, &'static str>(owned_name.into_bump_str()) };
279        Self(GcHeap::new(
280            SymbolCellData {
281                name: SymbolName::Uninterned(Cell::new(name)),
282                func: Some(Self::EMTPTY),
283                special: AtomicBool::new(false),
284            },
285            C,
286        ))
287    }
288
289    pub(crate) fn name(&self) -> &str {
290        match &self.0.name {
291            SymbolName::Interned(x) => x,
292            SymbolName::Uninterned(x) => x.get(),
293        }
294    }
295
296    pub(crate) fn interned(&self) -> bool {
297        matches!(self.0.name, SymbolName::Interned(_))
298    }
299
300    #[inline(always)]
301    /// Check if the symbol is constant like nil, t, or :keyword
302    pub(crate) fn is_const(&self) -> bool {
303        self.0.func.is_none()
304    }
305
306    pub(crate) fn has_func(&self) -> bool {
307        match &self.0.func {
308            Some(func) => !func.load(Ordering::Acquire).is_null(),
309            None => false,
310        }
311    }
312
313    fn get(&self) -> Option<Function<'_>> {
314        if let Some(func) = &self.0.func {
315            let ptr = func.load(Ordering::Acquire);
316            // nil is represented as zero (null pointer).
317            if !ptr.is_null() {
318                return Some(unsafe { Gc::from_raw_ptr(ptr) });
319            }
320        }
321        None
322    }
323
324    pub(crate) fn func<'a>(&self, _cx: &'a Context) -> Option<Function<'a>> {
325        self.get().map(|x| unsafe { x.with_lifetime() })
326    }
327
328    /// Follow the chain of symbols to find the function at the end, if any.
329    pub(crate) fn follow_indirect<'ob>(&self, cx: &'ob Context) -> Option<Function<'ob>> {
330        let func = self.func(cx)?;
331        match func.untag() {
332            FunctionType::Symbol(sym) => sym.follow_indirect(cx),
333            _ => Some(func),
334        }
335    }
336
337    /// Set the function for this symbol. This function is unsafe to call and
338    /// requires that the caller:
339    /// 1. Has marked the entire function as read only
340    /// 2. Has cloned the function into the `SymbolMap` block
341    /// 3. Ensured the symbol is not constant
342    pub(in crate::core) unsafe fn set_func(&self, func: Function) -> Result<()> {
343        let Some(fn_cell) = self.0.func.as_ref() else {
344            bail!("Attempt to set a constant symbol: {self}")
345        };
346        let val = func.into_ptr().cast_mut();
347        fn_cell.store(val, Ordering::Release);
348        Ok(())
349    }
350
351    pub(crate) fn unbind_func(&self) {
352        if let Some(func) = &self.0.func {
353            func.store(Self::NULL, Ordering::Release);
354        }
355    }
356}
357
358impl fmt::Display for SymbolCell {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        write!(f, "{}", self.name())
361    }
362}
363
364impl fmt::Debug for SymbolCell {
365    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
366        write!(f, "{}", self.name())
367    }
368}
369
370/// When copying uninterned symbols, we need to ensure that all instances share
371/// the same address if they did originally. This keeps a mapping from old
372/// symbols to new.
373#[derive(Default)]
374pub(in crate::core) struct UninternedSymbolMap {
375    map: std::cell::RefCell<Vec<(Symbol<'static>, Symbol<'static>)>>,
376}
377
378impl UninternedSymbolMap {
379    fn get<'a>(&'a self, symbol: Symbol) -> Option<Symbol<'a>> {
380        self.map.borrow().iter().find(|x| x.0 == symbol).map(|x| x.1)
381    }
382
383    fn insert(&self, old: Symbol, new: Symbol) {
384        self.map
385            .borrow_mut()
386            .push(unsafe { (old.with_lifetime(), new.with_lifetime()) });
387    }
388
389    pub(in crate::core) fn clear(&self) {
390        self.map.borrow_mut().clear();
391    }
392}