1#![allow(clippy::undocumented_unsafe_blocks)]
3
4#[allow(unused_imports, reason = "conditionally used")]
6use crate::platform::prelude::*;
7use core::{
8 cell::UnsafeCell,
9 ffi::CStr,
10 ffi::{c_int, c_void},
11 marker::PhantomData,
12};
13
14#[cfg(all(
15 not(any(PyPy, GraalPy)),
16 not(all(windows, Py_LIMITED_API, not(Py_3_10))),
17))]
18use core::sync::atomic::Ordering;
19
20#[cfg(all(
21 not(any(PyPy, GraalPy)),
22 not(all(windows, Py_LIMITED_API, not(Py_3_10))),
23 target_has_atomic = "64",
24))]
25use core::sync::atomic::AtomicI64;
26#[cfg(all(
27 not(any(PyPy, GraalPy)),
28 not(all(windows, Py_LIMITED_API, not(Py_3_10))),
29 not(target_has_atomic = "64"),
30))]
31use portable_atomic::AtomicI64;
32
33#[cfg(not(any(PyPy, GraalPy)))]
34use crate::exceptions::PyImportError;
35use crate::ffi_ptr_ext::FfiPtrExt;
36#[cfg(any(not(all(Py_LIMITED_API, Py_GIL_DISABLED)), Py_3_15))]
37use crate::internal_tricks::array_ptr_as_mut;
38use crate::prelude::PyTypeMethods;
39use crate::{err::error_on_minusone, py_result_ext::PyResultExt};
40use crate::{
41 ffi,
42 impl_::pyfunction::PyFunctionDef,
43 types::{PyModule, PyModuleMethods},
44 Bound, PyClass, PyResult, PyTypeInfo,
45};
46use crate::{
47 sync::PyOnceLock,
48 types::{any::PyAnyMethods, dict::PyDictMethods, PyDict},
49 Py, PyAny, Python,
50};
51
52pub struct ModuleDef {
54 #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
56 ffi_def: UnsafeCell<ffi::PyModuleDef>,
57 name: &'static CStr,
58 #[cfg(Py_3_15)]
59 slots: &'static PyModuleSlots,
60 #[cfg(all(
62 not(any(PyPy, GraalPy)),
63 not(all(windows, Py_LIMITED_API, not(Py_3_10)))
64 ))]
65 interpreter: AtomicI64,
66 module: PyOnceLock<Py<PyModule>>,
68}
69
70unsafe impl Sync for ModuleDef {}
71
72impl ModuleDef {
73 pub const fn new(
75 name: &'static CStr,
76 doc: &'static CStr,
77 slots: &'static PrimaryModuleSlots,
78 secondary_slots: &'static SecondaryModuleSlots,
79 ) -> Self {
80 #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
83 let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
84 m_base: ffi::PyModuleDef_HEAD_INIT,
85 m_name: name.as_ptr(),
86 m_doc: doc.as_ptr(),
87 m_size: 0,
88 m_methods: core::ptr::null_mut(),
89 m_slots: array_ptr_as_mut({
90 cfg_select! {
91 Py_3_15 => secondary_slots.0.get(),
92 _ => slots.0.get(),
93 }
94 }),
95 m_traverse: None,
96 m_clear: None,
97 m_free: None,
98 });
99
100 #[cfg(any(not(Py_3_15), all(Py_LIMITED_API, Py_GIL_DISABLED)))]
101 let _ = secondary_slots;
102 #[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
103 let _ = doc;
104
105 ModuleDef {
106 #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
107 ffi_def,
108 name,
109 #[cfg(Py_3_15)]
110 slots,
111 #[cfg(all(
113 not(any(PyPy, GraalPy)),
114 not(all(windows, Py_LIMITED_API, not(Py_3_10)))
115 ))]
116 interpreter: AtomicI64::new(-1),
117 module: PyOnceLock::new(),
118 }
119 }
120
121 #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
122 pub fn init_multi_phase(&'static self) -> *mut ffi::PyObject {
123 unsafe { ffi::PyModuleDef_Init(self.ffi_def.get()) }
124 }
125
126 pub fn make_module(&'static self, py: Python<'_>) -> PyResult<Py<PyModule>> {
128 #[cfg(not(any(PyPy, GraalPy)))]
136 {
137 #[cfg(not(all(windows, Py_LIMITED_API, not(Py_3_10))))]
140 {
141 let current_interpreter =
142 unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) };
143 crate::err::error_on_minusone(py, current_interpreter)?;
144 if let Err(initialized_interpreter) = self.interpreter.compare_exchange(
145 -1,
146 current_interpreter,
147 Ordering::SeqCst,
148 Ordering::SeqCst,
149 ) {
150 if initialized_interpreter != current_interpreter {
151 return Err(PyImportError::new_err(
152 "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
153 ));
154 }
155 }
156 }
157 #[cfg(all(windows, Py_LIMITED_API, not(Py_3_10)))]
158 {
159 if self.module.get(py).is_some() {
163 return Err(PyImportError::new_err(
164 "PyO3 modules compiled for the stable API on Windows targeting Python 3.9 may only be initialized once per interpreter process"
165 ));
166 }
167 }
168 }
169
170 static SIMPLE_NAMESPACE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
174 let simple_ns = SIMPLE_NAMESPACE.import(py, "types", "SimpleNamespace")?;
175
176 let kwargs = PyDict::new(py);
177 kwargs.set_item("name", self.name)?;
178 let spec = simple_ns.call((), Some(&kwargs))?;
179
180 self.module
181 .get_or_try_init(py, || {
182 let module: Bound<'_, PyModule> = unsafe {
185 cfg_select! {
186 Py_3_15 => ffi::PyModule_FromSlotsAndSpec(self.get_slots(), spec.as_ptr()),
187 not(Py_3_15) => ffi::PyModule_FromDefAndSpec(self.ffi_def.get(), spec.as_ptr()),
188 }.assume_owned_or_err(py)
189 .cast_into_unchecked()
190 }?;
191
192 error_on_minusone(py, unsafe {
194 cfg_select! {
195 Py_3_15 => ffi::PyModule_Exec(module.as_ptr()),
196 not(Py_3_15) => ffi::PyModule_ExecDef(module.as_ptr(), self.ffi_def.get()),
197 }
198 })?;
199
200 Ok(module.unbind())
201 })
202 .map(|py_module| py_module.clone_ref(py))
203 }
204
205 #[cfg(Py_3_15)]
206 pub fn get_slots(&'static self) -> *mut ffi::PySlot {
207 array_ptr_as_mut(self.slots.0.get())
208 }
209}
210
211#[cfg(Py_3_15)]
217#[doc(hidden)]
218#[macro_export]
219macro_rules! __pyo3_pymodexport {
220 ($symbol:literal, $def:path) => {
221 #[doc(hidden)]
222 #[export_name = $symbol]
223 pub unsafe extern "C" fn __pyo3_export() -> *mut $crate::ffi::PySlot {
224 $def.get_slots()
225 }
226 };
227}
228
229#[cfg(not(Py_3_15))]
230#[doc(hidden)]
231#[macro_export]
232macro_rules! __pyo3_pymodexport {
233 ($symbol:literal, $def:path) => {};
234}
235
236#[cfg(not(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED)))]
241#[doc(hidden)]
242#[macro_export]
243macro_rules! __pyo3_pyinit {
244 ($symbol:literal, $def:path) => {
245 #[doc(hidden)]
246 #[export_name = $symbol]
247 pub unsafe extern "C" fn __pyo3_init() -> *mut $crate::ffi::PyObject {
248 $def.init_multi_phase()
249 }
250 };
251}
252
253#[cfg(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED))]
254#[doc(hidden)]
255#[macro_export]
256macro_rules! __pyo3_pyinit {
257 ($symbol:literal, $def:path) => {};
258}
259
260pub type ModuleExecSlot = unsafe extern "C" fn(*mut ffi::PyObject) -> c_int;
262
263const MAX_SLOTS: usize =
264 1 +
266 cfg!(Py_3_13) as usize +
268 3 * (cfg!(Py_3_15) as usize);
270const MAX_SLOTS_WITH_TRAILING_NULL: usize = MAX_SLOTS + 1;
271
272#[cfg(Py_3_15)]
274pub type PrimaryModuleSlots = PyModuleSlots;
275#[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
276pub type SecondaryModuleSlots = PyModuleDefSlots;
277
278#[cfg(not(Py_3_15))]
280pub type PrimaryModuleSlots = PyModuleDefSlots;
281#[cfg(not(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED)))))]
282pub type SecondaryModuleSlots = ();
283
284pub const fn secondary_slots(slots: &'static PrimaryModuleSlots) -> SecondaryModuleSlots {
285 cfg_select! {
286 all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))) => PyModuleDefSlots(UnsafeCell::new([
289 ffi::PyModuleDef_Slot {
290 slot: ffi::Py_slot_subslots,
291 value: slots.0.get().cast(),
292 },
293 unsafe { core::mem::zeroed() },
295 ])),
296 _ => { let _ = slots; }
298 }
299}
300
301pub struct PyModuleSlotsBuilder {
305 slots: PrimaryModuleSlots,
307 len: usize,
309}
310
311impl PyModuleSlotsBuilder {
318 #[allow(clippy::new_without_default)]
319 pub const fn new() -> Self {
320 Self {
321 slots: cfg_select! {
322 Py_3_15 => PyModuleSlots(UnsafeCell::new(
323 [unsafe { core::mem::zeroed::<ffi::PySlot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
325 )),
326 _ => PyModuleDefSlots(UnsafeCell::new(
327 [unsafe { core::mem::zeroed::<ffi::PyModuleDef_Slot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
329 ))
330 },
331 len: 0,
332 }
333 }
334
335 pub const fn with_mod_exec(self, exec: ModuleExecSlot) -> Self {
336 #[cfg(not(Py_3_15))]
337 {
338 self.push(ffi::Py_mod_exec, exec as *mut c_void)
339 }
340 #[cfg(Py_3_15)]
341 {
342 self.push_value(unsafe { ffi::PySlot_FUNC(ffi::Py_mod_exec, exec as *mut c_void) })
344 }
345 }
346
347 pub const fn with_gil_used(self, gil_used: bool) -> Self {
348 #[cfg(all(Py_3_13, not(Py_3_15)))]
349 {
350 self.push(
351 ffi::Py_mod_gil,
352 if gil_used {
353 ffi::Py_MOD_GIL_USED
354 } else {
355 ffi::Py_MOD_GIL_NOT_USED
356 },
357 )
358 }
359
360 #[cfg(Py_3_15)]
361 {
362 self.push_value(ffi::PySlot_DATA(
363 ffi::Py_mod_gil,
364 if gil_used {
365 ffi::Py_MOD_GIL_USED
366 } else {
367 ffi::Py_MOD_GIL_NOT_USED
368 },
369 ))
370 }
371
372 #[cfg(not(Py_3_13))]
373 {
374 let _ = gil_used;
376 self
377 }
378 }
379
380 pub const fn with_name(self, name: &'static CStr) -> Self {
381 #[cfg(Py_3_15)]
382 {
383 self.push_value(ffi::PySlot_STATIC_DATA(
384 ffi::Py_mod_name,
385 name.as_ptr() as *mut c_void,
386 ))
387 }
388
389 #[cfg(not(Py_3_15))]
390 {
391 let _ = name;
393 self
394 }
395 }
396
397 pub const fn with_abi_info(self) -> Self {
398 #[cfg(Py_3_15)]
399 {
400 ffi::PyABIInfo_VAR!(ABI_INFO);
401 self.push_value(ffi::PySlot_STATIC_DATA(
402 ffi::Py_mod_abi,
403 (&raw mut ABI_INFO).cast(),
404 ))
405 }
406
407 #[cfg(not(Py_3_15))]
408 {
409 self
410 }
411 }
412
413 pub const fn with_doc(self, doc: &'static CStr) -> Self {
414 #[cfg(Py_3_15)]
415 {
416 self.push_value(ffi::PySlot_STATIC_DATA(
417 ffi::Py_mod_doc,
418 doc.as_ptr() as *mut c_void,
419 ))
420 }
421
422 #[cfg(not(Py_3_15))]
423 {
424 let _ = doc;
426 self
427 }
428 }
429
430 pub const fn build(self) -> PrimaryModuleSlots {
431 self.slots
432 }
433
434 #[cfg(not(Py_3_15))]
435 const fn push(mut self, slot: c_int, value: *mut c_void) -> Self {
436 assert!(
439 self.len < MAX_SLOTS,
440 "Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
441 );
442 self.slots.0.get_mut()[self.len] = ffi::PyModuleDef_Slot { slot, value };
443 self.len += 1;
444 self
445 }
446
447 #[cfg(Py_3_15)]
448 const fn push_value(mut self, value: ffi::PySlot) -> Self {
449 assert!(
450 self.len < MAX_SLOTS,
451 "Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
452 );
453 self.slots.0.get_mut()[self.len] = value;
454 self.len += 1;
455 self
456 }
457}
458
459pub struct PyModuleSlots(
461 #[cfg(Py_3_15)] UnsafeCell<[ffi::PySlot; MAX_SLOTS_WITH_TRAILING_NULL]>,
463);
464
465#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
468pub struct PyModuleDefSlots(
469 UnsafeCell<
470 [ffi::PyModuleDef_Slot; cfg_select! {
471 Py_3_15 => 2,
473 _ => MAX_SLOTS_WITH_TRAILING_NULL
474 }],
475 >,
476);
477
478unsafe impl Sync for PyModuleSlots {}
483#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
486unsafe impl Sync for PyModuleDefSlots {}
487
488pub trait PyAddToModule: crate::sealed::Sealed {
492 fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()>;
493}
494
495pub struct AddTypeToModule<T>(PhantomData<T>);
497
498impl<T> AddTypeToModule<T> {
499 #[allow(clippy::new_without_default)]
500 pub const fn new() -> Self {
501 AddTypeToModule(PhantomData)
502 }
503}
504
505impl<T: PyTypeInfo> PyAddToModule for AddTypeToModule<T> {
506 fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
507 let object = T::type_object(module.py());
508 module.add(object.name()?, object)
509 }
510}
511
512pub struct AddClassToModule<T>(PhantomData<T>);
514
515impl<T> AddClassToModule<T> {
516 #[allow(clippy::new_without_default)]
517 pub const fn new() -> Self {
518 AddClassToModule(PhantomData)
519 }
520}
521
522impl<T: PyClass> PyAddToModule for AddClassToModule<T> {
523 fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
524 module.add_class::<T>()
525 }
526}
527
528impl PyAddToModule for PyFunctionDef {
530 fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
531 module.add_function(self.create_py_c_function(module.py(), Some(module))?)
533 }
534}
535
536impl PyAddToModule for ModuleDef {
538 fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
539 module.add_submodule(self.make_module(module.py())?.bind(module.py()))
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use alloc::borrow::Cow;
546 use core::{ffi::c_int, ffi::CStr};
547
548 use crate::impl_::trampoline;
549
550 use super::*;
551
552 unsafe extern "C" fn module_exec(_module: *mut ffi::PyObject) -> c_int {
553 0
554 }
555
556 #[test]
557 fn module_init() {
558 unsafe extern "C" fn module_exec(module: *mut ffi::PyObject) -> c_int {
559 unsafe {
560 trampoline::module_exec(module, |m| {
561 m.add("SOME_CONSTANT", 42)?;
562 Ok(())
563 })
564 }
565 }
566
567 static NAME: &CStr = c"test_module";
568 static DOC: &CStr = c"some doc";
569
570 static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new()
571 .with_mod_exec(module_exec)
572 .with_gil_used(false)
573 .with_abi_info()
574 .with_name(NAME)
575 .with_doc(DOC)
576 .build();
577
578 static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
579
580 static MODULE_DEF: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
581
582 Python::attach(|py| {
583 let module = MODULE_DEF.make_module(py).unwrap().into_bound(py);
584 assert_eq!(
585 module
586 .getattr("__name__")
587 .unwrap()
588 .extract::<Cow<'_, str>>()
589 .unwrap(),
590 "test_module",
591 );
592 assert_eq!(
593 module
594 .getattr("__doc__")
595 .unwrap()
596 .extract::<Cow<'_, str>>()
597 .unwrap(),
598 "some doc",
599 );
600 assert_eq!(
601 module
602 .getattr("SOME_CONSTANT")
603 .unwrap()
604 .extract::<u8>()
605 .unwrap(),
606 42,
607 );
608 })
609 }
610
611 #[test]
612 fn module_def_new() {
613 static NAME: &CStr = c"test_module";
616 static DOC: &CStr = c"some doc";
617
618 static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new().build();
619 static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
620
621 let module_def: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
622
623 #[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
624 unsafe {
625 let expected_slots = cfg_select! {
626 Py_3_15 => SECONDARY_SLOTS.0.get().cast(),
627 _ => SLOTS.0.get().cast(),
628 };
629 assert_eq!((*module_def.ffi_def.get()).m_slots, expected_slots);
630 }
631 #[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
632 unsafe {
633 let secondary_slots = &*SECONDARY_SLOTS.0.get();
634 assert_eq!(secondary_slots[0].slot, ffi::Py_slot_subslots);
635 assert_eq!(secondary_slots[0].value, SLOTS.0.get().cast());
636 assert!(secondary_slots[1] == ffi::PyModuleDef_Slot::default());
637 }
638
639 assert_eq!(module_def.name, NAME);
640 }
641
642 #[test]
643 #[cfg(panic = "unwind")]
644 fn test_build_maximal_slots() {
645 let mut builder = PyModuleSlotsBuilder::new()
646 .with_mod_exec(module_exec)
647 .with_name(c"test_module")
648 .with_doc(c"some doc")
649 .with_gil_used(false)
650 .with_abi_info();
651
652 #[cfg(Py_3_15)]
653 {
654 let second_last = builder.slots.0.get_mut()[builder.len - 1];
655 let last = builder.slots.0.get_mut()[builder.len];
656 let zeroed = unsafe { core::mem::zeroed() };
657 fn raw_bytes(inst: &ffi::PySlot) -> &[u8] {
658 unsafe {
659 core::slice::from_raw_parts(
660 inst as *const ffi::PySlot as *const u8,
661 core::mem::size_of::<ffi::PySlot>(),
662 )
663 }
664 }
665 let zeroed_bytes = raw_bytes(&zeroed);
666 assert_eq!(raw_bytes(&last), zeroed_bytes);
667 assert_ne!(raw_bytes(&second_last), zeroed_bytes);
668 }
669 #[cfg(not(Py_3_15))]
670 {
671 let second_last = builder.slots.0.get_mut()[builder.len - 1];
672 let last = builder.slots.0.get_mut()[builder.len];
673 let zeroed = ffi::PyModuleDef_Slot::default();
674 assert!(last == zeroed);
675 assert!(second_last != zeroed);
676 }
677 assert!(builder.len == MAX_SLOTS);
678
679 let result = std::panic::catch_unwind(|| builder.with_mod_exec(module_exec).build());
680
681 assert!(result.is_err());
682 }
683
684 #[test]
685 #[should_panic]
686 fn test_module_slots_builder_overflow() {
687 let mut builder = PyModuleSlotsBuilder::new();
688 for _ in 0..MAX_SLOTS + 1 {
689 builder = builder.with_mod_exec(module_exec);
690 }
691 }
692}