Skip to main content

miniextendr_api/
mx_abi.rs

1//! Rust implementation of the mx_abi C-callable functions.
2//!
3//! These functions are registered with `R_RegisterCCallable` during package init
4//! and loaded by consumer packages via `R_GetCCallable`.
5//!
6//! This module replaces the C implementation that was previously in `mx_abi.c.in`.
7
8use crate::abi::{SIDECAR_INIT_TAG, SidecarInitVTable, mx_erased, mx_tag};
9use crate::gc_protect::OwnedProtect;
10use crate::sys::{
11    R_ClearExternalPtr, R_ExternalPtrAddr, R_ExternalPtrTag, R_MakeExternalPtr, R_PreserveObject,
12    R_RegisterCCallable, R_RegisterCFinalizerEx, Rf_install,
13};
14use crate::{Rboolean, SEXP, SEXPTYPE, SexpExt};
15use std::ffi::CStr;
16use std::sync::OnceLock;
17
18/// Reserved tag for trait-ABI storage, distinct from ordinary `ExternalPtr` boxes.
19const MX_TAG_NAME: &CStr = c"miniextendr::mx_erased";
20
21/// The tag symbol used to identify miniextendr external pointers.
22/// SEXP is Send+Sync so `OnceLock<SEXP>` works directly.
23static MX_TAG: OnceLock<SEXP> = OnceLock::new();
24
25/// Get the miniextendr tag symbol, initializing it if needed.
26///
27/// # Safety
28/// Must be called on R's main thread.
29#[inline]
30pub(crate) unsafe fn get_tag() -> SEXP {
31    *MX_TAG.get_or_init(|| unsafe {
32        let tag = Rf_install(MX_TAG_NAME.as_ptr());
33        R_PreserveObject(tag);
34        tag
35    })
36}
37
38/// Finalizer callback for R's garbage collector.
39///
40/// Called when an external pointer wrapping `mx_erased` is collected.
41/// Invokes the object's drop function to clean up the Rust allocation.
42unsafe extern "C-unwind" fn mx_externalptr_finalizer(ptr: SEXP) {
43    unsafe {
44        if true {
    {
        match (&ptr.type_of(), &crate::SEXPTYPE::EXTPTRSXP) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = ::core::panicking::AssertKind::Eq;
                    ::core::panicking::assert_failed(kind, &*left_val,
                        &*right_val, ::core::option::Option::None);
                }
            }
        }
    };
};debug_assert_eq!(ptr.type_of(), crate::SEXPTYPE::EXTPTRSXP,);
45        let erased = R_ExternalPtrAddr(ptr) as *mut mx_erased;
46        if !erased.is_null() {
47            let base = (*erased).base;
48            if !base.is_null() {
49                ((*base).drop)(erased);
50            }
51        }
52        R_ClearExternalPtr(ptr);
53    }
54}
55
56/// Wrap an erased object pointer in an R external pointer.
57///
58/// Registered as `"mx_wrap"` via `R_RegisterCCallable`.
59///
60/// # Safety
61///
62/// `ptr` must point to a valid `mx_erased` allocated by a miniextendr constructor.
63/// Must be called on R's main thread inside an R unwind boundary. R-backed
64/// pending sidecar values must stay rooted until this returns. The returned
65/// external pointer is unprotected; root it before the next allocation.
66#[unsafe(no_mangle)]
67pub unsafe extern "C-unwind" fn mx_wrap(ptr: *mut mx_erased) -> SEXP {
68    unsafe {
69        let tag = get_tag();
70        let guard = OwnedProtect::new(R_MakeExternalPtr(ptr.cast(), tag, SEXP::nil()));
71        R_RegisterCFinalizerEx(guard.get(), Some(mx_externalptr_finalizer), Rboolean::TRUE);
72        // Query the concrete allocation's initializer, without interpreting
73        // its Rust fields or changing the shared base-vtable layout.
74        let init = ((*(*ptr).base).query)(ptr, SIDECAR_INIT_TAG).cast::<SidecarInitVTable>();
75        if !init.is_null() {
76            ((*init).init)(ptr, guard.get());
77        }
78        guard.get()
79    }
80}
81
82/// Extract an erased object pointer from an R external pointer.
83///
84/// Returns null if the SEXP is not an external pointer or doesn't carry
85/// the miniextendr tag.
86///
87/// Registered as `"mx_get"` via `R_RegisterCCallable`.
88///
89/// # Safety
90///
91/// `sexp` must be a valid SEXP. Must be called on R's main thread.
92#[unsafe(no_mangle)]
93pub unsafe extern "C-unwind" fn mx_get(sexp: SEXP) -> *mut mx_erased {
94    unsafe {
95        if sexp.type_of() != SEXPTYPE::EXTPTRSXP {
96            return std::ptr::null_mut();
97        }
98        if R_ExternalPtrTag(sexp) != get_tag() {
99            return std::ptr::null_mut();
100        }
101        R_ExternalPtrAddr(sexp) as *mut mx_erased
102    }
103}
104
105/// Query an object for an interface vtable by tag.
106///
107/// Returns the vtable pointer, or null if the type does not implement
108/// the requested trait.
109///
110/// Registered as `"mx_query"` via `R_RegisterCCallable`.
111///
112/// # Safety
113///
114/// `sexp` must be a valid SEXP. Must be called on R's main thread.
115#[unsafe(no_mangle)]
116pub unsafe extern "C-unwind" fn mx_query(sexp: SEXP, tag: mx_tag) -> *const std::ffi::c_void {
117    unsafe {
118        let erased = mx_get(sexp);
119        if erased.is_null() {
120            return std::ptr::null();
121        }
122        let base = (*erased).base;
123        if base.is_null() {
124            return std::ptr::null();
125        }
126        ((*base).query)(erased, tag)
127    }
128}
129
130/// Register the mx_* C-callables with R.
131///
132/// Called during package init (`R_init_*`) to make `mx_wrap`, `mx_get`,
133/// and `mx_query` available to consumer packages via `R_GetCCallable`.
134///
135/// # Safety
136///
137/// Must be called from R's main thread during package initialization.
138/// `pkg_name` must be a valid null-terminated C string.
139pub unsafe fn mx_abi_register(pkg_name: &CStr) {
140    unsafe {
141        // Initialize the tag symbol
142        get_tag();
143
144        // Register C-callables for cross-package access.
145        // Cast function pointers to DL_FUNC (Option<unsafe extern "C-unwind" fn() -> *mut c_void>).
146        R_RegisterCCallable(
147            pkg_name.as_ptr(),
148            c"mx_wrap".as_ptr(),
149            Some(std::mem::transmute::<
150                unsafe extern "C-unwind" fn(*mut mx_erased) -> SEXP,
151                unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
152            >(mx_wrap)),
153        );
154        R_RegisterCCallable(
155            pkg_name.as_ptr(),
156            c"mx_get".as_ptr(),
157            Some(std::mem::transmute::<
158                unsafe extern "C-unwind" fn(SEXP) -> *mut mx_erased,
159                unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
160            >(mx_get)),
161        );
162        R_RegisterCCallable(
163            pkg_name.as_ptr(),
164            c"mx_query".as_ptr(),
165            Some(std::mem::transmute::<
166                unsafe extern "C-unwind" fn(SEXP, mx_tag) -> *const std::ffi::c_void,
167                unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
168            >(mx_query)),
169        );
170    }
171}