miniextendr_api/abi.rs
1//! # ABI Types for Cross-Package Trait Dispatch
2//!
3//! This module defines the stable, C-compatible ABI types used for cross-package
4//! trait dispatch in miniextendr. These types enable R packages written with
5//! miniextendr to share trait-based interfaces across package boundaries.
6//!
7//! ## Design Principles
8//!
9//! 1. **Stability**: All types are `#[repr(C)]` and append-only. Fields are never
10//! removed or reordered, only new fields may be added at the end.
11//!
12//! 2. **C Compatibility**: Types use only C-compatible primitives to ensure
13//! consistent layout across different Rust versions and compilation units.
14//!
15//! 3. **Type Safety**: Runtime type checking via [`mx_tag`] ensures type-safe
16//! downcasts even across package boundaries.
17//!
18//! ## Type Hierarchy
19//!
20//! ```text
21//! mx_erased (type-erased object)
22//! │
23//! └── points to → mx_base_vtable (base vtable)
24//! │
25//! ├── drop: fn pointer
26//! ├── concrete_tag: mx_tag
27//! └── query: fn pointer → interface vtables
28//! ```
29//!
30//! ## Usage Flow
31//!
32//! 1. Trait is declared with `#[miniextendr]`, impl uses `#[miniextendr] impl Trait for Type`
33//! 2. Type derives `ExternalPtr` (generates MxWrapper, base vtable, wrap fn)
34//! 3. Trait dispatch entries self-register via `distributed_slice`
35//! 4. Constructor returns `*mut mx_erased`
36//! 5. `mx_wrap()` wraps pointer in R's EXTPTRSXP
37//! 6. Method calls use `universal_query()` to get interface vtable
38//! 7. Vtable entry receives `(data, argc, argv)` and returns SEXP
39//!
40//! ## Example
41//!
42//! ```ignore
43//! // Define trait (generates TAG_FOO, FooVTable, FooView)
44//! #[miniextendr]
45//! trait Foo {
46//! fn len(&self) -> usize;
47//! }
48//!
49//! // Implement for concrete type (self-registers via distributed_slice)
50//! #[miniextendr]
51//! impl Foo for MyType { /* ... */ }
52//! ```
53//!
54//! ## Thread Safety
55//!
56//! All ABI operations are main-thread only. R invokes `.Call` on the main thread,
57//! and method shims do not route through `with_r_thread`.
58
59use crate::ffi::SEXP;
60use std::os::raw::c_void;
61
62/// Type tag for runtime type identification.
63///
64/// A 128-bit identifier split into two 64-bit halves for C compatibility.
65/// Used to identify concrete types and trait interfaces at runtime.
66///
67/// ## Generation
68///
69/// Tags should be generated as compile-time constants, typically using
70/// a hash of the fully-qualified type/trait path. The `#[miniextendr]`
71/// attribute macro handles this automatically.
72///
73/// ## Comparison
74///
75/// Tags are compared by value equality of both `lo` and `hi` fields.
76///
77/// ## Layout Guarantee
78///
79/// This type is `#[repr(C)]` and its layout is frozen. Fields will never
80/// be reordered, and new fields will only be appended.
81#[repr(C)]
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub struct mx_tag {
84 /// Lower 64 bits of the type tag.
85 pub lo: u64,
86 /// Upper 64 bits of the type tag.
87 pub hi: u64,
88}
89
90impl mx_tag {
91 /// Create a new type tag from two 64-bit values.
92 ///
93 /// # Arguments
94 ///
95 /// * `lo` - Lower 64 bits
96 /// * `hi` - Upper 64 bits
97 ///
98 /// # Example
99 ///
100 /// ```ignore
101 /// const MY_TAG: mx_tag = mx_tag::new(0x1234_5678_9abc_def0, 0xfed_cba9_8765_4321);
102 /// ```
103 #[inline]
104 pub const fn new(lo: u64, hi: u64) -> Self {
105 Self { lo, hi }
106 }
107}
108
109/// Method signature for trait vtable entries.
110///
111/// All trait methods are erased to this uniform signature:
112/// - `data`: Pointer to the concrete object data
113/// - `argc`: Number of arguments in `argv`
114/// - `argv`: Array of SEXP arguments from R
115/// - Returns: SEXP result to R
116///
117/// ## Argument Handling
118///
119/// The shim generated by `#[miniextendr]` on a trait is responsible for:
120/// 1. Checking `argc` matches expected arity
121/// 2. Converting each `argv[i]` via [`TryFromSexp`]
122/// 3. Calling the actual method
123/// 4. Converting the result via [`IntoR`]
124/// 5. Catching panics and converting to R errors
125///
126/// ## Safety
127///
128/// This function pointer is `unsafe` because:
129/// - `data` must point to valid, properly-aligned data of the expected type
130/// - `argv` must point to `argc` valid SEXP values
131/// - Must be called on R's main thread
132///
133/// [`TryFromSexp`]: crate::TryFromSexp
134/// [`IntoR`]: crate::IntoR
135#[allow(non_camel_case_types)]
136pub type mx_meth = unsafe extern "C" fn(data: *mut c_void, argc: i32, argv: *const SEXP) -> SEXP;
137
138/// Base vtable present in all erased objects.
139///
140/// This vtable provides the minimal operations needed for any erased object:
141/// - Destructor for cleanup when R garbage collects the wrapper
142/// - Concrete type tag for type-safe downcasts
143/// - Query function to retrieve interface vtables
144///
145/// ## Layout Guarantee
146///
147/// This type is `#[repr(C)]` and its layout is frozen. Fields will never
148/// be reordered, and new fields will only be appended at the end.
149///
150/// ## Generated By
151///
152/// `#[derive(ExternalPtr)]` emits a static instance of this vtable for each
153/// wrapped type.
154#[repr(C)]
155pub struct mx_base_vtable {
156 /// Destructor called when the R external pointer is garbage collected.
157 ///
158 /// Receives a pointer to the erased object (not the data pointer).
159 /// Must deallocate the entire erased wrapper structure.
160 ///
161 /// # Safety
162 ///
163 /// - `ptr` must be a valid pointer to `mx_erased` allocated by this type's constructor
164 /// - Must only be called once per object
165 /// - Must be called on R's main thread (during GC finalization)
166 pub drop: unsafe extern "C" fn(ptr: *mut mx_erased),
167
168 /// Tag identifying the concrete type wrapped by this object.
169 ///
170 /// Used for type-safe downcasts: if `concrete_tag` matches the expected
171 /// type's tag, the data pointer can be cast to that type.
172 pub concrete_tag: mx_tag,
173
174 /// Query function to retrieve interface vtables.
175 ///
176 /// Given a trait tag, returns a pointer to the vtable for that interface,
177 /// or null if the type does not implement that trait.
178 ///
179 /// # Arguments
180 ///
181 /// * `ptr` - Pointer to the erased object
182 /// * `trait_tag` - Tag identifying the requested trait interface
183 ///
184 /// # Returns
185 ///
186 /// - Non-null pointer to the trait's vtable if implemented
187 /// - Null pointer if the trait is not implemented
188 ///
189 /// # Safety
190 ///
191 /// - `ptr` must be a valid pointer to `mx_erased`
192 /// - The returned pointer (if non-null) must be cast to the correct vtable type
193 pub query: unsafe extern "C" fn(ptr: *mut mx_erased, trait_tag: mx_tag) -> *const c_void,
194
195 /// Byte offset from the start of the wrapper struct to the `data` field.
196 ///
197 /// The generated wrapper struct is `#[repr(C)] struct { erased: mx_erased, data: T }`.
198 /// When `T` has stricter alignment than `mx_erased`, padding exists between
199 /// `erased` and `data`. This field records the correct offset so that
200 /// `TraitView::try_from_sexp` can compute the data pointer without
201 /// assuming `offset == size_of::<mx_erased>()`.
202 pub data_offset: usize,
203}
204
205/// Type-erased object header.
206///
207/// This is the common prefix of all erased objects, providing access to
208/// the base vtable. The actual data follows this header in memory.
209///
210/// ## Memory Layout
211///
212/// ```text
213/// ┌─────────────────────────────────────┐
214/// │ mx_erased │
215/// │ base: *const mx_base_vtable ──────┼──► static vtable
216/// ├─────────────────────────────────────┤
217/// │ (type-specific data follows...) │
218/// │ data: T │
219/// │ interface_views: [...] │
220/// └─────────────────────────────────────┘
221/// ```
222///
223/// ## Layout Guarantee
224///
225/// This type is `#[repr(C)]` and its layout is frozen. The `base` field
226/// will always be at offset 0, and new fields will only be appended.
227///
228/// ## Generated By
229///
230/// `#[derive(ExternalPtr)]` generates wrapper structs that place `mx_erased`
231/// as the first field for proper layout.
232#[repr(C)]
233pub struct mx_erased {
234 /// Pointer to the base vtable.
235 ///
236 /// This must point to a valid, static vtable for the lifetime of the object.
237 /// The vtable is typically a `static` generated by `#[derive(ExternalPtr)]`.
238 pub base: *const mx_base_vtable,
239}
240
241// region: Tag generation
242
243/// FNV-1a 64-bit offset basis.
244const FNV1A_64_OFFSET: u64 = 0xcbf29ce484222325;
245
246/// FNV-1a 64-bit prime.
247const FNV1A_64_PRIME: u64 = 0x00000100000001b3;
248
249/// Compute FNV-1a 64-bit hash of a byte slice (const-compatible).
250const fn fnv1a_64(bytes: &[u8], seed: u64) -> u64 {
251 let mut hash = seed;
252 let mut i = 0;
253 while i < bytes.len() {
254 hash ^= bytes[i] as u64;
255 hash = hash.wrapping_mul(FNV1A_64_PRIME);
256 i += 1;
257 }
258 hash
259}
260
261/// Create a new type tag from a string path.
262///
263/// This is a helper for generating deterministic tags from type/trait paths.
264/// Uses FNV-1a hash to produce a 128-bit tag (two independent 64-bit hashes).
265///
266/// # Arguments
267///
268/// * `path` - Fully-qualified path like `"mypackage::MyType"` or `"mypackage::Foo"`
269///
270/// # Returns
271///
272/// A deterministic [`mx_tag`] for the given path.
273///
274/// # Example
275///
276/// ```
277/// use miniextendr_api::abi::mx_tag_from_path;
278///
279/// const TAG_FOO: miniextendr_api::abi::mx_tag = mx_tag_from_path("mypackage::Foo");
280/// const TAG_BAR: miniextendr_api::abi::mx_tag = mx_tag_from_path("mypackage::Bar");
281///
282/// // Same path produces same tag
283/// assert_eq!(TAG_FOO, mx_tag_from_path("mypackage::Foo"));
284///
285/// // Different paths produce different tags
286/// assert_ne!(TAG_FOO, TAG_BAR);
287/// ```
288///
289/// # Note
290///
291/// This function is `const` to enable compile-time tag generation.
292/// The hash is deterministic across compilations.
293#[inline]
294pub const fn mx_tag_from_path(path: &str) -> mx_tag {
295 let bytes = path.as_bytes();
296 // Use different seeds for lo and hi to get independent hash values
297 let lo = fnv1a_64(bytes, FNV1A_64_OFFSET);
298 // XOR with a different constant for the second hash to avoid correlation
299 let hi = fnv1a_64(bytes, FNV1A_64_OFFSET ^ 0x5555555555555555);
300 mx_tag::new(lo, hi)
301}
302
303#[cfg(test)]
304mod tests;
305// endregion