Repository navigation
Expose a stable native API for class and property access - #789
Open
t-kalinowski wants to merge 5 commits into
Open
t-kalinowski wants to merge 5 commits into
t-kalinowski wants to merge 5 commits into
Conversation
Register S7_get_class and S7_get_prop for native consumers, with a public C/C++ header and documented signatures for other bindings. Reuse S7's class lookup and property getters so consumers do not depend on storage details. Add compiled consumer tests and document the API contract. Closes #788
Provide shared function pointers initialized once by S7_init_api() in the consumer's DLL initialization routine. Register the existing accessors directly and remove the forwarding wrappers and per-call name validation. Document scalar, non-missing property names as a caller precondition. Update the compiled consumer tests to initialize the API once and use it from both C and C++.
Compare class lookup, stored properties, and a custom getter through the native API and through cached R calls evaluated from C. Batch 1000 accesses per timing sample, verify returned values, and save timings and build metadata for comparisons across fresh R sessions.
t-kalinowski
marked this pull request as ready for review
October 20, 2026 18:23
hadley
approved these changes
Oct 21, 2026
hadley
left a comment
Member
There was a problem hiding this comment.
I don't really have any strong feelings here, but I added a few questions as they occurred to me
Comment on lines
+3
to
+4
| #' S7 provides a stable native API for retrieving class definitions and reading | ||
| #' properties without depending on how S7 stores them. |
Member
There was a problem hiding this comment.
Suggested change
| #' S7 provides a stable native API for retrieving class definitions and reading | |
| #' properties without depending on how S7 stores them. | |
| #' S7 provides a stable native API for retrieving class definitions and reading | |
| #' properties. |
| #' S7 provides a stable native API for retrieving class definitions and reading | ||
| #' properties without depending on how S7 stores them. | ||
| #' | ||
| #' * `SEXP S7_get_class(SEXP object)` returns the stored S7 class definition, or |
Member
There was a problem hiding this comment.
get_class() or class_get()? Is one or the other more C like?
Comment on lines
+23
to
+28
| #' static SEXP object_value(SEXP object) { | ||
| #' SEXP name = PROTECT(Rf_mkString("value")); | ||
| #' SEXP value = S7_get_prop(object, name); | ||
| #' UNPROTECT(1); | ||
| #' return value; | ||
| #' } |
Member
There was a problem hiding this comment.
Would it make the example clearer to put this in a separate file, like you would typically?
| local_dev_S7_lib(frame = frame) | ||
| if (is.null(lib)) { | ||
| dir.create(lib <<- tempfile("S7-native-api-")) | ||
| install.packages( |
Member
There was a problem hiding this comment.
Does this make the tests substantially slower?
| local_native_api <- local({ | ||
| lib <- NULL | ||
| function(frame = parent.frame()) { | ||
| local_dev_S7_lib(frame = frame) |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Native extensions currently need to depend on S7's internal representation or call back into R to inspect objects. This adds a stable native API for retrieving S7 class definitions and reading properties through their getters.
S7_get_class()returns the stored S7 class definition, resolving shared class references, orR_NilValuewhen there is none.S7_get_prop()reads a property with the same getter and storage behavior asprop(). A non-missing scalar character name is a caller precondition.S7-init.hin their initialization source file, callS7_init_api()during DLL initialization, and includeS7.helsewhere. Calls use shared function pointers without per-call initialization checks.R_GetCCallable().The registered entry points use the existing C implementations directly. Documentation describes the two function contracts and includes a complete example of initialization and property access. A compiled consumer fixture exercises C, C++, direct registered lookup, and repeated header inclusion. The benchmark compares native access with evaluating cached and namespace-qualified R calls.
Local results from bench/native-api.R, using R 4.6.1 on an Apple M4 Max with clang
-O2:Rf_eval()Times are per access: the median of session medians from three fresh R sessions, with 1,000 accesses per C loop. R functions and complete call objects are cached before timing, and all paths repeatedly access the same object. Parsing and call construction are excluded.
Evaluating a namespace-qualified
S7::prop()call takes 0.32 µs for a stored property versus 0.063 µs through the native API (5.1x faster). Getters still execute R code. Class lookup also avoids the general classification logic in R'sS7_class().Closes #788.