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