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}