diff --git a/examples/unix/ffi_example.py b/examples/unix/ffi_example.py new file mode 100644 index 000000000..0ac12203e --- /dev/null +++ b/examples/unix/ffi_example.py @@ -0,0 +1,38 @@ +import ffi + +libc = ffi.open("libc.so.6") +print("libc:", libc) +print() + +# Declare few functions +perror = libc.func("v", "perror", ["s"]) +time = libc.func("i", "time", "p") +open = libc.func("i", "open", ["s", "i"]) +qsort = libc.func("v", "qsort", "piip") +# And one variable +errno = libc.var("i", "errno") + +print("time:", time) +print("UNIX time is:", time(None)) +print() + +perror("ffi before error") +open("somethingnonexistent__", 0) +print(errno) +perror("ffi after error") +print() + +def cmp(pa, pb): + a = ffi.as_bytearray(pa, 1) + b = ffi.as_bytearray(pb, 1) + print("cmp:", a, b) + return a[0] - b[0] + +cmp_c = ffi.callback("i", cmp, "pp") +print("callback:", cmp_c) + +# TODO: violates Py semantics, pass bytearray +s = "foobar" +print("org string:", s) +qsort(s, len(s), 1, cmp_c) +print("qsort'ed:", s) diff --git a/unix/Makefile b/unix/Makefile index 75825b7c4..795faeaa8 100644 --- a/unix/Makefile +++ b/unix/Makefile @@ -11,7 +11,7 @@ include ../py/py.mk # compiler settings CFLAGS = -I. -I$(PY_SRC) -Wall -Werror -ansi -std=gnu99 -DUNIX -LDFLAGS = -lm +LDFLAGS = -lm -ldl -lffi # Debugging/Optimization ifdef DEBUG @@ -25,6 +25,7 @@ SRC_C = \ main.c \ file.c \ socket.c \ + ffi.c \ OBJ = $(PY_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) LIB = -lreadline diff --git a/unix/ffi.c b/unix/ffi.c new file mode 100644 index 000000000..534eefce7 --- /dev/null +++ b/unix/ffi.c @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include + +#include "nlr.h" +#include "misc.h" +#include "mpconfig.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" + +typedef struct _mp_obj_opaque_t { + mp_obj_base_t base; + void *val; +} mp_obj_opaque_t; + +typedef struct _mp_obj_ffimod_t { + mp_obj_base_t base; + void *handle; +} mp_obj_ffimod_t; + +typedef struct _mp_obj_ffivar_t { + mp_obj_base_t base; + void *var; + char type; +// ffi_type *type; +} mp_obj_ffivar_t; + +typedef struct _mp_obj_ffifunc_t { + mp_obj_base_t base; + void *func; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_ffifunc_t; + +typedef struct _mp_obj_fficallback_t { + mp_obj_base_t base; + void *func; + ffi_closure *clo; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_fficallback_t; + +static const mp_obj_type_t opaque_type; +static const mp_obj_type_t ffimod_type; +static const mp_obj_type_t ffifunc_type; +static const mp_obj_type_t fficallback_type; +static const mp_obj_type_t ffivar_type; + +static ffi_type *char2ffi_type(char c) +{ + switch (c) { + case 'b': return &ffi_type_schar; + case 'B': return &ffi_type_uchar; + case 'i': return &ffi_type_sint; + case 'I': return &ffi_type_uint; + case 'l': return &ffi_type_slong; + case 'L': return &ffi_type_ulong; + case 'p': + case 's': return &ffi_type_pointer; + case 'v': return &ffi_type_void; + default: return NULL; + } +} + +static ffi_type *get_ffi_type(mp_obj_t o_in) +{ + if (MP_OBJ_IS_STR(o_in)) { + uint len; + const byte *s = mp_obj_str_get_data(o_in, &len); + ffi_type *t = char2ffi_type(*s); + if (t != NULL) { + return t; + } + } + // TODO: Support actual libffi type objects + + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Unknown type")); +} + +static mp_obj_t return_ffi_value(int val, char type) +{ + switch (type) { + case 's': { + const char *s = (const char *)val; + return mp_obj_new_str((const byte *)s, strlen(s), false); + } + case 'v': + return mp_const_none; + default: + return mp_obj_new_int(val); + } +} + +// FFI module + +static void ffimod_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_ffimod_t *self = self_in; + print(env, "", self->handle); +} + +static mp_obj_t ffimod_close(mp_obj_t self_in) { + mp_obj_ffimod_t *self = self_in; + dlclose(self->handle); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(ffimod_close_obj, ffimod_close); + +static mp_obj_t ffimod_func(uint n_args, const mp_obj_t *args) { + mp_obj_ffimod_t *self = args[0]; + const char *rettype = mp_obj_str_get_str(args[1]); + const char *symname = mp_obj_str_get_str(args[2]); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno)); + } + int nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(args[3])); + mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type*, nparams); + o->base.type = &ffifunc_type; + + o->func = sym; + o->rettype = *rettype; + + mp_obj_t iterable = rt_getiter(args[3]); + mp_obj_t item; + int i = 0; + while ((item = rt_iternext(iterable)) != mp_const_stop_iteration) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Error in ffi_prep_cif")); + } + + return o; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ffimod_func_obj, 4, 4, ffimod_func); + +static void call_py_func(ffi_cif *cif, void *ret, void** args, mp_obj_t func) { + mp_obj_t pyargs[cif->nargs]; + for (int i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(int*)args[i]); + } + mp_obj_t res = rt_call_function_n_kw(func, cif->nargs, 0, pyargs); + + *(ffi_arg*)ret = mp_obj_int_get(res); +} + +static mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + + int nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); + mp_obj_fficallback_t *o = m_new_obj_var(mp_obj_fficallback_t, ffi_type*, nparams); + o->base.type = &fficallback_type; + + o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); + + o->rettype = *rettype; + + mp_obj_t iterable = rt_getiter(paramtypes_in); + mp_obj_t item; + int i = 0; + while ((item = rt_iternext(iterable)) != mp_const_stop_iteration) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Error in ffi_prep_cif")); + } + + res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, func_in, o->func); + if (res != FFI_OK) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "ffi_prep_closure_loc")); + } + + return o; +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, 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 = self_in; + const char *rettype = mp_obj_str_get_str(vartype_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno)); + } + mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t); + o->base.type = &ffivar_type; + + o->var = sym; + o->type = *rettype; + return o; +} +MP_DEFINE_CONST_FUN_OBJ_3(ffimod_var_obj, ffimod_var); + +static mp_obj_t ffimod_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { + const char *fname = mp_obj_str_get_str(args[0]); + void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL); + + if (mod == NULL) { + nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno)); + } + mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t); + o->base.type = type_in; + o->handle = mod; + return o; +} + +static const mp_method_t ffimod_type_methods[] = { + { "func", &ffimod_func_obj }, + { "var", &ffimod_var_obj }, + { "close", &ffimod_close_obj }, + { NULL, NULL }, +}; + +static const mp_obj_type_t ffimod_type = { + { &mp_const_type }, + "ffimod", + .print = ffimod_print, + .make_new = ffimod_make_new, + .methods = ffimod_type_methods, +}; + +// FFI function + +static void ffifunc_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_ffifunc_t *self = self_in; + print(env, "", self->func); +} + +mp_obj_t ffifunc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) { + mp_obj_ffifunc_t *self = self_in; + assert(n_kw == 0); + assert(n_args == self->cif.nargs); + + int values[n_args]; + void *valueptrs[n_args]; + int i; + for (i = 0; i < n_args; i++) { + mp_obj_t a = args[i]; + if (a == mp_const_none) { + values[i] = 0; + } else if (MP_OBJ_IS_INT(a)) { + values[i] = mp_obj_int_get(a); + } else if (MP_OBJ_IS_STR(a) || MP_OBJ_IS_TYPE(a, &bytes_type)) { + const char *s = mp_obj_str_get_str(a); + values[i] = (int)s; + } else if (MP_OBJ_IS_TYPE(a, &fficallback_type)) { + mp_obj_fficallback_t *p = a; + values[i] = (int)p->func; + } else { + assert(0); + } + valueptrs[i] = &values[i]; + } + + int retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(retval, self->rettype); +} + +static const mp_obj_type_t ffifunc_type = { + { &mp_const_type }, + "ffifunc", + .print = ffifunc_print, + .call = ffifunc_call, +}; + +// FFI callback for Python function + +static void fficallback_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_fficallback_t *self = self_in; + print(env, "", self->func); +} + +static const mp_obj_type_t fficallback_type = { + { &mp_const_type }, + "fficallback", + .print = fficallback_print, +}; + +// FFI variable + +static void ffivar_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_ffivar_t *self = self_in; + print(env, "", self->var, *(int*)self->var); +} + +static const mp_obj_type_t ffivar_type = { + { &mp_const_type }, + "ffivar", + .print = ffivar_print, +}; + +// Generic opaque storage object + +static const mp_obj_type_t opaque_type = { + { &mp_const_type }, + "opaqueval", +// .print = opaque_print, +}; + + +mp_obj_t mod_ffi_open(uint n_args, const mp_obj_t *args) { + return ffimod_make_new((mp_obj_t)&ffimod_type, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ffi_open_obj, 1, 2, mod_ffi_open); + +mp_obj_t mod_ffi_as_bytearray(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get(size), (void*)mp_obj_int_get(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_ffi_as_bytearray_obj, mod_ffi_as_bytearray); + + +void ffi_init() { + mp_obj_t m = mp_obj_new_module(QSTR_FROM_STR_STATIC("ffi")); + rt_store_attr(m, MP_QSTR_open, (mp_obj_t)&mod_ffi_open_obj); + rt_store_attr(m, QSTR_FROM_STR_STATIC("callback"), (mp_obj_t)&mod_ffi_callback_obj); + // there would be as_bytes, but bytes currently is value, not reference type! + rt_store_attr(m, QSTR_FROM_STR_STATIC("as_bytearray"), (mp_obj_t)&mod_ffi_as_bytearray_obj); +} diff --git a/unix/main.c b/unix/main.c index 212bbe877..69bac044f 100644 --- a/unix/main.c +++ b/unix/main.c @@ -24,6 +24,7 @@ extern const mp_obj_fun_native_t mp_builtin_open_obj; void file_init(); void rawsocket_init(); +void ffi_init(); static void execute_from_lexer(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, bool is_repl) { if (lex == NULL) { @@ -227,6 +228,7 @@ int main(int argc, char **argv) { file_init(); rawsocket_init(); + ffi_init(); // Here is some example code to create a class and instance of that class. // First is the Python, then the C code.