klisp

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

commit 9cab3f60630432582e12fec00389f9e07cf07fd6
parent a0d8828c721590fb129df4b5dccc982f3b0b8749
Author: Oto Havle <havleoto@gmail.com>
Date:   Thu, 27 Oct 2011 15:46:08 +0200

Improved FFI: callbacks.

Diffstat:
Asrc/examples/ffi-gsl.k | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kgffi.c | 319++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 383 insertions(+), 2 deletions(-)

diff --git a/src/examples/ffi-gsl.k b/src/examples/ffi-gsl.k @@ -0,0 +1,66 @@ +;; +;; Dynamic FFI callback example. +;; Interfacing GNU Scientific Library. +;; +;; struct gsl_function_struct { +;; double (* function) (double x, void * params); +;; void * params; +;; }; +;; typedef struct gsl_function_struct gsl_function ; +;; +;; int gsl_deriv_central (const gsl_function * f, double x, double h, double * result, double * abserr) +;; +;; (ffi-make-callback APPLICATIVE CALL-INTERFACE) creates a C callable +;; function with interface CALL-INTERFACE and returns pointer +;; to the entry point. The function will in turn call APPLICATIVE. +;; + +($define! gsl (ffi-load-library "libgsl.so")) +($define! abi "FFI_DEFAULT_ABI") + +($define! make-gsl-function + ($let + ( ((pointer-size alingment ref set!) (ffi-type-suite "pointer")) + (cif (ffi-make-call-interface abi "double" (list "double" "pointer"))) ) + ($lambda (f) + ($let + ( (gslf (make-blob (* 2 pointer-size)) ) + (aux ($lambda (x params) (f x)))) + (set! (list gslf 0) (ffi-make-callback aux cif)) + gslf)))) + +($define! gsl-deriv-central + ($let + ( (gsl_deriv_central (ffi-make-applicative gsl "gsl_deriv_central" + (ffi-make-call-interface abi "sint" (list "pointer" "double" "double" "pointer" "pointer")))) + ((double-size alingment ref set!) (ffi-type-suite "double"))) + ($lambda (f) + ($let + ((gslf (make-gsl-function f))) + ($lambda (x h) + ($let + ( (result (make-blob double-size)) + (abserr (make-blob double-size))) + (gsl_deriv_central gslf x h result abserr) + (list (ref result) (ref abserr)))))))) + +(display "Testing gsl_deriv_central...") +(newline) + +($define! f + ($lambda (x) + (display (list "callback called with x = " x)) + (newline) + (+ (* 2.0 x x) 3.0))) +($define! df (gsl-deriv-central f)) + +(for-each + ($lambda (x) + ($let* + ( (fx (f x)) + ((dfx abserr) (df x 0.001))) + (for-each + display + (list "x = " x ", f(x) = " fx ", f'(x) = " dfx ", |error| <= " abserr)) + (newline))) + (list -1.0 0.0 1.0 2.0)) diff --git a/src/kgffi.c b/src/kgffi.c @@ -21,9 +21,12 @@ #include "kerror.h" #include "kblob.h" #include "kencapsulation.h" +#include "ktable.h" #include "kghelpers.h" #include "kgencapsulations.h" +#include "kgcombiners.h" +#include "kgcontinuations.h" #include "kgffi.h" /* Set to 0 to ignore aligment errors during direct @@ -48,6 +51,17 @@ typedef struct { ffi_codec_t **acodecs; } ffi_call_interface_t; +typedef struct { + ffi_closure libffi_closure; + klisp_State *K; + Table *table; + size_t index; +} ffi_callback_t; + +#define CB_INDEX_N 0 +#define CB_INDEX_STACK 1 +#define CB_INDEX_FIRST_CALLBACK 2 + static TValue ffi_decode_void(ffi_codec_t *self, klisp_State *K, const void *buf) { UNUSED(self); @@ -56,6 +70,16 @@ static TValue ffi_decode_void(ffi_codec_t *self, klisp_State *K, const void *buf return KINERT; } +static void ffi_encode_void(ffi_codec_t *self, klisp_State *K, TValue v, void *buf) +{ + /* useful only with callbacks */ + UNUSED(self); + UNUSED(K); + UNUSED(buf); + if (!ttisinert(v)) + klispE_throw_simple_with_irritants(K, "only inert can be cast to C void", 1, v); +} + static TValue ffi_decode_sint(ffi_codec_t *self, klisp_State *K, const void *buf) { UNUSED(self); @@ -301,9 +325,9 @@ static void ffi_encode_float(ffi_codec_t *self, klisp_State *K, TValue v, void * } static ffi_codec_t ffi_codecs[] = { - { "void", &ffi_type_void, ffi_decode_void, NULL }, { "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(void), SIMPLE_TYPE(sint), SIMPLE_TYPE(pointer), SIMPLE_TYPE(uint8), @@ -376,7 +400,7 @@ static ffi_codec_t *tv2ffi_codec(klisp_State *K, TValue v) return NULL; } -static inline size_t align(size_t offset, size_t alignment) +inline size_t align(size_t offset, size_t alignment) { assert(alignment > 0); return offset + (alignment - offset % alignment) % alignment; @@ -564,6 +588,285 @@ void ffi_make_applicative(klisp_State *K, TValue *xparams, kapply_cc(K, app); } +static void ffi_callback_push(ffi_callback_t *cb, TValue v) +{ + /* assume v is rooted */ + TValue *s = klispH_setfixint(cb->K, cb->table, CB_INDEX_STACK); + *s = kimm_cons(cb->K, v, *s); +} + +static TValue ffi_callback_pop(ffi_callback_t *cb) +{ + TValue *s = klispH_setfixint(cb->K, cb->table, CB_INDEX_STACK); + TValue v = kcar(*s); + krooted_tvs_push(cb->K, v); + *s = kcdr(*s); + krooted_tvs_pop(cb->K); + return v; +} + +static TValue ffi_callback_guard(ffi_callback_t *cb, klisp_Ofunc fn) +{ + TValue app = kmake_applicative(cb->K, fn, 1, p2tv(cb)); + krooted_tvs_push(cb->K, app); + TValue ls1 = kimm_list(cb->K, 2, cb->K->root_cont, app); + krooted_tvs_push(cb->K, ls1); + TValue ls2 = kimm_list(cb->K, 1, ls1); + krooted_tvs_pop(cb->K); + krooted_tvs_pop(cb->K); + return ls2; +} + +void do_ffi_callback_encode_result(klisp_State *K, TValue *xparams, + TValue obj) +{ + /* + ** xparams[0]: cif + ** xparams[1]: p2tv(libffi return buffer) + */ + ffi_call_interface_t *p = (ffi_call_interface_t *) kblob_buf(kget_enc_val(xparams[0])); + void *ret = pvalue(xparams[1]); + p->rcodec->encode(p->rcodec, K, obj, ret); + kapply_cc(K, KINERT); +} + +void do_ffi_callback_decode_arguments(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + /* + ** xparams[0]: p2tv(ffi_callback_t) + ** xparams[1]: p2tv(libffi return buffer) + ** xparams[2]: p2tv(libffi argument array) + */ + + ffi_callback_t *cb = pvalue(xparams[0]); + void *ret = pvalue(xparams[1]); + void **args = pvalue(xparams[2]); + + /* get the lisp applicative and the call interface + * from the auxilliary table. */ + + const TValue *slot = klispH_setfixint(K, cb->table, cb->index); + TValue app_tv = kcar(*slot); + TValue cif_tv = kcdr(*slot); + assert(ttisapplicative(app_tv)); + assert(ttisencapsulation(cif_tv)); + krooted_tvs_push(K, app_tv); + krooted_tvs_push(K, cif_tv); + ffi_call_interface_t *p = (ffi_call_interface_t *) kblob_buf(kget_enc_val(cif_tv)); + + /* Decode arguments. */ + + TValue tail = KNIL; + for (int i = p->nargs - 1; i >= 0; i--) { + krooted_tvs_push(K, ptree); + TValue arg = p->acodecs[i]->decode(p->acodecs[i], K, args[i]); + krooted_tvs_pop(K); + tail = kimm_cons(K, arg, tail); + } + krooted_tvs_push(K, tail); + + /* Setup continuation for encoding return value. */ + + TValue encoding_cont = kmake_continuation(K, kget_cc(K), do_ffi_callback_encode_result, 2, cif_tv, p2tv(ret)); + kset_cc(K, encoding_cont); + + /* apply the callback applicative */ + + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + + while(ttisapplicative(app_tv)) + app_tv = tv2app(app_tv)->underlying; + ktail_call(K, app_tv, tail, denv); +} + +void do_ffi_callback_return(klisp_State *K, TValue *xparams, TValue obj) +{ + UNUSED(obj); + /* + ** xparams[0]: p2tv(ffi_callback_t) + ** + ** Signal normal return and force the "inner" trampoline + ** loop to exit. + */ + ffi_callback_t *cb = pvalue(xparams[0]); + ffi_callback_push(cb, i2tv(1)); + K->next_func = NULL; +} + +void do_ffi_callback_entry_guard(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(denv); + UNUSED(xparams); + /* The entry guard is invoked only if the user captured + * the continuation under foreign callback and applied + * it later after the foreign callback terminated. + * + * The auxilliary stack (stored in the callback hash table) + * now does not correspond to the actual state of callback + * nesting. + */ + klispE_throw_simple(K, "tried to re-enter continuation under FFI callback"); +} + +void do_ffi_callback_exit_guard(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(denv); + /* + ** xparams[0]: p2tv(ffi_callback_t) + ** + ** Signal abnormal return and force the "inner" trampoline + ** loop to exit to ffi_callback_entry(). The parameter tree + ** will be processed there. + */ + ffi_callback_t *cb = pvalue(xparams[0]); + ffi_callback_push(cb, i2tv(0)); + K->next_func = NULL; +} + +static void ffi_callback_entry(ffi_cif *cif, void *ret, void **args, void *user_data) +{ + ffi_callback_t *cb = (ffi_callback_t *) user_data; + klisp_State *K = cb->K; + + /* save state of the interpreter */ + + volatile jmp_buf saved_error_jb; + memcpy(&saved_error_jb, &K->error_jb, sizeof(K->error_jb)); + ffi_callback_push(cb, K->curr_cont); + + /* Set up continuation for normal return path. */ + + TValue return_cont = kmake_continuation(K, K->curr_cont, do_ffi_callback_return, 1, p2tv(cb)); + krooted_tvs_push(K, return_cont); + kset_cc(K, return_cont); + + /* Do not decode arguments yet. The decoding may fail + * and raise errors. Let klisp core handle all errors + * inside guarded continuation. */ + + TValue app = kmake_applicative(K, do_ffi_callback_decode_arguments, 3, p2tv(cb), p2tv(ret), p2tv(args)); + krooted_tvs_push(K, app); + + TValue entry_guard = ffi_callback_guard(cb, do_ffi_callback_entry_guard); + krooted_tvs_push(K, entry_guard); + TValue exit_guard = ffi_callback_guard(cb, do_ffi_callback_exit_guard); + krooted_tvs_push(K, exit_guard); + + TValue ptree = kimm_list(K, 3, entry_guard, app, exit_guard); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + + guard_dynamic_extent(K, NULL, ptree, K->next_env); + + /* Enter new "inner" trampoline loop. */ + + klispS_run(K); + + /* restore longjump buffer of the outer trampoline loop */ + + memcpy(&K->error_jb, &saved_error_jb, sizeof(K->error_jb)); + + /* Now, the "inner" trampoline loop exited. The exit + was forced by return_cont or exit_guard. */ + + if (ivalue(ffi_callback_pop(cb))) { + /* Normal return - reinstall old continuation. It will be + * used after the foreign call which originally called + * this callback eventually returns. */ + kset_cc(K, ffi_callback_pop(cb)); + } else { + /* Abnormal return - throw away the old continuation + ** and longjump back in the "outer" trampoline loop. + ** Longjump unwinds the stack space used by the foreign + ** call which originally called this callback. After + ** that the interpreter state will look like normal + ** normal return from the exit guard. + */ + (void) ffi_callback_pop(cb); + klispS_apply_cc(K, kcar(K->next_value)); + longjmp(K->error_jb, 1); + } +} + + +void ffi_make_callback(klisp_State *K, TValue *xparams, + TValue ptree, TValue denv) +{ + UNUSED(denv); + /* + ** xparams[0]: encapsulation key denoting call interface + ** xparams[1]: callback data table + */ + + bind_2tp(K, ptree, + "applicative", ttisapplicative, app_tv, + "call interface", ttisencapsulation, cif_tv); + if (!kis_encapsulation_type(cif_tv, xparams[0])) { + klispE_throw_simple(K, "second argument shall be call interface"); + return; + } + ffi_call_interface_t *p = (ffi_call_interface_t *) kblob_buf(kget_enc_val(cif_tv)); + TValue cb_tab = xparams[1]; + + /* Allocate memory for libffi closure. */ + + void *code; + ffi_callback_t *cb = ffi_closure_alloc(sizeof(ffi_callback_t), &code); + + /* Get the index of this callback in the callback table. */ + + TValue *n_tv = klispH_setfixint(K, tv2table(cb_tab), 0); + assert(n_tv != &kfree); + int32_t new_index = ivalue(*n_tv); + *n_tv = i2tv(new_index + 1); + + /* Prepare the C part of callback data */ + + cb->K = K; + cb->table = tv2table(xparams[1]); + cb->index = new_index; + + /* TODO: The closure leaks. Should be finalized. */ + + /* Prepare the lisp part of callback data */ + + krooted_tvs_push(K, cb_tab); + krooted_tvs_push(K, app_tv); + krooted_tvs_push(K, cif_tv); + + TValue item_tv = kimm_cons(K, app_tv, cif_tv); + krooted_tvs_push(K, item_tv); + + TValue *slot = klispH_setfixint(K, tv2table(cb_tab), new_index); + *slot = item_tv; + + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + krooted_tvs_pop(K); + + /* Initialize callback. */ + + ffi_status status = ffi_prep_closure_loc(&cb->libffi_closure, &p->cif, ffi_callback_entry, cb, code); + if (status != FFI_OK) { + ffi_closure_free(cb); + klispE_throw_simple(K, "unknown error in ffi_prep_closure_loc"); + return; + } + + /* return the libffi closure entry point */ + + TValue funptr_tv = p2tv(code); + kapply_cc(K, funptr_tv); +} + static uint8_t * ffi_memory_location(klisp_State *K, bool allow_nesting, TValue v, bool mutable, size_t size) { @@ -720,9 +1023,21 @@ void kinit_ffi_ground_env(klisp_State *K) TValue dll_key = kmake_encapsulation_key(K); TValue cif_key = kmake_encapsulation_key(K); + /* TODO: should be rooted */ + + /* create table for callback data */ + TValue cb_tab = klispH_new(K, 0, 64, K_FLAG_WEAK_NOTHING); + + TValue *v; + v = klispH_setfixint(K, tv2table(cb_tab), CB_INDEX_N); + *v = i2tv(CB_INDEX_FIRST_CALLBACK); + v = klispH_setfixint(K, tv2table(cb_tab), CB_INDEX_STACK); + *v = KNIL; + 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-make-callback", ffi_make_callback, 2, cif_key, cb_tab); 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);