Skip to content

Using rcopy and sexp methods to create a .Call interface to a compiled Julia library #556

@dmbates

Description

@dmbates

Development versions of Julia-1.12 and 1.13 provide compilation of Julia code to a shared library and ways to shrink the size of the binary (juliac with the --trim option). One attractive application of this capability is to write compute-intensive parts of R packages in Julia instead of going the C/Fortran/C++ route. I think the preferred interface from R for such Julia code would be the .Call interface or the .External interface (https://cran.r-project.org/doc/manuals/R-exts.html#Interface-functions-_002eCall-and-_002eExternal-1).

For each of these interfaces the arguments are passed as pointers to a C struct called SEXPREC (the pointer type is called SEXP) and the return value must also be an SEXP. This the basic boxing/unboxing strategy in R. Doing the unboxing in Julia is straightforwardly accomplished by the rcopy methods from this package (i.e. RCall.jl). The boxing in RCall.jl is accomplished by methods for sexp. But these methods use functions from libR that manipulate locations in the R environment to do things like allocate storage within R and mark it as protected from garbage collection. An example in https://juliainterop.github.io/RCall.jl/dev/custom/#Julia-to-R-direction is

import RCall: sexp, protect, unprotect, setclass!, RClass

function sexp(::Type{RClass{:Bar}}, f::Foo)
    r = protect(sexp(Dict(:x => f.x, :y => f.y)))
    setclass!(r, sexp("Bar"))
    unprotect(1)
    r
end

which causes allocation in the inner calls to sexp, pushes and pops from a stack in the R environment in the protect and unprotect calls, and requires access to certain global constants in setclass!. These Julia function all eventually end up calling C functions in libR. Especially the allocation and the protect/unprotect must manipulate global locations in the environment of the R process calling the Julia code.

If a library was compiled from Julia code that contained code similar to src/types.jl, src/Const.jl and files in src/convert, and that library was opened by an R process, would the calls to libR functions be resolved against the copy of libR in use by the R process? It seems from the way that src/Const.jl contains both @load_const and @load_const_embedded macros that the answer is yes because @load_const_embedded doesn't specify libR.

So would creating Julia code to unbox the arguments from .Call or .External and boxing the result be as simple as creating a RconversionBase package with some of the code from src/types.jl, src/Const.jl, src/methods.jl and the files in src/convert then using that package to provide boxing/unboxing for RCall.jl and for Julia code to be called from R packages?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions