jlrs/data/
static_data.rs

1//! Static references to global Julia data.
2//!
3//! Accessing global Julia data through the module system can be expensive. If the global is a
4//! constant or never replaced with another value, this data is globally rooted so it's safe to
5//! hold on to a reference to this data. This module provides [`StaticGlobal`] and [`StaticRef`],
6//! and macros to create and access them.
7
8use std::{
9    marker::PhantomData,
10    ptr::{NonNull, null_mut},
11    sync::atomic::{AtomicPtr, Ordering},
12};
13
14use jl_sys::{jl_sym_t, jl_symbol_n, jl_value_t};
15
16use super::{
17    cache::Cache,
18    managed::private::ManagedPriv,
19    types::{construct_type::ConstructType, typecheck::Typecheck},
20};
21use crate::{
22    data::managed::{Managed, module::Module, value::ValueUnbound},
23    gc_safe::GcSafeOnceLock,
24    memory::{PTls, target::Target},
25    prelude::{Symbol, Value},
26    private::Private,
27};
28
29type CacheImpl = Cache<()>;
30
31static CACHE: CacheImpl = CacheImpl::new(());
32
33pub(crate) unsafe fn mark_static_data_cache(ptls: PTls, full: bool) {
34    unsafe {
35        CACHE.mark(ptls, full);
36    }
37}
38
39struct StaticDataInner<T>(ValueUnbound, PhantomData<T>);
40unsafe impl<T> Send for StaticDataInner<T> {}
41unsafe impl<T> Sync for StaticDataInner<T> {}
42
43/// Static reference to arbitrary managed data. Guaranteed to be initialized at most once.
44pub struct StaticGlobal<T> {
45    global: GcSafeOnceLock<StaticDataInner<T>>,
46    path: &'static str,
47}
48
49impl<T> StaticGlobal<T>
50where
51    T: Managed<'static, 'static> + Typecheck,
52{
53    /// Define a new static global available at `path`.
54    ///
55    /// The global is looked up only once when this data is accessedd for the first time. The
56    /// `path` argument must be the full path to the data, e.g. `"Main.Submodule.Foo"`.
57    #[inline]
58    pub const fn new(path: &'static str) -> StaticGlobal<T> {
59        StaticGlobal {
60            global: GcSafeOnceLock::new(),
61            path,
62        }
63    }
64
65    /// Get the global data, look it up if it doesn't exist yet.
66    ///
67    /// The global must exist and be an instance of `T`. Otherwise this method will panic.
68    #[inline]
69    pub fn get_or_init<'target, Tgt>(&self, target: &Tgt) -> T
70    where
71        Tgt: Target<'target>,
72    {
73        unsafe {
74            if let Some(global) = self.global.get() {
75                return global.0.cast_unchecked::<T>();
76            } else {
77                self.init(target)
78            }
79        }
80    }
81
82    #[inline(never)]
83    #[cold]
84    unsafe fn init<'target, Tgt>(&self, target: &Tgt) -> T
85    where
86        Tgt: Target<'target>,
87    {
88        unsafe {
89            // If multiple threads try to initialize the global, only one calls the init code and
90            // the others are parked. We call jlrs_gc_safe_enter to allow the GC to run while a
91            // thread is parked, and immediately transition back once we regain control.
92            let global = self.global.get_or_init(|| {
93                let split_path = self.path.split('.').collect::<Vec<_>>();
94                let n_parts = split_path.len();
95
96                let mut module = match split_path[0] {
97                    "Main" => Module::main(target),
98                    "Base" => Module::base(target),
99                    "Core" => Module::core(target),
100                    pkg => Module::package_root_module(target, pkg).unwrap(),
101                };
102
103                if n_parts == 1 {
104                    let global = module.leak().as_value().cast::<T>().unwrap();
105                    return StaticDataInner(global.as_value(), PhantomData);
106                }
107
108                for i in 1..n_parts - 1 {
109                    module = module
110                        .submodule(target, split_path[i])
111                        .unwrap()
112                        .as_managed();
113                }
114
115                let global = module
116                    .global(target, split_path[n_parts - 1])
117                    .unwrap()
118                    .leak()
119                    .as_value()
120                    .cast::<T>()
121                    .unwrap();
122
123                CACHE.write(|cache| {
124                    cache.roots_mut().insert(global.as_value());
125                });
126
127                return StaticDataInner(global.as_value(), PhantomData);
128            });
129
130            global.0.cast_unchecked()
131        }
132    }
133}
134
135impl StaticGlobal<ValueUnbound> {
136    /// Define a new static global available at `path`.
137    ///
138    /// The global is looked up only once when this data is accessedd for the first time. The
139    /// `path` argument must be the full path to the data, e.g. `"Main.Submodule.Foo"`.
140    pub const fn new_value(path: &'static str) -> StaticGlobal<ValueUnbound> {
141        StaticGlobal {
142            global: GcSafeOnceLock::new(),
143            path,
144        }
145    }
146}
147
148/// Static reference to arbitrary managed data. Can be initialized multiple times.
149///
150/// In general, a `StaticRef` is faster than a `StaticGlobal`.
151pub struct StaticSymbolRef {
152    sym: AtomicPtr<jl_sym_t>,
153    sym_s: &'static str,
154}
155
156impl StaticSymbolRef {
157    /// Define a new static symbol.
158    ///
159    /// The symbol is initialized the first time `get_or_init` is called. The `sym_s` argument
160    /// must be the symbol as a static string.
161    #[inline]
162    pub const fn new(sym_s: &'static str) -> StaticSymbolRef {
163        StaticSymbolRef {
164            sym: AtomicPtr::new(null_mut()),
165            sym_s,
166        }
167    }
168
169    /// Get the symbol, create it if it doesn't exist yet.
170    #[inline]
171    pub fn get_or_init<'target, Tgt>(&self, target: &Tgt) -> Symbol<'target>
172    where
173        Tgt: Target<'target>,
174    {
175        let ptr = self.sym.load(Ordering::Relaxed);
176        if ptr.is_null() {
177            // It's fine to initialize this multiple times. We're going to store the same data each time.
178            self.init(target)
179        } else {
180            unsafe { Symbol::wrap_non_null(NonNull::new_unchecked(ptr), Private) }
181        }
182    }
183
184    #[cold]
185    #[inline(never)]
186    fn init<'target, Tgt>(&self, _: &Tgt) -> Symbol<'target>
187    where
188        Tgt: Target<'target>,
189    {
190        unsafe {
191            let bytes = self.sym_s.as_bytes();
192            let n_bytes = bytes.len();
193            let bytes_ptr = bytes.as_ptr().cast();
194
195            let sym = jl_symbol_n(bytes_ptr, n_bytes);
196            self.sym.store(sym, Ordering::Relaxed);
197
198            Symbol::wrap_non_null(NonNull::new_unchecked(sym), Private)
199        }
200    }
201}
202
203/// Static reference to arbitrary managed data. Can be initialized multiple times.
204///
205/// In general, a `StaticRef` is faster than a `StaticGlobal`.
206pub struct StaticRef<T: Managed<'static, 'static>> {
207    global: AtomicPtr<T::Wraps>,
208    path: &'static str,
209}
210
211impl<T> StaticRef<T>
212where
213    T: Managed<'static, 'static> + Typecheck,
214{
215    /// Define a new static ref available at `path`.
216    ///
217    /// The global is looked up if the ref is uninitialized. The `path` argument must be the full
218    /// path to the data, e.g. `"Main.Submodule.Foo"`.
219    #[inline]
220    pub const fn new(path: &'static str) -> StaticRef<T> {
221        StaticRef {
222            global: AtomicPtr::new(null_mut()),
223            path,
224        }
225    }
226
227    /// Get the global data, look it up if it doesn't exist yet.
228    ///
229    /// The global must exist and be an instance of `T`. Otherwise this method will panic.
230    #[inline]
231    pub fn get_or_init<'target, Tgt>(&self, target: &Tgt) -> T
232    where
233        Tgt: Target<'target>,
234    {
235        let ptr = self.global.load(Ordering::Relaxed);
236        if ptr.is_null() {
237            // It's fine to initialize this multiple times. We're going to store the same data each time.
238            self.init(target)
239        } else {
240            unsafe { T::wrap_non_null(NonNull::new_unchecked(ptr), Private) }
241        }
242    }
243
244    #[cold]
245    #[inline(never)]
246    fn init<'target, Tgt>(&self, target: &Tgt) -> T
247    where
248        Tgt: Target<'target>,
249    {
250        unsafe {
251            let split_path = self.path.split('.').collect::<Vec<_>>();
252            let n_parts = split_path.len();
253
254            let mut module = match split_path[0] {
255                "Main" => Module::main(target),
256                "Base" => Module::base(target),
257                "Core" => Module::core(target),
258                pkg => Module::package_root_module(target, pkg).unwrap(),
259            };
260
261            if n_parts == 1 {
262                let global = module.leak().as_value().cast::<T>().unwrap();
263                let ptr = global.unwrap(Private);
264                self.global.store(ptr, Ordering::Relaxed);
265                return T::wrap_non_null(NonNull::new_unchecked(ptr), Private);
266            }
267
268            for i in 1..n_parts - 1 {
269                module = module
270                    .submodule(target, split_path[i])
271                    .unwrap()
272                    .as_managed();
273            }
274
275            let global = module
276                .global(target, split_path[n_parts - 1])
277                .unwrap()
278                .leak()
279                .as_value()
280                .cast::<T>()
281                .unwrap();
282
283            CACHE.write(|cache| {
284                cache.roots_mut().insert(global.as_value());
285            });
286
287            let ptr = global.unwrap(Private);
288            self.global.store(ptr, Ordering::Relaxed);
289            T::wrap_non_null(NonNull::new_unchecked(ptr), Private)
290        }
291    }
292
293    // Safety: The result of the evaluated command must be globally rooted.
294    #[inline]
295    pub(crate) unsafe fn get_or_eval<'target, Tgt>(&self, target: &Tgt) -> T
296    where
297        Tgt: Target<'target>,
298    {
299        unsafe {
300            let ptr = self.global.load(Ordering::Relaxed);
301            if ptr.is_null() {
302                self.eval(target)
303            } else {
304                T::wrap_non_null(NonNull::new_unchecked(ptr), Private)
305            }
306        }
307    }
308
309    // Safety: The result of the evaluated command must be globally rooted.
310    #[cold]
311    #[inline(never)]
312    unsafe fn eval<'target, Tgt>(&self, target: &Tgt) -> T
313    where
314        Tgt: Target<'target>,
315    {
316        unsafe {
317            let v = Value::eval_string(target, self.path)
318                .unwrap()
319                .leak()
320                .as_value()
321                .cast::<T>()
322                .unwrap();
323
324            CACHE.write(|cache| {
325                cache.roots_mut().insert(v.as_value());
326            });
327
328            let ptr = v.unwrap(Private);
329            self.global.store(ptr, Ordering::Relaxed);
330            T::wrap_non_null(NonNull::new_unchecked(ptr), Private)
331        }
332    }
333}
334
335/// Static reference to a constructible type.
336pub struct StaticConstructibleType<T: ConstructType> {
337    global: AtomicPtr<jl_value_t>,
338    _marker: PhantomData<T>,
339}
340
341impl<T> StaticConstructibleType<T>
342where
343    T: ConstructType,
344{
345    /// Define a new static ref for the constructible type `T`.
346    #[inline]
347    pub const fn new() -> StaticConstructibleType<T> {
348        StaticConstructibleType {
349            global: AtomicPtr::new(null_mut()),
350            _marker: PhantomData,
351        }
352    }
353
354    /// Get the constructed type, construct it if it doesn't exist yet.
355    #[inline]
356    pub fn get_or_init<'target, Tgt>(&self, target: &Tgt) -> Value<'target, 'static>
357    where
358        Tgt: Target<'target>,
359    {
360        let ptr = self.global.load(Ordering::Relaxed);
361        if ptr.is_null() {
362            // It's fine to initialize this multiple times. We're going to store the same data each time.
363            self.init(target)
364        } else {
365            unsafe { Value::wrap_non_null(NonNull::new_unchecked(ptr), Private) }
366        }
367    }
368
369    #[cold]
370    #[inline(never)]
371    fn init<'target, Tgt>(&self, target: &Tgt) -> Value<'target, 'static>
372    where
373        Tgt: Target<'target>,
374    {
375        unsafe {
376            let v = T::construct_type(target).as_value();
377            CACHE.write(|cache| {
378                cache.roots_mut().insert(v);
379            });
380            self.global.store(v.unwrap(Private), Ordering::Relaxed);
381
382            v
383        }
384    }
385}
386
387/// Define a static global
388#[macro_export]
389macro_rules! define_static_global {
390    ($(#[$meta:meta])* $vis:vis $ty:ident, $type:ty, $path:expr_2021) => {
391        $(#[$meta])*
392        $vis static $name: $crate::data::static_data::StaticGlobal<$type> =
393            $crate::data::static_data::StaticGlobal::new($path);
394    };
395    ($(#[$meta:meta])* $vis:vis $name:ident, $path:expr_2021) => {
396        $(#[$meta])*
397        $vis static $name: $crate::data::static_data::StaticGlobal<
398            $crate::data::managed::value::ValueUnbound,
399        > = $crate::data::static_data::StaticGlobal::new_value($path);
400    };
401}
402
403/// Define a static ref
404#[macro_export]
405macro_rules! define_static_ref {
406    ($(#[$meta:meta])* $vis:vis $name:ident, $type:ty, $path:expr_2021) => {
407        $(#[$meta])*
408        $vis static $name: $crate::data::static_data::StaticRef<$type> =
409            $crate::data::static_data::StaticRef::new($path);
410    };
411}
412
413/// Define a static symbol
414#[macro_export]
415macro_rules! define_static_symbol_ref {
416    ($(#[$meta:meta])+ $vis:vis $name:ident, $sym:expr_2021) => {
417        $(#[$meta])+
418        $vis static $name: $crate::data::static_data::StaticSymbolRef =
419            $crate::data::static_data::StaticSymbolRef::new($sym);
420    };
421}
422
423/// Use a previously defined static global
424#[macro_export]
425macro_rules! static_global {
426    ($name:ident, $target:expr_2021) => {{ $name.get_or_init(&$target) }};
427}
428/// Use a previously defined static ref
429#[macro_export]
430macro_rules! static_ref {
431    ($name:ident, $target:expr_2021) => {{ $name.get_or_init(&$target) }};
432}
433/// Use a previously defined static ref
434#[macro_export]
435macro_rules! static_symbol_ref {
436    ($name:ident, $target:expr_2021) => {{ $name.get_or_init(&$target) }};
437}
438
439pub use define_static_global;
440pub use define_static_ref;
441pub use define_static_symbol_ref;
442pub use static_global;
443pub use static_ref;
444pub use static_symbol_ref;
445
446/// Define an inline static global.
447///
448/// `inline_static_global!(NAME, T, path, target)` is equivalent to
449/// `{ define_static_global!(NAME, T, path); static_global!(NAME, target) }`
450#[macro_export]
451macro_rules! inline_static_global {
452    ($(#[$meta:meta])* $name:ident, $type:ty, $path:expr_2021, $target:expr_2021) => {{
453        $crate::data::static_data::define_static_global!($(#[$meta])* $name, $type, $path);
454        $crate::data::static_data::static_global!($name, $target)
455    }};
456    ($(#[$meta:meta])* $name:ident, $path:expr_2021, $target:expr_2021) => {{
457        $crate::data::static_data::define_static_global!($(#[$meta])* $name, $path);
458        $crate::data::static_data::static_global!($name, $target)
459    }};
460}
461
462/// Define an inline static ref.
463///
464/// `inline_static_ref!(NAME, T, path, target)` is equivalent to
465/// `{ define_static_ref!(NAME, T, path); static_ref!(NAME, target) }`
466#[macro_export]
467macro_rules! inline_static_ref {
468    ($(#[$meta:meta])* $name:ident, $type:ty, $path:expr_2021, $target:expr_2021) => {{
469        $crate::data::static_data::define_static_ref!($(#[$meta])* $name, $type, $path);
470        $crate::data::static_data::static_ref!($name, $target)
471    }};
472}
473
474/// Define an inline static ref.
475///
476/// `inline_static_ref!(NAME, T, path, target)` is equivalent to
477/// `{ define_static_ref!(NAME, T, path); static_ref!(NAME, target) }`
478#[macro_export]
479macro_rules! inline_static_symbol_ref {
480    ($(#[$meta:meta])* $name:ident, $sym:expr_2021, $target:expr_2021) => {{
481        $crate::data::static_data::define_static_symbol_ref!($(#[$meta])* $name, $sym);
482        $crate::data::static_data::static_symbol_ref!($name, $target)
483    }};
484}
485
486pub use inline_static_global;
487pub use inline_static_ref;
488pub use inline_static_symbol_ref;