Skip to content

Commit

Permalink
unix/modffi: Add option to lock GC in callback, and cfun access.
Browse files Browse the repository at this point in the history
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 <amirgonnen@gmail.com>
  • Loading branch information
amirgon authored and dpgeorge committed Jun 24, 2021
1 parent 413f34c commit cb332dd
Showing 1 changed file with 71 additions and 5 deletions.
76 changes: 71 additions & 5 deletions ports/unix/modffi.c
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -493,10 +547,22 @@ STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_prin
mp_printf(print, "<fficallback %p>", 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
Expand Down

0 comments on commit cb332dd

Please sign in to comment.