[Rd] [External] Data pointer caching vs ALTREP

iuke-tier@ey m@iii@g oii uiow@@edu iuke-tier@ey m@iii@g oii uiow@@edu
Thu Jul 9 16:16:28 CEST 2026


Thanks for the report and the careful analysis. This looks like a bug
in the wrapper implementation. I'll look more carefully and patch
R-devel/R-patched next week when I am back from useR. For caching it
should be sufficient for now to cache the writable pointer.

Best,

luke

On Mon, 6 Jul 2026, Ivan Krylov via R-devel wrote:

> Hello R-devel,
>
> Here's a simplification of the problem that currently plagues lme4:
>
> x <- local({
>  # y[[1]] is large enough to have its own mmap() allocation
>  y <- list(rep(1, 1e7))
>  # y[[1]] is also large enough (>=64 elements) so structure() will
>  # create a wrapper instead of duplicating
>  structure(y[[1]], foo = 'bar')
>  # y[[1]] now has a reference count of >=2
> })
> # now x has the only reference to the original vector
>
> writeLines('
> #include <Rinternals.h>
>
> static double run_arbitrary_R_code(SEXP x) {
>  const double * xd = REAL(x); // oops, forgot REAL_RO
>  // since the underlying vector is MAYBE_SHARED, the
>  // wrapper in x duplicated the underlying vector and
>  // returned a different data pointer
>  Rprintf("REAL(x) = %p\\n", xd);
>
>  double sum = 0;
>  for (R_xlen_t i = 0; i < XLENGTH(x); ++i) sum += xd[i];
>
>  R_gc();
>  // the original vector is now freed, memory unmapped
>
>  return sum;
> }
>
> SEXP foo(SEXP x) {
>  // cache a read-only pointer to the data of x
>  const double *xd = REAL_RO(x);
>  Rprintf("REAL_RO(x) = %p\\n", xd);
>
>  double ret1 = run_arbitrary_R_code(x);
>  // xd is now invalid
>
>  double ret2 = 0;
>  for (R_xlen_t i = 0; i < XLENGTH(x); ++i) ret2 += xd[i]; // <-- crash
>  return ScalarLogical(ret1 == ret2);
> }
> ', 'foo.c')
> stopifnot(tools::Rcmd('SHLIB foo.c') == 0)
> dyn.load(paste0('foo', .Platform$dynlib.ext))
> .Call('foo', x)
> # REAL_RO(x) = 0x7f11527b4040
> # REAL(x) = 0x7f114db68040
> #
> #  *** caught segfault ***
> # address 0x7f11527b4040, cause 'memory not mapped'
>
> In the original lme4 code the data pointers are cached inside Eigen
> objects courtesy of RcppEigen (shared with the R session, ensuring that
> the R objects underlying the data pointers stay protected), and the role
> of run_arbitrary_R_code() is played by a %*% operation (which uses
> REAL() to read a vector).
>
> Is there a nice way to solve this problem of a protected object's data
> pointer suddenly becoming invalid? (Where is the best place to solve
> it? lme4, Rcpp, R itself?) Would it be safe for lme4 to cache writable
> data pointers (e.g. REAL() instead of DATAPTR_RO()), assuming that the
> former will stay the same as long as the object exists? Or does it need
> an extensive rewrite to always refresh cached data pointers?
>
>

-- 
Luke Tierney
Professor Emeritus
Department of Statistics and
    Actuarial Science
University of Iowa
241 Schaeffer Hall                  email:   luke-tierney using uiowa.edu
Iowa City, IA 52242                 WWW:  http://stat.uiowa.edu



More information about the R-devel mailing list