knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) tcc_bind <- Rtinycc::tcc_bind tcc_compile <- Rtinycc::tcc_compile tcc_ffi <- Rtinycc::tcc_ffi tcc_free <- Rtinycc::tcc_free tcc_malloc <- Rtinycc::tcc_malloc tcc_source <- Rtinycc::tcc_source
This article describes the FFI type names implemented in Rtinycc.
The mapping below is taken from the package type table and wrapper-generation
code, not from a separate design document.
The core scalar types are:
i8, i16, i32, i64u8, u16, u32, u64f32, f64boolcstringptrsexpvoidRtinycc converts R values to these C types inside generated wrappers, but the
R-side scalar carriers depend on what R can represent directly:
i8, i16, i32, u8, and u16 are mediated through R integer scalarsu32, i64, u64, f32, and f64 are mediated through R numeric
(double) coercion and boxingbool uses R logicalcstring uses an R character scalarSo the FFI names are C-side type names, not promises that R has a matching
native scalar type for every width. Integer-like paths are validated, not just
cast silently, and i64 / u64 are only exact up to 2^53 on the R side.
ffi <- tcc_ffi() |> tcc_source( " int add_i32(int a, int b) { return a + b; } double mul_f64(double x, double y) { return x * y; } int negate_bool(_Bool x) { return !x; } " ) |> tcc_bind( add_i32 = list(args = list("i32", "i32"), returns = "i32"), mul_f64 = list(args = list("f64", "f64"), returns = "f64"), negate_bool = list(args = list("bool"), returns = "bool") ) |> tcc_compile() ffi$add_i32(2L, 3L) ffi$mul_f64(2, 4) ffi$negate_bool(TRUE)
cstring and ptr are intentionally different:
cstring converts an R character scalar into a C char * view for the callptr passes an external pointer address through unchangedffi_str <- tcc_ffi() |> tcc_source( " const char* echo_cstring(const char* s) { return s; } void* echo_ptr(void* p) { return p; } " ) |> tcc_bind( echo_cstring = list(args = list("cstring"), returns = "cstring"), echo_ptr = list(args = list("ptr"), returns = "ptr") ) |> tcc_compile() ptr <- tcc_malloc(8) ffi_str$echo_cstring("hello") inherits(ffi_str$echo_ptr(ptr), "externalptr") tcc_free(ptr)
The important semantic difference is discussed in the boundary-semantics
article: returned cstring values are copied into R strings, while returned
ptr values stay as raw addresses.
The implemented array input types are:
rawinteger_arraynumeric_arraylogical_arraycharacter_arraycstring_arrayThe first four map directly onto R vectors. character_array passes the
underlying CHARSXP cells as a read-only SEXP *, not a char **.
Use cstring_array when the C side expects a temporary const char **.
ffi_arr <- tcc_ffi() |> tcc_source( " int first_int(int* x) { return x[0]; } double second_num(double* x) { return x[1]; } " ) |> tcc_bind( first_int = list(args = list("integer_array"), returns = "i32"), second_num = list(args = list("numeric_array"), returns = "f64") ) |> tcc_compile() ffi_arr$first_int(as.integer(c(10, 20, 30))) ffi_arr$second_num(c(1.5, 2.5))
sexpsexp passes the R object through the wrapper without conversion.
ffi_sexp <- tcc_ffi() |> tcc_source( " #include <Rinternals.h> SEXP id_sexp(SEXP x) { return x; } " ) |> tcc_bind( id_sexp = list(args = list("sexp"), returns = "sexp") ) |> tcc_compile() ffi_sexp$id_sexp(list(a = 1, b = 2))
This is the lowest-friction way to cross the boundary when you want to work in terms of the R C API directly rather than the stricter scalar/vector FFI types.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.