cffi-ecl.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       cffi-ecl.lisp (17282B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; cffi-ecl.lisp --- ECL backend for CFFI.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, James Bielman  <jamesjb@jamesjb.com>
            6 ;;;
            7 ;;; Permission is hereby granted, free of charge, to any person
            8 ;;; obtaining a copy of this software and associated documentation
            9 ;;; files (the "Software"), to deal in the Software without
           10 ;;; restriction, including without limitation the rights to use, copy,
           11 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
           12 ;;; of the Software, and to permit persons to whom the Software is
           13 ;;; furnished to do so, subject to the following conditions:
           14 ;;;
           15 ;;; The above copyright notice and this permission notice shall be
           16 ;;; included in all copies or substantial portions of the Software.
           17 ;;;
           18 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           19 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           20 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           21 ;;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
           22 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
           23 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           24 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
           25 ;;; DEALINGS IN THE SOFTWARE.
           26 ;;;
           27 
           28 ;;;# Administrivia
           29 
           30 (defpackage #:cffi-sys
           31   (:use #:common-lisp #:alexandria)
           32   (:import-from #:si #:null-pointer-p)
           33   (:export
           34    #:*cffi-ecl-method*
           35    #:canonicalize-symbol-name-case
           36    #:foreign-pointer
           37    #:pointerp
           38    #:pointer-eq
           39    #:%foreign-alloc
           40    #:foreign-free
           41    #:with-foreign-pointer
           42    #:null-pointer
           43    #:null-pointer-p
           44    #:inc-pointer
           45    #:make-pointer
           46    #:pointer-address
           47    #:%mem-ref
           48    #:%mem-set
           49    #:%foreign-funcall
           50    #:%foreign-funcall-pointer
           51    #:%foreign-funcall-varargs
           52    #:%foreign-funcall-pointer-varargs
           53    #:%foreign-type-alignment
           54    #:%foreign-type-size
           55    #:%load-foreign-library
           56    #:%close-foreign-library
           57    #:native-namestring
           58    #:make-shareable-byte-vector
           59    #:with-pointer-to-vector-data
           60    #:%defcallback
           61    #:%callback
           62    #:%foreign-symbol-pointer))
           63 
           64 (in-package #:cffi-sys)
           65 
           66 ;;;
           67 ;;; ECL allows many ways of calling a foreign function, and also many
           68 ;;; ways of finding the pointer associated to a function name. They
           69 ;;; depend on whether the FFI relies on libffi or on the C/C++ compiler,
           70 ;;; and whether they use the shared library loader to locate symbols
           71 ;;; or they are linked by the linker.
           72 ;;;
           73 ;;;  :DFFI
           74 ;;;
           75 ;;;  ECL uses libffi to call foreign functions. The only way to find out
           76 ;;;  foreign symbols is by loading shared libraries and using dlopen()
           77 ;;;  or similar.
           78 ;;;
           79 ;;;  :DLOPEN
           80 ;;;
           81 ;;;  ECL compiles FFI code as C/C++ statements. The names are resolved
           82 ;;;  at run time by the shared library loader every time the function
           83 ;;;  is called
           84 ;;;
           85 ;;;  :C/C++
           86 ;;;
           87 ;;;  ECL compiles FFI code as C/C++ statements, but the name resolution
           88 ;;;  happens at link time. In this case you have to tell the ECL
           89 ;;;  compiler which are the right ld-flags (c:*ld-flags*) to link in
           90 ;;;  the library.
           91 ;;;
           92 (defvar *cffi-ecl-method*
           93   #+dffi :dffi
           94   #+(and dlopen (not dffi)) :dlopen
           95   #-(or dffi dlopen) :c/c++
           96   "The type of code that CFFI generates for ECL: :DFFI when using the
           97 dynamical foreign function interface; :DLOPEN when using C code and
           98 dynamical references to symbols; :C/C++ for C/C++ code with static
           99 references to symbols.")
          100 
          101 ;;;# Mis-features
          102 
          103 #-long-long
          104 (pushnew 'no-long-long *features*)
          105 (pushnew 'flat-namespace *features*)
          106 
          107 ;;;# Symbol Case
          108 
          109 (defun canonicalize-symbol-name-case (name)
          110   (declare (string name))
          111   (string-upcase name))
          112 
          113 ;;;# Allocation
          114 
          115 (defun %foreign-alloc (size)
          116   "Allocate SIZE bytes of foreign-addressable memory."
          117   (si:allocate-foreign-data :void size))
          118 
          119 (defun foreign-free (ptr)
          120   "Free a pointer PTR allocated by FOREIGN-ALLOC."
          121   (si:free-foreign-data ptr))
          122 
          123 (defmacro with-foreign-pointer ((var size &optional size-var) &body body)
          124   "Bind VAR to SIZE bytes of foreign memory during BODY.  The
          125 pointer in VAR is invalid beyond the dynamic extent of BODY, and
          126 may be stack-allocated if supported by the implementation.  If
          127 SIZE-VAR is supplied, it will be bound to SIZE during BODY."
          128   (unless size-var
          129     (setf size-var (gensym "SIZE")))
          130   `(let* ((,size-var ,size)
          131           (,var (%foreign-alloc ,size-var)))
          132      (unwind-protect
          133           (progn ,@body)
          134        (foreign-free ,var))))
          135 
          136 ;;;# Misc. Pointer Operations
          137 
          138 (deftype foreign-pointer ()
          139   'si:foreign-data)
          140 
          141 (defun null-pointer ()
          142   "Construct and return a null pointer."
          143   (si:allocate-foreign-data :void 0))
          144 
          145 (defun inc-pointer (ptr offset)
          146   "Return a pointer OFFSET bytes past PTR."
          147   (ffi:make-pointer (+ (ffi:pointer-address ptr) offset) :void))
          148 
          149 (defun pointerp (ptr)
          150   "Return true if PTR is a foreign pointer."
          151   (typep ptr 'si:foreign-data))
          152 
          153 (defun pointer-eq (ptr1 ptr2)
          154   "Return true if PTR1 and PTR2 point to the same address."
          155   (= (ffi:pointer-address ptr1) (ffi:pointer-address ptr2)))
          156 
          157 (defun make-pointer (address)
          158   "Return a pointer pointing to ADDRESS."
          159   (ffi:make-pointer address :void))
          160 
          161 (defun pointer-address (ptr)
          162   "Return the address pointed to by PTR."
          163   (ffi:pointer-address ptr))
          164 
          165 ;;;# Shareable Vectors
          166 ;;;
          167 ;;; This interface is very experimental.  WITH-POINTER-TO-VECTOR-DATA
          168 ;;; should be defined to perform a copy-in/copy-out if the Lisp
          169 ;;; implementation can't do this.
          170 
          171 (defun make-shareable-byte-vector (size)
          172   "Create a Lisp vector of SIZE bytes that can passed to
          173 WITH-POINTER-TO-VECTOR-DATA."
          174   (make-array size :element-type '(unsigned-byte 8)))
          175 
          176 (defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
          177   "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
          178   `(let ((,ptr-var (si:make-foreign-data-from-array ,vector)))
          179      ,@body))
          180 
          181 ;;;# Type Operations
          182 
          183 (defconstant +translation-table+
          184   '((:char            :byte            "char")
          185     (:unsigned-char   :unsigned-byte   "unsigned char")
          186     (:short           :short           "short")
          187     (:unsigned-short  :unsigned-short  "unsigned short")
          188     (:int             :int             "int")
          189     (:unsigned-int    :unsigned-int    "unsigned int")
          190     (:long            :long            "long")
          191     (:unsigned-long   :unsigned-long   "unsigned long")
          192     #+long-long
          193     (:long-long       :long-long       "long long")
          194     #+long-long
          195     (:unsigned-long-long :unsigned-long-long "unsigned long long")
          196     (:float           :float           "float")
          197     (:double          :double          "double")
          198     (:pointer         :pointer-void    "void*")
          199     (:void            :void            "void")))
          200 
          201 (defun cffi-type->ecl-type (type-keyword)
          202   "Convert a CFFI type keyword to an ECL type keyword."
          203   (or (second (find type-keyword +translation-table+ :key #'first))
          204       (error "~S is not a valid CFFI type" type-keyword)))
          205 
          206 (defun ecl-type->c-type (type-keyword)
          207   "Convert a CFFI type keyword to an valid C type keyword."
          208   (or (third (find type-keyword +translation-table+ :key #'second))
          209       (error "~S is not a valid CFFI type" type-keyword)))
          210 
          211 (defun %foreign-type-size (type-keyword)
          212   "Return the size in bytes of a foreign type."
          213   (nth-value 0 (ffi:size-of-foreign-type
          214                 (cffi-type->ecl-type type-keyword))))
          215 
          216 (defun %foreign-type-alignment (type-keyword)
          217   "Return the alignment in bytes of a foreign type."
          218   (nth-value 1 (ffi:size-of-foreign-type
          219                 (cffi-type->ecl-type type-keyword))))
          220 
          221 ;;;# Dereferencing
          222 
          223 (defun %mem-ref (ptr type &optional (offset 0))
          224   "Dereference an object of TYPE at OFFSET bytes from PTR."
          225   (let* ((type (cffi-type->ecl-type type))
          226          (type-size (ffi:size-of-foreign-type type)))
          227     (si:foreign-data-ref-elt
          228      (si:foreign-data-recast ptr (+ offset type-size) :void) offset type)))
          229 
          230 (defun %mem-set (value ptr type &optional (offset 0))
          231   "Set an object of TYPE at OFFSET bytes from PTR."
          232   (let* ((type (cffi-type->ecl-type type))
          233          (type-size (ffi:size-of-foreign-type type)))
          234     (si:foreign-data-set-elt
          235      (si:foreign-data-recast ptr (+ offset type-size) :void)
          236      offset type value)))
          237 
          238 ;;; Inline versions that use C expressions instead of function calls.
          239 
          240 (defparameter +mem-ref-strings+
          241   (loop for (cffi-type ecl-type c-string) in +translation-table+
          242         for string = (format nil "*((~A *)(((char*)#0)+#1))" c-string)
          243         collect (list cffi-type ecl-type string)))
          244 
          245 (defparameter +mem-set-strings+
          246   (loop for (cffi-type ecl-type c-string) in +translation-table+
          247         for string = (format nil "*((~A *)(((char*)#0)+#1))=#2" c-string)
          248         collect (list cffi-type ecl-type string)))
          249 
          250 (define-compiler-macro %mem-ref (&whole whole ptr type &optional (offset 0))
          251   (if (and (constantp type) (constantp offset))
          252       (let ((record (assoc (eval type) +mem-ref-strings+)))
          253         `(ffi:c-inline (,ptr ,offset)
          254                        (:pointer-void :cl-index) ; argument types
          255                        ,(second record)          ; return type
          256                        ,(third record)  ; the precomputed expansion
          257                        :one-liner t))
          258       whole))
          259 
          260 (define-compiler-macro %mem-set (&whole whole value ptr type &optional (offset 0))
          261   (if (and (constantp type) (constantp offset))
          262       (let ((record (assoc (eval type) +mem-set-strings+)))
          263         `(ffi:c-inline (,ptr ,offset ,value) ; arguments with type translated
          264                        (:pointer-void :cl-index ,(second record))
          265                        :void            ; does not return anything
          266                        ,(third record)  ; precomputed expansion
          267                        :one-liner t))
          268       whole))
          269 
          270 ;;;# Calling Foreign Functions
          271 
          272 (defconstant +ecl-inline-codes+ "#0,#1,#2,#3,#4,#5,#6,#7,#8,#9,#a,#b,#c,#d,#e,#f,#g,#h,#i,#j,#k,#l,#m,#n,#o,#p,#q,#r,#s,#t,#u,#v,#w,#x,#y,#z")
          273 
          274 (defun c-inline-function-call (thing fixed-types types values return-type dynamic-call variadic)
          275   (when dynamic-call
          276     (when (stringp thing)
          277       (setf thing `(%foreign-symbol-pointer ,thing nil)))
          278     (push thing values)
          279     (push :pointer-void types))
          280   (let* ((decl-args
          281           (format nil "~{~A~^, ~}~A"
          282                   (mapcar #'ecl-type->c-type fixed-types) (if (null variadic) "" ", ...")))
          283          (call-args
          284           (if dynamic-call
          285               ;; #0 is already used in a cast (it is a function pointer)
          286               (subseq +ecl-inline-codes+ 3 (max 3 (1- (* (length values) 3))))
          287               ;; #0 is not used, so we start from the beginning
          288               (subseq +ecl-inline-codes+ 0 (max 0 (1- (* (length values) 3))))))
          289          (clines
          290           (if dynamic-call
          291               nil
          292               (format nil "extern ~A ~A(~A);"
          293                       (ecl-type->c-type return-type) thing decl-args)))
          294          (call-code
          295           (if dynamic-call
          296               (format nil "((~A (*)(~A))(#0))(~A)"
          297                       (ecl-type->c-type return-type) decl-args call-args)
          298               (format nil "~A(~A)" thing call-args))))
          299     `(progn
          300        (ffi:clines ,@(ensure-list clines))
          301        (ffi:c-inline ,values ,types ,return-type ,call-code :one-liner t :side-effects t))))
          302 
          303 (defun dffi-function-pointer-call (pointer types values return-type)
          304   (when (stringp pointer)
          305     (setf pointer `(%foreign-symbol-pointer ,pointer nil)))
          306   #-dffi
          307   `(error "In interpreted code, attempted to call a foreign function~% ~A~%~
          308              but ECL was built without support for that." ,pointer)
          309   #+dffi
          310   `(si::call-cfun ,pointer ,return-type (list ,@types) (list ,@values)))
          311 
          312 (defun foreign-funcall-parse-args (args)
          313   "Return three values, lists of arg types, values, and result type."
          314   (let ((return-type :void))
          315     (loop for (type arg) on args by #'cddr
          316           if arg collect (cffi-type->ecl-type type) into types
          317           and collect arg into values
          318           else do (setf return-type (cffi-type->ecl-type type))
          319           finally (return (values types values return-type)))))
          320 
          321 (defmacro %foreign-funcall (name args &key library convention)
          322   "Call a foreign function."
          323   (declare (ignore library convention))
          324   (multiple-value-bind (types values return-type)
          325       (foreign-funcall-parse-args args)
          326     `(ext:with-backend
          327       :bytecodes
          328       ,(dffi-function-pointer-call name types values return-type)
          329       :c/c++
          330       ,(ecase *cffi-ecl-method*
          331          (:dffi   (dffi-function-pointer-call name types values return-type))
          332          (:dlopen (c-inline-function-call name types types values return-type t nil))
          333          (:c/c++  (c-inline-function-call name types types values return-type nil nil))))))
          334 
          335 (defmacro %foreign-funcall-pointer (pointer args &key convention)
          336   "Funcall a pointer to a foreign function."
          337   (declare (ignore convention))
          338   (multiple-value-bind (types values return-type)
          339       (foreign-funcall-parse-args args)
          340     `(ext:with-backend
          341       :bytecodes
          342       ,(dffi-function-pointer-call pointer types values return-type)
          343       :c/c++
          344       ,(if (eq *cffi-ecl-method* :dffi)
          345            (dffi-function-pointer-call pointer types values return-type)
          346            (c-inline-function-call pointer types types values return-type t nil)))))
          347 
          348 (defmacro %foreign-funcall-varargs (name args varargs &key library convention)
          349   (declare (ignore library convention))
          350   (multiple-value-bind (fixed-types fixed-values)
          351       (foreign-funcall-parse-args args)
          352     (multiple-value-bind (varargs-types varargs-values return-type)
          353         (foreign-funcall-parse-args varargs)
          354       (let ((all-types (append fixed-types varargs-types))
          355             (values (append fixed-values varargs-values)))
          356        `(ext:with-backend
          357          :bytecodes
          358          ,(dffi-function-pointer-call name all-types values return-type)
          359          :c/c++
          360          ,(ecase *cffi-ecl-method*
          361             (:dffi   (dffi-function-pointer-call name all-types values return-type))
          362             (:dlopen (c-inline-function-call name fixed-types all-types values return-type t t))
          363             (:c/c++  (c-inline-function-call name fixed-types all-types values return-type nil t))))))))
          364 
          365 (defmacro %foreign-funcall-pointer-varargs (pointer args varargs &key convention)
          366   (declare (ignore convention))
          367   (multiple-value-bind (fixed-types fixed-values)
          368       (foreign-funcall-parse-args args)
          369     (multiple-value-bind (varargs-types varargs-values return-type)
          370         (foreign-funcall-parse-args varargs)
          371       (let ((all-types (append fixed-types varargs-types))
          372             (values (append fixed-values varargs-values)))
          373        `(ext:with-backend
          374          :bytecodes
          375          ,(dffi-function-pointer-call pointer all-types values return-type)
          376          :c/c++
          377          ,(if (eq *cffi-ecl-method* :dffi)
          378             (dffi-function-pointer-call pointer all-types values return-type)
          379             (c-inline-function-call pointer fixed-types all-types values return-type t t)))))))
          380 
          381 ;;;# Foreign Libraries
          382 
          383 (defun %load-foreign-library (name path)
          384   "Load a foreign library."
          385   (declare (ignore name))
          386   #-dffi (error "LOAD-FOREIGN-LIBRARY requires ECL's DFFI support. Use ~
          387                  FFI:LOAD-FOREIGN-LIBRARY with a constant argument instead.")
          388   #+dffi
          389   (handler-case (si:load-foreign-module path)
          390     (file-error ()
          391       (error "file error while trying to load `~A'" path))))
          392 
          393 (defun %close-foreign-library (handle)
          394   "Close a foreign library."
          395   (handler-case (si::unload-foreign-module handle)
          396     (undefined-function ()
          397       (restart-case (error "Detected ECL prior to version 15.2.21. ~
          398                             Function CFFI:CLOSE-FOREIGN-LIBRARY isn't implemented yet.")
          399         (ignore () :report "Continue anyway (foreign library will remain opened).")))))
          400 
          401 (defun native-namestring (pathname)
          402   (namestring pathname))
          403 
          404 ;;;# Callbacks
          405 
          406 ;;; Create a package to contain the symbols for callback functions.
          407 ;;; We want to redefine callbacks with the same symbol so the internal
          408 ;;; data structures are reused.
          409 (defpackage #:cffi-callbacks
          410   (:use))
          411 
          412 (defvar *callbacks* (make-hash-table))
          413 
          414 ;;; Intern a symbol in the CFFI-CALLBACKS package used to name the
          415 ;;; internal callback for NAME.
          416 (eval-when (:compile-toplevel :load-toplevel :execute)
          417   (defun intern-callback (name)
          418     (intern (format nil "~A::~A"
          419                     (if-let (package (symbol-package name))
          420                       (package-name package)
          421                       "#")
          422                     (symbol-name name))
          423             '#:cffi-callbacks)))
          424 
          425 (defmacro %defcallback (name rettype arg-names arg-types body
          426                         &key convention)
          427   (declare (ignore convention))
          428   (let ((cb-name (intern-callback name))
          429         (cb-type #.(if (> ext:+ecl-version-number+ 160102)
          430                        :default :cdecl)))
          431     `(progn
          432        (ffi:defcallback (,cb-name ,cb-type)
          433            ,(cffi-type->ecl-type rettype)
          434            ,(mapcar #'list arg-names
          435                     (mapcar #'cffi-type->ecl-type arg-types))
          436          ,body)
          437        (setf (gethash ',name *callbacks*) ',cb-name))))
          438 
          439 (defun %callback (name)
          440   (multiple-value-bind (symbol winp)
          441       (gethash name *callbacks*)
          442     (unless winp
          443       (error "Undefined callback: ~S" name))
          444     (ffi:callback symbol)))
          445 
          446 ;;;# Foreign Globals
          447 
          448 (defun %foreign-symbol-pointer (name library)
          449   "Returns a pointer to a foreign symbol NAME."
          450   (declare (ignore library))
          451   (handler-case
          452       (si:find-foreign-symbol (coerce name 'base-string)
          453                               :default :pointer-void 0)
          454     (error (c) nil)))