diff --git a/ChangeLog b/ChangeLog index 7ec6570b6..bf4308ef2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2026-09-30 Kevin Ushey + + * inst/include/Rcpp/vector/traits.h: Move the out-of-bounds warning into + a cold, out-of-line helper; add get_size() to the vector caches + * inst/include/Rcpp/vector/Vector.h: Return the length cached by + r_vector_cache from size() and length() rather than calling + Rf_xlength() each time + * inst/include/Rcpp/vector/Matrix.h: Derive ncol() from the cached + length and nrow() instead of reading the dim attribute; document that + no data members may be added, as the layout is shared across packages + 2026-09-22 Iñaki Ucar * inst/include/Rcpp/sugar/matrix/col.h: Fix Col constructor using ncol() diff --git a/inst/NEWS.Rd b/inst/NEWS.Rd index 0bac0eee9..d70841ee7 100644 --- a/inst/NEWS.Rd +++ b/inst/NEWS.Rd @@ -27,6 +27,12 @@ warnings (Iñaki in \ghpr{1508} closing \ghit{1497}) \item The \code{col} constructor now uses \code{nrow} in an initialization (Iñaki in \ghpr{1509}) + \item Bounds-checked element access is faster, \code{size()} of + atomic vectors uses the cached length, and \code{ncol()} no longer + reads the \code{dim} attribute; object layouts are unchanged, as + packages such as \pkg{rust} and \pkg{revdbayes} exchange Rcpp + objects across shared libraries (Kevin in \ghpr{1511} closing + \ghit{1510}) } \item Changes in Rcpp Documentation: \itemize{ diff --git a/inst/include/Rcpp/vector/Matrix.h b/inst/include/Rcpp/vector/Matrix.h index 137de2500..8bef4fe4b 100644 --- a/inst/include/Rcpp/vector/Matrix.h +++ b/inst/include/Rcpp/vector/Matrix.h @@ -27,6 +27,10 @@ namespace Rcpp{ template class StoragePolicy = PreserveStorage > class Matrix : public Vector, public MatrixBase > { + // The only data member. Packages pass Rcpp objects by reference across + // shared-library boundaries (e.g. rust calls into revdbayes through XPtr + // function pointers), and the two sides may have been compiled against + // different Rcpp versions. Adding a member changes the layout they share. int nrows ; public: @@ -92,14 +96,17 @@ class Matrix : public Vector, public MatrixBase( VECTOR::size() / nrows ) ; } inline int nrow() const { return nrows ; } inline int cols() const { - return VECTOR::dims()[1]; + return ncol() ; } inline int rows() const { return nrows ; diff --git a/inst/include/Rcpp/vector/Vector.h b/inst/include/Rcpp/vector/Vector.h index bfb78106c..8abd8c221 100644 --- a/inst/include/Rcpp/vector/Vector.h +++ b/inst/include/Rcpp/vector/Vector.h @@ -263,17 +263,17 @@ class Vector : #endif /** - * the length of the vector, uses Rf_xlength + * the length of the vector, as cached by update() */ inline R_xlen_t length() const { - return ::Rf_xlength( Storage::get__() ) ; + return cache.get_size() ; } /** * alias of length */ inline R_xlen_t size() const { - return ::Rf_xlength( Storage::get__() ) ; + return cache.get_size() ; } /** diff --git a/inst/include/Rcpp/vector/traits.h b/inst/include/Rcpp/vector/traits.h index 3e1dcecec..9a3086e39 100644 --- a/inst/include/Rcpp/vector/traits.h +++ b/inst/include/Rcpp/vector/traits.h @@ -24,6 +24,16 @@ namespace Rcpp{ namespace traits{ + // Kept out of line and marked cold so that the (almost never taken) + // warning path doesn't bloat element access and block optimization of + // hot loops, e.g. in sugar expressions. +#if defined(__GNUC__) + __attribute__((noinline, cold)) +#endif + inline void warn_index_out_of_bounds(R_xlen_t i, R_xlen_t size) { + warning("subscript out of bounds (index %s >= vector size %s)", i, size); // #nocov + } + template class StoragePolicy = PreserveStorage > class r_vector_cache{ public: @@ -38,11 +48,12 @@ namespace traits{ inline void update( const VECTOR& v ) { start = ::Rcpp::internal::r_vector_start(v) ; - size = v.size(); + size = ::Rf_xlength(v.get__()); } inline iterator get() const { return start; } inline const_iterator get_const() const { return start; } + inline R_xlen_t get_size() const { return size; } inline proxy ref() { check_index(0); return start[0] ;} inline proxy ref(R_xlen_t i) { check_index(i); return start[i] ; } @@ -55,7 +66,7 @@ namespace traits{ void check_index(R_xlen_t i) const { #ifndef RCPP_NO_BOUNDS_CHECK if (i >= size) { - warning("subscript out of bounds (index %s >= vector size %s)", i, size); // #nocov + warn_index_out_of_bounds(i, size); // #nocov } #endif } @@ -80,6 +91,10 @@ namespace traits{ } inline iterator get() const { return iterator( proxy(*p, 0 ) ) ;} inline const_iterator get_const() const { return const_iterator( const_proxy(*p, 0) ) ; } + // Not cached: a size member here would change the layout of List and + // CharacterVector, which packages pass by reference across + // shared-library boundaries (see the note in Matrix.h). + inline R_xlen_t get_size() const { return ::Rf_xlength(p->get__()); } inline proxy ref() { check_index(0); return proxy(*p,0) ; } inline proxy ref(R_xlen_t i) { check_index(i); return proxy(*p,i);} @@ -92,8 +107,9 @@ namespace traits{ void check_index(R_xlen_t i) const { #ifndef RCPP_NO_BOUNDS_CHECK - if (i >= p->size()) { - warning("subscript out of bounds (index %s >= vector size %s)", i, p->size()); // #nocov + R_xlen_t size = get_size() ; + if (i >= size) { + warn_index_out_of_bounds(i, size); // #nocov } #endif }