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::{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/// The tag symbol used to identify miniextendr external pointers.
19/// SEXP is Send+Sync so `OnceLock<SEXP>` works directly.
20static MX_TAG: OnceLock<SEXP> = OnceLock::new();
21
22/// Get the miniextendr tag symbol, initializing it if needed.
23///
24/// # Safety
25/// Must be called on R's main thread.
26#[inline]
27unsafe fn get_tag() -> SEXP {
28 *MX_TAG.get_or_init(|| unsafe {
29 let tag = Rf_install(c"miniextendr::mx_erased".as_ptr());
30 R_PreserveObject(tag);
31 tag
32 })
33}
34
35/// Finalizer callback for R's garbage collector.
36///
37/// Called when an external pointer wrapping `mx_erased` is collected.
38/// Invokes the object's drop function to clean up the Rust allocation.
39unsafe extern "C-unwind" fn mx_externalptr_finalizer(ptr: SEXP) {
40 unsafe {
41 debug_assert_eq!(ptr.type_of(), crate::SEXPTYPE::EXTPTRSXP,);
42 let erased = R_ExternalPtrAddr(ptr) as *mut mx_erased;
43 if !erased.is_null() {
44 let base = (*erased).base;
45 if !base.is_null() {
46 ((*base).drop)(erased);
47 }
48 }
49 R_ClearExternalPtr(ptr);
50 }
51}
52
53/// Wrap an erased object pointer in an R external pointer.
54///
55/// Registered as `"mx_wrap"` via `R_RegisterCCallable`.
56///
57/// # Safety
58///
59/// `ptr` must point to a valid `mx_erased` allocated by a miniextendr constructor.
60/// Must be called on R's main thread.
61#[unsafe(no_mangle)]
62pub unsafe extern "C-unwind" fn mx_wrap(ptr: *mut mx_erased) -> SEXP {
63 unsafe {
64 let tag = get_tag();
65 let guard = OwnedProtect::new(R_MakeExternalPtr(ptr.cast(), tag, SEXP::nil()));
66 R_RegisterCFinalizerEx(guard.get(), Some(mx_externalptr_finalizer), Rboolean::TRUE);
67 guard.get()
68 }
69}
70
71/// Extract an erased object pointer from an R external pointer.
72///
73/// Returns null if the SEXP is not an external pointer or doesn't carry
74/// the miniextendr tag.
75///
76/// Registered as `"mx_get"` via `R_RegisterCCallable`.
77///
78/// # Safety
79///
80/// `sexp` must be a valid SEXP. Must be called on R's main thread.
81#[unsafe(no_mangle)]
82pub unsafe extern "C-unwind" fn mx_get(sexp: SEXP) -> *mut mx_erased {
83 unsafe {
84 if sexp.type_of() != SEXPTYPE::EXTPTRSXP {
85 return std::ptr::null_mut();
86 }
87 if R_ExternalPtrTag(sexp) != get_tag() {
88 return std::ptr::null_mut();
89 }
90 R_ExternalPtrAddr(sexp) as *mut mx_erased
91 }
92}
93
94/// Query an object for an interface vtable by tag.
95///
96/// Returns the vtable pointer, or null if the type does not implement
97/// the requested trait.
98///
99/// Registered as `"mx_query"` via `R_RegisterCCallable`.
100///
101/// # Safety
102///
103/// `sexp` must be a valid SEXP. Must be called on R's main thread.
104#[unsafe(no_mangle)]
105pub unsafe extern "C-unwind" fn mx_query(sexp: SEXP, tag: mx_tag) -> *const std::ffi::c_void {
106 unsafe {
107 let erased = mx_get(sexp);
108 if erased.is_null() {
109 return std::ptr::null();
110 }
111 let base = (*erased).base;
112 if base.is_null() {
113 return std::ptr::null();
114 }
115 ((*base).query)(erased, tag)
116 }
117}
118
119/// Register the mx_* C-callables with R.
120///
121/// Called during package init (`R_init_*`) to make `mx_wrap`, `mx_get`,
122/// and `mx_query` available to consumer packages via `R_GetCCallable`.
123///
124/// # Safety
125///
126/// Must be called from R's main thread during package initialization.
127/// `pkg_name` must be a valid null-terminated C string.
128pub unsafe fn mx_abi_register(pkg_name: &CStr) {
129 unsafe {
130 // Initialize the tag symbol
131 get_tag();
132
133 // Register C-callables for cross-package access.
134 // Cast function pointers to DL_FUNC (Option<unsafe extern "C-unwind" fn() -> *mut c_void>).
135 R_RegisterCCallable(
136 pkg_name.as_ptr(),
137 c"mx_wrap".as_ptr(),
138 Some(std::mem::transmute::<
139 unsafe extern "C-unwind" fn(*mut mx_erased) -> SEXP,
140 unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
141 >(mx_wrap)),
142 );
143 R_RegisterCCallable(
144 pkg_name.as_ptr(),
145 c"mx_get".as_ptr(),
146 Some(std::mem::transmute::<
147 unsafe extern "C-unwind" fn(SEXP) -> *mut mx_erased,
148 unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
149 >(mx_get)),
150 );
151 R_RegisterCCallable(
152 pkg_name.as_ptr(),
153 c"mx_query".as_ptr(),
154 Some(std::mem::transmute::<
155 unsafe extern "C-unwind" fn(SEXP, mx_tag) -> *const std::ffi::c_void,
156 unsafe extern "C-unwind" fn() -> *mut std::os::raw::c_void,
157 >(mx_query)),
158 );
159 }
160}