unix/modffi: Use a union for passing/returning FFI values.

This fixes a bug where double arguments on a 32-bit architecture would not
be passed correctly because they only had 4 bytes of storage (not 8).  It
also fixes a compiler warning/error in return_ffi_value on certian
architectures: array subscript 'double[0]' is partly outside array bounds
of 'ffi_arg[1]' {aka 'long unsigned int[1]'}.

Fixes issue #7064.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George 2021-04-14 15:37:33 +10:00
parent 8172c2e9c5
commit 9e29217c73
3 changed files with 50 additions and 45 deletions

View file

@ -56,6 +56,13 @@
* but may be later. * but may be later.
*/ */
// This union is large enough to hold any supported argument/return value.
typedef union _ffi_union_t {
ffi_arg ffi;
float flt;
double dbl;
} ffi_union_t;
typedef struct _mp_obj_opaque_t { typedef struct _mp_obj_opaque_t {
mp_obj_base_t base; mp_obj_base_t base;
void *val; void *val;
@ -151,10 +158,10 @@ STATIC ffi_type *get_ffi_type(mp_obj_t o_in) {
mp_raise_TypeError(MP_ERROR_TEXT("unknown type")); mp_raise_TypeError(MP_ERROR_TEXT("unknown type"));
} }
STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { STATIC mp_obj_t return_ffi_value(ffi_union_t *val, char type) {
switch (type) { switch (type) {
case 's': { case 's': {
const char *s = (const char *)(intptr_t)val; const char *s = (const char *)(intptr_t)val->ffi;
if (!s) { if (!s) {
return mp_const_none; return mp_const_none;
} }
@ -164,20 +171,16 @@ STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) {
return mp_const_none; return mp_const_none;
#if MICROPY_PY_BUILTINS_FLOAT #if MICROPY_PY_BUILTINS_FLOAT
case 'f': { case 'f': {
union { ffi_arg ffi; return mp_obj_new_float_from_f(val->flt);
float flt;
} val_union = { .ffi = val };
return mp_obj_new_float_from_f(val_union.flt);
} }
case 'd': { case 'd': {
double *p = (double *)&val; return mp_obj_new_float_from_d(val->dbl);
return mp_obj_new_float_from_d(*p);
} }
#endif #endif
case 'O': case 'O':
return (mp_obj_t)(intptr_t)val; return (mp_obj_t)(intptr_t)val->ffi;
default: default:
return mp_obj_new_int(val); return mp_obj_new_int(val->ffi);
} }
} }
@ -368,28 +371,26 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
assert(n_kw == 0); assert(n_kw == 0);
assert(n_args == self->cif.nargs); assert(n_args == self->cif.nargs);
ffi_arg values[n_args]; ffi_union_t values[n_args];
void *valueptrs[n_args]; void *valueptrs[n_args];
const char *argtype = self->argtypes; const char *argtype = self->argtypes;
for (uint i = 0; i < n_args; i++, argtype++) { for (uint i = 0; i < n_args; i++, argtype++) {
mp_obj_t a = args[i]; mp_obj_t a = args[i];
if (*argtype == 'O') { if (*argtype == 'O') {
values[i] = (ffi_arg)(intptr_t)a; values[i].ffi = (ffi_arg)(intptr_t)a;
#if MICROPY_PY_BUILTINS_FLOAT #if MICROPY_PY_BUILTINS_FLOAT
} else if (*argtype == 'f') { } else if (*argtype == 'f') {
float *p = (float *)&values[i]; values[i].flt = mp_obj_get_float_to_f(a);
*p = mp_obj_get_float_to_f(a);
} else if (*argtype == 'd') { } else if (*argtype == 'd') {
double *p = (double *)&values[i]; values[i].dbl = mp_obj_get_float_to_d(a);
*p = mp_obj_get_float_to_d(a);
#endif #endif
} else if (a == mp_const_none) { } else if (a == mp_const_none) {
values[i] = 0; values[i].ffi = 0;
} else if (mp_obj_is_int(a)) { } else if (mp_obj_is_int(a)) {
values[i] = mp_obj_int_get_truncated(a); values[i].ffi = mp_obj_int_get_truncated(a);
} else if (mp_obj_is_str(a)) { } else if (mp_obj_is_str(a)) {
const char *s = mp_obj_str_get_str(a); const char *s = mp_obj_str_get_str(a);
values[i] = (ffi_arg)(intptr_t)s; values[i].ffi = (ffi_arg)(intptr_t)s;
} else if (((mp_obj_base_t *)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { } else if (((mp_obj_base_t *)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) {
mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(a); mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(a);
mp_buffer_info_t bufinfo; mp_buffer_info_t bufinfo;
@ -397,32 +398,19 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
if (ret != 0) { if (ret != 0) {
goto error; goto error;
} }
values[i] = (ffi_arg)(intptr_t)bufinfo.buf; values[i].ffi = (ffi_arg)(intptr_t)bufinfo.buf;
} else if (mp_obj_is_type(a, &fficallback_type)) { } else if (mp_obj_is_type(a, &fficallback_type)) {
mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a);
values[i] = (ffi_arg)(intptr_t)p->func; values[i].ffi = (ffi_arg)(intptr_t)p->func;
} else { } else {
goto error; goto error;
} }
valueptrs[i] = &values[i]; valueptrs[i] = &values[i];
} }
// If ffi_arg is not big enough to hold a double, then we must pass along a ffi_union_t retval;
// pointer to a memory location of the correct size. ffi_call(&self->cif, self->func, &retval, valueptrs);
// TODO check if this needs to be done for other types which don't fit into return return_ffi_value(&retval, self->rettype);
// ffi_arg.
#if MICROPY_PY_BUILTINS_FLOAT
if (sizeof(ffi_arg) == 4 && self->rettype == 'd') {
double retval;
ffi_call(&self->cif, self->func, &retval, valueptrs);
return mp_obj_new_float_from_d(retval);
} else
#endif
{
ffi_arg retval;
ffi_call(&self->cif, self->func, &retval, valueptrs);
return return_ffi_value(retval, self->rettype);
}
error: error:
mp_raise_TypeError(MP_ERROR_TEXT("don't know how to pass object to native function")); mp_raise_TypeError(MP_ERROR_TEXT("don't know how to pass object to native function"));

View file

@ -35,6 +35,15 @@ print("%.6f" % strtod("1.23", None))
# test passing double and float args # test passing double and float args
libm = ffi_open(("libm.so", "libm.so.6", "libc.so.0", "libc.so.6", "libc.dylib")) libm = ffi_open(("libm.so", "libm.so.6", "libc.so.0", "libc.so.6", "libc.dylib"))
tgamma = libm.func("d", "tgamma", "d") tgamma = libm.func("d", "tgamma", "d")
for fun in (tgamma,): for fun_name in ("tgamma",):
fun = globals()[fun_name]
for val in (0.5, 1, 1.0, 1.5, 4, 4.0): for val in (0.5, 1, 1.0, 1.5, 4, 4.0):
print("%.6f" % fun(val)) print(fun_name, "%.5f" % fun(val))
# test passing 2x float/double args
powf = libm.func("f", "powf", "ff")
pow = libm.func("d", "pow", "dd")
for fun_name in ("powf", "pow"):
fun = globals()[fun_name]
for args in ((0, 1), (1, 0), (2, 0.5), (3, 4)):
print(fun_name, "%.5f" % fun(*args))

View file

@ -1,8 +1,16 @@
1.230000 1.230000
1.230000 1.230000
1.772454 tgamma 1.77245
1.000000 tgamma 1.00000
1.000000 tgamma 1.00000
0.886227 tgamma 0.88623
6.000000 tgamma 6.00000
6.000000 tgamma 6.00000
powf 0.00000
powf 1.00000
powf 1.41421
powf 81.00000
pow 0.00000
pow 1.00000
pow 1.41421
pow 81.00000