Can the Gambit FFI return a Scheme numeric vector that is a window into an existing C array? (I.e. the Scheme object header specifies the base and length of the C array but the array elements are not copied.)
For example, can it wrap the following C function
const void *get_some_data(size_t *size) { const char *str = "hello world"; *size = strlen(str); return str; }
such that (get-some-data) returns #u8(104 101 108 108 111 32 119 111 114 108 100) and the contents of that bytevector are not a copy?
There are two interesting cases to consider: one where the C array is constant, and one where its elements can be modified. (In both cases, a simple object header pointing to the array only makes sense when the array cannot shrink and cannot be freed. This is similar to slices in Go, and displaced arrays in Common Lisp.)
Skimming gambit.h, there are:
#define ___U8VECTORREF(v,i) \ ___FIX(___FETCH_U8(___BODY_AS(v,___tSUBTYPED),(i)>>___TB))
#define ___BODY_AS(obj,tag)___BODY0_AS(obj,tag) #define ___BODY0_AS(obj,tag)(___UNTAG_AS(obj,tag)+___REFERENCE_TO_BODY) #define ___UNTAG_AS(obj,tag)___CAST(___WORD*,(obj)-(tag)) #define ___FETCH_U8(base,i)*(___CAST(___U8*,base)+(i)) #define ___REFERENCE_TO_BODY 1
It looks to me like a vector's body is stored directly after the header, but I'm not sure I deciphered the pointer indirection correctly.
Afficher les réponses par date
Homogeneous vectors are implemented without indirection, so a u8vector is stored in a contiguous area of memory that starts with a 1 word header (that indicates the length, type and GC information), followed by the bytes of the u8vector. Avoiding the indirection allows faster access to the elements of the u8vector.
To avoid copying it is best to leave the memory allocation to the Scheme code and pass a pointer to the u8vector to the C function that fills this buffer. For example, this is how you could interface to the C “read” function:
$ cat read.scm (c-declare "#include <unistd.h>")
(define read-into-buffer (c-lambda (int int int scheme-object) ssize_t #<<end-of-c-lambda
int fd = ___arg1; int start = ___arg2; int end = ___arg3; ___U8 *buf = ___CAST(___U8*, ___BODY(___arg4));
ssize_t n = read(fd, buf+start, end-start);
___return(n);
end-of-c-lambda ))
(define buf (make-u8vector 10 0))
(define stdin 0)
(define n (read-into-buffer stdin 2 5 buf)) ;; read 3 bytes into buf
(pp (list buf n))
$ gsc -exe read.scm $ echo abcdef | ./read (#u8(0 0 97 98 99 0 0 0 0 0) 3)
If you want the C code to asynchronously fill this buffer, then the u8vector should be allocated as a “still” object. You can do this with:
(define buf (##still-copy (make-u8vector 10 0)))
or interface to the C function ___alloc_scmobj in the runtime system.
An alternative is to reverse this logic, i.e. the C code manages the memory allocation and you return a pointer to Scheme (in the form of a foreign object). You also need to define Scheme procedures to access the bytes through this foreign object. For example:
$ cat read.scm (c-declare "#include <unistd.h>")
(c-declare "___U8 buf[100];")
(define get-buffer (c-lambda () (pointer unsigned-int8) "___return(buf);"))
(define read-into-buffer (c-lambda (int int int) ssize_t #<<end-of-c-lambda
int fd = ___arg1; int start = ___arg2; int end = ___arg3;
ssize_t n = read(fd, buf+start, end-start);
___return(n);
end-of-c-lambda ))
(define buffer-ref (c-lambda ((pointer unsigned-int8) int) unsigned-int8 "___return(___arg1[___arg2]);"))
(define buf (get-buffer))
(define stdin 0)
(define n (read-into-buffer stdin 2 5)) ;; read 3 bytes into buf
(pp (list (buffer-ref buf 2) (buffer-ref buf 3) (buffer-ref buf 4) n))
$ gsc -exe read.scm $ echo abcdef | ./read (97 98 99 3)
Marc
On Apr 3, 2024, at 8:13 AM, Lassi Kortela lassi@lassi.io wrote:
Can the Gambit FFI return a Scheme numeric vector that is a window into an existing C array? (I.e. the Scheme object header specifies the base and length of the C array but the array elements are not copied.)
For example, can it wrap the following C function
const void *get_some_data(size_t *size) { const char *str = "hello world"; *size = strlen(str); return str; }
such that (get-some-data) returns #u8(104 101 108 108 111 32 119 111 114 108 100) and the contents of that bytevector are not a copy?
There are two interesting cases to consider: one where the C array is constant, and one where its elements can be modified. (In both cases, a simple object header pointing to the array only makes sense when the array cannot shrink and cannot be freed. This is similar to slices in Go, and displaced arrays in Common Lisp.)
Skimming gambit.h, there are:
#define ___U8VECTORREF(v,i) \ ___FIX(___FETCH_U8(___BODY_AS(v,___tSUBTYPED),(i)>>___TB))
#define ___BODY_AS(obj,tag)___BODY0_AS(obj,tag) #define ___BODY0_AS(obj,tag)(___UNTAG_AS(obj,tag)+___REFERENCE_TO_BODY) #define ___UNTAG_AS(obj,tag)___CAST(___WORD*,(obj)-(tag)) #define ___FETCH_U8(base,i)*(___CAST(___U8*,base)+(i)) #define ___REFERENCE_TO_BODY 1
It looks to me like a vector's body is stored directly after the header, but I'm not sure I deciphered the pointer indirection correctly.
Gambit-list mailing list -- gambit-list@iro.umontreal.ca To unsubscribe send an email to gambit-list-leave@iro.umontreal.ca