While CFFI is very useful API for calling C functions, using foreign functions that expect pointers to arrays has always been something that is tricky to do in Common Lisp. The two basic approaches are
- allocating a chunk of foreign memory and accessing it from Lisp as needed (as implemented in the fnv package by rif)
- using native CL arrays and either copying them to/from foreign memory on demand, or taking advantage of implementation-specific features (eg pinning arrays) for direct access where possible
The first approach has some advantages, but I find that I am more comfortable with the second one, even if it leads to some loss in speed when the arrays are large and the implementation can't provide direct access for foreign functions. My
ffa (Foreign Friendly Arrays) package provides some convenience functions and macros that take advantage of implementation-specific features when available.
The most important features are the function
make-ffa
and the macro
with-pointer-to-array
. The function
make-ffa
is similar to
make-array
, except that it recognizes some CFFI types (eg
:double
,
:uint32
) and when asked to allocate a multidimensional array, it will do so by displacing a simple array which it creates first. This allows direct access in some implementations, especially SBCL, while providing multidimensional arrays at the same time.
The macro
(with-pointer-to-array (array pointer cffi-type length direction) body)
makes sure that the contents of
array
will be mapped to a memory area designated by
pointer
during the execution of
body
.
cffi-type
determines the foreign element type,
direction
tells the macro whether to copy the array to and/or from the memory area (so, for example, if you foreign function needs data but does not modify it, you don't need to copy it back), and by providing length, you ensure that the array contains as many elements as your foreign function needs. When possible, no copying/conversion will take place (eg a double-float array in SBCL with cffi-type
:double
), but the macro will take care of it automatically when needed (issuing some efficiency warnings when practical).
Displacing all arrays from a one-dimensional simple array also allows a few convenience functions, please check the code or the tutorial (provided in the package) for details. Currently, only SBCL-specific code is provided, for all other implementations, the macro defaults to copying/conversion. Contributions for other implementations and suggestions for improvements are welcome.
I would like to thank all those who commented on an earlier version of ffa on
lisp-matrix-devel.
do you have any plans to incorporate this into cffi?
ReplyDeleteAttila: that could be a possibility (with the agreement of cffi developers), but for now I would like to keep this separate before I finalize the API. Also, it would be good to see how other implementations (besides SBCL) can be handled in ffa.
ReplyDeleteTamas -- awesome work! I've been distracted this year by teaching but would like to pick up lisp-matrix again. This looks like a good building block :-)
ReplyDeletelisp-matrix-devel had a good discussion about different CL implementations, which developed into the following writeup:
http://www.cs.berkeley.edu/~mhoemmen/lisp-matrix/www/gc.html
ffa's "if you can't pin it, copy it" approach sounds best when one wishes to implement matrices as (lightly wrapped) Lisp arrays, rather than foreign arrays. We talked a lot about that in lisp-matrix-devel; I had complex matrices in mind and was worried about CLs boxing the complex numbers (and therefore introducing a copying penalty), but using Lisp arrays has its own advantages (better locality, avoiding possible limitations on malloc space, and making it easier to port to VM-hosed Lisps).
Thanks again Tamas! I hope the winter break gives me a chance to code some :-)
Isn't cffi supposed to have a shareable byte-vector interface that does some of this
ReplyDelete