Skip to main content

ext_php_rs/zend/
module.rs

1//! Builder and objects for creating modules in PHP. A module is the base of a
2//! PHP extension.
3
4use std::cell::UnsafeCell;
5use std::ffi::CString;
6use std::mem::MaybeUninit;
7use std::sync::Once;
8
9use crate::args::ArgInfo;
10use crate::ffi::zend_module_entry;
11use crate::zend::FunctionEntry;
12
13/// A Zend module entry, also known as an extension.
14pub type ModuleEntry = zend_module_entry;
15
16/// The heap allocations a [`ModuleEntry`] points into.
17///
18/// A C extension puts its `zend_function_entry[]`, name and version in
19/// `.rodata`, so the linker owns them for the life of the process. ext-php-rs
20/// builds them at MINIT from an arbitrary `#[php_module]` body, so this struct
21/// owns them instead, stored inside the extension's own [`StaticModuleEntry`].
22///
23/// None of it can be freed. `module_destructor` walks `module->functions`
24/// reading `fname` *after* MSHUTDOWN for `dl()`-loaded modules,
25/// `get_extension_funcs()` walks it during a request, and
26/// `zend_register_functions` keeps `zend_internal_function.arg_info` pointing
27/// into `arg_info` for the whole process.
28pub struct ModuleAllocations {
29    /// The module's function table, NUL-terminated.
30    pub functions: Box<[FunctionEntry]>,
31    /// Argument info of each module function, one table per function.
32    pub arg_info: Box<[Box<[ArgInfo]>]>,
33    /// The module name handed to PHP.
34    pub name: CString,
35    /// The module version handed to PHP.
36    pub version: CString,
37}
38
39/// Static storage for a [`ModuleEntry`] and everything it points into.
40///
41/// Mimics how C extensions declare a `static zend_module_entry` alongside a
42/// `static zend_function_entry[]`: the extension owns its own tables, and
43/// nothing is shared between extensions.
44pub struct StaticModuleEntry {
45    init: Once,
46    inner: UnsafeCell<MaybeUninit<ModuleEntry>>,
47    owned: UnsafeCell<MaybeUninit<ModuleAllocations>>,
48}
49
50// SAFETY: both cells are written exactly once, inside `Once::call_once`, before
51// PHP has spawned any request thread, and are never read back through this
52// handle afterwards. The raw pointers they hold are handed to the engine, which
53// only reads through them.
54unsafe impl Sync for StaticModuleEntry {}
55
56impl Default for StaticModuleEntry {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl StaticModuleEntry {
63    /// Creates a new uninitialized static module entry.
64    #[must_use]
65    pub const fn new() -> Self {
66        Self {
67            init: Once::new(),
68            inner: UnsafeCell::new(MaybeUninit::uninit()),
69            owned: UnsafeCell::new(MaybeUninit::uninit()),
70        }
71    }
72
73    /// Initialises the entry on first call, returning a stable `*mut` pointer.
74    ///
75    /// `f` returns the entry together with the allocations it points into; both
76    /// are stored here for the life of the process. Subsequent calls skip `f`
77    /// and return the same pointer.
78    pub fn get_or_init(
79        &self,
80        f: impl FnOnce() -> (ModuleEntry, ModuleAllocations),
81    ) -> *mut ModuleEntry {
82        self.init.call_once(|| {
83            let (entry, owned) = f();
84            unsafe {
85                (*self.owned.get()).write(owned);
86                (*self.inner.get()).write(entry);
87            }
88        });
89        unsafe { (*self.inner.get()).as_mut_ptr() }
90    }
91}