From cb332ddae8075001a1a9e6af0fa8aa191602dee8 Mon Sep 17 00:00:00 2001 From: Amir Gonnen Date: Tue, 15 Jun 2021 09:02:50 +0300 Subject: [PATCH] unix/modffi: Add option to lock GC in callback, and cfun access. Add an optional 'lock' kwarg to callback that locks GC and scheduler. This allows the callback to be invoked asynchronously in 'interrupt context', for example as a signal handler. Also add the 'cfun' member function to callback, that allows retrieving the C callback function address. This is needed when the callback should be set to a struct field. See related #7373. Signed-off-by: Amir Gonnen --- ports/unix/modffi.c | 76 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/ports/unix/modffi.c b/ports/unix/modffi.c index 12a33619e0..0ff37a99f5 100644 --- a/ports/unix/modffi.c +++ b/ports/unix/modffi.c @@ -36,6 +36,7 @@ #include "py/binary.h" #include "py/mperrno.h" #include "py/objint.h" +#include "py/gc.h" /* * modffi uses character codes to encode a value type, based on "struct" @@ -100,6 +101,7 @@ typedef struct _mp_obj_fficallback_t { void *func; ffi_closure *clo; char rettype; + mp_obj_t pyfunc; ffi_cif cif; ffi_type *params[]; } mp_obj_fficallback_t; @@ -266,19 +268,69 @@ STATIC mp_obj_t mod_ffi_func(mp_obj_t rettype, mp_obj_t addr_in, mp_obj_t argtyp } MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_func_obj, mod_ffi_func); -STATIC void call_py_func(ffi_cif *cif, void *ret, void **args, void *func) { +STATIC void call_py_func(ffi_cif *cif, void *ret, void **args, void *user_data) { mp_obj_t pyargs[cif->nargs]; + mp_obj_fficallback_t *o = user_data; + mp_obj_t pyfunc = o->pyfunc; + for (uint i = 0; i < cif->nargs; i++) { pyargs[i] = mp_obj_new_int(*(mp_int_t *)args[i]); } - mp_obj_t res = mp_call_function_n_kw(MP_OBJ_FROM_PTR(func), cif->nargs, 0, pyargs); + mp_obj_t res = mp_call_function_n_kw(pyfunc, cif->nargs, 0, pyargs); if (res != mp_const_none) { *(ffi_arg *)ret = mp_obj_int_get_truncated(res); } } -STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { +STATIC void call_py_func_with_lock(ffi_cif *cif, void *ret, void **args, void *user_data) { + mp_obj_t pyargs[cif->nargs]; + mp_obj_fficallback_t *o = user_data; + mp_obj_t pyfunc = o->pyfunc; + nlr_buf_t nlr; + + #if MICROPY_ENABLE_SCHEDULER + mp_sched_lock(); + #endif + gc_lock(); + + if (nlr_push(&nlr) == 0) { + for (uint i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(mp_int_t *)args[i]); + } + mp_obj_t res = mp_call_function_n_kw(pyfunc, cif->nargs, 0, pyargs); + + if (res != mp_const_none) { + *(ffi_arg *)ret = mp_obj_int_get_truncated(res); + } + nlr_pop(); + } else { + // Uncaught exception + mp_printf(MICROPY_ERROR_PRINTER, "Uncaught exception in FFI callback\n"); + mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + gc_unlock(); + #if MICROPY_ENABLE_SCHEDULER + mp_sched_unlock(); + #endif +} + +STATIC mp_obj_t mod_ffi_callback(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // first 3 args are positional: retttype, func, paramtypes. + mp_obj_t rettype_in = pos_args[0]; + mp_obj_t func_in = pos_args[1]; + mp_obj_t paramtypes_in = pos_args[2]; + + // arg parsing is used only for additional kwargs + enum { ARG_lock }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_lock, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 3, pos_args + 3, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + bool lock_in = args[ARG_lock].u_bool; + const char *rettype = mp_obj_str_get_str(rettype_in); mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); @@ -288,6 +340,7 @@ STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); o->rettype = *rettype; + o->pyfunc = func_in; mp_obj_iter_buf_t iter_buf; mp_obj_t iterable = mp_getiter(paramtypes_in, &iter_buf); @@ -302,14 +355,15 @@ STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t mp_raise_ValueError(MP_ERROR_TEXT("error in ffi_prep_cif")); } - res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, MP_OBJ_TO_PTR(func_in), o->func); + res = ffi_prep_closure_loc(o->clo, &o->cif, + lock_in? call_py_func_with_lock: call_py_func, o, o->func); if (res != FFI_OK) { mp_raise_ValueError(MP_ERROR_TEXT("ffi_prep_closure_loc")); } return MP_OBJ_FROM_PTR(o); } -MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, mod_ffi_callback); +MP_DEFINE_CONST_FUN_OBJ_KW(mod_ffi_callback_obj, 3, mod_ffi_callback); STATIC mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symname_in) { mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); @@ -493,10 +547,22 @@ STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_prin mp_printf(print, "", self->func); } +STATIC mp_obj_t fficallback_cfun(mp_obj_t self_in) { + mp_obj_fficallback_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_ull((uintptr_t)self->func); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(fficallback_cfun_obj, fficallback_cfun); + +STATIC const mp_rom_map_elem_t fficallback_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_cfun), MP_ROM_PTR(&fficallback_cfun_obj) } +}; +STATIC MP_DEFINE_CONST_DICT(fficallback_locals_dict, fficallback_locals_dict_table); + STATIC const mp_obj_type_t fficallback_type = { { &mp_type_type }, .name = MP_QSTR_fficallback, .print = fficallback_print, + .locals_dict = (mp_obj_dict_t *)&fficallback_locals_dict }; // FFI variable