[gambit-list] Allocate Scheme vector before or after C call?
Lassi Kortela
lassi at lassi.io
Mon Jul 20 07:07:18 EDT 2020
Marc, thank you once again for the excellent explanation. You have a
knack for explaining complex topics clearly. We should start collecting
and editing this stuff into the Gambit manual proper.
Here's a Unix getgroups() wrapper that allocates increasing powers of
two until the result fits. We should finish up SRFI 170 (POSIX APIs)
with Harold; since the Unix syscall interface is designed such that the
kernel copies data into statically-sized userland buffers, many syscalls
are best wrapped by trying powers of two.
The below code raises more potential problems:
- I'm still not confident that I understand when and how to release
objects. In the code below, __release_scmobj is used to wrap up
accessing ___BODY; is this right?
- getgroups() returns a gid_t array but POSIX doesn't say how many bits
wide gid_t is. (In fact, it doesn't even say whether it's a signed or
unsigned type.) This is the case for many/most C datatypes. An API like
___alloc_sint_vector(NULL, count, sizeof(gid_t)) would help with this.
- getgroups() can return fewer entries than we allocated buffer space
for. Does Gambit have a way of truncating vectors? In general, there is
no way to know ahead of time the exact size needed for result buffers:
some syscalls accept a NULL buffer to not copy any data and only return
the right buffer size, but relying on that size can lead to race
conditions between that call and the next call that supplies a non-NULL
buffer to actually copy the data.
Clojure transients <https://clojure.org/reference/transients> might be a
good model for Scheme FFI in situations like this. Clojure is mostly an
immutable language, but a transient is a mutable collection that is
"sealed" into an immutable one once it has been filled; transients were
added for performance reasons. While Scheme strings and vectors are
mutable, their size cannot change (at least not by RnRS means) so in
that respect they are immutable.
Perhaps Gambit's object header can be hacked from C code to mutate a
vector to have fewer actual elements than space is allocated for. Is
such a thing safe to do, and would it be workable to have an official
way in the FFI to truncate a vector?
----------------------------------------------------------------------
(c-declare "#include <unistd.h>")
(define getgroups
(c-lambda () scheme-object #<<c-end
for (int cap = 2; cap <= 256; cap *= 2) {
size_t nbytes = cap * sizeof(___S32);
___SCMOBJ vector = ___EXT(___alloc_scmobj)(NULL, ___sS32VECTOR,
nbytes);
if (___FIXNUMP(vector)) ___return(___FAL);
___S32 *gids = ___CAST(___S32 *, ___BODY(vector));
int len = getgroups(cap, gids);
___EXT(___release_scmobj)(vector);
if (len >= 0) ___return(vector);
}
___return(___FAL);
c-end
))
(define (writeln x) (write x) (newline))
(let ((gs (getgroups)))
(writeln gs)
(writeln (s32vector-length gs)))
More information about the Gambit-list
mailing list