cffi-allegro.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-allegro.lisp (16300B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; cffi-allegro.lisp --- CFFI-SYS implementation for Allegro CL.
            4 ;;;
            5 ;;; Copyright (C) 2005-2009, Luis Oliveira  <loliveira(@)common-lisp.net>
            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)
           32   (:import-from #:alexandria #:if-let #:with-unique-names #:once-only)
           33   (:export
           34    #:canonicalize-symbol-name-case
           35    #:foreign-pointer
           36    #:pointerp
           37    #:pointer-eq
           38    #:null-pointer
           39    #:null-pointer-p
           40    #:inc-pointer
           41    #:make-pointer
           42    #:pointer-address
           43    #:%foreign-alloc
           44    #:foreign-free
           45    #:with-foreign-pointer
           46    #:%foreign-funcall
           47    #:%foreign-funcall-pointer
           48    #:%foreign-type-alignment
           49    #:%foreign-type-size
           50    #:%load-foreign-library
           51    #:%close-foreign-library
           52    #:native-namestring
           53    #:%mem-ref
           54    #:%mem-set
           55    #:make-shareable-byte-vector
           56    #:with-pointer-to-vector-data
           57    #:%foreign-symbol-pointer
           58    #:defcfun-helper-forms
           59    #:%defcallback
           60    #:%callback))
           61 
           62 (in-package #:cffi-sys)
           63 
           64 ;;;# Mis-features
           65 
           66 #-64bit (pushnew 'no-long-long *features*)
           67 (pushnew 'flat-namespace *features*)
           68 
           69 ;;;# Symbol Case
           70 
           71 (defun canonicalize-symbol-name-case (name)
           72   (declare (string name))
           73   (if (eq excl:*current-case-mode* :case-sensitive-lower)
           74       (string-downcase name)
           75       (string-upcase name)))
           76 
           77 ;;;# Basic Pointer Operations
           78 
           79 (deftype foreign-pointer ()
           80   'ff:foreign-address)
           81 
           82 (defun pointerp (ptr)
           83   "Return true if PTR is a foreign pointer."
           84   (ff:foreign-address-p ptr))
           85 
           86 (defun pointer-eq (ptr1 ptr2)
           87   "Return true if PTR1 and PTR2 point to the same address."
           88   (eql ptr1 ptr2))
           89 
           90 (defun null-pointer ()
           91   "Return a null pointer."
           92   0)
           93 
           94 (defun null-pointer-p (ptr)
           95   "Return true if PTR is a null pointer."
           96   (zerop ptr))
           97 
           98 (defun inc-pointer (ptr offset)
           99   "Return a pointer pointing OFFSET bytes past PTR."
          100   (+ ptr offset))
          101 
          102 (defun make-pointer (address)
          103   "Return a pointer pointing to ADDRESS."
          104   (check-type address ff:foreign-address)
          105   address)
          106 
          107 (defun pointer-address (ptr)
          108   "Return the address pointed to by PTR."
          109   (check-type ptr ff:foreign-address)
          110   ptr)
          111 
          112 ;;;# Allocation
          113 ;;;
          114 ;;; Functions and macros for allocating foreign memory on the stack
          115 ;;; and on the heap.  The main CFFI package defines macros that wrap
          116 ;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
          117 ;;; when the memory has dynamic extent.
          118 
          119 (defun %foreign-alloc (size)
          120   "Allocate SIZE bytes on the heap and return a pointer."
          121   (ff:allocate-fobject :char :c size))
          122 
          123 (defun foreign-free (ptr)
          124   "Free a PTR allocated by FOREIGN-ALLOC."
          125   (ff:free-fobject ptr))
          126 
          127 (defmacro with-foreign-pointer ((var size &optional size-var) &body body)
          128   "Bind VAR to SIZE bytes of foreign memory during BODY.  The
          129 pointer in VAR is invalid beyond the dynamic extent of BODY, and
          130 may be stack-allocated if supported by the implementation.  If
          131 SIZE-VAR is supplied, it will be bound to SIZE during BODY."
          132   (unless size-var
          133     (setf size-var (gensym "SIZE")))
          134   #+(version>= 8 1)
          135   (when (and (constantp size) (<= (eval size) ff:*max-stack-fobject-bytes*))
          136     (return-from with-foreign-pointer
          137       `(let ((,size-var ,(eval size)))
          138          (declare (ignorable ,size-var))
          139          (ff:with-static-fobject (,var '(:array :char ,(eval size))
          140                                        :allocation :foreign-static-gc)
          141            ;; (excl::stack-allocated-p var) => T
          142            (let ((,var (ff:fslot-address ,var)))
          143              ,@body)))))
          144   `(let* ((,size-var ,size)
          145           (,var (ff:allocate-fobject :char :c ,size-var)))
          146      (unwind-protect
          147           (progn ,@body)
          148        (ff:free-fobject ,var))))
          149 
          150 ;;;# Shareable Vectors
          151 ;;;
          152 ;;; This interface is very experimental.  WITH-POINTER-TO-VECTOR-DATA
          153 ;;; should be defined to perform a copy-in/copy-out if the Lisp
          154 ;;; implementation can't do this.
          155 
          156 (defun make-shareable-byte-vector (size)
          157   "Create a Lisp vector of SIZE bytes can passed to
          158 WITH-POINTER-TO-VECTOR-DATA."
          159   (make-array size :element-type '(unsigned-byte 8)
          160               :allocation :static-reclaimable))
          161 
          162 (defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
          163   "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
          164   ;; An array allocated in static-reclamable is a non-simple array in
          165   ;; the normal Lisp allocation area, pointing to a simple array in
          166   ;; the static-reclaimable allocation area. Therefore we have to get
          167   ;; out the simple-array to find the pointer to the actual contents.
          168   (with-unique-names (simple-vec)
          169     `(excl:with-underlying-simple-vector (,vector ,simple-vec)
          170        (let ((,ptr-var (ff:fslot-address-typed :unsigned-char :lisp
          171                                                ,simple-vec)))
          172          ,@body))))
          173 
          174 ;;;# Dereferencing
          175 
          176 (defun convert-foreign-type (type-keyword)
          177   "Convert a CFFI type keyword to an Allegro type."
          178   (ecase type-keyword
          179     (:char             :char)
          180     (:unsigned-char    :unsigned-char)
          181     (:short            :short)
          182     (:unsigned-short   :unsigned-short)
          183     (:int              :int)
          184     (:unsigned-int     :unsigned-int)
          185     (:long             :long)
          186     (:unsigned-long    :unsigned-long)
          187     (:long-long
          188      #+64bit :nat
          189      #-64bit (error "this platform does not support :long-long."))
          190     (:unsigned-long-long
          191      #+64bit :unsigned-nat
          192      #-64bit (error "this platform does not support :unsigned-long-long"))
          193     (:float            :float)
          194     (:double           :double)
          195     (:pointer          :unsigned-nat)
          196     (:void             :void)))
          197 
          198 (defun %mem-ref (ptr type &optional (offset 0))
          199   "Dereference an object of TYPE at OFFSET bytes from PTR."
          200   (unless (zerop offset)
          201     (setf ptr (inc-pointer ptr offset)))
          202   (ff:fslot-value-typed (convert-foreign-type type) :c ptr))
          203 
          204 ;;; Compiler macro to open-code the call to FSLOT-VALUE-TYPED when the
          205 ;;; CFFI type is constant.  Allegro does its own transformation on the
          206 ;;; call that results in efficient code.
          207 (define-compiler-macro %mem-ref (&whole form ptr type &optional (off 0))
          208   (if (constantp type)
          209       (let ((ptr-form (if (eql off 0) ptr `(+ ,ptr ,off))))
          210         `(ff:fslot-value-typed ',(convert-foreign-type (eval type))
          211                                :c ,ptr-form))
          212       form))
          213 
          214 (defun %mem-set (value ptr type &optional (offset 0))
          215   "Set the object of TYPE at OFFSET bytes from PTR."
          216   (unless (zerop offset)
          217     (setf ptr (inc-pointer ptr offset)))
          218   (setf (ff:fslot-value-typed (convert-foreign-type type) :c ptr) value))
          219 
          220 ;;; Compiler macro to open-code the call to (SETF FSLOT-VALUE-TYPED)
          221 ;;; when the CFFI type is constant.  Allegro does its own
          222 ;;; transformation on the call that results in efficient code.
          223 (define-compiler-macro %mem-set (&whole form val ptr type &optional (off 0))
          224   (if (constantp type)
          225       (once-only (val)
          226         (let ((ptr-form (if (eql off 0) ptr `(+ ,ptr ,off))))
          227           `(setf (ff:fslot-value-typed ',(convert-foreign-type (eval type))
          228                                        :c ,ptr-form) ,val)))
          229       form))
          230 
          231 ;;;# Calling Foreign Functions
          232 
          233 (defun %foreign-type-size (type-keyword)
          234   "Return the size in bytes of a foreign type."
          235   (ff:sizeof-fobject (convert-foreign-type type-keyword)))
          236 
          237 (defun %foreign-type-alignment (type-keyword)
          238   "Returns the alignment in bytes of a foreign type."
          239   #+(and powerpc macosx32)
          240   (when (eq type-keyword :double)
          241     (return-from %foreign-type-alignment 8))
          242   ;; No override necessary for the remaining types....
          243   (ff::sized-ftype-prim-align
          244    (ff::iforeign-type-sftype
          245     (ff:get-foreign-type
          246      (convert-foreign-type type-keyword)))))
          247 
          248 (defun foreign-funcall-type-and-args (args)
          249   "Returns a list of types, list of args and return type."
          250   (let ((return-type :void))
          251     (loop for (type arg) on args by #'cddr
          252           if arg collect type into types
          253           and collect arg into fargs
          254           else do (setf return-type type)
          255           finally (return (values types fargs return-type)))))
          256 
          257 (defun convert-to-lisp-type (type)
          258   (ecase type
          259     ((:char :short :int :long :nat)
          260      `(signed-byte ,(* 8 (ff:sizeof-fobject type))))
          261     ((:unsigned-char :unsigned-short :unsigned-int :unsigned-long :unsigned-nat)
          262      `(unsigned-byte ,(* 8 (ff:sizeof-fobject type))))
          263     (:float 'single-float)
          264     (:double 'double-float)
          265     (:void 'null)))
          266 
          267 (defun allegro-type-pair (cffi-type)
          268   ;; the :FOREIGN-ADDRESS pseudo-type accepts both pointers and
          269   ;; arrays. We need the latter for shareable byte vector support.
          270   (if (eq cffi-type :pointer)
          271       (list :foreign-address)
          272       (let ((ftype (convert-foreign-type cffi-type)))
          273         (list ftype (convert-to-lisp-type ftype)))))
          274 
          275 #+ignore
          276 (defun note-named-foreign-function (symbol name types rettype)
          277   "Give Allegro's compiler a hint to perform a direct call."
          278   `(eval-when (:compile-toplevel :load-toplevel :execute)
          279      (setf (get ',symbol 'system::direct-ff-call)
          280            (list '(,name :language :c)
          281                  t  ; callback
          282                  :c ; convention
          283                  ;; return type '(:c-type lisp-type)
          284                  ',(allegro-type-pair rettype)
          285                  ;; arg types '({(:c-type lisp-type)}*)
          286                  '(,@(mapcar #'allegro-type-pair types))
          287                  nil ; arg-checking
          288                  ff::ep-flag-never-release))))
          289 
          290 (defmacro %foreign-funcall (name args &key convention library)
          291   (declare (ignore convention library))
          292   (multiple-value-bind (types fargs rettype)
          293       (foreign-funcall-type-and-args args)
          294     `(system::ff-funcall
          295       (load-time-value (excl::determine-foreign-address
          296                         '(,name :language :c)
          297                         #-(version>= 8 1) ff::ep-flag-never-release
          298                         #+(version>= 8 1) ff::ep-flag-always-release
          299                         nil ; method-index
          300                         ))
          301       ;; arg types {'(:c-type lisp-type) argN}*
          302       ,@(mapcan (lambda (type arg)
          303                   `(',(allegro-type-pair type) ,arg))
          304                 types fargs)
          305       ;; return type '(:c-type lisp-type)
          306       ',(allegro-type-pair rettype))))
          307 
          308 (defun defcfun-helper-forms (name lisp-name rettype args types options)
          309   "Return 2 values for DEFCFUN. A prelude form and a caller form."
          310   (declare (ignore options))
          311   (let ((ff-name (intern (format nil "%cffi-foreign-function/~A" lisp-name))))
          312     (values
          313       `(ff:def-foreign-call (,ff-name ,name)
          314            ,(loop for type in types
          315                   collect (list* (gensym) (allegro-type-pair type)))
          316          :returning ,(allegro-type-pair rettype)
          317          ;; Don't use call-direct when there are no arguments.
          318          ,@(unless (null args) '(:call-direct t))
          319          :arg-checking nil
          320          :strings-convert nil
          321          #+(version>= 8 1) ,@'(:release-heap :when-ok
          322                                :release-heap-ignorable t)
          323          #+smp ,@'(:release-heap-implies-allow-gc t))
          324       `(,ff-name ,@args))))
          325 
          326 ;;; See doc/allegro-internals.txt for a clue about entry-vec.
          327 (defmacro %foreign-funcall-pointer (ptr args &key convention)
          328   (declare (ignore convention))
          329   (multiple-value-bind (types fargs rettype)
          330       (foreign-funcall-type-and-args args)
          331     (with-unique-names (entry-vec)
          332       `(let ((,entry-vec (excl::make-entry-vec-boa)))
          333          (setf (aref ,entry-vec 1) ,ptr) ; set jump address
          334          (system::ff-funcall
          335           ,entry-vec
          336           ;; arg types {'(:c-type lisp-type) argN}*
          337           ,@(mapcan (lambda (type arg)
          338                       `(',(allegro-type-pair type) ,arg))
          339                     types fargs)
          340           ;; return type '(:c-type lisp-type)
          341           ',(allegro-type-pair rettype))))))
          342 
          343 ;;;# Callbacks
          344 
          345 ;;; The *CALLBACKS* hash table contains information about a callback
          346 ;;; for the Allegro FFI.  The key is the name of the CFFI callback,
          347 ;;; and the value is a cons, the car containing the symbol the
          348 ;;; callback was defined on in the CFFI-CALLBACKS package, the cdr
          349 ;;; being an Allegro FFI pointer (a fixnum) that can be passed to C
          350 ;;; functions.
          351 ;;;
          352 ;;; These pointers must be restored when a saved Lisp image is loaded.
          353 ;;; The RESTORE-CALLBACKS function is added to *RESTART-ACTIONS* to
          354 ;;; re-register the callbacks during Lisp startup.
          355 (defvar *callbacks* (make-hash-table))
          356 
          357 ;;; Register a callback in the *CALLBACKS* hash table.
          358 (defun register-callback (cffi-name callback-name)
          359   (setf (gethash cffi-name *callbacks*)
          360         (cons callback-name (ff:register-foreign-callable
          361                              callback-name :reuse t))))
          362 
          363 ;;; Restore the saved pointers in *CALLBACKS* when loading an image.
          364 (defun restore-callbacks ()
          365   (maphash (lambda (key value)
          366              (register-callback key (car value)))
          367            *callbacks*))
          368 
          369 ;;; Arrange for RESTORE-CALLBACKS to run when a saved image containing
          370 ;;; CFFI is restarted.
          371 (eval-when (:load-toplevel :execute)
          372   (pushnew 'restore-callbacks excl:*restart-actions*))
          373 
          374 ;;; Create a package to contain the symbols for callback functions.
          375 (defpackage #:cffi-callbacks
          376   (:use))
          377 
          378 (defun intern-callback (name)
          379   (intern (format nil "~A::~A"
          380                   (if-let (package (symbol-package name))
          381                     (package-name package)
          382                     "#")
          383                   (symbol-name name))
          384           '#:cffi-callbacks))
          385 
          386 (defun convert-calling-convention (convention)
          387   (ecase convention
          388     (:cdecl :c)
          389     (:stdcall :stdcall)))
          390 
          391 (defmacro %defcallback (name rettype arg-names arg-types body
          392                         &key convention)
          393   (declare (ignore rettype))
          394   (let ((cb-name (intern-callback name)))
          395     `(progn
          396        (ff:defun-foreign-callable ,cb-name
          397            ,(mapcar (lambda (sym type) (list sym (convert-foreign-type type)))
          398                     arg-names arg-types)
          399          (declare (:convention ,(convert-calling-convention convention)))
          400          ,body)
          401        (register-callback ',name ',cb-name))))
          402 
          403 ;;; Return the saved Lisp callback pointer from *CALLBACKS* for the
          404 ;;; CFFI callback named NAME.
          405 (defun %callback (name)
          406   (or (cdr (gethash name *callbacks*))
          407       (error "Undefined callback: ~S" name)))
          408 
          409 ;;;# Loading and Closing Foreign Libraries
          410 
          411 (defun %load-foreign-library (name path)
          412   "Load a foreign library."
          413   ;; ACL 8.0 honors the :FOREIGN option and always tries to foreign load
          414   ;; the argument. However, previous versions do not and will only
          415   ;; foreign load the argument if its type is a member of the
          416   ;; EXCL::*LOAD-FOREIGN-TYPES* list. Therefore, we bind that special
          417   ;; to a list containing whatever type NAME has.
          418   (declare (ignore name))
          419   (let ((excl::*load-foreign-types*
          420          (list (pathname-type (parse-namestring path)))))
          421     (handler-case
          422         (progn
          423           #+(version>= 7) (load path :foreign t)
          424           #-(version>= 7) (load path))
          425       (file-error (fe)
          426         (error (change-class fe 'simple-error))))
          427     path))
          428 
          429 (defun %close-foreign-library (name)
          430   "Close the foreign library NAME."
          431   (ff:unload-foreign-library name))
          432 
          433 (defun native-namestring (pathname)
          434   (namestring pathname))
          435 
          436 ;;;# Foreign Globals
          437 
          438 (defun convert-external-name (name)
          439   "Add an underscore to NAME if necessary for the ABI."
          440   #+macosx (concatenate 'string "_" name)
          441   #-macosx name)
          442 
          443 (defun %foreign-symbol-pointer (name library)
          444   "Returns a pointer to a foreign symbol NAME."
          445   (declare (ignore library))
          446   (prog1 (ff:get-entry-point (convert-external-name name))))