Next: , Previous: Foreign Type Translators, Up: Foreign Types


5.5 Optimizing Type Translators

Being based on generic functions, the type translation mechanism described above can add a bit of overhead. This is usually not significant, but we nevertheless provide a way of getting rid of the overhead for the cases where it matters.

A good way to understand this issue is to look at the code generated by defcfun. Consider the following example using the my-boolean type defined above:

  CFFI> (macroexpand-1 '(defcfun foo my-boolean (x my-boolean)))
  (DEFUN FOO (X)
    (MULTIPLE-VALUE-BIND (#:G3148 #:PARAM3149)
        (TRANSLATE-TYPE-TO-FOREIGN X #<FOREIGN-TYPEDEF MY-BOOLEAN>)
      (UNWIND-PROTECT
          (PROGN
            (TRANSLATE-TYPE-FROM-FOREIGN
             (%FOREIGN-FUNCALL "foo" :INT #:G3148 :INT)
             #<FOREIGN-TYPEDEF MY-BOOLEAN>))
        (FREE-TYPE-TRANSLATED-OBJECT #:G3148
                                     #<FOREIGN-TYPEDEF MY-BOOLEAN>
                                     #:PARAM3149))))

In order to get rid of those generic function calls, CFFI has another set of extensible generic functions that provide functionality similar to CL's compiler macros: expand-to-foreign-dyn, expand-to-foreign and expand-from-foreign. Here's how one could define my-boolean with them:

  (defmethod expand-to-foreign (value (type (eql 'my-boolean)))
    `(if ,value 1 0))
   
  (defmethod expand-from-foreign (value (type (eql 'my-boolean)))
    `(not (zerop ,value)))

And here's what the macroexpansion of foo now looks like:

  CFFI> (macroexpand-1 '(defcfun foo my-boolean (x my-boolean)))
  (DEFUN FOO (X)
    (LET ((#:G3182 (IF X 1 0)))
      (NOT (ZEROP (%FOREIGN-FUNCALL "foo" :INT #:G3182 :INT)))))

Much better.

The expansion interface has no equivalent of free-translated-object; you must instead define a method on expand-to-foreign-dyn, the third generic function in this interface. This is especially useful when you can allocate something much more efficiently if you know the object has dynamic extent, as is the case with function calls that don't save the relevant allocated arguments. Consider the following example:

  ;;; This type inherits :string's translators.
  (defctype stack-allocated-string :string)
   
  (defmethod expand-to-foreign-dyn
      (value var body (type (eql 'stack-allocated-string)))
    `(with-foreign-string (,var ,value)
       ,@body))

To short-circuit expansion and use the translate-* functions instead, simply call the next method. Return its result in cases where your method cannot generate an appropriate replacement for it.

The expand-* methods have precedence over their translate-* counterparts and are guaranteed to be used in defcfun, foreign-funcall, defcvar and defcallback. If you define a method on each of the expand-* generic functions, you are guaranteed to have full control over the expressions generated for type translation in these macros.

They may or may not be used in other CFFI operators that need to translate between Lisp and C data; you may only assume that expand-* methods will probably only be called during Lisp compilation.

expand-to-foreign-dyn has precedence over expand-to-foreign and is only used in defcfun and foreign-funcall, only making sense in those contexts. If you do not define a method on expand-to-foreign-dyn, however, please note that this expand method for the hypothetical type my-string is not the same as defining no method at all:

  (defmethod expand-to-foreign (value-form (type-name (eql 'my-string)))
    (call-next-method))

Without this method, your runtime translate-to-foreign method will be called, and its result will be passed to free-translated-object. However, if you define this method, translate-to-foreign will still be called, but its result will not be passed to free-translated-object. If you need to free values with this interface, you must define an expand-to-foreign-dyn method.

Important note: this set of generic functions is called at macroexpansion time. Methods are defined when loaded or evaluated, not compiled. You are responsible for ensuring that your expand-* methods are defined when the foreign-funcall or other forms that use them are compiled. One way to do this is to put the method definitions earlier in the file and inside an appropriate eval-when form; another way is to always load a separate Lisp or FASL file containing your expand-* definitions before compiling files with forms that ought to use them. Otherwise, they will not be found and the runtime translators will be used instead.