cisco / ChezScheme

Chez Scheme
Apache License 2.0
6.96k stars 984 forks source link

ftype-pointer and foreign-procedure: counter-intuitive behavior #270

Open stergiotis opened 6 years ago

stergiotis commented 6 years ago

Consider the following example program:

(import (chezscheme))
(load-shared-object "libc.so.6")
(define memcpy/void* (foreign-procedure "memcpy" (void* void* size_t) void*))

(define sz (ftype-sizeof uptr))
(define a-addr (foreign-alloc sz))
(define b-addr (foreign-alloc sz))
(define a (make-ftype-pointer uptr a-addr))
(define b (make-ftype-pointer uptr b-addr))

(ftype-set! uptr () a #xdeadbeef)
(memcpy/void* b-addr a-addr sz)
(pretty-print (number->string (ftype-ref uptr () b) 16))

(foreign-free a-addr)
(foreign-free b-addr)

It works but is very weakly typed as we supply fixnums as argument for the foreign function: Supplying a-addr and b-addr instead of a and b circumvents the type checks. To strengthen the checks I expected that all of the following three variations are valid. They are however rejected as invalid.

;; 1) declare memcpy with typed pointers to be able to use ftype-pointer
(define memcpy/uptr* (foreign-procedure "memcpy" (uptr* uptr* size_t) void*)) ;; --> exception: invalid foreign-procedure argument type specifier uptr*
(memcpy/uptr* b a sz)

;; 2) rely on C rule that a typed pointer will be down-cast to void*
(memcpy/void* b a sz) ;; --> Exception in memcpy/void*: invalid foreign-procedure argument #<ftype-pointer uptr 94627085533504>

;; 3) make a void* ftype-pointer
(memcpy/void* (make-ftype-pointer void b-addr)
              (make-ftype-pointer void a-addr)) ;; --> Exception: unrecognized ftype name void

In my opinion this would be a valuable addition to the beautiful Chez ftype interface. 1) Would be neat as it would allow to declare pointer to pointers correctly. Currently only base types do seem to be supported. 2) Is a bit of a hack as it mimics the behavior of C but not that of C++. 3) Seems to be quite nice as it allows to explicitly cast any pointer to void* which is a useful and proven idiom.

Any thoughts on this?

jltaylor-us commented 6 years ago

;; 1) declare memcpy with typed pointers to be able to use ftype-pointer (define memcpy/uptr (foreign-procedure "memcpy" (uptr uptr size_t) void)) ;; --> exception: invalid foreign-procedure argument type specifier uptr*

That's not how you say "pointer to an object of type uptr".

> (define memcpy/uptr* (foreign-procedure "memcpy" ((* uptr) (* uptr) size_t) void*))
> (memcpy/uptr* a b sz)

;; 3) make a void ftype-pointer (memcpy/void (make-ftype-pointer void b-addr) (make-ftype-pointer void a-addr))

  1. Seems to be quite nice as it allows to explicitly cast any pointer to void* which is a useful and proven idiom.

Your code and descriptive text don't match. You can make an ftype pointer to objects of void* just fine, but that's not the same thing as an ftype pointer to the non-existent void ftype. It's true that Chez does not have any way to make an ftype pointer that points to an unknown ftype.

stergiotis commented 6 years ago

Thanks a lot for your quick response.

1) Is solved by using the proper syntax.

I am sorry for the confusion, the minimal example I gave above was perhaps a bit too minimalistic. The code I have been experimenting with established various ftype-names by means of the define-ftype special form. These user-defined ftype-names do not seem to be available in the foreign-procedure declaration: invalid (non-base) foreign-procedure argument ftype).

Regarding the void pointer: You are right that constructing a void** pointer is perfectly feasible in Chez (i.e. (make-ftype-pointer void* 0)). It is also clear that the void* case however is special as the void type is not a proper datatype (e.g. (foreign-sizeof 'void) is doomed). This however means proposition 2) would make sense as void* pointers are not first-class citizens in Chez whereas they are in the interfaced language.

jltaylor-us commented 6 years ago

These user-defined ftype-names do not seem to be available in the foreign-procedure declaration: invalid (non-base) foreign-procedure argument ftype).

Foreign procedure definitions can use pointers to user-defined ftypes, but it cannot pass structs directly. There is a pull request currently under review ( #213 ) to enhance the foreign procedure system to allow passing and returning structs by value, but I don't know what the status of that review is.

I don't think that will help with your desire to have something closer to C's void* type, though.

evilbinary commented 6 years ago

you just need a cffi for scheme like this https://github.com/evilbinary/scheme-lib/blob/master/apps/cffi-test.ss ,pretty eazy to call ffi.

amirouche commented 5 years ago

Any update on this?

akeep commented 5 years ago

Pull request #213 has been merged. It describes how to pass structs directly.

stergiotis commented 5 years ago

The issue was not about passing structs using the C ABI . What I wanted to discuss originally were the following two limitations of the current ftype implementation: 1) Pointers to non-base types are currently not supported (e.g. (* (* char)) or (* my-ftype) can not be used as argument types). 2) Pointers supplied for arguments declared as void* do not get automatically cast when passed as function arguments.

In practice I found the ftype FFI system really nice to work with but I regularly need to work around these two limitations.

dybvig commented 5 years ago

You can use (* ftype-name) as a foreign-procedure argument type or return-value type for any user-defined ftype name as well as for any base ftype name. You're correct, however, that you can't use (* (* char)), because the syntax requires _x_ in (* _x_) to be an identifier. That seems like a reasonable restriction since it's easy to give an ftype a name and easier to use when it has one. You're also correct that actual parameters for arguments declared as void* aren't automatically cast, which would be a welcome extension.