klisp

an open source interpreter for the Kernel Programming Language.
git clone http://git.hanabi.in/repos/klisp.git
Log | Files | Refs | README

commit a0d8828c721590fb129df4b5dccc982f3b0b8749
parent 1e036d3d1b4535d7bb7eb93839acaaab72b78b12
Author: Oto Havle <havleoto@gmail.com>
Date:   Wed, 26 Oct 2011 11:30:17 +0200

Improved FFI - conditional compilation, new functions ffi-memmove and ffi-type-suite, more examples.

Diffstat:
Msrc/Makefile | 13+++++++++++--
Asrc/examples/ffi-sdl.k | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/examples/ffi.k | 300++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/kgffi.c | 369++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/kgffi.h | 3+++
Msrc/kground.c | 7++++++-
6 files changed, 851 insertions(+), 46 deletions(-)

diff --git a/src/Makefile b/src/Makefile @@ -15,6 +15,10 @@ RANLIB= ranlib RM= rm -f LIBS= -lm $(MYLIBS) +# Set USE_LIBFFI=1 (or other nonempty string) to enable libffi-dependent +# code. +USE_LIBFFI= + MYCFLAGS= MYLDFLAGS= MYLIBS= @@ -34,7 +38,9 @@ CORE_O= kobject.o ktoken.o kpair.o kstring.o ksymbol.o kread.o \ kgsymbols.o kgcontrol.o kgpairs_lists.o kgpair_mut.o kgenvironments.o \ kgenv_mut.o kgcombiners.o kgcontinuations.o kgencapsulations.o \ kgpromises.o kgkd_vars.o kgks_vars.o kgports.o kgchars.o kgnumbers.o \ - kgstrings.o kgblobs.o kgsystem.o kgffi.o + kgstrings.o kgblobs.o kgsystem.o \ + $(if $(USE_LIBFFI),kgffi.o) + # TEMP: in klisp there is no distinction between core & lib LIB_O= @@ -91,8 +97,11 @@ mingw: "AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \ "MYCFLAGS=-DKLISP_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" klisp.exe #lisp_use_posix isn't used right now... +# TEMP: rename read() and write() here to avoid name conflicts with foreign code posix: - $(MAKE) all MYCFLAGS=-DKLISP_USE_POSIX MYLIBS="-ldl -lffi" + $(MAKE) all \ + "MYCFLAGS=-DKLISP_USE_POSIX $(if $(USE_LIBFFI),-DKUSE_LIBFFI=1 -Dread=klisp_read -Dwrite=klisp_write)" \ + "MYLIBS=$(if $(USE_LIBFFI), -ldl -lffi)" # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all default o clean diff --git a/src/examples/ffi-sdl.k b/src/examples/ffi-sdl.k @@ -0,0 +1,205 @@ +;; +;; Dynamic FFI example. +;; Interfacing Simple DirectMedia Layer Library. +;; +;; Tested with SDL 1.2 on Debian Squeeze, x86. +;; It is quite likely that this program will not work +;; with other versions and other operating systems. +;; +;; Dynamic FFI is inherently unsafe. The user is responsible +;; for specifying correct argument types, data structure +;; layout, alignment, etc. even though it is platform dependent. +;; +;; usage: +;; .../src$ make posix USE_LIBFFI=1 +;; .../src$ ./klisp examples/ffi-sdl.k +;; +;; The program shall create a window and responding +;; to mouse click. +;; + +($define! sdl-import + ($let + ( (libsdl (ffi-load-library "libSDL.so")) + (abi "FFI_DEFAULT_ABI")) + ($lambda (rtype name . args) + (ffi-make-applicative libsdl name + (ffi-make-call-interface abi rtype args))))) + +($define! SDL_INIT_TIMER #x00000001) +($define! SDL_INIT_AUDIO #x00000010) +($define! SDL_INIT_VIDEO #x00000020) +($define! SDL_INIT_NOPARACHUTE #x01000000) + +($define! sdl-init (sdl-import "sint" "SDL_Init" "uint32")) +($define! sdl-quit (sdl-import "void" "SDL_Quit")) + +($define! SDL_SWSURFACE #x00000000) +($define! SDL_HWSURFACE #x00000001) + +($define! sdl-set-video-mode (sdl-import "pointer" "SDL_SetVideoMode" "sint" "sint" "sint" "uint32")) +($define! sdl-wm-set-caption (sdl-import "void" "SDL_WM_SetCaption" "string" "pointer")) + +($define! sdl-wait-event + ($let + ((SDL_WaitEvent (sdl-import "sint" "SDL_WaitEvent" "pointer"))) + ($lambda () + ($let* + ( (buffer (make-blob 512)) + (ok (SDL_WaitEvent buffer))) + ($if (zero? ok) + (apply-continuation error-continuation "SDL_WaitEvent signalled error") + buffer))))) + +($define! align + ($lambda (offset alignment) + (+ offset (mod (- alignment (mod offset alignment)) alignment)))) + +($define! $quote + ($vau (x) denv x)) + +($define! $define-struct-projectors! + ($letrec* + ( (aux + ($lambda (fields offset denv) + ($if (null? fields) + () + ($let* + ( (((projector-name type-string) . tail) fields) + ((size alignment ref set!) (ffi-type-suite type-string)) + (aligned-offset (align offset alignment)) + (projector-function ($lambda (blob) (ref (list blob aligned-offset))))) + (write (list projector-name size alignment aligned-offset)) + (newline) + (eval + (list + ($quote $define!) + projector-name + (list ($quote $quote) projector-function)) + denv) + (aux tail (+ size aligned-offset) denv)))))) + ($vau fields denv + (aux fields 0 denv)))) + +($define! SDL_QUIT 12) +($define! SDL_MOUSEMOTION 4) +($define! SDL_MOUSEBUTTONDOWN 5) +($define! SDL_MOUSEBUTTONUP 6) + +($define-struct-projectors! + (event-type "uint8")) + +($define-struct-projectors! + (MouseMotionEvent.type "uint8") + (MouseMotionEvent.state "uint8") + (MouseMotionEvent.which "uint8") + (MouseMotionEvent.x "uint16") + (MouseMotionEvent.y "uint16") + (MouseMotionEvent.xrel "sint16") + (MouseMotionEvent.yrel "sint16")) + +($define-struct-projectors! + (MouseButtonEvent.type "uint8") + (MouseButtonEvent.which "uint8") + (MouseButtonEvent.button "uint8") + (MouseButtonEvent.state "uint8") + (MouseButtonEvent.x "uint16") + (MouseButtonEvent.y "uint16")) + +($define! with-sdl + ($lambda (window-title worker) + (display "Initializing SDL...") + ($let ((status (sdl-init SDL_INIT_VIDEO))) + (write status) + (newline) + ($if (<? status 0) + (apply error-continuation "error initializing SDL") + ($sequence + (guard-dynamic-extent + () + ($lambda () + ($let* ((screen (sdl-set-video-mode 640 480 32 SDL_HWSURFACE))) + ($if (null? screen) + (apply-continuation error-contination "unable to set video mode") + ($sequence + (sdl-wm-set-caption window-title ()) + (worker screen))))) + (list + (list + error-continuation + ($lambda (v divert) + (display "Error. Deinitializing SDL...") + (sdl-quit) + (display "done.") + (newline) + v)))) + (display "Finished. Deinitializing SDL...") + (sdl-quit) + (display "done.") + (newline)))))) + +($define-struct-projectors! + (SDL_Surface.flags "uint32") + (SDL_Surface.format "pointer") + (SDL_Surface.w "sint") + (SDL_Surface.h "sint") + (SDL_Surface.pitch "uint16") + (SDL_Surface.pixels "pointer") + (SDL_Surface.offset "sint")) + +($define! draw-pixel + ($let + ( ((pixel-size pixel-alignment pixel-ref pixel-set!) (ffi-type-suite "uint32")) + (SDL_MapRGB (sdl-import "uint32" "SDL_MapRGB" "pointer" "uint8" "uint8" "uint8")) + (SDL_LockSurface (sdl-import "sint" "SDL_LockSurface" "pointer")) + (SDL_UnlockSurface (sdl-import "void" "SDL_UnlockSurface" "pointer")) + (SDL_Flip (sdl-import "sint" "SDL_Flip" "pointer"))) + ($lambda (screen (x y) (r g b)) + ($if (<? (SDL_LockSurface screen) 0) + (apply error-continuation "SDL_LockSurface failed") + ()) + ($let + ( (pixels (SDL_Surface.pixels screen)) + (pitch (SDL_Surface.pitch screen)) + (color (SDL_MapRGB (SDL_Surface.format screen) r g b))) + ;(display (list color x y pixel-size pitch (SDL_Surface.flags screen))) + (pixel-set! + (list pixels (+ (* x pixel-size) (* y pitch))) + color)) + (SDL_UnlockSurface screen) + (SDL_Flip screen)))) + +($define! event-loop + ($lambda (screen drawing) + ($let* + ( (ev (sdl-wait-event)) + (t (event-type ev))) + ($cond + ( (equal? t SDL_QUIT) + ()) + ( (and? drawing (equal? t SDL_MOUSEMOTION)) + (draw-pixel + screen + (list (MouseMotionEvent.x ev) (MouseMotionEvent.y ev)) + (list 0 255 0)) + (event-loop screen #t)) + ( (equal? t SDL_MOUSEBUTTONDOWN) + (draw-pixel + screen + (list (MouseButtonEvent.x ev) (MouseButtonEvent.y ev)) + (list 255 0 0)) + (event-loop screen #t)) + ( (equal? t SDL_MOUSEBUTTONUP) + (draw-pixel + screen + (list (MouseButtonEvent.x ev) (MouseButtonEvent.y ev)) + (list 0 0 255)) + (event-loop screen #f)) + (#t + (event-loop screen drawing)))))) + +($define! main + ($lambda (argv) + (with-sdl "klisp ffi demo" + ($lambda (screen) (event-loop screen #f))))) + diff --git a/src/examples/ffi.k b/src/examples/ffi.k @@ -1,4 +1,12 @@ ;; +;; Basic FFI examples. +;; +;; usage: +;; .../src$ make posix USE_LIBFFI=1 +;; .../src$ ./klisp examples/ffi-sdl.k +;; + + ;; (ffi-load-library DLLNAME) ... loads the C library DLLNAME ;; and returns opaque handle. ;; @@ -27,16 +35,26 @@ ;; "void" void inert (only return) ;; "sint" signed int fixint ;; "string" (char *) string -;; "pointer" (void *) blob, string, nil (only arguments) +;; "uint8" uint8_t fixint +;; "uint16" uint16_t fixint +;; "uint32" uint32_t fixint, bigint +;; "uint64" uint64_t fixint, bigint +;; "float" float double +;; "double" double double +;; "pointer" (void *) blob (only for arguments) +;; string (only for arguments) +;; nil +;; pointer (TAG_USER) ;; ;; Other data types not supported yet. Varargs function ;; not supported by libffi. ;; ($define! abi "FFI_DEFAULT_ABI") -($define! cif1 (ffi-make-call-interface abi "sint" ())) -($define! cif2 (ffi-make-call-interface abi "sint" (list "string"))) -($define! cif3 (ffi-make-call-interface abi "string" (list "string"))) +($define! cif-int-void (ffi-make-call-interface abi "sint" ())) +($define! cif-string-string (ffi-make-call-interface abi "string" (list "string"))) +($define! cif-int-string (ffi-make-call-interface abi "sint" (list "string"))) +($define! cif-double-double (ffi-make-call-interface abi "double" (list "double"))) ;; (ffi-make-applicative LIB-HANDLE FUNCTION-NAME CALL-INTERFACE) ;; @@ -50,34 +68,28 @@ ;; otherwise. ;; -($define! getpid (ffi-make-applicative self "getpid" cif1)) -($define! getppid (ffi-make-applicative self "getppid" cif1)) -($define! system (ffi-make-applicative self "system" cif2)) -($define! getenv (ffi-make-applicative self "getenv" cif3)) +($define! getpid (ffi-make-applicative self "getpid" cif-int-void)) +($define! getppid (ffi-make-applicative self "getppid" cif-int-void)) +($define! system (ffi-make-applicative self "system" cif-int-string)) +($define! getenv (ffi-make-applicative self "getenv" cif-string-string)) +($define! lgamma (ffi-make-applicative self "lgamma" cif-double-double)) -($define! horner - ($lambda (polynomial x acc) - ($if (null? polynomial) - acc - (horner (cdr polynomial) x (+ (car polynomial) (* x acc)))))) +(display "Testing getpid(), getppid() ...") +(write (list (getpid) (getppid))) +(newline) -($define! u32-of-u8-list - ($lambda (list) - (horner list 256 0))) +(display "Testing getenv(\"HOME\")...") +(write (getenv "HOME")) +(newline) -;; warning: 32-bit little endian only -($define! gettimeofday - ($let - ( (unix-gettimeofday (ffi-make-applicative libc "gettimeofday" - (ffi-make-call-interface abi - "sint" (list "pointer" "pointer"))))) - ($lambda () - ($let* ( (buffer (make-blob 8)) - (b ($lambda (i) (blob-u8-ref buffer i)))) - (unix-gettimeofday buffer ()) - (list - (u32-of-u8-list (map b (list 3 2 1 0))) - (/ (u32-of-u8-list (map b (list 7 6 5 4))) 1000000)))))) +(display "Testing system(\"ls /\")...") +(newline) +(write (system "ls /")) +(newline) + +(display "Testing lgamma(9.87)...") +(write (lgamma 9.87)) +(newline) ($define! unix-write-string ($let* @@ -90,25 +102,235 @@ (unix-write-string "ABCDEFGH") (newline) -(display "Testing getpid()...") -(write (getpid)) -(newline) +($define! strtoull + (ffi-make-applicative libc "strtoull" + (ffi-make-call-interface abi + "uint64" (list "string" "pointer" "sint")))) -(display "Testing getppid()...") -(write (getppid)) +(display "Testing strtoull(\"0x123456789ABCDEF\", NULL, 0)...") +(write (strtoull "0x123456789ABCDEF" () 0)) +(display "...") +(write #x123456789ABCDEF) +(display "= #x123456789ABCDEF") (newline) -(display "Testing getenv(\"HOME\")...") -(write (getenv "HOME")) +;; (ffi-type-suite TYPE) returns a four-element list +;; (SIZE ALIGNMENT REF SET!). SIZE is the size of +;; the data type in bytes. ALIGNMENT is preferred +;; alignment. REF and SET! are applicatives. +;; +;; (REF MEMORY-LOCATION) +;; (SET! MEMORY-LOCATION VALUE) +;; +;; MEMORY-LOCATION is either blob, string, pointer, +;; or a two-element list (MEMORY-LOCATION OFFSET). +;; The offset specification can not be nested, i.e. +;; ((blob 1) 2) is not valid memory location. +;; +($define! + (sint-size sint-alignment sint-ref sint-set!) + (ffi-type-suite "sint")) + +(display "\"sint\" data type size and alignment: ") +(write (list sint-size sint-alignment)) (newline) -(display "Testing gettimeofday(), assuming 32-bit intel arch...") +;; Using ffi-type-suite, one can define means to convert +;; C structs (stored in blobs or arbitrary memory locations) +;; to lists. +;; +($define! align + ($lambda (offset alignment) + (+ offset (mod (- alignment offset) alignment)))) + +($define! regularize-location + ($lambda (location) + ($if (pair? location) + location + (list location 0)))) + +($define! decode-struct + ($lambda type-strings + ($letrec* + ( (suites (map ffi-type-suite type-strings)) + (decode ($lambda (base offset tail) + ($if (null? tail) + () + ($let (((size alignment ref set!) (car tail))) + (cons + (ref (list base (align offset alignment))) + (decode + base + (+ size (align offset alignment)) + (cdr tail)))))))) + ($lambda (location) + ($let (((base offset) (regularize-location location))) + (decode base offset suites)))))) + +;; For example, +;; +;; struct timeval { +;; time_t tv_sec; /* seconds */ +;; suseconds_t tv_usec; /* microseconds */ +;; }; +;; +($define! struct-timeval-ref + (decode-struct "sint" "sint")) + +($define! gettimeofday + ($let + ( (unix-gettimeofday + (ffi-make-applicative libc "gettimeofday" + (ffi-make-call-interface abi + "sint" (list "pointer" "pointer"))))) + ($lambda () + ($let* ((buffer (make-blob (* 2 sint-size)))) + (unix-gettimeofday buffer ()) + ($let (((tv_sec tv_usec) (struct-timeval-ref buffer))) + (list tv_sec (/ tv_usec 1000000))))))) + +(display "Testing gettimeofday(), assuming 32-bit arch...") (write (gettimeofday)) (newline) -(display "Testing system(\"ls /\")...") +($define! localtime + ($let + ( (localtime-r + (ffi-make-applicative libc "localtime_r" + (ffi-make-call-interface abi + "pointer" (list "pointer" "pointer")))) + (decoder + (decode-struct "sint" "sint" "sint" "sint" "sint" "sint" "sint" "sint"))) + ($lambda (t) + ($let* + ( (t-buf (make-blob sint-size)) + (tm-buf (make-blob 128)) ) + (sint-set! t-buf t) + (localtime-r t-buf tm-buf) + ($let + (((tm_sec tm_min tm_hour tm_mday tm_mon tm_year . rest) + (decoder tm-buf))) + (list + (list (+ 1900 tm_year) (+ 1 tm_mon) tm_mday) + (list tm_hour tm_min tm_sec))))))) + +(display "Testing localtime()...") +(write + ($let (((tm_sec tm_usec) (gettimeofday))) + (localtime tm_sec))) (newline) -(write (system "ls /")) + +;; Some C structs are more complex: +;; +;; struct hostent { +;; char *h_name; /* official name of host */ +;; char **h_aliases; /* alias list */ +;; int h_addrtype; /* host address type */ +;; int h_length; /* length of address */ +;; char **h_addr_list; /* list of addresses */ +;; } +;; +;; Network address is just byte array. IPv4 address +;; contains 4 bytes, IPv6 address contains 16 bytes. +;; +;; (ffi-memmove DESTINATION SOURCE SIZE) copies +;; SIZE bytes from SOURCE to DESTINATION. Both SOURCE +;; and DESTINATION are memory locations as described above. +;; ffi-memmove can copy data between blobs and arbitrary +;; memory locations. +;; +($define! copy-location + ($lambda (location size) + ($let ((blob (make-blob size))) + (ffi-memmove blob location size) + blob))) + +($define! blob->list + ($letrec + ((aux ($lambda (blob index) + ($if (<? index (blob-length blob)) + (cons + (blob-u8-ref blob index) + (aux blob (+ 1 index))) + ())))) + ($lambda (blob) + (aux blob 0)))) + +($define! parse-address + ($lambda (location size) + (blob->list (copy-location location size)))) + +($define! + (voidptr-size voidptr-alignment voidptr-ref voidptr-set!) + (ffi-type-suite "pointer")) + +($define! null-terminated-array->list + ($letrec + ( (aux + ($lambda (base offset) + ($if (null? (voidptr-ref (list base offset))) + () + (cons + (list base offset) + (aux base (+ offset voidptr-size))))))) + ($lambda (location) + (apply aux (regularize-location location))))) + +($define! + (charptr-size charptr-alignment charptr-ref charptr-set!) + (ffi-type-suite "string")) + +($define! parse-hostent + ($letrec* + ( (decode-1 + (decode-struct "string" "pointer" "sint" "sint" "pointer"))) + ($lambda (pointer) + ($let + ( ((h_name h_aliases h_addrtype h_length h_addr_list) (decode-1 pointer))) + (list + h_name + (map + charptr-ref + (null-terminated-array->list h_aliases)) + (map + ($lambda (a) (parse-address (voidptr-ref a) h_length)) + (null-terminated-array->list h_addr_list))))))) + +($define! gethostbyname + ($let + ((unix-gethostbyname + (ffi-make-applicative libc "gethostbyname" + (ffi-make-call-interface abi "pointer" (list "string"))))) + ($lambda (hostname) + (parse-hostent (unix-gethostbyname hostname))))) + +(display "Testing gehostbyname(\"localhost\")...") +(write (gethostbyname "localhost")) (newline) +(display "Testing gehostbyname(\"www.google.com\")...") +(write (gethostbyname "www.google.com")) +(newline) + +;; +;; Detecting machine endianess. +;; +($define! + (uint32-size uint32-alignment uint32-ref uint32-set!) + (ffi-type-suite "uint32")) + +($define! endianess + ($let + ((buffer (make-blob 4))) + (uint32-set! buffer #x01020304) + ($let + ((bytes (blob->list buffer))) + ($cond + ((equal? bytes (list 1 2 3 4)) "big-endian") + ((equal? bytes (list 4 3 2 1)) "little-endian") + (#t "unknown"))))) + +(display "Guessing endianess...") +(write endianess) +(newline) diff --git a/src/kgffi.c b/src/kgffi.c @@ -13,8 +13,10 @@ #include <dlfcn.h> #include <ffi.h> +#include "imath.h" #include "kstate.h" #include "kobject.h" +#include "kinteger.h" #include "kpair.h" #include "kerror.h" #include "kblob.h" @@ -24,6 +26,11 @@ #include "kgencapsulations.h" #include "kgffi.h" +/* Set to 0 to ignore aligment errors during direct + * memory read/writes. */ + +#define KGFFI_CHECK_ALIGNMENT 1 + typedef struct ffi_codec_s ffi_codec_t; struct ffi_codec_s { const char *name; @@ -66,6 +73,13 @@ static void ffi_encode_sint(ffi_codec_t *self, klisp_State *K, TValue v, void *b * (int *) buf = ivalue(v); } +static TValue ffi_decode_pointer(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + void *p = *(void **)buf; + return (p) ? p2tv(p) : KNIL; +} + static void ffi_encode_pointer(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) { if (ttisblob(v)) { @@ -74,15 +88,19 @@ static void ffi_encode_pointer(ffi_codec_t *self, klisp_State *K, TValue v, void *(void **)buf = kstring_buf(v); } else if (ttisnil(v)) { *(void **)buf = NULL; + } else if (tbasetype_(v) == K_TAG_USER) { + /* TODO: do not use internal macro tbasetype_ */ + *(void **)buf = pvalue(v); } else { - klispE_throw_simple_with_irritants(K, "neither blob, string or nil", 1, v); + klispE_throw_simple_with_irritants(K, "neither blob, string, pointer or nil", 1, v); } } static TValue ffi_decode_string(ffi_codec_t *self, klisp_State *K, const void *buf) { UNUSED(self); - return kstring_new_b_imm(K, *(char **)buf); + char *s = *(char **) buf; + return (s) ? kstring_new_b_imm(K, s) : KNIL; } static void ffi_encode_string(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) @@ -94,12 +112,208 @@ static void ffi_encode_string(ffi_codec_t *self, klisp_State *K, TValue v, void } } +static TValue ffi_decode_uint8(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + UNUSED(K); + return i2tv(*(uint8_t *)buf); +} + +static void ffi_encode_uint8(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisfixint(v) && 0 <= ivalue(v) && ivalue(v) <= UINT8_MAX) { + *(uint8_t *) buf = ivalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C uint8_t", 1, v); + return; + } +} + +static TValue ffi_decode_sint8(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + UNUSED(K); + return i2tv(*(int8_t *)buf); +} + +static void ffi_encode_sint8(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisfixint(v) && INT8_MIN <= ivalue(v) && ivalue(v) <= INT8_MAX) { + *(int8_t *) buf = ivalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C int8_t", 1, v); + return; + } +} + +static TValue ffi_decode_uint16(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + return i2tv(*(uint16_t *)buf); +} + +static void ffi_encode_uint16(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisfixint(v) && 0 <= ivalue(v) && ivalue(v) <= UINT16_MAX) { + *(uint16_t *) buf = ivalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C uint16_t", 1, v); + return; + } +} + +static TValue ffi_decode_sint16(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + return i2tv(*(int16_t *)buf); +} + +static void ffi_encode_sint16(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisfixint(v) && INT16_MIN <= ivalue(v) && ivalue(v) <= INT16_MAX) { + *(int16_t *) buf = ivalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C int16_t", 1, v); + return; + } +} + +static TValue ffi_decode_uint32(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + uint32_t x = *(uint32_t *)buf; + if (x <= INT32_MAX) { + return i2tv((int32_t) x); + } else { + TValue res = kbigint_make_simple(K); + krooted_tvs_push(K, res); + + uint8_t d[4]; + for (int i = 3; i >= 0; i--) { + d[i] = (x & 0xFF); + x >>= 8; + } + mp_int_read_unsigned(K, tv2bigint(res), d, 4); + + krooted_tvs_pop(K); + return res; + } +} + +static void ffi_encode_uint32(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + uint32_t tmp; + + if (ttisfixint(v) && 0 <= ivalue(v)) { + *(uint32_t *) buf = ivalue(v); + } else if (ttisbigint(v) && mp_int_to_uint(tv2bigint(v), &tmp) == MP_OK) { + *(uint32_t *) buf = tmp; + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C uint32_t", 1, v); + return; + } +} + +static TValue ffi_decode_uint64(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + /* TODO */ + UNUSED(self); + uint64_t x = *(uint64_t *)buf; + if (x <= INT32_MAX) { + return i2tv((int32_t) x); + } else { + TValue res = kbigint_make_simple(K); + krooted_tvs_push(K, res); + + uint8_t d[8]; + for (int i = 7; i >= 0; i--) { + d[i] = (x & 0xFF); + x >>= 8; + } + + mp_int_read_unsigned(K, tv2bigint(res), d, 8); + krooted_tvs_pop(K); + return res; + } +} + +static void ffi_encode_uint64(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + /* TODO */ + UNUSED(self); + + if (ttisfixint(v) && 0 <= ivalue(v)) { + *(uint64_t *) buf = ivalue(v); + } else if (ttisbigint(v) + && mp_int_compare_zero(tv2bigint(v)) >= 0 + && mp_int_unsigned_len(tv2bigint(v)) <= 8) { + uint8_t d[8]; + + mp_int_to_unsigned(K, tv2bigint(v), d, 8); + uint64_t tmp = d[0]; + for (int i = 1; i < 8; i++) + tmp = (tmp << 8) | d[i]; + *(uint64_t *) buf = tmp; + } else { + klispE_throw_simple_with_irritants(K, "unable to convert to C uint64_t", 1, v); + return; + } +} + +static TValue ffi_decode_double(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + return d2tv(*(double *)buf); +} + +static void ffi_encode_double(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisdouble(v)) { + *(double *) buf = dvalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to cast to C double", 1, v); + return; + } +} + +static TValue ffi_decode_float(ffi_codec_t *self, klisp_State *K, const void *buf) +{ + UNUSED(self); + return d2tv((double) *(float *)buf); +} + +static void ffi_encode_float(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + UNUSED(self); + if (ttisdouble(v)) { + /* TODO: avoid double rounding for rationals/bigints ?*/ + *(float *) buf = dvalue(v); + } else { + klispE_throw_simple_with_irritants(K, "unable to cast to C float", 1, v); + return; + } +} + static ffi_codec_t ffi_codecs[] = { { "void", &ffi_type_void, ffi_decode_void, NULL }, - { "pointer", &ffi_type_pointer, NULL, ffi_encode_pointer }, { "string", &ffi_type_pointer, ffi_decode_string, ffi_encode_string }, #define SIMPLE_TYPE(t) { #t, &ffi_type_ ## t, ffi_decode_ ## t, ffi_encode_ ## t } - SIMPLE_TYPE(sint) + SIMPLE_TYPE(sint), + SIMPLE_TYPE(pointer), + SIMPLE_TYPE(uint8), + SIMPLE_TYPE(sint8), + SIMPLE_TYPE(uint16), + SIMPLE_TYPE(sint16), + SIMPLE_TYPE(uint32), + SIMPLE_TYPE(uint64), + SIMPLE_TYPE(float), + SIMPLE_TYPE(double) #undef SIMPLE_TYPE }; @@ -350,6 +564,151 @@ void ffi_make_applicative(klisp_State *K, TValue *xparams, kapply_cc(K, app); } +static uint8_t * ffi_memory_location(klisp_State *K, bool allow_nesting, + TValue v, bool mutable, size_t size) +{ + if (ttisblob(v)) { + if (mutable && kblob_immutablep(v)) { + klispE_throw_simple_with_irritants(K, "blob not mutable", 1, v); + return NULL; + } else if (size > kblob_size(v)) { + klispE_throw_simple_with_irritants(K, "blob too small", 1, v); + return NULL; + } else { + return kblob_buf(v); + } + } else if (ttisstring(v)) { + if (mutable && kstring_immutablep(v)) { + klispE_throw_simple_with_irritants(K, "string not mutable", 1, v); + return NULL; + } else if (size > kstring_size(v)) { + klispE_throw_simple_with_irritants(K, "string too small", 1, v); + return NULL; + } else { + return (uint8_t *) kstring_buf(v); + } + } else if (tbasetype_(v) == K_TAG_USER) { + /* TODO: do not use internal macro tbasetype_ */ + return (pvalue(v)); + } else if (ttispair(v) && ttispair(kcdr(v)) && ttisnil(kcddr(v))) { + if (!allow_nesting) { + klispE_throw_simple_with_irritants(K, "offset specifications cannot be nested", 1, v); + return NULL; + } + TValue base_tv = kcar(v); + TValue offset_tv = kcadr(v); + if (!ttisfixint(offset_tv) || ivalue(offset_tv) < 0) { + klispE_throw_simple_with_irritants(K, "offset should be nonnegative fixint", 1, v); + return NULL; + } else { + size_t offset = ivalue(offset_tv); + uint8_t * p = ffi_memory_location(K, false, base_tv, mutable, size + offset); + return (p + offset); + } + } else { + klispE_throw_simple_with_irritants(K, "not a memory location", 1, v); + return NULL; + } +} + +void ffi_memmove(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(xparams); + UNUSED(denv); + + bind_3tp(K, ptree, + "any", anytype, dst_tv, + "any", anytype, src_tv, + "integer", ttisfixint, sz_tv); + + if (ivalue(sz_tv) < 0) + klispE_throw_simple(K, "size should be nonnegative fixint"); + + size_t sz = (size_t) ivalue(sz_tv); + uint8_t * dst = ffi_memory_location(K, true, dst_tv, true, sz); + const uint8_t * src = ffi_memory_location(K, true, src_tv, false, sz); + memmove(dst, src, sz); + + kapply_cc(K, KINERT); +} + +static void ffi_type_ref(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(denv); + /* + ** xparams[0]: pointer to ffi_codec_t + */ + + bind_1tp(K, ptree, "any", anytype, location_tv); + ffi_codec_t *codec = pvalue(xparams[0]); + const uint8_t *ptr = ffi_memory_location(K, true, location_tv, false, codec->libffi_type->size); +#if KGFFI_CHECK_ALIGNMENT + if ((size_t) ptr % codec->libffi_type->alignment != 0) + klispE_throw_simple(K, "unaligned memory read through FFI"); +#endif + + TValue result = codec->decode(codec, K, ptr); + kapply_cc(K, result); +} + +static void ffi_type_set(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(denv); + /* + ** xparams[0]: pointer to ffi_codec_t + */ + + bind_2tp(K, ptree, + "any", anytype, location_tv, + "any", anytype, value_tv); + ffi_codec_t *codec = pvalue(xparams[0]); + uint8_t *ptr = ffi_memory_location(K, true, location_tv, false, codec->libffi_type->size); +#if KGFFI_CHECK_ALIGNMENT + if ((size_t) ptr % codec->libffi_type->alignment != 0) + klispE_throw_simple(K, "unaligned memory write through FFI"); +#endif + + codec->encode(codec, K, value_tv, ptr); + kapply_cc(K, KINERT); +} + +void ffi_type_suite(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + bind_1tp(K, ptree, "string", ttisstring, type_tv); + ffi_codec_t *codec = tv2ffi_codec(K, type_tv); + + TValue size_tv = i2tv(codec->libffi_type->size); + krooted_tvs_push(K, size_tv); + + TValue alignment_tv = i2tv(codec->libffi_type->alignment); + krooted_tvs_push(K, alignment_tv); + + TValue getter_tv = + (codec->decode) + ? kmake_applicative(K, ffi_type_ref, 1, p2tv(codec)) + : KINERT; + krooted_tvs_push(K, getter_tv); + + TValue setter_tv = + (codec->encode) + ? kmake_applicative(K, ffi_type_set, 1, p2tv(codec)) + : KINERT; + krooted_tvs_push(K, setter_tv); + + TValue suite_tv = kimm_list(K, 4, size_tv, alignment_tv, getter_tv, setter_tv); + + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + + kapply_cc(K, suite_tv); +} + /* init ground */ void kinit_ffi_ground_env(klisp_State *K) { @@ -364,6 +723,8 @@ void kinit_ffi_ground_env(klisp_State *K) add_applicative(K, ground_env, "ffi-load-library", ffi_load_library, 1, dll_key); add_applicative(K, ground_env, "ffi-make-call-interface", ffi_make_call_interface, 1, cif_key); add_applicative(K, ground_env, "ffi-make-applicative", ffi_make_applicative, 2, dll_key, cif_key); + add_applicative(K, ground_env, "ffi-memmove", ffi_memmove, 0); + add_applicative(K, ground_env, "ffi-type-suite", ffi_type_suite, 0); add_applicative(K, ground_env, "ffi-library?", enc_typep, 1, dll_key); add_applicative(K, ground_env, "ffi-call-interface?", enc_typep, 1, cif_key); } diff --git a/src/kgffi.h b/src/kgffi.h @@ -7,6 +7,9 @@ #ifndef kgffi_h #define kgffi_h +#if (KUSE_LIBFFI != 1) +# error "Compiling FFI code, but KUSE_LIBFFI != 1." +#endif #include <assert.h> #include <stdio.h> #include <stdlib.h> diff --git a/src/kground.c b/src/kground.c @@ -37,7 +37,10 @@ #include "kgports.h" #include "kgblobs.h" #include "kgsystem.h" -#include "kgffi.h" + +#if KUSE_LIBFFI +# include "kgffi.h" +#endif /* for initing cont names */ #include "ktable.h" @@ -140,7 +143,9 @@ void kinit_ground_env(klisp_State *K) kinit_ports_ground_env(K); kinit_blobs_ground_env(K); kinit_system_ground_env(K); +#if KUSE_LIBFFI kinit_ffi_ground_env(K); +#endif /* ** Initialize the names of the continuation used in