savvy/altrep/
altlist.rs

1use std::{
2    ffi::CString,
3    os::raw::{c_int, c_void},
4};
5
6use savvy_ffi::{
7    altrep::{
8        R_altrep_data2, R_make_altlist_class, R_set_altlist_Elt_method, R_set_altrep_Coerce_method,
9        R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method,
10        R_set_altrep_data2, R_set_altvec_Dataptr_or_null_method,
11    },
12    R_NilValue, R_xlen_t, Rboolean, Rboolean_FALSE, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate,
13    Rf_protect, Rf_unprotect, Rf_xlength, DATAPTR_RO, SET_VECTOR_ELT, SEXP, SEXPTYPE, VECSXP,
14    VECTOR_ELT,
15};
16
17use crate::{IntoExtPtrSexp, ListSexp};
18
19pub trait AltList: Sized + IntoExtPtrSexp {
20    /// Class name to identify the ALTREP class.
21    const CLASS_NAME: &'static str;
22
23    /// Package name to identify the ALTREP class.
24    const PACKAGE_NAME: &'static str;
25
26    /// Returns the length of the data.
27    fn length(&mut self) -> usize;
28
29    /// Returns the value of `i`-th element. Note that, it seems R handles the
30    /// out-of-bound check, so you don't need to implement it here.
31    fn elt(&mut self, i: usize) -> crate::Sexp;
32
33    /// What gets printed when `.Internal(inspect(x))` is used.
34    fn inspect(&mut self, is_materialized: bool) {
35        crate::io::r_print(
36            &format!("{} (materialized: {is_materialized})\n", Self::CLASS_NAME),
37            false,
38        );
39    }
40
41    /// Converts the struct into an ALTREP object.
42    fn into_altrep(self) -> crate::Result<crate::Sexp> {
43        match super::create_altrep_instance(self, Self::CLASS_NAME) {
44            Ok(x) => Ok(crate::Sexp(x)),
45            Err(e) => Err(e),
46        }
47    }
48
49    /// Extracts the reference (`&T`) of the underlying data.
50    fn try_from_altrep_ref(x: &ListSexp) -> crate::Result<&Self> {
51        super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
52        super::extract_ref_from_altrep(&x.0)
53    }
54
55    /// Extracts the mutable reference (`&mut T`) of the underlying data.
56    fn try_from_altrep_mut(x: &mut ListSexp, invalidate_cache: bool) -> crate::Result<&mut Self> {
57        super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
58        if invalidate_cache {
59            unsafe { R_set_altrep_data2(x.0, R_NilValue) }
60        }
61        super::extract_mut_from_altrep(&mut x.0)
62    }
63
64    /// Takes the underlying data. After this operation, the external pointer is
65    /// replaced with a null pointer.
66    fn try_from_altrep(x: ListSexp) -> crate::Result<Self> {
67        super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
68        super::extract_from_altrep(x.0)
69    }
70}
71
72#[allow(clippy::not_unsafe_ptr_arg_deref)]
73pub fn register_altlist_class<T: AltList>(
74    dll_info: *mut crate::ffi::DllInfo,
75) -> crate::error::Result<()> {
76    let class_name = CString::new(T::CLASS_NAME).unwrap_or_default();
77    let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default();
78    let class_t =
79        unsafe { R_make_altlist_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) };
80
81    #[allow(clippy::mut_from_ref)]
82    #[inline]
83    fn get_materialized_sexp<T: AltList>(x: &mut SEXP, allow_allocate: bool) -> Option<SEXP> {
84        let data = unsafe { R_altrep_data2(*x) };
85        if unsafe { data != R_NilValue } {
86            return Some(data);
87        }
88
89        // If the allocation is unpreferable, give up here.
90        if !allow_allocate {
91            return None;
92        }
93
94        let self_: &mut T = match super::extract_mut_from_altrep(x) {
95            Ok(self_) => self_,
96            Err(_) => return None,
97        };
98
99        let len = self_.length();
100
101        let new = crate::alloc_vector(VECSXP, len).unwrap();
102        unsafe { Rf_protect(new) };
103
104        for i in 0..len {
105            unsafe { SET_VECTOR_ELT(new, i as _, self_.elt(i).0) };
106        }
107
108        crate::log::debug!("A {} object is materialized", T::CLASS_NAME);
109
110        // Cache the materialized data in data2.
111        unsafe { R_set_altrep_data2(*x, new) };
112
113        // new doesn't need protection because it's used as long as this ALTREP exists.
114        unsafe { Rf_unprotect(1) };
115
116        Some(new)
117    }
118
119    unsafe extern "C" fn altrep_duplicate<T: AltList>(mut x: SEXP, _deep_copy: Rboolean) -> SEXP {
120        crate::log::trace!("A {} object is duplicated", T::CLASS_NAME);
121
122        let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
123        unsafe { Rf_duplicate(materialized) }
124    }
125
126    unsafe extern "C" fn altrep_coerce<T: AltList>(mut x: SEXP, sexp_type: SEXPTYPE) -> SEXP {
127        crate::log::trace!("A {} object is coerced", T::CLASS_NAME);
128
129        let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
130        unsafe { Rf_coerceVector(materialized, sexp_type) }
131    }
132
133    // TODO: uncomment this when DATAPTR() becomes available.
134    //
135    // unsafe extern "C" fn altvec_dataptr<T: AltList>(x: SEXP, _writable: Rboolean) -> *mut c_void {
136    //     crate::log::trace!("DATAPTR({}) is called", T::CLASS_NAME);
137    //
138    //     altvec_dataptr_inner::<T>(x, true)
139    // }
140
141    unsafe extern "C" fn altvec_dataptr_or_null<T: AltList>(mut x: SEXP) -> *const c_void {
142        crate::log::trace!("DATAPTR_OR_NULL({}) is called", T::CLASS_NAME);
143
144        match get_materialized_sexp::<T>(&mut x, false) {
145            Some(materialized) => unsafe { DATAPTR_RO(materialized) as _ },
146            // Returning C NULL (not R NULL!) is the convention
147            None => std::ptr::null_mut(),
148        }
149    }
150
151    unsafe extern "C" fn altrep_length<T: AltList>(mut x: SEXP) -> R_xlen_t {
152        if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
153            unsafe { Rf_xlength(materialized) }
154        } else {
155            match super::extract_mut_from_altrep::<T>(&mut x) {
156                Ok(self_) => self_.length() as _,
157                Err(_) => 0,
158            }
159        }
160    }
161
162    unsafe extern "C" fn altrep_inspect<T: AltList>(
163        mut x: SEXP,
164        _: c_int,
165        _: c_int,
166        _: c_int,
167        _: Option<unsafe extern "C" fn(SEXP, c_int, c_int, c_int)>,
168    ) -> Rboolean {
169        let is_materialized = unsafe { R_altrep_data2(x) != R_NilValue };
170        match super::extract_mut_from_altrep::<T>(&mut x) {
171            Ok(self_) => {
172                self_.inspect(is_materialized);
173                Rboolean_TRUE
174            }
175            Err(_) => Rboolean_FALSE,
176        }
177    }
178
179    unsafe extern "C" fn altlist_elt<T: AltList>(mut x: SEXP, i: R_xlen_t) -> SEXP {
180        crate::log::trace!("VECTOR_ELT({}, {i}) is called", T::CLASS_NAME);
181
182        if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
183            unsafe { VECTOR_ELT(materialized, i) }
184        } else {
185            match super::extract_mut_from_altrep::<T>(&mut x) {
186                Ok(self_) => self_.elt(i as _).0,
187                Err(_) => unsafe { R_NilValue },
188            }
189        }
190    }
191
192    unsafe {
193        R_set_altrep_Length_method(class_t, Some(altrep_length::<T>));
194        R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::<T>));
195        R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::<T>));
196        R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::<T>));
197        // R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::<T>));
198        R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::<T>));
199        R_set_altlist_Elt_method(class_t, Some(altlist_elt::<T>));
200
201        // Do not implement set_elt.
202        //
203        // R_set_altlist_Set_elt_method(class_t, Some(altlist_set_elt::<T>));
204    }
205
206    super::register_altrep_class(T::CLASS_NAME, class_t)?;
207    Ok(())
208}