Skip to main content

nros_rmw_cffi/
section.rs

1//! RMW backend self-registration (Phase 249 P4b.1 — `.init_array` ctor).
2//!
3//! Every `nros-rmw-<name>` crate (or C/C++ static lib) self-registers
4//! its vtable with the cffi registry. The trigger differs by tier:
5//!
6//! - **Hosted (Rust + C/C++):** the backend emits a `#[used]` ctor
7//!   function pointer into the platform loader's pre-`main` init
8//!   section (`.init_array` on ELF). The loader fires every ctor before `main`, so each
9//!   backend's [`crate::nros_rmw_cffi_register_named`] call has already
10//!   run by the time the runtime opens a session. No runtime walk, no
11//!   `linkme` distributed slice.
12//!
13//! - **Embedded (`target_os = "none"`):** the ctor expands to nothing.
14//!   Bare-metal firmware has no loader that walks `.init_array` in the
15//!   shape the registry needs, so the board / typed carrier calls
16//!   `nros_rmw_<x>::register()` EXPLICITLY (phase-249 P1). The RTOS
17//!   targets (NuttX / Zephyr / ESP-IDF / VxWorks) keep that explicit
18//!   call too; the ctor there is harmless (register is idempotent).
19//!
20//! The native app keeps its `#[used] __FORCE_LINK_*` anchor (phase-244
21//! D7 Shape B): the anchor defeats dead-code elimination so the backend
22//! closure AND its ctor survive into the final binary — it is NOT a
23//! registration call.
24//!
25//! This replaces the phase-128 `linkme` distributed-slice walker
26//! (`RMW_INIT_ENTRIES` + `nros_rmw_cffi_walk_init_section`), removed in
27//! phase-249 P4b.1 (RFC-0042 §D3.3).
28//!
29//! # C / C++ backends
30//!
31//! Static-lib backends emit their entry via the
32//! [`NROS_RMW_REGISTER_BACKEND`] macro in `<nros/rmw_vtable.h>`; the
33//! cmake `nano_ros_link_rmw` strong stub handles the C/C++-via-cmake
34//! path (phase-249 P2b/P4a).
35//!
36//! [`NROS_RMW_REGISTER_BACKEND`]: ../../include/nros/rmw_vtable.h
37
38/// Macro emitted by backend crates to self-register their vtable.
39///
40/// On hosted targets (`not(target_os = "none")`) it expands to a
41/// `#[used]` ctor function pointer landed in the loader's pre-`main`
42/// init section (`.init_array` on ELF). The loader invokes the ctor before `main`; the ctor body is
43/// `$body`, which calls the backend's `register()`. On embedded
44/// (`target_os = "none"`) it expands to nothing — the board calls
45/// `register()` explicitly.
46///
47/// Usage (inside the backend crate):
48///
49/// ```ignore
50/// nros_rmw_cffi::nros_rmw_register_backend! {
51///     fn() { let _ = nros_rmw_zenoh_register(); }
52/// }
53/// ```
54#[macro_export]
55macro_rules! nros_rmw_register_backend {
56    (fn() $body:block) => {
57        // Hosted only. Bare-metal (`target_os = "none"`) has no loader
58        // that fires `.init_array` for the registry, so the carrier
59        // calls `register()` explicitly and the macro emits nothing.
60        #[cfg(not(target_os = "none"))]
61        const _: () = {
62            unsafe extern "C" fn __nros_rmw_backend_auto_register() $body
63
64            // The loader walks this section before transferring control
65            // to `main`, invoking every fn pointer found there.
66            // `#[used]` keeps the symbol against `--gc-sections`.
67            // macOS/Apple dropped (phase-260) — ELF `.init_array` only.
68            #[used]
69            #[unsafe(link_section = ".init_array")]
70            static __NROS_RMW_BACKEND_AUTO_REGISTER_CTOR: unsafe extern "C" fn() =
71                __nros_rmw_backend_auto_register;
72        };
73    };
74}