diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 7960728a..37e0816f 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -76,7 +76,7 @@ jobs: crate-type = ["staticlib"] [dependencies] - savvy = { path = "../savvy", features = ["complex"] } + savvy = { path = "../savvy", features = ["complex", "altrep"] } savvy-ffi = { path = "../savvy/savvy-ffi" } [workspace.package] diff --git a/.github/workflows/wasm_and_arm64.yaml b/.github/workflows/wasm_and_arm64.yaml index 482bbf93..5f2e6d6f 100644 --- a/.github/workflows/wasm_and_arm64.yaml +++ b/.github/workflows/wasm_and_arm64.yaml @@ -32,7 +32,7 @@ jobs: crate-type = ["staticlib"] [dependencies] - savvy = { path = "../savvy", features = ["complex"] } + savvy = { path = "../savvy", features = ["complex", "altrep"] } savvy-ffi = { path = "../savvy/savvy-ffi" } [workspace.package] diff --git a/Cargo.toml b/Cargo.toml index e4ad9606..697aa694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,14 @@ default = [] # Support complex complex = ["num-complex", "savvy-ffi/complex"] +# Support ALTREP +altrep = ["savvy-ffi/altrep"] + [build-dependencies] cc = "1" [package.metadata.docs.rs] -features = ["complex"] +features = ["complex", "altrep"] [workspace.metadata.release] tag = false # do not create tags for individual crates (e.g. "savvy-cli-v0.2.5") diff --git a/R-package/R/000-wrappers.R b/R-package/R/000-wrappers.R index fe419aca..62591b18 100644 --- a/R-package/R/000-wrappers.R +++ b/R-package/R/000-wrappers.R @@ -147,6 +147,26 @@ set_name_external <- function(x, name) { } +altint <- function() { + .Call(savvy_altint__impl) +} + + +altreal <- function() { + .Call(savvy_altreal__impl) +} + + +altlogical <- function() { + .Call(savvy_altlogical__impl) +} + + +altstring <- function() { + .Call(savvy_altstring__impl) +} + + get_class_int <- function(x) { .Call(savvy_get_class_int__impl, x) } diff --git a/R-package/src/init.c b/R-package/src/init.c index 14d7772b..88a433e8 100644 --- a/R-package/src/init.c +++ b/R-package/src/init.c @@ -119,8 +119,28 @@ SEXP savvy_set_name_external__impl(SEXP x, SEXP name) { return handle_result(res); } -SEXP savvy_init_foo__impl(DllInfo* _dll_info) { - SEXP res = savvy_init_foo__ffi(_dll_info); +SEXP savvy_altint__impl(void) { + SEXP res = savvy_altint__ffi(); + return handle_result(res); +} + +SEXP savvy_altreal__impl(void) { + SEXP res = savvy_altreal__ffi(); + return handle_result(res); +} + +SEXP savvy_altlogical__impl(void) { + SEXP res = savvy_altlogical__ffi(); + return handle_result(res); +} + +SEXP savvy_altstring__impl(void) { + SEXP res = savvy_altstring__ffi(); + return handle_result(res); +} + +SEXP savvy_init_altrep_class__impl(DllInfo* dll_info) { + SEXP res = savvy_init_altrep_class__ffi(dll_info); return handle_result(res); } @@ -508,6 +528,10 @@ static const R_CallMethodDef CallEntries[] = { {"savvy_external_person_new__impl", (DL_FUNC) &savvy_external_person_new__impl, 0}, {"savvy_get_name_external__impl", (DL_FUNC) &savvy_get_name_external__impl, 1}, {"savvy_set_name_external__impl", (DL_FUNC) &savvy_set_name_external__impl, 2}, + {"savvy_altint__impl", (DL_FUNC) &savvy_altint__impl, 0}, + {"savvy_altreal__impl", (DL_FUNC) &savvy_altreal__impl, 0}, + {"savvy_altlogical__impl", (DL_FUNC) &savvy_altlogical__impl, 0}, + {"savvy_altstring__impl", (DL_FUNC) &savvy_altstring__impl, 0}, {"savvy_get_class_int__impl", (DL_FUNC) &savvy_get_class_int__impl, 1}, {"savvy_get_names_int__impl", (DL_FUNC) &savvy_get_names_int__impl, 1}, {"savvy_get_dim_int__impl", (DL_FUNC) &savvy_get_dim_int__impl, 1}, @@ -589,5 +613,5 @@ void R_init_savvyExamples(DllInfo *dll) { R_useDynamicSymbols(dll, FALSE); // Functions for initialzation, if any. - savvy_init_foo__impl(dll); + savvy_init_altrep_class__impl(dll); } diff --git a/R-package/src/rust/Cargo.toml b/R-package/src/rust/Cargo.toml index 8fc07b46..08f65f42 100644 --- a/R-package/src/rust/Cargo.toml +++ b/R-package/src/rust/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" crate-type = ["staticlib", "lib"] [dependencies] -savvy = { path = "../../../", features = ["complex"] } +savvy = { path = "../../../", features = ["complex", "altrep"] } +# for calling Rf_errorcall() to test error handling savvy-ffi = { path = "../../../savvy-ffi" } [profile.release] diff --git a/R-package/src/rust/api.h b/R-package/src/rust/api.h index 429204af..462c3112 100644 --- a/R-package/src/rust/api.h +++ b/R-package/src/rust/api.h @@ -15,7 +15,11 @@ SEXP savvy_list_with_names_and_values__ffi(void); SEXP savvy_external_person_new__ffi(void); SEXP savvy_get_name_external__ffi(SEXP x); SEXP savvy_set_name_external__ffi(SEXP x, SEXP name); -SEXP savvy_init_foo__ffi(DllInfo* _dll_info); +SEXP savvy_altint__ffi(void); +SEXP savvy_altreal__ffi(void); +SEXP savvy_altlogical__ffi(void); +SEXP savvy_altstring__ffi(void); +SEXP savvy_init_altrep_class__ffi(DllInfo* dll_info); SEXP savvy_get_class_int__ffi(SEXP x); SEXP savvy_get_names_int__ffi(SEXP x); SEXP savvy_get_dim_int__ffi(SEXP x); diff --git a/R-package/src/rust/src/altrep.rs b/R-package/src/rust/src/altrep.rs new file mode 100644 index 00000000..d2a63136 --- /dev/null +++ b/R-package/src/rust/src/altrep.rs @@ -0,0 +1,140 @@ +use savvy::altrep::{ + register_altinteger_class, register_altlogical_class, register_altreal_class, + register_altstring_class, AltInteger, AltLogical, AltReal, AltString, +}; +use savvy::savvy; + +// integer + +struct MyAltInt(Vec); +impl savvy::IntoExtPtrSexp for MyAltInt {} + +impl MyAltInt { + fn new(x: Vec) -> Self { + Self(x) + } +} + +impl AltInteger for MyAltInt { + const CLASS_NAME: &'static str = "MyAltInt"; + const PACKAGE_NAME: &'static str = "TestPackage"; + + fn length(&mut self) -> usize { + self.0.len() + } + + fn elt(&mut self, i: usize) -> i32 { + self.0[i] + } +} + +#[savvy] +fn altint() -> savvy::Result { + let v = MyAltInt::new(vec![1, 2, 3]); + let v_altrep = v.into_altrep()?; + Ok(savvy::Sexp(v_altrep)) +} + +// real + +struct MyAltReal(Vec); +impl savvy::IntoExtPtrSexp for MyAltReal {} + +impl MyAltReal { + fn new(x: Vec) -> Self { + Self(x) + } +} + +impl AltReal for MyAltReal { + const CLASS_NAME: &'static str = "MyAltReal"; + const PACKAGE_NAME: &'static str = "TestPackage"; + + fn length(&mut self) -> usize { + self.0.len() + } + + fn elt(&mut self, i: usize) -> f64 { + self.0[i] + } +} + +#[savvy] +fn altreal() -> savvy::Result { + let v = MyAltReal::new(vec![1.0, 2.0, 3.0]); + let v_altrep = v.into_altrep()?; + Ok(savvy::Sexp(v_altrep)) +} + +// logical + +struct MyAltLogical(Vec); +impl savvy::IntoExtPtrSexp for MyAltLogical {} + +impl MyAltLogical { + fn new(x: Vec) -> Self { + Self(x) + } +} + +impl AltLogical for MyAltLogical { + const CLASS_NAME: &'static str = "MyAltLogical"; + const PACKAGE_NAME: &'static str = "TestPackage"; + + fn length(&mut self) -> usize { + self.0.len() + } + + fn elt(&mut self, i: usize) -> bool { + self.0[i] + } +} + +#[savvy] +fn altlogical() -> savvy::Result { + let v = MyAltLogical::new(vec![true, false, true]); + let v_altrep = v.into_altrep()?; + Ok(savvy::Sexp(v_altrep)) +} + +// string + +struct MyAltString(Vec); +impl savvy::IntoExtPtrSexp for MyAltString {} + +impl MyAltString { + fn new(x: Vec) -> Self { + Self(x) + } +} + +impl AltString for MyAltString { + const CLASS_NAME: &'static str = "MyAltString"; + const PACKAGE_NAME: &'static str = "TestPackage"; + + fn length(&mut self) -> usize { + self.0.len() + } + + fn elt(&mut self, i: usize) -> &str { + self.0[i].as_str() + } +} + +#[savvy] +fn altstring() -> savvy::Result { + let v = MyAltString::new(vec!["1".to_string(), "2".to_string(), "3".to_string()]); + let v_altrep = v.into_altrep()?; + Ok(savvy::Sexp(v_altrep)) +} + +// initialization + +#[savvy] +fn init_altrep_class(dll_info: *mut savvy::ffi::DllInfo) -> savvy::Result<()> { + register_altinteger_class::(dll_info)?; + register_altreal_class::(dll_info)?; + register_altlogical_class::(dll_info)?; + register_altstring_class::(dll_info)?; + Ok(()) +} diff --git a/R-package/src/rust/src/lib.rs b/R-package/src/rust/src/lib.rs index eace9c90..8314fd0a 100644 --- a/R-package/src/rust/src/lib.rs +++ b/R-package/src/rust/src/lib.rs @@ -1,5 +1,6 @@ #![allow(unused_variables)] +mod altrep; mod attributes; mod complex; mod consuming_type; @@ -21,7 +22,7 @@ mod mod1; // This should not be parsed // mod mod2; -use savvy::{r_eprintln, r_print, savvy, OwnedListSexp}; +use savvy::{r_print, savvy, OwnedListSexp}; use savvy::{ IntegerSexp, ListSexp, LogicalSexp, OwnedIntegerSexp, OwnedLogicalSexp, OwnedRealSexp, @@ -422,9 +423,3 @@ mod tests { Ok(()) } } - -#[savvy] -fn init_foo(_dll_info: *mut savvy::ffi::DllInfo) -> savvy::Result<()> { - r_eprintln!("Initialized!"); - Ok(()) -} diff --git a/R-package/tests/testthat/test-altrep.R b/R-package/tests/testthat/test-altrep.R new file mode 100644 index 00000000..30013698 --- /dev/null +++ b/R-package/tests/testthat/test-altrep.R @@ -0,0 +1,39 @@ +test_that("altinteger works", { + x <- altint() + expect_equal(x[1], 1L) # ELT method + expect_equal(length(x), 3L) # length method + expect_equal(as.character(x), c("1", "2", "3")) # coerce method + # duplicate method? dataptr method? I'm not sure + x[1] <- 2L + expect_equal(x, c(2L, 2L, 3L)) +}) + +test_that("altreal works", { + x <- altreal() + expect_equal(x[1], 1) # ELT method + expect_equal(length(x), 3L) # length method + expect_equal(as.character(x), c("1", "2", "3")) # coerce method + # duplicate method? dataptr method? I'm not sure + x[1] <- 2 + expect_equal(x, c(2, 2, 3)) +}) + +test_that("altlogical works", { + x <- altlogical() + expect_equal(x[1], TRUE) # ELT method + expect_equal(length(x), 3L) # length method + expect_equal(as.character(x), c("TRUE", "FALSE", "TRUE")) # coerce method + # duplicate method? dataptr method? I'm not sure + x[1] <- FALSE + expect_equal(x, c(FALSE, FALSE, TRUE)) +}) + +test_that("altstring works", { + x <- altstring() + expect_equal(x[1], "1") # ELT method + expect_equal(length(x), 3L) # length method + expect_equal(as.integer(x), c(1L, 2L, 3L)) # coerce method + # duplicate method? dataptr method? I'm not sure + x[1] <- "foo" + expect_equal(x, c("foo", "2", "3")) +}) diff --git a/savvy-ffi/Cargo.toml b/savvy-ffi/Cargo.toml index 1e75dfec..d3b8c842 100644 --- a/savvy-ffi/Cargo.toml +++ b/savvy-ffi/Cargo.toml @@ -17,3 +17,4 @@ num-complex = { version = "0.4.5", optional = true } [features] default = [] complex = ["num-complex"] +altrep = [] diff --git a/savvy-ffi/src/altrep.rs b/savvy-ffi/src/altrep.rs new file mode 100644 index 00000000..3f28c50f --- /dev/null +++ b/savvy-ffi/src/altrep.rs @@ -0,0 +1,238 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use std::os::raw::{c_char, c_int, c_void}; + +use crate::{R_xlen_t, Rboolean, SEXP, SEXPTYPE}; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct R_altrep_class_t { + pub ptr: SEXP, +} + +// I'm not fully confident, but R_altrep_class_t should be thread safe in the +// sense that this can be set during the initialization. +unsafe impl Send for R_altrep_class_t {} +unsafe impl Sync for R_altrep_class_t {} + +extern "C" { + // Note: this function is not limited to ALTREP, but this is placed here + // because it's currently needed only for ALTREP. + pub fn MARK_NOT_MUTABLE(x: SEXP); + + pub fn ALTREP(x: SEXP) -> c_int; + pub fn ALTREP_CLASS(x: SEXP) -> SEXP; + pub fn R_new_altrep(aclass: R_altrep_class_t, data1: SEXP, data2: SEXP) -> SEXP; + pub fn R_altrep_data1(x: SEXP) -> SEXP; + pub fn R_altrep_data2(x: SEXP) -> SEXP; + pub fn R_set_altrep_data1(x: SEXP, v: SEXP); + pub fn R_set_altrep_data2(x: SEXP, v: SEXP); +} + +// general + +extern "C" { + pub fn R_set_altrep_Unserialize_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altrep_Serialized_state_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altrep_Duplicate_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altrep_Coerce_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altrep_Inspect_method( + cls: R_altrep_class_t, + fun: Option< + unsafe extern "C" fn( + arg1: SEXP, + arg2: c_int, + arg3: c_int, + arg4: c_int, + arg5: Option< + unsafe extern "C" fn(arg1: SEXP, arg2: c_int, arg3: c_int, arg4: c_int), + >, + ) -> Rboolean, + >, + ); + pub fn R_set_altrep_Length_method( + cls: R_altrep_class_t, + fun: Option R_xlen_t>, + ); +} + +// vector common + +extern "C" { + pub fn R_set_altvec_Dataptr_method( + cls: R_altrep_class_t, + fun: Option *mut c_void>, + ); + pub fn R_set_altvec_Dataptr_or_null_method( + cls: R_altrep_class_t, + fun: Option *const c_void>, + ); + +} + +// integer + +extern "C" { + pub fn R_make_altinteger_class( + cname: *const c_char, + pname: *const c_char, + info: *mut crate::DllInfo, + ) -> R_altrep_class_t; + pub fn R_set_altinteger_Elt_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altinteger_Get_region_method( + cls: R_altrep_class_t, + fun: Option< + unsafe extern "C" fn( + arg1: SEXP, + arg2: R_xlen_t, + arg3: R_xlen_t, + arg4: *mut c_int, + ) -> R_xlen_t, + >, + ); + pub fn R_set_altinteger_Is_sorted_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altinteger_No_NA_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altinteger_Sum_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altinteger_Min_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altinteger_Max_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); +} + +// Real + +extern "C" { + pub fn R_make_altreal_class( + cname: *const c_char, + pname: *const c_char, + info: *mut crate::DllInfo, + ) -> R_altrep_class_t; + pub fn R_set_altreal_Elt_method( + cls: R_altrep_class_t, + fun: Option f64>, + ); + pub fn R_set_altreal_Get_region_method( + cls: R_altrep_class_t, + fun: Option< + unsafe extern "C" fn( + arg1: SEXP, + arg2: R_xlen_t, + arg3: R_xlen_t, + arg4: *mut f64, + ) -> R_xlen_t, + >, + ); + pub fn R_set_altreal_Is_sorted_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altreal_No_NA_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altreal_Sum_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altreal_Min_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altreal_Max_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); +} + +// logical + +extern "C" { + pub fn R_make_altlogical_class( + cname: *const c_char, + pname: *const c_char, + info: *mut crate::DllInfo, + ) -> R_altrep_class_t; + pub fn R_set_altlogical_Elt_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altlogical_Get_region_method( + cls: R_altrep_class_t, + fun: Option< + unsafe extern "C" fn( + arg1: SEXP, + arg2: R_xlen_t, + arg3: R_xlen_t, + arg4: *mut c_int, + ) -> R_xlen_t, + >, + ); + pub fn R_set_altlogical_Is_sorted_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altlogical_No_NA_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altlogical_Sum_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); +} + +// string + +extern "C" { + pub fn R_make_altstring_class( + cname: *const c_char, + pname: *const c_char, + info: *mut crate::DllInfo, + ) -> R_altrep_class_t; + pub fn R_set_altstring_Elt_method( + cls: R_altrep_class_t, + fun: Option SEXP>, + ); + pub fn R_set_altstring_Set_elt_method( + cls: R_altrep_class_t, + fun: Option, + ); + pub fn R_set_altstring_Is_sorted_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); + pub fn R_set_altstring_No_NA_method( + cls: R_altrep_class_t, + fun: Option c_int>, + ); +} diff --git a/savvy-ffi/src/lib.rs b/savvy-ffi/src/lib.rs index 598985b5..49c119e7 100644 --- a/savvy-ffi/src/lib.rs +++ b/savvy-ffi/src/lib.rs @@ -2,6 +2,9 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#[cfg(feature = "altrep")] +pub mod altrep; + // internal types pub type R_xlen_t = isize; @@ -78,6 +81,7 @@ extern "C" { // Integer extern "C" { pub fn INTEGER(x: SEXP) -> *mut ::std::os::raw::c_int; + pub fn INTEGER_RO(x: SEXP) -> *const ::std::os::raw::c_int; pub fn INTEGER_ELT(x: SEXP, i: R_xlen_t) -> ::std::os::raw::c_int; pub fn SET_INTEGER_ELT(x: SEXP, i: R_xlen_t, v: ::std::os::raw::c_int); pub fn Rf_ScalarInteger(arg1: ::std::os::raw::c_int) -> SEXP; @@ -87,6 +91,7 @@ extern "C" { // Real extern "C" { pub fn REAL(x: SEXP) -> *mut f64; + pub fn REAL_RO(x: SEXP) -> *const f64; pub fn REAL_ELT(x: SEXP, i: R_xlen_t) -> f64; pub fn SET_REAL_ELT(x: SEXP, i: R_xlen_t, v: f64); pub fn Rf_ScalarReal(arg1: f64) -> SEXP; @@ -112,6 +117,7 @@ extern "C" { #[cfg(feature = "complex")] extern "C" { pub fn COMPLEX(x: SEXP) -> *mut num_complex::Complex64; + pub fn COMPLEX_RO(x: SEXP) -> *mut num_complex::Complex64; pub fn COMPLEX_ELT(x: SEXP, i: R_xlen_t) -> num_complex::Complex64; pub fn SET_COMPLEX_ELT(x: SEXP, i: R_xlen_t, v: num_complex::Complex64); pub fn Rf_ScalarComplex(arg1: num_complex::Complex64) -> SEXP; @@ -121,6 +127,7 @@ extern "C" { // Logical extern "C" { pub fn LOGICAL(x: SEXP) -> *mut ::std::os::raw::c_int; + pub fn LOGICAL_RO(x: SEXP) -> *const ::std::os::raw::c_int; pub fn LOGICAL_ELT(x: SEXP, i: R_xlen_t) -> ::std::os::raw::c_int; pub fn SET_LOGICAL_ELT(x: SEXP, i: R_xlen_t, v: ::std::os::raw::c_int); pub fn Rf_ScalarLogical(arg1: ::std::os::raw::c_int) -> SEXP; @@ -138,6 +145,8 @@ pub const cetype_t_CE_ANY: cetype_t = 99; pub type cetype_t = ::std::os::raw::c_int; extern "C" { + pub fn STRING_PTR(x: SEXP) -> *mut SEXP; + pub fn STRING_PTR_RO(x: SEXP) -> *const SEXP; pub fn STRING_ELT(x: SEXP, i: R_xlen_t) -> SEXP; pub fn SET_STRING_ELT(x: SEXP, i: R_xlen_t, v: SEXP); pub fn Rf_ScalarString(arg1: SEXP) -> SEXP; @@ -245,3 +254,7 @@ extern "C" { // misc pub type DllInfo = *mut ::std::os::raw::c_void; +extern "C" { + pub fn Rf_coerceVector(arg1: SEXP, arg2: SEXPTYPE) -> SEXP; + pub fn Rf_duplicate(arg1: SEXP) -> SEXP; +} diff --git a/src/altrep/altinteger.rs b/src/altrep/altinteger.rs new file mode 100644 index 00000000..722b1bf5 --- /dev/null +++ b/src/altrep/altinteger.rs @@ -0,0 +1,157 @@ +use std::{ + ffi::CString, + os::raw::{c_int, c_void}, +}; + +use savvy_ffi::{ + altrep::{ + R_altrep_data2, R_make_altinteger_class, R_set_altinteger_Elt_method, + R_set_altrep_Coerce_method, R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, + R_set_altrep_Length_method, R_set_altrep_data2, R_set_altvec_Dataptr_method, + R_set_altvec_Dataptr_or_null_method, + }, + R_NilValue, R_xlen_t, Rboolean, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate, Rf_protect, + Rf_unprotect, INTEGER, INTEGER_RO, INTSXP, SEXP, SEXPTYPE, +}; + +use crate::IntoExtPtrSexp; + +pub trait AltInteger: Sized + IntoExtPtrSexp { + /// Class name to identify the ALTREP class. + const CLASS_NAME: &'static str; + + /// Package name to identify the ALTREP class. + const PACKAGE_NAME: &'static str; + + fn into_altrep(self) -> crate::Result { + super::create_altrep_instance(self, Self::CLASS_NAME) + } + + /// Copies all the data into a new memory. This is used when the ALTREP + /// needs to be materialized. + /// + /// For example, you can use `copy_from_slice()` for more efficient copying + /// of the values. + fn copy_data(&mut self, new: &mut [i32]) { + for (i, v) in new.iter_mut().enumerate() { + *v = self.elt(i); + } + } + + /// What gets printed when `.Internal(inspect(x))` is used. + fn inspect(&mut self) { + crate::io::r_print(&format!("({})", Self::CLASS_NAME), false); + } + + /// Return the length of the data. + fn length(&mut self) -> usize; + + /// Returns the value of `i`-th element. Note that, it seems R handles the + /// out-of-bound check, so you don't need to implement it here. + fn elt(&mut self, i: usize) -> i32; +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn register_altinteger_class( + dll_info: *mut crate::ffi::DllInfo, +) -> crate::error::Result<()> { + let class_name = CString::new(T::CLASS_NAME).unwrap_or_default(); + let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default(); + let class_t = + unsafe { R_make_altinteger_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) }; + + #[allow(clippy::mut_from_ref)] + #[inline] + fn materialize(x: &SEXP) -> SEXP { + let data = unsafe { R_altrep_data2(*x) }; + if unsafe { data != R_NilValue } { + return data; + } + + let self_: &mut T = super::extract_self_from_altrep(x); + + let len = self_.length(); + let new = crate::alloc_vector(INTSXP, len).unwrap(); + + unsafe { Rf_protect(new) }; + + let dst = unsafe { std::slice::from_raw_parts_mut(INTEGER(new), len) }; + + self_.copy_data(dst); + + // Cache the materialized data in data2. + // + // Note that, for example arrow stores it in `CAR()` of data2, but this + // implementation naively uses data2. Probably that should be clever + // because data2 can be used for other purposes. + unsafe { R_set_altrep_data2(*x, new) }; + + // new doesn't need protection because it's used as long as this ALTREP exists. + unsafe { Rf_unprotect(1) }; + + new + } + + unsafe extern "C" fn altrep_duplicate(x: SEXP, _deep_copy: Rboolean) -> SEXP { + let materialized = materialize::(&x); + + // let attrs = unsafe { Rf_protect(Rf_duplicate(ATTRIB(x))) }; + // unsafe { SET_ATTRIB(materialized, attrs) }; + + unsafe { Rf_duplicate(materialized) } + } + + unsafe extern "C" fn altrep_coerce(x: SEXP, sexp_type: SEXPTYPE) -> SEXP { + let materialized = materialize::(&x); + unsafe { Rf_coerceVector(materialized, sexp_type) } + } + + unsafe extern "C" fn altvec_dataptr( + x: SEXP, + _writable: Rboolean, + ) -> *mut c_void { + let materialized = materialize::(&x); + unsafe { INTEGER(materialized) as _ } + } + + unsafe extern "C" fn altvec_dataptr_or_null(x: SEXP) -> *const c_void { + let materialized = materialize::(&x); + unsafe { INTEGER_RO(materialized) as _ } + } + + unsafe extern "C" fn altrep_length(x: SEXP) -> R_xlen_t { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.length() as _ + } + + unsafe extern "C" fn altrep_inspect( + x: SEXP, + _: c_int, + _: c_int, + _: c_int, + _: Option, + ) -> Rboolean { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.inspect(); + + Rboolean_TRUE + } + + unsafe extern "C" fn altinteger_elt(x: SEXP, i: R_xlen_t) -> c_int { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.elt(i as _) as _ + } + + unsafe { + R_set_altrep_Length_method(class_t, Some(altrep_length::)); + R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::)); + R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::)); + R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::)); + R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::)); + R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::)); + R_set_altinteger_Elt_method(class_t, Some(altinteger_elt::)); + } + + super::register_altrep_class(T::CLASS_NAME, class_t)?; + Ok(()) +} diff --git a/src/altrep/altlogical.rs b/src/altrep/altlogical.rs new file mode 100644 index 00000000..cc95483f --- /dev/null +++ b/src/altrep/altlogical.rs @@ -0,0 +1,157 @@ +use std::{ + ffi::CString, + os::raw::{c_int, c_void}, +}; + +use savvy_ffi::{ + altrep::{ + R_altrep_data2, R_make_altlogical_class, R_set_altlogical_Elt_method, + R_set_altrep_Coerce_method, R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, + R_set_altrep_Length_method, R_set_altrep_data2, R_set_altvec_Dataptr_method, + R_set_altvec_Dataptr_or_null_method, + }, + R_NilValue, R_xlen_t, Rboolean, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate, Rf_protect, + Rf_unprotect, LGLSXP, LOGICAL, LOGICAL_RO, SEXP, SEXPTYPE, +}; + +use crate::IntoExtPtrSexp; + +pub trait AltLogical: Sized + IntoExtPtrSexp { + /// Class name to identify the ALTREP class. + const CLASS_NAME: &'static str; + + /// Package name to identify the ALTREP class. + const PACKAGE_NAME: &'static str; + + fn into_altrep(self) -> crate::Result { + super::create_altrep_instance(self, Self::CLASS_NAME) + } + + /// Copies all the data into a new memory. This is used when the ALTREP + /// needs to be materialized. + /// + /// For example, you can use `copy_from_slice()` for more efficient copying + /// of the values. + fn copy_data(&mut self, new: &mut [i32]) { + for (i, v) in new.iter_mut().enumerate() { + *v = self.elt(i) as _; + } + } + + /// What gets printed when `.Internal(inspect(x))` is used. + fn inspect(&mut self) { + crate::io::r_print(&format!("({})", Self::CLASS_NAME), false); + } + + /// Return the length of the data. + fn length(&mut self) -> usize; + + /// Returns the value of `i`-th element. Note that, it seems R handles the + /// out-of-bound check, so you don't need to implement it here. + fn elt(&mut self, i: usize) -> bool; +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn register_altlogical_class( + dll_info: *mut crate::ffi::DllInfo, +) -> crate::error::Result<()> { + let class_name = CString::new(T::CLASS_NAME).unwrap_or_default(); + let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default(); + let class_t = + unsafe { R_make_altlogical_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) }; + + #[allow(clippy::mut_from_ref)] + #[inline] + fn materialize(x: &SEXP) -> SEXP { + let data = unsafe { R_altrep_data2(*x) }; + if unsafe { data != R_NilValue } { + return data; + } + + let self_: &mut T = super::extract_self_from_altrep(x); + + let len = self_.length(); + let new = crate::alloc_vector(LGLSXP, len).unwrap(); + + unsafe { Rf_protect(new) }; + + let dst = unsafe { std::slice::from_raw_parts_mut(LOGICAL(new), len) }; + + self_.copy_data(dst); + + // Cache the materialized data in data2. + // + // Note that, for example arrow stores it in `CAR()` of data2, but this + // implementation naively uses data2. Probably that should be clever + // because data2 can be used for other purposes. + unsafe { R_set_altrep_data2(*x, new) }; + + // new doesn't need protection because it's used as long as this ALTREP exists. + unsafe { Rf_unprotect(1) }; + + new + } + + unsafe extern "C" fn altrep_duplicate(x: SEXP, _deep_copy: Rboolean) -> SEXP { + let materialized = materialize::(&x); + + // let attrs = unsafe { Rf_protect(Rf_duplicate(ATTRIB(x))) }; + // unsafe { SET_ATTRIB(materialized, attrs) }; + + unsafe { Rf_duplicate(materialized) } + } + + unsafe extern "C" fn altrep_coerce(x: SEXP, sexp_type: SEXPTYPE) -> SEXP { + let materialized = materialize::(&x); + unsafe { Rf_coerceVector(materialized, sexp_type) } + } + + unsafe extern "C" fn altvec_dataptr( + x: SEXP, + _writable: Rboolean, + ) -> *mut c_void { + let materialized = materialize::(&x); + unsafe { LOGICAL(materialized) as _ } + } + + unsafe extern "C" fn altvec_dataptr_or_null(x: SEXP) -> *const c_void { + let materialized = materialize::(&x); + unsafe { LOGICAL_RO(materialized) as _ } + } + + unsafe extern "C" fn altrep_length(x: SEXP) -> R_xlen_t { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.length() as _ + } + + unsafe extern "C" fn altrep_inspect( + x: SEXP, + _: c_int, + _: c_int, + _: c_int, + _: Option, + ) -> Rboolean { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.inspect(); + + Rboolean_TRUE + } + + unsafe extern "C" fn altlogical_elt(x: SEXP, i: R_xlen_t) -> c_int { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.elt(i as _) as _ + } + + unsafe { + R_set_altrep_Length_method(class_t, Some(altrep_length::)); + R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::)); + R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::)); + R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::)); + R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::)); + R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::)); + R_set_altlogical_Elt_method(class_t, Some(altlogical_elt::)); + } + + super::register_altrep_class(T::CLASS_NAME, class_t)?; + Ok(()) +} diff --git a/src/altrep/altreal.rs b/src/altrep/altreal.rs new file mode 100644 index 00000000..0ad68d62 --- /dev/null +++ b/src/altrep/altreal.rs @@ -0,0 +1,153 @@ +use std::{ + ffi::CString, + os::raw::{c_int, c_void}, +}; + +use savvy_ffi::{ + altrep::{ + R_altrep_data2, R_make_altreal_class, R_set_altreal_Elt_method, R_set_altrep_Coerce_method, + R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method, + R_set_altrep_data2, R_set_altvec_Dataptr_method, R_set_altvec_Dataptr_or_null_method, + }, + R_NilValue, R_xlen_t, Rboolean, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate, Rf_protect, + Rf_unprotect, REAL, REALSXP, REAL_RO, SEXP, SEXPTYPE, +}; + +use crate::IntoExtPtrSexp; + +pub trait AltReal: Sized + IntoExtPtrSexp { + /// Class name to identify the ALTREP class. + const CLASS_NAME: &'static str; + + /// Package name to identify the ALTREP class. + const PACKAGE_NAME: &'static str; + + fn into_altrep(self) -> crate::Result { + super::create_altrep_instance(self, Self::CLASS_NAME) + } + + /// Copies all the data into a new memory. This is used when the ALTREP + /// needs to be materialized. + /// + /// For example, you can use `copy_from_slice()` for more efficient copying + /// of the values. + fn copy_data(&mut self, new: &mut [f64]) { + for (i, v) in new.iter_mut().enumerate() { + *v = self.elt(i); + } + } + + /// What gets printed when `.Internal(inspect(x))` is used. + fn inspect(&mut self) { + crate::io::r_print(&format!("({})", Self::CLASS_NAME), false); + } + + /// Return the length of the data. + fn length(&mut self) -> usize; + + /// Returns the value of `i`-th element. Note that, it seems R handles the + /// out-of-bound check, so you don't need to implement it here. + fn elt(&mut self, i: usize) -> f64; +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn register_altreal_class( + dll_info: *mut crate::ffi::DllInfo, +) -> crate::error::Result<()> { + let class_name = CString::new(T::CLASS_NAME).unwrap_or_default(); + let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default(); + let class_t = + unsafe { R_make_altreal_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) }; + + #[allow(clippy::mut_from_ref)] + #[inline] + fn materialize(x: &SEXP) -> SEXP { + let data = unsafe { R_altrep_data2(*x) }; + if unsafe { data != R_NilValue } { + return data; + } + + let self_: &mut T = super::extract_self_from_altrep(x); + + let len = self_.length(); + let new = crate::alloc_vector(REALSXP, len).unwrap(); + + unsafe { Rf_protect(new) }; + + let dst = unsafe { std::slice::from_raw_parts_mut(REAL(new), len) }; + + self_.copy_data(dst); + + // Cache the materialized data in data2. + // + // Note that, for example arrow stores it in `CAR()` of data2, but this + // implementation naively uses data2. Probably that should be clever + // because data2 can be used for other purposes. + unsafe { R_set_altrep_data2(*x, new) }; + + // new doesn't need protection because it's used as long as this ALTREP exists. + unsafe { Rf_unprotect(1) }; + + new + } + + unsafe extern "C" fn altrep_duplicate(x: SEXP, _deep_copy: Rboolean) -> SEXP { + let materialized = materialize::(&x); + + // let attrs = unsafe { Rf_protect(Rf_duplicate(ATTRIB(x))) }; + // unsafe { SET_ATTRIB(materialized, attrs) }; + + unsafe { Rf_duplicate(materialized) } + } + + unsafe extern "C" fn altrep_coerce(x: SEXP, sexp_type: SEXPTYPE) -> SEXP { + let materialized = materialize::(&x); + unsafe { Rf_coerceVector(materialized, sexp_type) } + } + + unsafe extern "C" fn altvec_dataptr(x: SEXP, _writable: Rboolean) -> *mut c_void { + let materialized = materialize::(&x); + unsafe { REAL(materialized) as _ } + } + + unsafe extern "C" fn altvec_dataptr_or_null(x: SEXP) -> *const c_void { + let materialized = materialize::(&x); + unsafe { REAL_RO(materialized) as _ } + } + + unsafe extern "C" fn altrep_length(x: SEXP) -> R_xlen_t { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.length() as _ + } + + unsafe extern "C" fn altrep_inspect( + x: SEXP, + _: c_int, + _: c_int, + _: c_int, + _: Option, + ) -> Rboolean { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.inspect(); + + Rboolean_TRUE + } + + unsafe extern "C" fn altreal_elt(x: SEXP, i: R_xlen_t) -> f64 { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.elt(i as _) as _ + } + + unsafe { + R_set_altrep_Length_method(class_t, Some(altrep_length::)); + R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::)); + R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::)); + R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::)); + R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::)); + R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::)); + R_set_altreal_Elt_method(class_t, Some(altreal_elt::)); + } + + super::register_altrep_class(T::CLASS_NAME, class_t)?; + Ok(()) +} diff --git a/src/altrep/altstring.rs b/src/altrep/altstring.rs new file mode 100644 index 00000000..d96a629b --- /dev/null +++ b/src/altrep/altstring.rs @@ -0,0 +1,149 @@ +use std::{ + ffi::CString, + os::raw::{c_int, c_void}, +}; + +use savvy_ffi::{ + altrep::{ + R_altrep_data2, R_make_altstring_class, R_set_altrep_Coerce_method, + R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method, + R_set_altrep_data2, R_set_altstring_Elt_method, R_set_altvec_Dataptr_method, + R_set_altvec_Dataptr_or_null_method, + }, + R_NaString, R_NilValue, R_xlen_t, Rboolean, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate, + Rf_protect, Rf_unprotect, SET_STRING_ELT, SEXP, SEXPTYPE, STRING_PTR, STRING_PTR_RO, STRSXP, +}; + +use crate::IntoExtPtrSexp; + +pub trait AltString: Sized + IntoExtPtrSexp { + /// Class name to identify the ALTREP class. + const CLASS_NAME: &'static str; + + /// Package name to identify the ALTREP class. + const PACKAGE_NAME: &'static str; + + fn into_altrep(self) -> crate::Result { + super::create_altrep_instance(self, Self::CLASS_NAME) + } + + /// What gets printed when `.Internal(inspect(x))` is used. + fn inspect(&mut self) { + crate::io::r_print(&format!("({})", Self::CLASS_NAME), false); + } + + /// Return the length of the data. + fn length(&mut self) -> usize; + + /// Returns the value of `i`-th element. Note that, it seems R handles the + /// out-of-bound check, so you don't need to implement it here. + fn elt(&mut self, i: usize) -> &str; +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn register_altstring_class( + dll_info: *mut crate::ffi::DllInfo, +) -> crate::error::Result<()> { + let class_name = CString::new(T::CLASS_NAME).unwrap_or_default(); + let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default(); + let class_t = + unsafe { R_make_altstring_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) }; + + #[allow(clippy::mut_from_ref)] + #[inline] + fn materialize(x: &SEXP) -> SEXP { + let data = unsafe { R_altrep_data2(*x) }; + if unsafe { data != R_NilValue } { + return data; + } + + let self_: &mut T = super::extract_self_from_altrep(x); + + let len = self_.length(); + let new = crate::alloc_vector(STRSXP, len).unwrap(); + + unsafe { Rf_protect(new) }; + + for i in 0..len { + unsafe { + SET_STRING_ELT( + new, + i as _, + crate::sexp::utils::str_to_charsxp(self_.elt(i)).unwrap_or(R_NaString), + ) + }; + } + + // Cache the materialized data in data2. + // + // Note that, for example arrow stores it in `CAR()` of data2, but this + // implementation naively uses data2. Probably that should be clever + // because data2 can be used for other purposes. + unsafe { R_set_altrep_data2(*x, new) }; + + // new doesn't need protection because it's used as long as this ALTREP exists. + unsafe { Rf_unprotect(1) }; + + new + } + + unsafe extern "C" fn altrep_duplicate(x: SEXP, _deep_copy: Rboolean) -> SEXP { + let materialized = materialize::(&x); + + // let attrs = unsafe { Rf_protect(Rf_duplicate(ATTRIB(x))) }; + // unsafe { SET_ATTRIB(materialized, attrs) }; + + unsafe { Rf_duplicate(materialized) } + } + + unsafe extern "C" fn altrep_coerce(x: SEXP, sexp_type: SEXPTYPE) -> SEXP { + let materialized = materialize::(&x); + unsafe { Rf_coerceVector(materialized, sexp_type) } + } + + unsafe extern "C" fn altvec_dataptr(x: SEXP, _writable: Rboolean) -> *mut c_void { + let materialized = materialize::(&x); + unsafe { STRING_PTR(materialized) as _ } + } + + unsafe extern "C" fn altvec_dataptr_or_null(x: SEXP) -> *const c_void { + let materialized = materialize::(&x); + unsafe { STRING_PTR_RO(materialized) as _ } + } + + unsafe extern "C" fn altrep_length(x: SEXP) -> R_xlen_t { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.length() as _ + } + + unsafe extern "C" fn altrep_inspect( + x: SEXP, + _: c_int, + _: c_int, + _: c_int, + _: Option, + ) -> Rboolean { + let self_: &mut T = super::extract_self_from_altrep(&x); + self_.inspect(); + + Rboolean_TRUE + } + + unsafe extern "C" fn altstring_elt(x: SEXP, i: R_xlen_t) -> SEXP { + let self_: &mut T = super::extract_self_from_altrep(&x); + unsafe { crate::sexp::utils::str_to_charsxp(self_.elt(i as _)).unwrap_or(R_NaString) } + } + + unsafe { + R_set_altrep_Length_method(class_t, Some(altrep_length::)); + R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::)); + R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::)); + R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::)); + R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::)); + R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::)); + R_set_altstring_Elt_method(class_t, Some(altstring_elt::)); + } + + super::register_altrep_class(T::CLASS_NAME, class_t)?; + Ok(()) +} diff --git a/src/altrep/mod.rs b/src/altrep/mod.rs new file mode 100644 index 00000000..5415d3be --- /dev/null +++ b/src/altrep/mod.rs @@ -0,0 +1,87 @@ +mod altinteger; +mod altlogical; +mod altreal; +mod altstring; + +pub use altinteger::*; +pub use altlogical::*; +pub use altreal::*; +pub use altstring::*; + +use std::{collections::HashMap, sync::Mutex}; + +use once_cell::sync::OnceCell; +use savvy_ffi::{ + altrep::{R_altrep_class_t, R_altrep_data1, R_new_altrep, MARK_NOT_MUTABLE}, + R_NilValue, SEXP, +}; + +use crate::{protect::local_protect, IntoExtPtrSexp}; + +static ALTREP_CLASS_CATALOGUE: OnceCell>> = + OnceCell::new(); + +pub(crate) fn create_altrep_instance( + x: T, + class_name: &'static str, +) -> crate::Result { + let sexp = x.into_external_pointer().0; + local_protect(sexp); + + let catalogue_mutex = match ALTREP_CLASS_CATALOGUE.get() { + Some(catalogue_mutex) => catalogue_mutex, + None => return Err("ALTREP_CLASS_CATALOGUE is not initialized".into()), + }; + let catalogue = match catalogue_mutex.lock() { + Ok(catalogue) => catalogue, + Err(e) => return Err(e.to_string().into()), + }; + let class = match catalogue.get(class_name) { + Some(class) => class, + None => return Err("Failed to get the ALTREP class".into()), + }; + + let altrep = unsafe { R_new_altrep(*class, sexp, R_NilValue) }; + local_protect(altrep); + unsafe { MARK_NOT_MUTABLE(altrep) }; + + Ok(altrep) +} + +fn register_altrep_class( + class_name: &'static str, + class_t: R_altrep_class_t, +) -> crate::error::Result<()> { + // There's no way to let global + ALTREP_CLASS_CATALOGUE.get_or_init(|| Mutex::new(HashMap::new())); + + let catalogue_mutex = match ALTREP_CLASS_CATALOGUE.get() { + Some(catalogue_mutex) => catalogue_mutex, + None => return Err("ALTREP_CLASS_CATALOGUE is not initialized".into()), + }; + + let mut catalogue = match catalogue_mutex.lock() { + Ok(catalogue) => catalogue, + Err(e) => return Err(e.to_string().into()), + }; + + let existing_entry = catalogue.insert(class_name, class_t); + + if existing_entry.is_some() { + return Err( + "[WARN] ALTREP class {class_name} is already defined. Something seems wrong.".into(), + ); + } + + Ok(()) +} + +// Some helpers + +#[allow(clippy::mut_from_ref)] +#[inline] +pub(crate) fn extract_self_from_altrep(x: &SEXP) -> &mut T { + let x = unsafe { crate::get_external_pointer_addr(R_altrep_data1(*x)).unwrap() as *mut T }; + let self_ = unsafe { x.as_mut() }; + self_.expect("Failed to convert the external pointer to the Rust object") +} diff --git a/src/lib.rs b/src/lib.rs index 4a802a54..32174e3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,9 @@ pub mod protect; pub mod sexp; pub mod unwind_protect; +#[cfg(feature = "altrep")] +pub mod altrep; + use std::os::raw::c_char; pub use error::{Error, Result}; diff --git a/wrapper.h b/wrapper.h index 9cecb5aa..fc92f670 100644 --- a/wrapper.h +++ b/wrapper.h @@ -10,3 +10,6 @@ // For R_ParseVector() #include + +// For ALTREP +#include diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c86e5ab0..527dca55 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -90,12 +90,14 @@ fn show() -> Result<(), DynError> { .allowlist_function("Rf_setAttrib") // Integer .allowlist_function("INTEGER") + .allowlist_function("INTEGER_RO") .allowlist_function("INTEGER_ELT") .allowlist_function("SET_INTEGER_ELT") .allowlist_function("Rf_ScalarInteger") .allowlist_function("Rf_isInteger") // Real .allowlist_function("REAL") + .allowlist_function("REAL_RO") .allowlist_function("REAL_ELT") .allowlist_function("SET_COMPLEX_ELT") .allowlist_function("Rf_ScalarReal") @@ -103,17 +105,21 @@ fn show() -> Result<(), DynError> { // Complex .allowlist_type("RComplex") .allowlist_function("COMPLEX") + .allowlist_function("COMPLEX_RO") .allowlist_function("COMPLEX_ELT") .allowlist_function("SET_COMPLEX_ELT") .allowlist_function("Rf_ScalarComplex") .allowlist_function("Rf_isComplex") // Logical .allowlist_function("LOGICAL") + .allowlist_function("LOGICAL_RO") .allowlist_function("LOGICAL_ELT") .allowlist_function("SET_LOGICAL_ELT") .allowlist_function("Rf_ScalarLogical") .allowlist_function("Rf_isLogical") // String and character + .allowlist_function("STRING_PTR") + .allowlist_function("STRING_PTR_RO") .allowlist_function("STRING_ELT") .allowlist_function("SET_STRING_ELT") .allowlist_function("Rf_ScalarString") @@ -157,7 +163,62 @@ fn show() -> Result<(), DynError> { // I/O .allowlist_function("Rprintf") .allowlist_function("REprintf") - .allowlist_type("DllInfo"); + // misc + .allowlist_type("DllInfo") + .allowlist_function("Rf_duplicate") + .allowlist_function("Rf_coerceVector"); + + let builder = builder + // ALTREP + .allowlist_function("MARK_NOT_MUTABLE") + .allowlist_function("ALTREP") + .allowlist_function("ALTREP_CLASS") + .allowlist_function("R_new_altrep") + .allowlist_function("R_altrep_data1") + .allowlist_function("R_altrep_data2") + .allowlist_function("R_set_altrep_data1") + .allowlist_function("R_set_altrep_data2") + .allowlist_item("R_altrep_class_t") + .allowlist_function("R_set_altrep_Coerce_method") + .allowlist_function("R_set_altrep_Length_method") + .allowlist_function("R_set_altrep_Inspect_method") + .allowlist_function("R_set_altrep_Duplicate_method") + .allowlist_function("R_set_altrep_Unserialize_method") + .allowlist_function("R_set_altrep_Serialized_state_method") + // ALTVEC + .allowlist_function("R_set_altvec_Dataptr_method") + .allowlist_function("R_set_altvec_Dataptr_or_null_method") + // ALTINTEGER + .allowlist_function("R_set_altinteger_Elt_method") + .allowlist_function("R_set_altinteger_Max_method") + .allowlist_function("R_set_altinteger_Min_method") + .allowlist_function("R_set_altinteger_Sum_method") + .allowlist_function("R_set_altinteger_No_NA_method") + .allowlist_function("R_set_altinteger_Is_sorted_method") + .allowlist_function("R_set_altinteger_Get_region_method") + .allowlist_function("R_make_altinteger_class") + // ALTREAL + .allowlist_function("R_set_altreal_Elt_method") + .allowlist_function("R_set_altreal_Max_method") + .allowlist_function("R_set_altreal_Min_method") + .allowlist_function("R_set_altreal_Sum_method") + .allowlist_function("R_set_altreal_No_NA_method") + .allowlist_function("R_set_altreal_Is_sorted_method") + .allowlist_function("R_set_altreal_Get_region_method") + .allowlist_function("R_make_altreal_class") + // altlogical + .allowlist_item("R_set_altlogical_Elt_method") + .allowlist_item("R_set_altlogical_Sum_method") + .allowlist_item("R_set_altlogical_No_NA_method") + .allowlist_item("R_set_altlogical_Is_sorted_method") + .allowlist_item("R_set_altlogical_Get_region_method") + .allowlist_item("R_make_altlogical_class") + // altstring + .allowlist_item("R_set_altstring_Elt_method") + .allowlist_item("R_set_altstring_No_NA_method") + .allowlist_item("R_set_altstring_Is_sorted_method") + .allowlist_item("R_set_altstring_Set_elt_method") + .allowlist_item("R_make_altstring_class"); let bindings = builder.generate().expect("Unable to generate bindings");