diff --git a/collects/ffi/unsafe.rkt b/collects/ffi/unsafe.rkt index e722d5c198..28df2e3f7a 100644 --- a/collects/ffi/unsafe.rkt +++ b/collects/ffi/unsafe.rkt @@ -10,7 +10,8 @@ cpointer? ptr-equal? ptr-add ptr-ref ptr-set! (protect-out cast) ptr-offset ptr-add! offset-ptr? set-ptr-offset! vector->cpointer flvector->cpointer saved-errno lookup-errno - ctype? make-ctype make-cstruct-type make-sized-byte-string ctype->layout + ctype? make-ctype make-cstruct-type make-array-type make-union-type + make-sized-byte-string ctype->layout _void _int8 _uint8 _int16 _uint16 _int32 _uint32 _int64 _uint64 _fixint _ufixint _fixnum _ufixnum _float _double _double* @@ -973,6 +974,90 @@ [(_ . xs) (_bytes . xs)] [_ _bytes])) +;; (_array ...+) +(provide _array + array? array-ptr + (protect-out array-ref array-set!)) + +(define _array + (case-lambda + [(t n) + (make-ctype (make-array-type t n) + (lambda (v) (array-ptr v)) + (lambda (v) (make-array v t n)))] + [(t n . ns) + (_array (apply _array t ns) n)])) + +(define-struct array (ptr type len)) +(define array-ref + (case-lambda + [(a i) + (let ([len (array-len a)]) + (if (< -1 i len) + (ptr-ref (array-ptr a) (array-type a) i) + (raise-mismatch-error 'array-ref "index out of bounds: " i)))] + [(a . is) + (let loop ([a a] [is is]) + (if (null? is) + a + (loop (array-ref a (car is)) (cdr is))))])) +(define array-set! + (case-lambda + [(a i v) + (let ([len (array-len a)]) + (if (< -1 i len) + (ptr-set! (array-ptr a) (array-type a) i v) + (raise-mismatch-error 'array-ref "index out of bounds: " i)))] + [(a i i1 . is+v) + (let ([is+v (reverse (list* i i1 is+v))]) + (define v (car is+v)) + (define i (cadr is+v)) + (let loop ([a a] [is (cddr is+v)]) + (if (null? is) + (array-set! a i v) + (loop (array-ref a (car is)) (cdr is)))))])) + +;; (_array/list ...+) +;; Like _list, but for arrays instead of pointers at the C level. +(provide _array/list) +(define _array/list + (case-lambda + [(t n) + (make-ctype (make-array-type t n) + (lambda (v) (list->cblock v t n)) + (lambda (v) (cblock->list v t n)))] + [(t n . ns) + (_array/list (apply _array/list t ns) n)])) + +;; (_array/vector ...+) +;; Like _vector, but for arrays instead of pointers at the C level. +(provide _array/vector) +(define _array/vector + (case-lambda + [(t n) + (make-ctype (make-array-type t n) + (lambda (v) (vector->cblock v t n)) + (lambda (v) (cblock->vector v t n)))] + [(t n . ns) + (_array/vector (apply _array/vector t ns) n)])) + +;; (_union ...+) +(provide _union + union? union-ptr + (protect-out union-ref union-set!)) + +(define (_union t . ts) + (let ([ts (cons t ts)]) + (make-ctype (apply make-union-type ts) + (lambda (v) (union-ptr v)) + (lambda (v) (make-union v ts))))) + +(define-struct union (ptr types)) +(define (union-ref u i) + (ptr-ref (union-ptr u) (list-ref (union-types u) i))) +(define (union-set! u i v) + (ptr-set! (union-ptr u) (list-ref (union-types u) i) v)) + ;; ---------------------------------------------------------------------------- ;; Tagged pointers @@ -1418,6 +1503,7 @@ (cond [(ctype? b) (ctype->layout b)] [(list? b) (map ctype->layout b)] + [(vector? b) (vector (ctype->layout (vector-ref b 0)) (vector-ref b 1))] [else (hash-ref prim-synonyms b b)]))) ;; ---------------------------------------------------------------------------- @@ -1432,10 +1518,14 @@ (values x type)))) ;; Converting Scheme lists to/from C vectors (going back requires a length) -(define* (list->cblock l type) +(define* (list->cblock l type [need-len #f]) + (define len (length l)) + (when need-len + (unless (= len need-len) + (error 'list->cblock "list does not have the expected length: ~e" l))) (if (null? l) #f ; null => NULL - (let ([cblock (malloc (length l) type)]) + (let ([cblock (malloc len type)]) (let loop ([l l] [i 0]) (unless (null? l) (ptr-set! cblock type i (car l)) @@ -1453,8 +1543,11 @@ "expecting a non-void pointer, got ~s" cblock)])) ;; Converting Scheme vectors to/from C vectors -(define* (vector->cblock v type) +(define* (vector->cblock v type [need-len #f]) (let ([len (vector-length v)]) + (when need-len + (unless (= need-len len) + (error 'vector->cblock "vector does not have the expected length: ~e" v))) (if (zero? len) #f ; #() => NULL (let ([cblock (malloc len type)]) diff --git a/collects/scribblings/foreign/types.scrbl b/collects/scribblings/foreign/types.scrbl index 2d61451ccb..91f8b48960 100644 --- a/collects/scribblings/foreign/types.scrbl +++ b/collects/scribblings/foreign/types.scrbl @@ -62,7 +62,9 @@ type. It can be any of the following symbols: ] The result can also be a list, which describes a C struct whose -element representations are provided in order within the list.} +element representations are provided in order within the +list. Finally, the result can be a vector of size 2 containing an +element representation followed by an exact-integer count.} @defproc[(compiler-sizeof [sym symbol?]) exact-nonnegative-integer?]{ @@ -70,7 +72,7 @@ element representations are provided in order within the list.} Possible values for @racket[symbol] are @racket['int], @racket['char], @racket['short], @racket['long], @racket['*], @racket['void], @racket['float], @racket['double]. The result is the size of the -correspond type according to the C @cpp{sizeof} operator for the +corresponding type according to the C @cpp{sizeof} operator for the current platform. The @racket[compiler-sizeof] operation should be used to gather information about the current platform, such as defining alias type like @racket[_int] to a known type like @@ -1029,6 +1031,136 @@ expects arguments for both the super fields and the new ones: ]} +@; ------------------------------------------------------------ + +@section{C Array Types} + +@defproc[(make-array-type [type ctype?] + [count exact-nonnegative-integer?]) + ctype?]{ + +The primitive type constructor for creating new C array types. Like C +struct types, array types are new primitive types with no conversion +functions associated. When used as a function argument or return type, +array types behave like pointer types; otherwise, array types behave +like struct types (i.e., a struct with as many fields as the array has +elements), particularly when used for a field within a struct type. + +Since an array is treated like a struct, @racket[cast]ing a +pointer type to an array type does not work. Instead, use +@racket[ptr-ref] with a pointer, an array type constructed with +@racket[_array], and index @racket[0] to convert a pointer to a Racket +representation that works with @racket[array-ref] and +@racket[array-set!].} + + +@defproc[(_array [type ctype?] [count exact-nonnegative-integer?] ...+) + ctype?]{ + +Creates an array type whose Racket representation is an array that +works with @racket[array-ref] and @racket[array-set!]. The array is +not copied; the Racket representation is backed by the underlying C +representation. + +Supply multiple @racket[count]s for a multidimensional array. Since C +uses row-major order for arrays, @racket[(_array _t _n _m)] is +equivalent to @racket[(_array (_array _t _m) _n)], which is different +from an array of pointers to arrays.} + + +@defproc[(array? [v any/c]) boolean?]{ + +Returns @racket[#t] if @racket[v] is a Racket representation of a C +value via @racket[_array], @racket[#f] otherwise.} + + +@defproc[(array-ref [a array?] [i exact-nonnegative-integer?] ...+) + any/c]{ + +Extracts an element from an array. Use multiple @racket[i] indices for +a multidimensional array access; using fewer indices than the array +dimension produces a sub-array.} + + +@defproc[(array-set! [a array?] + [i exact-nonnegative-integer?] ...+ + [v any/c]) + void?]{ + +Sets an element in an array. Use multiple @racket[i] indices for a +multidimensional array update; using fewer indices than the array +dimension sets a sub-array (i.e., @racket[v] must be an array of the +same size as the sub-array and @racket[v] is copied into the +sub-array).} + + +@defproc[(array-ptr [a array?]) cpointer?]{ + +Extracts the pointer for an array's storage.} + + +@defproc[(_array/list [type ctype?] [count exact-nonnegative-integer?] ...+) + ctype?]{ + +Like @racket[_array], but the Racket representation is a list (or list +of lists for a multidimensional array) of elements copied to and from +an underlying C array.} + + +@defproc[(_array/vector [type ctype?] [count exact-nonnegative-integer?] ...+) + ctype?]{ + +Like @racket[_array], but the Racket representation is a vector (or +vector of vectors for a multidimensional array) of elements copied to +and from an underlying C array.} + + +@; ------------------------------------------------------------ + +@section{C Union Types} + +@defproc[(make-union-type [type ctype?] ...+) + ctype?]{ + +The primitive type constructor for creating new C union types. Like C +struct types, union types are new primitive types with no conversion +functions associated. Unions are always treated like structs.} + + +@defproc[(_union [type ctype?] ...+) + ctype?]{ + +Creates a union type whose Racket representation is a union that +works with @racket[union-ref] and @racket[union-set!]. The union is +not copied; the Racket representation is backed by the underlying C +representation.} + + +@defproc[(union? [v any/c]) boolean?]{ + +Returns @racket[#t] if @racket[v] is a Racket representation of a C +value via @racket[_union], @racket[#f] otherwise.} + + +@defproc[(union-ref [u union?] [i exact-nonnegative-integer?]) + any/c]{ + +Extracts a variant from a union.} + + +@defproc[(union-set! [u union?] + [i exact-nonnegative-integer?] + [v any/c]) + void?]{ + +Sets a variant in a union..} + + +@defproc[(union-ptr [u array?]) cpointer?]{ + +Extracts the pointer for a union's storage.} + + @; ------------------------------------------------------------ @section{Enumerations and Masks} diff --git a/collects/tests/racket/foreign-test.c b/collects/tests/racket/foreign-test.c index b14d713a4b..c5249d2521 100644 --- a/collects/tests/racket/foreign-test.c +++ b/collects/tests/racket/foreign-test.c @@ -78,3 +78,125 @@ X char_int charint_swap(char_int x) { int(*grabbed_callback)(int) = NULL; X void grab_callback(int(*f)(int)) { grabbed_callback = f; } X int use_grabbed_callback(int n) { return grabbed_callback(n); } + +typedef char c7_array[7]; + +X char* increment_c_array(c7_array c) { + int i; + for (i = 0; i < 7; i++) + c[i]++; + return c; +} + +struct char7 { + char c[7]; +}; +struct int_char7_int { + int i1; + struct char7 c7; + int i2; +}; + +X struct int_char7_int increment_ic7i(struct int_char7_int v) +{ + int i; + + v.i1++; + for (i = 0; i < 7; i++) + v.c7.c[i]++; + v.i2++; + + return v; +} + +X struct int_char7_int ic7i_cb(struct int_char7_int v, + struct int_char7_int (*cb)(struct int_char7_int)) +{ + int i; + + --v.i1; + for (i = 0; i < 7; i++) + --v.c7.c[i]; + --v.i2; + + v = cb(v); + + v.i1++; + for (i = 0; i < 7; i++) + v.c7.c[i]++; + v.i2++; + + return v; +} + +X char* increment_2d_array(char c[3][7]) { + int i, j; + for (i = 0; i < 7; i++) { + for (j = 0; j < 3; j++) { + c[j][i] += (i + j); + } + } + return (char *)c; +} + +X int with_2d_array_cb(void (*cb)(char c[3][7])) +{ + char c[3][7]; + int i, j, r; + + for (i = 0; i < 3; i++) { + for (j = 0; j < 7; j++) { + c[i][j] = (i + (2 * j)); + } + } + + cb(c); + + r = 0; + for (i = 0; i < 3; i++) { + for (j = 0; j < 7; j++) { + r += (c[i][j] - ((2 * i) + (2 * j))); + } + } + + return r; +} + +union borl { + char b; + long l; +}; + +X union borl increment_borl(int which, union borl v) +{ + int i; + + if (which) { + v.l++; + } else { + v.b++; + } + + return v; +} + +union ic7iorl { + struct int_char7_int ic7i; + long l; +}; + +X union ic7iorl increment_ic7iorl(int which, union ic7iorl v) +{ + int i; + + if (which) { + v.l++; + } else { + v.ic7i.i1++; + for (i = 0; i < 7; i++) + v.ic7i.c7.c[i]++; + v.ic7i.i2++; + } + + return v; +} diff --git a/collects/tests/racket/foreign-test.rktl b/collects/tests/racket/foreign-test.rktl index b829c4e73b..14c062140e 100644 --- a/collects/tests/racket/foreign-test.rktl +++ b/collects/tests/racket/foreign-test.rktl @@ -52,6 +52,20 @@ (when (file-exists? o) (delete-file o)) (when (file-exists? so) (delete-file so))))) +;; Test arrays +(define _c7_list (_array/list _byte 7)) +(test 7 ctype-sizeof _c7_list) +(test 1 ctype-alignof _c7_list) + +(test 21 ctype-sizeof (_array _byte 3 7)) + +(define-cstruct _ic7i ([i1 _int] + [c7 _c7_list] + [i2 _int])) + +(define _borl (_union _byte _long)) +(define _ic7iorl (_union _ic7i _long)) + (define test-lib (ffi-lib "./foreign-test")) (for ([n (in-range 5)]) @@ -173,6 +187,82 @@ (test '(1 2) (get-ffi-obj 'scheme_make_pair #f (_fun _scheme _scheme -> _scheme)) 1 '(2)) + ;; --- + ;; test arrays + (let ([p (malloc _c7_list)]) ;; should allocate the right size + (for ([i 7]) (ptr-set! p _byte i (+ i 10))) + (test (for/list ([i 7]) (+ i 10)) cast p _pointer (_list o _byte 7)) + (t (for/list ([i 7]) (+ i 11)) 'increment_c_array (_fun _pointer -> (_list o _byte 7)) p) + (let ([a (ptr-ref p (_array _byte 7))]) + (test 12 array-ref a 1) + (ptr-set! p _byte 1 17) + (test 17 array-ref a 1))) + (let ([v (for/list ([i 7]) i)]) + ;; pass array as pointer: + ;; FIXME: these tests wrap the result pointer as non-GCable, + ;; but _c7_list allocates the argument array as GCable. + (t (for/list ([i 7]) (add1 i)) 'increment_c_array (_fun _c7_list -> (_list o _byte 7)) v) + (t (for/list ([i 7]) (add1 i)) 'increment_c_array (_fun _c7_list -> _c7_list) v) + (let ([r ((ffi 'increment_c_array (_fun _c7_list -> (_array _byte 7))) v)]) + (test 2 array-ref r 1)) + ;; Array within struct argument and result: + (let* ([ic7i (make-ic7i 13 v 14)] + [ic7i-2 ((ffi 'increment_ic7i (_fun _ic7i -> _ic7i)) ic7i)]) + (test v ptr-ref (cast ic7i _ic7i-pointer _pointer) _c7_list 'abs (ctype-sizeof _int)) + (test 13 ic7i-i1 ic7i) + (test v ic7i-c7 ic7i) + (test 14 ic7i-i2 ic7i) + (test 14 ic7i-i1 ic7i-2) + (test (map add1 v) ic7i-c7 ic7i-2) + (test 15 ic7i-i2 ic7i-2) + (let ([ic7i-3 ((ffi 'ic7i_cb (_fun _ic7i (_fun _ic7i -> _ic7i) -> _ic7i)) + ic7i + (lambda (ic7i-4) + (test 12 ic7i-i1 ic7i-4) + (test (cons 255 (map sub1 (cdr v))) ic7i-c7 ic7i-4) + (test 13 ic7i-i2 ic7i-4) + (make-ic7i 2 (map (lambda (x) (- 252 x)) v) 9)))]) + (test 3 ic7i-i1 ic7i-3) + (test (map add1 (map (lambda (x) (- 252 x)) v)) ic7i-c7 ic7i-3) + (test 10 ic7i-i2 ic7i-3)))) + ;; Two-dimensional array: + ;; FIXME: same allocation bug for result as above + (let ([v (for/list ([j 3]) (for/list ([i 7]) (+ i j)))] + [v2 (for*/vector ([j 3] [i 7]) (+ i i j j))] + [v3 (for/vector ([j 3]) (for/vector ([i 7]) (+ i i j j)))]) + (t v2 'increment_2d_array (_fun (_array/list _byte 3 7) -> (_vector o _byte 21)) v) + (t v2 'increment_2d_array (_fun (_array/list (_array/list _byte 7) 3) -> (_vector o _byte 21)) v) + (t v3 'increment_2d_array (_fun (_array/list (_array/list _byte 7) 3) -> (_array/vector _byte 3 7)) v)) + (t 0 'with_2d_array_cb (_fun (_fun (_array _byte 3 7) -> _void) -> _int) + (lambda (a) (for ([i 3]) + (for ([j 7]) + (test (+ i (* 2 j)) array-ref a i j) + (array-set! a i j (+ (* 2 i) (* 2 j))))))) + ;; --- + ;; test union + (let ([u (ptr-ref (malloc _borl) _borl)]) + (union-set! u 0 190) + (test 190 union-ref u 0) + (let ([u2 ((ffi 'increment_borl (_fun _int _borl -> _borl)) 0 u)]) + (test 191 union-ref u2 0)) + (union-set! u 1 (expt 2 19)) + (test (expt 2 19) union-ref u 1) + (let ([u2 ((ffi 'increment_borl (_fun _int _borl -> _borl)) 1 u)]) + (test (add1 (expt 2 19)) union-ref u2 1))) + (let ([u (ptr-ref (malloc _ic7iorl) _ic7iorl)]) + (union-set! u 1 190) + (test 190 union-ref u 1) + (let ([u2 ((ffi 'increment_ic7iorl (_fun _int _ic7iorl -> _ic7iorl)) 1 u)]) + (test 191 union-ref u2 1)) + (let ([v (for/list ([i 7]) i)]) + (union-set! u 0 (make-ic7i 3 v 88)) + (test 3 ic7i-i1 (union-ref u 0)) + (set-ic7i-i1! (union-ref u 0) 9) + (test 9 ic7i-i1 (union-ref u 0)) + (let ([u2 ((ffi 'increment_ic7iorl (_fun _int _ic7iorl -> _ic7iorl)) 0 u)]) + (test 10 ic7i-i1 (union-ref u2 0)) + (test 89 ic7i-i2 (union-ref u2 0)) + (test (map add1 v) ic7i-c7 (union-ref u2 0))))) ) ;; test setting vector elements diff --git a/src/foreign/foreign.c b/src/foreign/foreign.c index a858f67e39..53692af8ae 100644 --- a/src/foreign/foreign.c +++ b/src/foreign/foreign.c @@ -847,6 +847,8 @@ typedef union _ForeignAny { /* This is a tag that is used to identify user-made struct types. */ #define FOREIGN_struct (27) +#define FOREIGN_array (28) +#define FOREIGN_union (29) static int is_gcable_pointer(Scheme_Object *o) { return !SCHEME_CPTRP(o) @@ -915,6 +917,8 @@ static ffi_type ffi_type_gcpointer; #define CTYPE_USER_S2C(x) (((ctype_struct*)(x))->scheme_to_c) #define CTYPE_USER_C2S(x) (((ctype_struct*)(x))->c_to_scheme) +#define CTYPE_ARG_PRIMTYPE(x) ((CTYPE_PRIMLABEL(x) == FOREIGN_array) ? &ffi_type_pointer : CTYPE_PRIMTYPE(x)) + /* Returns #f for primitive types. */ #define MYNAME "ctype-basetype" static Scheme_Object *foreign_ctype_basetype(int argc, Scheme_Object *argv[]) @@ -985,7 +989,7 @@ static int ctype_sizeof(Scheme_Object *type) case FOREIGN_gcpointer: return sizeof(void*); case FOREIGN_scheme: return sizeof(Scheme_Object*); case FOREIGN_fpointer: return sizeof(void*); - /* for structs */ + /* for structs and arrays */ default: return CTYPE_PRIMTYPE(type)->size; } } @@ -1144,6 +1148,138 @@ static Scheme_Object *foreign_make_cstruct_type(int argc, Scheme_Object *argv[]) } #undef MYNAME +/*****************************************************************************/ +/* array types */ + +/* (make-array-type type len) -> ctype */ +/* This creates a new primitive type that is an array. An array is the + * same as a cpointer as an argument, but it behave differently within + * a struct or for allocation. Marshaling to lists or whatever should + * be done in Scheme. */ +#define MYNAME "make-array-type" +static Scheme_Object *foreign_make_array_type(int argc, Scheme_Object *argv[]) +{ + Scheme_Object *base, *basetype; + GC_CAN_IGNORE ffi_type *libffi_type, **elements; + ctype_struct *type; + intptr_t len; + + if (NULL == (base = get_ctype_base(argv[0]))) + scheme_wrong_type(MYNAME, "C-type", 0, argc, argv); + if (!scheme_get_int_val(argv[1], &len) || (len < 0)) + scheme_wrong_type(MYNAME, "pointer-sized nonnegative exact integer", 1, argc, argv); + + /* libffi doesn't seem to support array types, but we try to make + libffi work anyway by making a structure type that is used when + an array appears as a struct field. If the array size is 4 or + less, or if the total size is 32 bytes or less, then we make a + full `elements' array, because the x86_64 ABI always shifts + to memory mode after 32 bytes. */ + + /* Allocate the new libffi type object, which is only provided to + libffi as a type for a structure field. When a FOREIGN_array + type is used for a function argument or result, it is replaced + with FOREIGN_pointer. We put FFI_TYPE_STRUCT in + libffi_type->type and make an elements array that contains + a single instance of the element type... which seems to work + ok so far. */ + libffi_type = malloc(sizeof(ffi_type)); + libffi_type->size = CTYPE_PRIMTYPE(base)->size * len; + libffi_type->alignment = CTYPE_PRIMTYPE(base)->alignment; + libffi_type->type = FFI_TYPE_STRUCT; + + if ((libffi_type->size <= 32) || (len <= 4)) { + int i; + elements = malloc((len + 1) * sizeof(ffi_type*)); + for (i = 0; i < len; i++) { + elements[i] = CTYPE_PRIMTYPE(base); + } + elements[len] = NULL; + } else { + elements = malloc(2 * sizeof(ffi_type*)); + elements[0] = CTYPE_PRIMTYPE(base); + elements[1] = NULL; + } + libffi_type->elements = elements; + + basetype = scheme_make_vector(2, argv[0]); + SCHEME_VEC_ELS(basetype)[1] = argv[1]; + + type = (ctype_struct*)scheme_malloc_tagged(sizeof(ctype_struct)); + type->so.type = ctype_tag; + type->basetype = (basetype); + type->scheme_to_c = ((Scheme_Object*)libffi_type); + type->c_to_scheme = ((Scheme_Object*)FOREIGN_array); + + scheme_register_finalizer(type, free_libffi_type, libffi_type, NULL, NULL); + + return (Scheme_Object*)type; +} +#undef MYNAME + +/*****************************************************************************/ +/* union types */ + +/* (make-union-type type ...+) -> ctype */ +/* This creates a new primitive type that is a union. All unions + * behave like structs. Marshaling to lists or whatever should + * be done in Scheme. */ +#define MYNAME "make-union-type" +static Scheme_Object *foreign_make_union_type(int argc, Scheme_Object *argv[]) +{ + Scheme_Object *base, *basetype; + GC_CAN_IGNORE ffi_type *libffi_type, **elements; + ctype_struct *type; + int i, align = 1, a, sz = 0; + + elements = malloc((argc + 1) * sizeof(ffi_type*)); + + /* find max required alignment and size: */ + for (i = 0; i < argc; i++) { + if (NULL == (base = get_ctype_base(argv[i]))) { + free(elements); + scheme_wrong_type(MYNAME, "C-type", i, argc, argv); + } + a = CTYPE_PRIMTYPE(base)->alignment; + if (a > align) align = a; + a = CTYPE_PRIMTYPE(base)->size; + if (sz < a) sz = a; + elements[i] = CTYPE_PRIMTYPE(base); + } + + elements[argc] = NULL; + + /* round size up to alignment: */ + if ((sz % align) != 0) { + sz += (align - (sz % align)); + } + + /* libffi doesn't seem to support union types, but we try to make + libffi work anyway by making a structure type. We put all the + element types in the `elements' array, because their shapes may + affect argument passing. */ + + /* Allocate the new libffi type object. */ + libffi_type = malloc(sizeof(ffi_type)); + libffi_type->size = sz; + libffi_type->alignment = align; + libffi_type->type = FFI_TYPE_STRUCT; + libffi_type->elements = elements; + + basetype = scheme_box(scheme_build_list(argc, argv)); + + type = (ctype_struct*)scheme_malloc_tagged(sizeof(ctype_struct)); + type->so.type = ctype_tag; + type->basetype = (basetype); + type->scheme_to_c = ((Scheme_Object*)libffi_type); + type->c_to_scheme = ((Scheme_Object*)FOREIGN_union); + + scheme_register_finalizer(type, free_libffi_type, libffi_type, NULL, NULL); + + return (Scheme_Object*)type; +} +#undef MYNAME + /*****************************************************************************/ /* Callback type */ @@ -1320,6 +1456,8 @@ static Scheme_Object *C2SCHEME(Scheme_Object *type, void *src, case FOREIGN_scheme: return REF_CTYPE(Scheme_Object*); case FOREIGN_fpointer: return (REF_CTYPE(void*)); case FOREIGN_struct: + case FOREIGN_array: + case FOREIGN_union: if (gcsrc) return scheme_make_foreign_offset_cpointer(src, delta); else @@ -1338,9 +1476,9 @@ static Scheme_Object *C2SCHEME(Scheme_Object *type, void *src, * ptr */ /* Usually writes the C object to dst and returns NULL. When basetype_p is not - * NULL, then any pointer value (any pointer or a struct) is returned, and the + * NULL, then any pointer value (any pointer or a struct or array) is returned, and the * basetype_p is set to the corrsponding number tag. If basetype_p is NULL, - * then a struct value will be *copied* into dst. */ + * then a struct or array value will be *copied* into dst. */ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, Scheme_Object *val, intptr_t *basetype_p, intptr_t *_offset, int ret_loc) @@ -1740,6 +1878,8 @@ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, if (!(ret_loc)) scheme_wrong_type("Scheme->C","fpointer",0,1,&(val)); break; case FOREIGN_struct: + case FOREIGN_array: + case FOREIGN_union: if (!SCHEME_FFIANYPTRP(val)) scheme_wrong_type("Scheme->C", "pointer", 0, 1, &val); { @@ -1752,7 +1892,7 @@ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, CTYPE_PRIMTYPE(type)->size); return NULL; } else { - *basetype_p = FOREIGN_struct; + *basetype_p = CTYPE_PRIMLABEL(type); if (_offset && is_gcable_pointer(val)) { *_offset = poff; return p; @@ -2559,7 +2699,7 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) * be ignored by the GC is avalues.) */ GC_CAN_IGNORE ForeignAny *ivals, oval; - void **avalues, *p, *newp, *tmp; + void **avalues, *p, *newp; GC_CAN_IGNORE ForeignAny stack_ivals[MAX_QUICK_ARGS]; void *stack_avalues[MAX_QUICK_ARGS]; intptr_t stack_offsets[MAX_QUICK_ARGS]; @@ -2592,7 +2732,8 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) /* If this is a struct return value, then need to malloc in any case, even if * the size is smaller than ForeignAny, because this value will be * returned. */ - if (CTYPE_PRIMLABEL(base) == FOREIGN_struct) { + if ((CTYPE_PRIMLABEL(base) == FOREIGN_struct) + || (CTYPE_PRIMLABEL(base) == FOREIGN_union)) { /* need to have p be a pointer that is invisible to the GC */ p = malloc(CTYPE_PRIMTYPE(base)->size); newp = scheme_malloc_atomic(CTYPE_PRIMTYPE(base)->size); @@ -2605,7 +2746,8 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) for (i=0; isize); free(p); p = newp; break; - default: - /* not sure why this code is here, looks fine to remove this case */ - if (CTYPE_PRIMTYPE(base) == &ffi_type_pointer) { - tmp = ((void**)p)[0]; - p = &tmp; - } + case FOREIGN_array: + /* array as result is treated as a pointer, so + adjust `p' to make C2SCHEME work right */ + p = *(void **)p; break; } return C2SCHEME(otype, p, 0, 1, 1); @@ -2689,7 +2830,7 @@ static Scheme_Object *foreign_ffi_call(int argc, Scheme_Object *argv[]) scheme_wrong_type(MYNAME, "proper list", 1, argc, argv); if (NULL == (base = get_ctype_base(otype))) scheme_wrong_type(MYNAME, "C-type", 2, argc, argv); - rtype = CTYPE_PRIMTYPE(base); + rtype = CTYPE_ARG_PRIMTYPE(base); abi = GET_ABI(MYNAME,3); if (argc > 4) { save_errno = -1; @@ -2719,7 +2860,7 @@ static Scheme_Object *foreign_ffi_call(int argc, Scheme_Object *argv[]) scheme_wrong_type(MYNAME, "list-of-C-types", 1, argc, argv); if (CTYPE_PRIMLABEL(base) == FOREIGN_void) scheme_wrong_type(MYNAME, "list-of-non-void-C-types", 1, argc, argv); - atypes[i] = CTYPE_PRIMTYPE(base); + atypes[i] = CTYPE_ARG_PRIMTYPE(base); } cif = malloc(sizeof(ffi_cif)); if (ffi_prep_cif(cif, abi, nargs, rtype, atypes) != FFI_OK) @@ -2776,7 +2917,7 @@ void ffi_do_callback(ffi_cif* cif, void* resultp, void** args, void *userdata) ffi_callback_struct *data; Scheme_Object *argv_stack[MAX_QUICK_ARGS]; int argc = cif->nargs, i; - Scheme_Object **argv, *p, *v; + Scheme_Object **argv, *p, *v, *t; data = extract_ffi_callback(userdata); @@ -2787,7 +2928,12 @@ void ffi_do_callback(ffi_cif* cif, void* resultp, void** args, void *userdata) if (data->sync && !SCHEME_PROCP(data->sync)) scheme_start_in_scheduler(); for (i=0, p=data->itypes; iproc, argc, argv); @@ -3035,7 +3181,7 @@ static Scheme_Object *foreign_ffi_callback(int argc, Scheme_Object *argv[]) scheme_wrong_type(MYNAME, "proper list", 1, argc, argv); if (NULL == (base = get_ctype_base(otype))) scheme_wrong_type(MYNAME, "C-type", 2, argc, argv); - rtype = CTYPE_PRIMTYPE(base); + rtype = CTYPE_ARG_PRIMTYPE(base); abi = GET_ABI(MYNAME,3); is_atomic = ((argc > 4) && SCHEME_TRUEP(argv[4])); sync = (is_atomic ? scheme_true : NULL); @@ -3072,7 +3218,7 @@ static Scheme_Object *foreign_ffi_callback(int argc, Scheme_Object *argv[]) scheme_wrong_type(MYNAME, "list-of-C-types", 1, argc, argv); if (CTYPE_PRIMLABEL(base) == FOREIGN_void) scheme_wrong_type(MYNAME, "list-of-non-void-C-types", 1, argc, argv); - atypes[i] = CTYPE_PRIMTYPE(base); + atypes[i] = CTYPE_ARG_PRIMTYPE(base); } if (ffi_prep_cif(cif, abi, nargs, rtype, atypes) != FFI_OK) scheme_signal_error("internal error: ffi_prep_cif did not return FFI_OK"); @@ -3303,6 +3449,10 @@ void scheme_init_foreign(Scheme_Env *env) scheme_make_prim_w_arity(foreign_make_ctype, "make-ctype", 3, 3), menv); scheme_add_global("make-cstruct-type", scheme_make_prim_w_arity(foreign_make_cstruct_type, "make-cstruct-type", 1, 3), menv); + scheme_add_global("make-array-type", + scheme_make_prim_w_arity(foreign_make_array_type, "make-array-type", 2, 2), menv); + scheme_add_global("make-union-type", + scheme_make_prim_w_arity(foreign_make_union_type, "make-union-type", 1, -1), menv); scheme_add_global("ffi-callback?", scheme_make_prim_w_arity(foreign_ffi_callback_p, "ffi-callback?", 1, 1), menv); scheme_add_global("cpointer?", @@ -3612,6 +3762,10 @@ void scheme_init_foreign(Scheme_Env *env) scheme_make_prim_w_arity((Scheme_Prim *)foreign_make_ctype, "make-ctype", 3, 3), menv); scheme_add_global("make-cstruct-type", scheme_make_prim_w_arity((Scheme_Prim *)unimplemented, "make-cstruct-type", 1, 3), menv); + scheme_add_global("make-array-type", + scheme_make_prim_w_arity((Scheme_Prim *)unimplemented, "make-array-type", 2, 2), menv); + scheme_add_global("make-union-type", + scheme_make_prim_w_arity((Scheme_Prim *)unimplemented, "make-union-type", 1, -1), menv); scheme_add_global("ffi-callback?", scheme_make_prim_w_arity((Scheme_Prim *)unimplemented, "ffi-callback?", 1, 1), menv); scheme_add_global("cpointer?", diff --git a/src/foreign/foreign.rktc b/src/foreign/foreign.rktc index 09fa3c9d72..0944b08e46 100755 --- a/src/foreign/foreign.rktc +++ b/src/foreign/foreign.rktc @@ -754,7 +754,9 @@ typedef union _ForeignAny { /* This is a tag that is used to identify user-made struct types. */ @; last makes sure this is the last one value that gets used -#define FOREIGN_struct (@(type-counter 'last)) +#define FOREIGN_struct (@(type-counter)) +#define FOREIGN_array (@(type-counter)) +#define FOREIGN_union (@(type-counter 'last)) static int is_gcable_pointer(Scheme_Object *o) { return !SCHEME_CPTRP(o) @@ -790,6 +792,8 @@ static ffi_type ffi_type_gcpointer; #define CTYPE_USER_S2C(x) (((ctype_struct*)(x))->scheme_to_c) #define CTYPE_USER_C2S(x) (((ctype_struct*)(x))->c_to_scheme) +#define CTYPE_ARG_PRIMTYPE(x) ((CTYPE_PRIMLABEL(x) == FOREIGN_array) ? &ffi_type_pointer : CTYPE_PRIMTYPE(x)) + /* Returns #f for primitive types. */ @cdefine[ctype-basetype 1]{ if (!SCHEME_CTYPEP(argv[0])) @@ -827,7 +831,7 @@ static int ctype_sizeof(Scheme_Object *type) switch (CTYPE_PRIMLABEL(type)) { @(map-types @list{case FOREIGN_@|cname|: @; return @(if ctype @list{sizeof(@ctype)} "0")}) - /* for structs */ + /* for structs and arrays */ default: return CTYPE_PRIMTYPE(type)->size; } } @@ -972,6 +976,128 @@ ffi_abi sym_to_abi(char *who, Scheme_Object *sym) return (Scheme_Object*)type; } +/*****************************************************************************/ +/* array types */ + +/* (make-array-type type len) -> ctype */ +/* This creates a new primitive type that is an array. An array is the + * same as a cpointer as an argument, but it behave differently within + * a struct or for allocation. Marshaling to lists or whatever should + * be done in Scheme. */ +@cdefine[make-array-type 2 2]{ + Scheme_Object *base, *basetype; + GC_CAN_IGNORE ffi_type *libffi_type, **elements; + ctype_struct *type; + intptr_t len; + + if (NULL == (base = get_ctype_base(argv[0]))) + scheme_wrong_type(MYNAME, "C-type", 0, argc, argv); + if (!scheme_get_int_val(argv[1], &len) || (len < 0)) + scheme_wrong_type(MYNAME, "pointer-sized nonnegative exact integer", 1, argc, argv); + + /* libffi doesn't seem to support array types, but we try to make + libffi work anyway by making a structure type that is used when + an array appears as a struct field. If the array size is 4 or + less, or if the total size is 32 bytes or less, then we make a + full `elements' array, because the x86_64 ABI always shifts + to memory mode after 32 bytes. */ + + /* Allocate the new libffi type object, which is only provided to + libffi as a type for a structure field. When a FOREIGN_array + type is used for a function argument or result, it is replaced + with FOREIGN_pointer. We put FFI_TYPE_STRUCT in + libffi_type->type and make an elements array that contains + a single instance of the element type... which seems to work + ok so far. */ + libffi_type = malloc(sizeof(ffi_type)); + libffi_type->size = CTYPE_PRIMTYPE(base)->size * len; + libffi_type->alignment = CTYPE_PRIMTYPE(base)->alignment; + libffi_type->type = FFI_TYPE_STRUCT; + + if ((libffi_type->size <= 32) || (len <= 4)) { + int i; + elements = malloc((len + 1) * sizeof(ffi_type*)); + for (i = 0; i < len; i++) { + elements[i] = CTYPE_PRIMTYPE(base); + } + elements[len] = NULL; + } else { + elements = malloc(2 * sizeof(ffi_type*)); + elements[0] = CTYPE_PRIMTYPE(base); + elements[1] = NULL; + } + libffi_type->elements = elements; + + basetype = scheme_make_vector(2, argv[0]); + SCHEME_VEC_ELS(basetype)[1] = argv[1]; + + @cmake["type" ctype "basetype" + "(Scheme_Object*)libffi_type" + "(Scheme_Object*)FOREIGN_array"] + + scheme_register_finalizer(type, free_libffi_type, libffi_type, NULL, NULL); + + return (Scheme_Object*)type; +} + +/*****************************************************************************/ +/* union types */ + +/* (make-union-type type ...+) -> ctype */ +/* This creates a new primitive type that is a union. All unions + * behave like structs. Marshaling to lists or whatever should + * be done in Scheme. */ +@cdefine[make-union-type 1 -1]{ + Scheme_Object *base, *basetype; + GC_CAN_IGNORE ffi_type *libffi_type, **elements; + ctype_struct *type; + int i, align = 1, a, sz = 0; + + elements = malloc((argc + 1) * sizeof(ffi_type*)); + + /* find max required alignment and size: */ + for (i = 0; i < argc; i++) { + if (NULL == (base = get_ctype_base(argv[i]))) { + free(elements); + scheme_wrong_type(MYNAME, "C-type", i, argc, argv); + } + a = CTYPE_PRIMTYPE(base)->alignment; + if (a > align) align = a; + a = CTYPE_PRIMTYPE(base)->size; + if (sz < a) sz = a; + elements[i] = CTYPE_PRIMTYPE(base); + } + + elements[argc] = NULL; + + /* round size up to alignment: */ + if ((sz % align) != 0) { + sz += (align - (sz % align)); + } + + /* libffi doesn't seem to support union types, but we try to make + libffi work anyway by making a structure type. We put all the + element types in the `elements' array, because their shapes may + affect argument passing. */ + + /* Allocate the new libffi type object. */ + libffi_type = malloc(sizeof(ffi_type)); + libffi_type->size = sz; + libffi_type->alignment = align; + libffi_type->type = FFI_TYPE_STRUCT; + libffi_type->elements = elements; + + basetype = scheme_box(scheme_build_list(argc, argv)); + + @cmake["type" ctype "basetype" + "(Scheme_Object*)libffi_type" + "(Scheme_Object*)FOREIGN_union"] + + scheme_register_finalizer(type, free_libffi_type, libffi_type, NULL, NULL); + + return (Scheme_Object*)type; +} + /*****************************************************************************/ /* Callback type */ @@ -1084,6 +1210,8 @@ static Scheme_Object *C2SCHEME(Scheme_Object *type, void *src, (if (procedure? c->s) (c->s x) (list c->s"("x")"))) "scheme_void")}) case FOREIGN_struct: + case FOREIGN_array: + case FOREIGN_union: if (gcsrc) return scheme_make_foreign_offset_cpointer(src, delta); else @@ -1102,9 +1230,9 @@ static Scheme_Object *C2SCHEME(Scheme_Object *type, void *src, * ptr */ /* Usually writes the C object to dst and returns NULL. When basetype_p is not - * NULL, then any pointer value (any pointer or a struct) is returned, and the + * NULL, then any pointer value (any pointer or a struct or array) is returned, and the * basetype_p is set to the corrsponding number tag. If basetype_p is NULL, - * then a struct value will be *copied* into dst. */ + * then a struct or array value will be *copied* into dst. */ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, Scheme_Object *val, intptr_t *basetype_p, intptr_t *_offset, int ret_loc) @@ -1187,6 +1315,8 @@ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, @hush }}]))}) case FOREIGN_struct: + case FOREIGN_array: + case FOREIGN_union: if (!SCHEME_FFIANYPTRP(val)) scheme_wrong_type("Scheme->C", "pointer", 0, 1, &val); { @@ -1199,7 +1329,7 @@ static void* SCHEME2C(Scheme_Object *type, void *dst, intptr_t delta, CTYPE_PRIMTYPE(type)->size); return NULL; } else { - *basetype_p = FOREIGN_struct; + *basetype_p = CTYPE_PRIMLABEL(type); if (_offset && is_gcable_pointer(val)) { *_offset = poff; return p; @@ -1924,7 +2054,7 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) * be ignored by the GC is avalues.) */ GC_CAN_IGNORE ForeignAny *ivals, oval; - void **avalues, *p, *newp, *tmp; + void **avalues, *p, *newp; GC_CAN_IGNORE ForeignAny stack_ivals[MAX_QUICK_ARGS]; void *stack_avalues[MAX_QUICK_ARGS]; intptr_t stack_offsets[MAX_QUICK_ARGS]; @@ -1957,7 +2087,8 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) /* If this is a struct return value, then need to malloc in any case, even if * the size is smaller than ForeignAny, because this value will be * returned. */ - if (CTYPE_PRIMLABEL(base) == FOREIGN_struct) { + if ((CTYPE_PRIMLABEL(base) == FOREIGN_struct) + || (CTYPE_PRIMLABEL(base) == FOREIGN_union)) { /* need to have p be a pointer that is invisible to the GC */ p = malloc(CTYPE_PRIMTYPE(base)->size); newp = scheme_malloc_atomic(CTYPE_PRIMTYPE(base)->size); @@ -1970,7 +2101,8 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[]) for (i=0; isize); free(p); p = newp; break; - default: - /* not sure why this code is here, looks fine to remove this case */ - if (CTYPE_PRIMTYPE(base) == &ffi_type_pointer) { - tmp = ((void**)p)[0]; - p = &tmp; - } + case FOREIGN_array: + /* array as result is treated as a pointer, so + adjust `p' to make C2SCHEME work right */ + p = *(void **)p; break; } return C2SCHEME(otype, p, 0, 1, 1); @@ -2052,7 +2183,7 @@ void free_fficall_data(void *ignored, void *p) scheme_wrong_type(MYNAME, "proper list", 1, argc, argv); if (NULL == (base = get_ctype_base(otype))) scheme_wrong_type(MYNAME, "C-type", 2, argc, argv); - rtype = CTYPE_PRIMTYPE(base); + rtype = CTYPE_ARG_PRIMTYPE(base); abi = GET_ABI(MYNAME,3); if (argc > 4) { save_errno = -1; @@ -2082,7 +2213,7 @@ void free_fficall_data(void *ignored, void *p) scheme_wrong_type(MYNAME, "list-of-C-types", 1, argc, argv); if (CTYPE_PRIMLABEL(base) == FOREIGN_void) scheme_wrong_type(MYNAME, "list-of-non-void-C-types", 1, argc, argv); - atypes[i] = CTYPE_PRIMTYPE(base); + atypes[i] = CTYPE_ARG_PRIMTYPE(base); } cif = malloc(sizeof(ffi_cif)); if (ffi_prep_cif(cif, abi, nargs, rtype, atypes) != FFI_OK) @@ -2138,7 +2269,7 @@ void ffi_do_callback(ffi_cif* cif, void* resultp, void** args, void *userdata) ffi_callback_struct *data; Scheme_Object *argv_stack[MAX_QUICK_ARGS]; int argc = cif->nargs, i; - Scheme_Object **argv, *p, *v; + Scheme_Object **argv, *p, *v, *t; data = extract_ffi_callback(userdata); @@ -2149,7 +2280,12 @@ void ffi_do_callback(ffi_cif* cif, void* resultp, void** args, void *userdata) if (data->sync && !SCHEME_PROCP(data->sync)) scheme_start_in_scheduler(); for (i=0, p=data->itypes; iproc, argc, argv); @@ -2395,7 +2531,7 @@ void free_cl_cif_queue_args(void *ignored, void *p) scheme_wrong_type(MYNAME, "proper list", 1, argc, argv); if (NULL == (base = get_ctype_base(otype))) scheme_wrong_type(MYNAME, "C-type", 2, argc, argv); - rtype = CTYPE_PRIMTYPE(base); + rtype = CTYPE_ARG_PRIMTYPE(base); abi = GET_ABI(MYNAME,3); is_atomic = ((argc > 4) && SCHEME_TRUEP(argv[4])); sync = (is_atomic ? scheme_true : NULL); @@ -2432,7 +2568,7 @@ void free_cl_cif_queue_args(void *ignored, void *p) scheme_wrong_type(MYNAME, "list-of-C-types", 1, argc, argv); if (CTYPE_PRIMLABEL(base) == FOREIGN_void) scheme_wrong_type(MYNAME, "list-of-non-void-C-types", 1, argc, argv); - atypes[i] = CTYPE_PRIMTYPE(base); + atypes[i] = CTYPE_ARG_PRIMTYPE(base); } if (ffi_prep_cif(cif, abi, nargs, rtype, atypes) != FFI_OK) scheme_signal_error("internal error: ffi_prep_cif did not return FFI_OK"); diff --git a/src/racket/src/schvers.h b/src/racket/src/schvers.h index b5cfd8cdc5..6c0e045afb 100644 --- a/src/racket/src/schvers.h +++ b/src/racket/src/schvers.h @@ -13,12 +13,12 @@ consistently.) */ -#define MZSCHEME_VERSION "5.1.3.2" +#define MZSCHEME_VERSION "5.1.3.3" #define MZSCHEME_VERSION_X 5 #define MZSCHEME_VERSION_Y 1 #define MZSCHEME_VERSION_Z 3 -#define MZSCHEME_VERSION_W 2 +#define MZSCHEME_VERSION_W 3 #define MZSCHEME_VERSION_MAJOR ((MZSCHEME_VERSION_X * 100) + MZSCHEME_VERSION_Y) #define MZSCHEME_VERSION_MINOR ((MZSCHEME_VERSION_Z * 1000) + MZSCHEME_VERSION_W)