[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