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::ffi::{
10 R_ClearExternalPtr, R_ExternalPtrAddr, R_ExternalPtrTag, R_MakeExternalPtr, R_PreserveObject,
11 R_RegisterCCallable, R_RegisterCFinalizerEx, Rboolean, Rf_install, Rf_protect, Rf_unprotect,
12 SEXP, SEXPTYPE, SexpExt,
13};
14use std::ffi::CStr;
15use std::sync::OnceLock;
16
17/// The tag symbol used to identify miniextendr external pointers.
18/// SEXP is Send+Sync so OnceLock<SEXP> works directly.
19static MX_TAG: OnceLock<SEXP> = OnceLock::new();
20
21/// Get the miniextendr tag symbol, initializing it if needed.
22///
23/// # Safety
24/// Must be called on R's main thread.
25#[inline]
26unsafe fn get_tag() -> SEXP {
27 *MX_TAG.get_or_init(|| unsafe {
28 let tag = Rf_install(c"miniextendr::mx_erased".as_ptr());
29 R_PreserveObject(tag);
30 tag
31 })
32}
33
34/// Finalizer callback for R's garbage collector.
35///
36/// Called when an external pointer wrapping `mx_erased` is collected.
37/// Invokes the object's drop function to clean up the Rust allocation.
38unsafe extern "C-unwind" fn mx_externalptr_finalizer(ptr: SEXP) {
39 unsafe {
40 debug_assert_eq!(ptr.type_of(), crate::ffi::SEXPTYPE::EXTPTRSXP,);
41 let erased = R_ExternalPtrAddr(ptr) as *mut mx_erased;
42 if !erased.is_null() {
43 let base = (*erased).base;
44 if !base.is_null() {
45 ((*base).drop)(erased);
46 }
47 }
48 R_ClearExternalPtr(ptr);
49 }
50}
51
52/// Wrap an erased object pointer in an R external pointer.
53///
54/// Registered as `"mx_wrap"` via `R_RegisterCCallable`.
55///
56/// # Safety
57///
58/// `ptr` must point to a valid `mx_erased` allocated by a miniextendr constructor.
59/// Must be called on R's main thread.
60#[unsafe(no_mangle)]
61pub unsafe extern "C-unwind" fn mx_wrap(ptr: *mut mx_erased) -> SEXP {
62 unsafe {
63 let tag = get_tag();
64 let sexp = Rf_protect(R_MakeExternalPtr(ptr.cast(), tag, SEXP::nil()));
65 R_RegisterCFinalizerEx(sexp, Some(mx_externalptr_finalizer), Rboolean::TRUE);
66 Rf_unprotect(1);
67 sexp
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}