FFI Boundary Semantics

This article is about what actually crosses the R/C boundary in Rtinycc:

The statements below are based on the implemented wrapper generator and runtime helpers.

Scalar Inputs Are Converted

Scalar inputs are converted at the boundary. For example:

So scalar arguments are not zero-copy views into R objects. They become C scalars inside the wrapper.

Vector Inputs Are Usually Borrowed

The array input types:

are passed as direct pointers into the underlying R vector storage.

That means:

This is the main zero-copy part of the FFI boundary.

cstring_array Is Rebuilt Per Call

cstring_array is different. The wrapper allocates a temporary const char ** with R_alloc() and fills it by translating each R string element.

So:

Returned Arrays Are Copied into Fresh R Vectors

Array returns are always copied into a newly allocated R vector. The wrapper uses the declared length_arg to size the R result, then memcpy() copies the returned C buffer into that vector.

If free = TRUE, the wrapper also frees the original returned buffer after the copy.

So array returns are not borrowed views into C memory.

Returned cstring Values Are Copied

For cstring returns, the wrapper creates an R string with mkString() when the returned pointer is non-NULL.

That means the resulting R value is a copy in R-managed memory, not a retained external pointer to the original C string.

Returned ptr Values Stay as Pointers

For ptr returns, the wrapper constructs an external pointer around the raw address.

That means:

The same distinction matters for globals and struct fields.

sexp Passes Through Directly

sexp is the most direct boundary mode:

This is useful when you want the R C API contract rather than the stricter FFI conversion layer.

Owned vs Borrowed Helper Pointers

At the helper level:

Use tcc_ptr_is_owned() when you need to distinguish these cases in R code.

Bitfields Are Scalar Helpers, Not Addressable Views

Bitfield helpers behave like scalar getter/setter helpers at the R boundary, but that does not make them ordinary addressable fields.

In particular:

So bitfields are intentionally excluded from the borrowed-address helper model.

Serialization Boundary

Compiled tcc_compiled objects store enough recipe information to recompile after serialize() / unserialize() or readRDS().

Raw pointers and raw tcc_state objects do not gain that behavior. After serialization they are just dead addresses or invalid states, not auto-reconstructed resources. The same applies to callback tokens, struct/union external pointers, and helper allocations from tcc_malloc() or tcc_cstring(): they do not serialize as live native resources.