Author: Alex Gaynor <alex.gay...@gmail.com> Branch: Changeset: r77594:d93ad0d33f48 Date: 2015-05-26 14:05 -0400 http://bitbucket.org/pypy/pypy/changeset/d93ad0d33f48/
Log: merged upstream diff --git a/lib_pypy/cffi.egg-info/PKG-INFO b/lib_pypy/cffi.egg-info/PKG-INFO --- a/lib_pypy/cffi.egg-info/PKG-INFO +++ b/lib_pypy/cffi.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: cffi -Version: 1.0.3 +Version: 1.0.4 Summary: Foreign Function Interface for Python calling C code. Home-page: http://cffi.readthedocs.org Author: Armin Rigo, Maciej Fijalkowski diff --git a/lib_pypy/cffi/__init__.py b/lib_pypy/cffi/__init__.py --- a/lib_pypy/cffi/__init__.py +++ b/lib_pypy/cffi/__init__.py @@ -4,8 +4,8 @@ from .api import FFI, CDefError, FFIError from .ffiplatform import VerificationError, VerificationMissing -__version__ = "1.0.3" -__version_info__ = (1, 0, 3) +__version__ = "1.0.4" +__version_info__ = (1, 0, 4) # The verifier module file names are based on the CRC32 of a string that # contains the following version number. It may be older than __version__ diff --git a/lib_pypy/cffi/_cffi_include.h b/lib_pypy/cffi/_cffi_include.h --- a/lib_pypy/cffi/_cffi_include.h +++ b/lib_pypy/cffi/_cffi_include.h @@ -51,6 +51,11 @@ # endif #endif +#ifdef __GNUC__ +# define _CFFI_UNUSED_FN __attribute__((unused)) +#else +# define _CFFI_UNUSED_FN /* nothing */ +#endif /********** CPython-specific section **********/ #ifndef PYPY_VERSION @@ -82,7 +87,8 @@ PyLong_FromLongLong((long long)x))) #define _cffi_to_c_int(o, type) \ - (sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ : (type)_cffi_to_c_i8(o)) : \ sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ : (type)_cffi_to_c_i16(o)) : \ @@ -90,7 +96,7 @@ : (type)_cffi_to_c_i32(o)) : \ sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ : (type)_cffi_to_c_i64(o)) : \ - (Py_FatalError("unsupported size for type " #type), (type)0)) + (Py_FatalError("unsupported size for type " #type), (type)0))) #define _cffi_to_c_i8 \ ((int(*)(PyObject *))_cffi_exports[1]) @@ -181,6 +187,20 @@ return NULL; } +_CFFI_UNUSED_FN +static PyObject **_cffi_unpack_args(PyObject *args_tuple, Py_ssize_t expected, + const char *fnname) +{ + if (PyTuple_GET_SIZE(args_tuple) != expected) { + PyErr_Format(PyExc_TypeError, + "%.150s() takes exactly %zd arguments (%zd given)", + fnname, expected, PyTuple_GET_SIZE(args_tuple)); + return NULL; + } + return &PyTuple_GET_ITEM(args_tuple, 0); /* pointer to the first item, + the others follow */ +} + #endif /********** end CPython-specific section **********/ @@ -200,12 +220,6 @@ ((got_nonpos) == (expected <= 0) && \ (got) == (unsigned long long)expected) -#ifdef __GNUC__ -# define _CFFI_UNUSED_FN __attribute__((unused)) -#else -# define _CFFI_UNUSED_FN /* nothing */ -#endif - #ifdef __cplusplus } #endif diff --git a/lib_pypy/cffi/parse_c_type.h b/lib_pypy/cffi/parse_c_type.h --- a/lib_pypy/cffi/parse_c_type.h +++ b/lib_pypy/cffi/parse_c_type.h @@ -83,7 +83,8 @@ const char *name; void *address; _cffi_opcode_t type_op; - size_t size; // 0 if unknown + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function }; struct _cffi_getconst_s { diff --git a/lib_pypy/cffi/recompiler.py b/lib_pypy/cffi/recompiler.py --- a/lib_pypy/cffi/recompiler.py +++ b/lib_pypy/cffi/recompiler.py @@ -19,7 +19,7 @@ self.check_value = check_value def as_c_expr(self): - return ' { "%s", (void *)%s, %s, %s },' % ( + return ' { "%s", (void *)%s, %s, (void *)%s },' % ( self.name, self.address, self.type_op.as_c_expr(), self.size) def as_python_expr(self): @@ -386,7 +386,7 @@ prnt('# ifdef _MSC_VER') prnt(' PyMODINIT_FUNC') prnt('# if PY_MAJOR_VERSION >= 3') - prnt(' PyInit_%s(void) { return -1; }' % (base_module_name,)) + prnt(' PyInit_%s(void) { return NULL; }' % (base_module_name,)) prnt('# else') prnt(' init%s(void) { }' % (base_module_name,)) prnt('# endif') @@ -602,6 +602,26 @@ else: argname = 'args' # + # ------------------------------ + # the 'd' version of the function, only for addressof(lib, 'func') + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arguments.append(type.get_c_name(' x%d' % i, context)) + call_arguments.append('x%d' % i) + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '_cffi_d_%s(%s)' % (name, repr_arguments) + prnt('static %s' % (tp.result.get_c_name(name_and_arguments),)) + prnt('{') + call_arguments = ', '.join(call_arguments) + result_code = 'return ' + if isinstance(tp.result, model.VoidType): + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, call_arguments)) + prnt('}') + # prnt('#ifndef PYPY_VERSION') # ------------------------------ # prnt('static PyObject *') @@ -632,10 +652,13 @@ rng = range(len(tp.args)) for i in rng: prnt(' PyObject *arg%d;' % i) + prnt(' PyObject **aa;') prnt() - prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( - 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' aa = _cffi_unpack_args(args, %d, "%s");' % (len(rng), name)) + prnt(' if (aa == NULL)') prnt(' return NULL;') + for i in rng: + prnt(' arg%d = aa[%d];' % (i, i)) prnt() # for i, type in enumerate(tp.args): @@ -668,6 +691,7 @@ # the PyPy version: need to replace struct/union arguments with # pointers, and if the result is a struct/union, insert a first # arg that is a pointer to the result. + difference = False arguments = [] call_arguments = [] context = 'argument of %s' % name @@ -675,6 +699,7 @@ indirection = '' if isinstance(type, model.StructOrUnion): indirection = '*' + difference = True arg = type.get_c_name(' %sx%d' % (indirection, i), context) arguments.append(arg) call_arguments.append('%sx%d' % (indirection, i)) @@ -686,18 +711,22 @@ tp_result = model.void_type result_decl = None result_code = '*result = ' - repr_arguments = ', '.join(arguments) - repr_arguments = repr_arguments or 'void' - name_and_arguments = '_cffi_f_%s(%s)' % (name, repr_arguments) - prnt('static %s' % (tp_result.get_c_name(name_and_arguments),)) - prnt('{') - if result_decl: - prnt(result_decl) - call_arguments = ', '.join(call_arguments) - prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) - if result_decl: - prnt(' return result;') - prnt('}') + difference = True + if difference: + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '_cffi_f_%s(%s)' % (name, repr_arguments) + prnt('static %s' % (tp_result.get_c_name(name_and_arguments),)) + prnt('{') + if result_decl: + prnt(result_decl) + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + if result_decl: + prnt(' return result;') + prnt('}') + else: + prnt('# define _cffi_f_%s _cffi_d_%s' % (name, name)) # prnt('#endif') # ------------------------------ prnt() @@ -718,7 +747,8 @@ meth_kind = OP_CPYTHON_BLTN_V # 'METH_VARARGS' self._lsts["global"].append( GlobalExpr(name, '_cffi_f_%s' % name, - CffiOp(meth_kind, type_index), check_value=0)) + CffiOp(meth_kind, type_index), check_value=0, + size='_cffi_d_%s' % name)) # ---------- # named structs or unions diff --git a/lib_pypy/cffi/vengine_cpy.py b/lib_pypy/cffi/vengine_cpy.py --- a/lib_pypy/cffi/vengine_cpy.py +++ b/lib_pypy/cffi/vengine_cpy.py @@ -886,7 +886,8 @@ PyLong_FromLongLong((long long)x))) #define _cffi_to_c_int(o, type) \ - (sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ : (type)_cffi_to_c_i8(o)) : \ sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ : (type)_cffi_to_c_i16(o)) : \ @@ -894,7 +895,7 @@ : (type)_cffi_to_c_i32(o)) : \ sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ : (type)_cffi_to_c_i64(o)) : \ - (Py_FatalError("unsupported size for type " #type), (type)0)) + (Py_FatalError("unsupported size for type " #type), (type)0))) #define _cffi_to_c_i8 \ ((int(*)(PyObject *))_cffi_exports[1]) diff --git a/pypy/module/_cffi_backend/__init__.py b/pypy/module/_cffi_backend/__init__.py --- a/pypy/module/_cffi_backend/__init__.py +++ b/pypy/module/_cffi_backend/__init__.py @@ -2,7 +2,7 @@ from pypy.interpreter.mixedmodule import MixedModule from rpython.rlib import rdynload -VERSION = "1.0.3" +VERSION = "1.0.4" class Module(MixedModule): diff --git a/pypy/module/_cffi_backend/ffi_obj.py b/pypy/module/_cffi_backend/ffi_obj.py --- a/pypy/module/_cffi_backend/ffi_obj.py +++ b/pypy/module/_cffi_backend/ffi_obj.py @@ -10,7 +10,7 @@ from pypy.module._cffi_backend import parse_c_type, realize_c_type from pypy.module._cffi_backend import newtype, cerrno, ccallback, ctypearray from pypy.module._cffi_backend import ctypestruct, ctypeptr, handle -from pypy.module._cffi_backend import cbuffer, func, cgc, structwrapper +from pypy.module._cffi_backend import cbuffer, func, cgc, wrapper from pypy.module._cffi_backend import cffi_opcode from pypy.module._cffi_backend.ctypeobj import W_CType from pypy.module._cffi_backend.cdataobj import W_CData @@ -201,13 +201,13 @@ in case of nested structures or arrays. 3. ffi.addressof(<library>, "name") returns the address of the named -global variable.""" +function or global variable.""" # from pypy.module._cffi_backend.lib_obj import W_LibObject space = self.space if isinstance(w_arg, W_LibObject) and len(args_w) == 1: # case 3 in the docstring - return w_arg.address_of_global_var(space.str_w(args_w[0])) + return w_arg.address_of_func_or_global_var(space.str_w(args_w[0])) # w_ctype = self.ffi_type(w_arg, ACCEPT_CDATA) if len(args_w) == 0: @@ -478,7 +478,7 @@ corresponding <ctype> object. It can also be used on 'cdata' instance to get its C type.""" # - if isinstance(w_arg, structwrapper.W_StructWrapper): + if isinstance(w_arg, wrapper.W_FunctionWrapper): return w_arg.typeof(self) return self.ffi_type(w_arg, ACCEPT_STRING | ACCEPT_CDATA) diff --git a/pypy/module/_cffi_backend/lib_obj.py b/pypy/module/_cffi_backend/lib_obj.py --- a/pypy/module/_cffi_backend/lib_obj.py +++ b/pypy/module/_cffi_backend/lib_obj.py @@ -12,7 +12,7 @@ from pypy.module._cffi_backend.realize_c_type import getop, getarg from pypy.module._cffi_backend.cdataobj import W_CData from pypy.module._cffi_backend.ctypefunc import W_CTypeFunc -from pypy.module._cffi_backend.structwrapper import W_StructWrapper +from pypy.module._cffi_backend.wrapper import W_FunctionWrapper class W_LibObject(W_Root): @@ -49,7 +49,7 @@ num += 1 self.ffi.included_ffis_libs = includes[:] - def _build_cpython_func(self, g): + def _build_cpython_func(self, g, fnname): # Build a function: in the PyPy version, these are all equivalent # and 'g->address' is a pointer to a function of exactly the # C type specified --- almost: arguments that are structs or @@ -64,10 +64,8 @@ # ptr = rffi.cast(rffi.CCHARP, g.c_address) assert ptr - w_cdata = W_CData(self.space, ptr, w_ct) - if locs is not None: - w_cdata = W_StructWrapper(w_cdata, locs, rawfunctype) - return w_cdata + return W_FunctionWrapper(self.space, ptr, g.c_size_or_direct_fn, w_ct, + locs, rawfunctype, fnname) @jit.elidable_promote() def _get_attr_elidable(self, attr): @@ -100,13 +98,13 @@ op == cffi_opcode.OP_CPYTHON_BLTN_N or op == cffi_opcode.OP_CPYTHON_BLTN_O): # A function - w_result = self._build_cpython_func(g) + w_result = self._build_cpython_func(g, attr) # elif op == cffi_opcode.OP_GLOBAL_VAR: # A global variable of the exact type specified here w_ct = realize_c_type.realize_c_type( self.ffi, self.ctx.c_types, getarg(g.c_type_op)) - g_size = rffi.getintfield(g, 'c_size') + g_size = rffi.cast(lltype.Signed, g.c_size_or_direct_fn) if g_size != w_ct.size and g_size != 0 and w_ct.size > 0: raise oefmt(self.ffi.w_FFIError, "global variable '%s' should be %d bytes " @@ -199,7 +197,7 @@ for i in range(total)] return space.newlist(names_w) - def address_of_global_var(self, varname): + def address_of_func_or_global_var(self, varname): # rebuild a string object from 'varname', to do typechecks and # to force a unicode back to a plain string space = self.space @@ -208,9 +206,15 @@ # regular case: a global variable return w_value.address() # - if ((isinstance(w_value, W_CData) and - isinstance(w_value.ctype, W_CTypeFunc)) - or isinstance(w_value, W_StructWrapper)): + if isinstance(w_value, W_FunctionWrapper): + # '&func' returns a regular cdata pointer-to-function + if w_value.directfnptr: + return W_CData(space, w_value.directfnptr, w_value.ctype) + else: + return w_value # backward compatibility + # + if (isinstance(w_value, W_CData) and + isinstance(w_value.ctype, W_CTypeFunc)): # '&func' is 'func' in C, for a constant function 'func' return w_value # diff --git a/pypy/module/_cffi_backend/parse_c_type.py b/pypy/module/_cffi_backend/parse_c_type.py --- a/pypy/module/_cffi_backend/parse_c_type.py +++ b/pypy/module/_cffi_backend/parse_c_type.py @@ -23,7 +23,7 @@ ('name', rffi.CCHARP), ('address', rffi.VOIDP), ('type_op', _CFFI_OPCODE_T), - ('size', rffi.SIZE_T)) + ('size_or_direct_fn', rffi.CCHARP)) CDL_INTCONST_S = lltype.Struct('cdl_intconst_s', ('value', rffi.ULONGLONG), ('neg', rffi.INT)) diff --git a/pypy/module/_cffi_backend/src/parse_c_type.h b/pypy/module/_cffi_backend/src/parse_c_type.h --- a/pypy/module/_cffi_backend/src/parse_c_type.h +++ b/pypy/module/_cffi_backend/src/parse_c_type.h @@ -1,5 +1,5 @@ -/* See doc/parse_c_type.rst in the source of CFFI for more information */ +/* See doc/misc/parse_c_type.rst in the source of CFFI for more information */ typedef void *_cffi_opcode_t; @@ -83,7 +83,8 @@ const char *name; void *address; _cffi_opcode_t type_op; - size_t size; // 0 if unknown + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function }; struct _cffi_getconst_s { diff --git a/pypy/module/_cffi_backend/structwrapper.py b/pypy/module/_cffi_backend/structwrapper.py deleted file mode 100644 --- a/pypy/module/_cffi_backend/structwrapper.py +++ /dev/null @@ -1,86 +0,0 @@ -from pypy.interpreter.baseobjspace import W_Root -from pypy.interpreter.typedef import TypeDef -from pypy.interpreter.gateway import interp2app -from rpython.rlib import jit - -from pypy.module._cffi_backend.cdataobj import W_CData -from pypy.module._cffi_backend.cdataobj import W_CDataPtrToStructOrUnion -from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray -from pypy.module._cffi_backend.ctypefunc import W_CTypeFunc -from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion - - -class W_StructWrapper(W_Root): - """A wrapper around a real W_CData which points to a function - generated in the C code. The real W_CData has got no struct/union - argument (only pointers to it), and no struct/union return type - (it is replaced by a hidden pointer as first argument). This - wrapper is callable, and the arguments it expects and returns - are directly the struct/union. Calling ffi.typeof(wrapper) - also returns the original struct/union signature. - """ - _immutable_ = True - - def __init__(self, w_cdata, locs, rawfunctype): - space = w_cdata.space - ctype = w_cdata.ctype - assert isinstance(ctype, W_CTypeFunc) - assert len(ctype.fargs) == len(locs) - # - self.space = space - self.w_cdata = w_cdata - self.locs = locs - self.fargs = ctype.fargs - self.rawfunctype = rawfunctype - - def typeof(self, ffi): - return self.rawfunctype.unwrap_as_fnptr(ffi) - - @jit.unroll_safe - def _prepare(self, args_w, start_index): - # replaces struct/union arguments with ptr-to-struct/union arguments - space = self.space - locs = self.locs - result_w = args_w[:] - for i in range(start_index, min(len(args_w), len(locs))): - if locs[i] != 'A': - continue - w_arg = args_w[i] - farg = self.fargs[i] # <ptr to struct/union> - assert isinstance(farg, W_CTypePtrOrArray) - if isinstance(w_arg, W_CData) and w_arg.ctype is farg.ctitem: - # fast way: we are given a W_CData "struct", so just make - # a new W_CData "ptr-to-struct" which points to the same - # raw memory. We use unsafe_escaping_ptr(), so we have to - # make sure the original 'w_arg' stays alive; the easiest - # is to build an instance of W_CDataPtrToStructOrUnion. - w_arg = W_CDataPtrToStructOrUnion( - space, w_arg.unsafe_escaping_ptr(), farg, w_arg) - else: - # slow way: build a new "ptr to struct" W_CData by calling - # the equivalent of ffi.new() - if space.is_w(w_arg, space.w_None): - continue - w_arg = farg.newp(w_arg) - result_w[i] = w_arg - return result_w - - def descr_call(self, args_w): - # If the result we want to present to the user is "returns struct", - # then internally allocate the struct and pass a pointer to it as - # a first argument. - if self.locs[0] == 'R': - w_result_cdata = self.fargs[0].newp(self.space.w_None) - args_w = [w_result_cdata] + args_w - self.w_cdata.call(self._prepare(args_w, 1)) - assert isinstance(w_result_cdata, W_CDataPtrToStructOrUnion) - return w_result_cdata.structobj - else: - return self.w_cdata.call(self._prepare(args_w, 0)) - - -W_StructWrapper.typedef = TypeDef( - 'FFIFuncStructWrapper', - __call__ = interp2app(W_StructWrapper.descr_call), - ) -W_StructWrapper.typedef.acceptable_as_base_class = False diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py b/pypy/module/_cffi_backend/test/_backend_test_c.py --- a/pypy/module/_cffi_backend/test/_backend_test_c.py +++ b/pypy/module/_cffi_backend/test/_backend_test_c.py @@ -3335,4 +3335,4 @@ def test_version(): # this test is here mostly for PyPy - assert __version__ == "1.0.3" + assert __version__ == "1.0.4" diff --git a/pypy/module/_cffi_backend/test/test_recompiler.py b/pypy/module/_cffi_backend/test/test_recompiler.py --- a/pypy/module/_cffi_backend/test/test_recompiler.py +++ b/pypy/module/_cffi_backend/test/test_recompiler.py @@ -9,11 +9,14 @@ @unwrap_spec(cdef=str, module_name=str, source=str) def prepare(space, cdef, module_name, source, w_includes=None): try: + import cffi from cffi import FFI # <== the system one, which - from cffi import recompiler # needs to be at least cffi 1.0.0 + from cffi import recompiler # needs to be at least cffi 1.0.4 from cffi import ffiplatform except ImportError: py.test.skip("system cffi module not found or older than 1.0.0") + if cffi.__version_info__ < (1, 0, 4): + py.test.skip("system cffi module needs to be at least 1.0.4") space.appexec([], """(): import _cffi_backend # force it to be initialized """) @@ -739,4 +742,60 @@ # raises(AttributeError, ffi.addressof, lib, 'unknown_var') raises(AttributeError, ffi.addressof, lib, "FOOBAR") - assert ffi.addressof(lib, 'FetchRectBottom') == lib.FetchRectBottom + + def test_defines__CFFI_(self): + # Check that we define the macro _CFFI_ automatically. + # It should be done before including Python.h, so that PyPy's Python.h + # can check for it. + ffi, lib = self.prepare(""" + #define CORRECT 1 + """, "test_defines__CFFI_", """ + #ifdef _CFFI_ + # define CORRECT 1 + #endif + """) + assert lib.CORRECT == 1 + + def test_unpack_args(self): + ffi, lib = self.prepare( + "void foo0(void); void foo1(int); void foo2(int, int);", + "test_unpack_args", """ + void foo0(void) { } + void foo1(int x) { } + void foo2(int x, int y) { } + """) + assert 'foo0' in repr(lib.foo0) + assert 'foo1' in repr(lib.foo1) + assert 'foo2' in repr(lib.foo2) + lib.foo0() + lib.foo1(42) + lib.foo2(43, 44) + e1 = raises(TypeError, lib.foo0, 42) + e2 = raises(TypeError, lib.foo0, 43, 44) + e3 = raises(TypeError, lib.foo1) + e4 = raises(TypeError, lib.foo1, 43, 44) + e5 = raises(TypeError, lib.foo2) + e6 = raises(TypeError, lib.foo2, 42) + e7 = raises(TypeError, lib.foo2, 45, 46, 47) + assert str(e1.value) == "foo0() takes no arguments (1 given)" + assert str(e2.value) == "foo0() takes no arguments (2 given)" + assert str(e3.value) == "foo1() takes exactly one argument (0 given)" + assert str(e4.value) == "foo1() takes exactly one argument (2 given)" + assert str(e5.value) == "foo2() takes exactly 2 arguments (0 given)" + assert str(e6.value) == "foo2() takes exactly 2 arguments (1 given)" + assert str(e7.value) == "foo2() takes exactly 2 arguments (3 given)" + + def test_address_of_function(self): + ffi, lib = self.prepare( + "long myfunc(long x);", + "test_addressof_function", + "char myfunc(char x) { return (char)(x + 42); }") + assert lib.myfunc(5) == 47 + assert lib.myfunc(0xABC05) == 47 + assert not isinstance(lib.myfunc, ffi.CData) + assert ffi.typeof(lib.myfunc) == ffi.typeof("long(*)(long)") + addr = ffi.addressof(lib, 'myfunc') + assert addr(5) == 47 + assert addr(0xABC05) == 47 + assert isinstance(addr, ffi.CData) + assert ffi.typeof(addr) == ffi.typeof("long(*)(long)") diff --git a/pypy/module/_cffi_backend/wrapper.py b/pypy/module/_cffi_backend/wrapper.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cffi_backend/wrapper.py @@ -0,0 +1,115 @@ +from pypy.interpreter.error import oefmt +from pypy.interpreter.baseobjspace import W_Root +from pypy.interpreter.typedef import TypeDef +from pypy.interpreter.gateway import interp2app +from rpython.rlib import jit + +from pypy.module._cffi_backend.cdataobj import W_CData +from pypy.module._cffi_backend.cdataobj import W_CDataPtrToStructOrUnion +from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray +from pypy.module._cffi_backend.ctypefunc import W_CTypeFunc +from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion + + +class W_FunctionWrapper(W_Root): + """A wrapper around a real W_CData which points to a function + generated in the C code. The real W_CData has got no struct/union + argument (only pointers to it), and no struct/union return type + (it is replaced by a hidden pointer as first argument). This + wrapper is callable, and the arguments it expects and returns + are directly the struct/union. Calling ffi.typeof(wrapper) + also returns the original struct/union signature. + """ + _immutable_ = True + + def __init__(self, space, fnptr, directfnptr, ctype, + locs, rawfunctype, fnname): + assert isinstance(ctype, W_CTypeFunc) + assert ctype.cif_descr is not None # not for '...' functions + assert locs is None or len(ctype.fargs) == len(locs) + # + self.space = space + self.fnptr = fnptr + self.directfnptr = directfnptr + self.ctype = ctype + self.locs = locs + self.rawfunctype = rawfunctype + self.fnname = fnname + self.nargs_expected = len(ctype.fargs) - (locs is not None and + locs[0] == 'R') + + def typeof(self, ffi): + return self.rawfunctype.unwrap_as_fnptr(ffi) + + @jit.unroll_safe + def _prepare(self, args_w, start_index): + # replaces struct/union arguments with ptr-to-struct/union arguments + space = self.space + locs = self.locs + fargs = self.ctype.fargs + for i in range(start_index, len(locs)): + if locs[i] != 'A': + continue + w_arg = args_w[i] + farg = fargs[i] # <ptr to struct/union> + assert isinstance(farg, W_CTypePtrOrArray) + if isinstance(w_arg, W_CData) and w_arg.ctype is farg.ctitem: + # fast way: we are given a W_CData "struct", so just make + # a new W_CData "ptr-to-struct" which points to the same + # raw memory. We use unsafe_escaping_ptr(), so we have to + # make sure the original 'w_arg' stays alive; the easiest + # is to build an instance of W_CDataPtrToStructOrUnion. + w_arg = W_CDataPtrToStructOrUnion( + space, w_arg.unsafe_escaping_ptr(), farg, w_arg) + else: + # slow way: build a new "ptr to struct" W_CData by calling + # the equivalent of ffi.new() + if space.is_w(w_arg, space.w_None): + continue + w_arg = farg.newp(w_arg) + args_w[i] = w_arg + + def descr_call(self, args_w): + if len(args_w) != self.nargs_expected: + space = self.space + if self.nargs_expected == 0: + raise oefmt(space.w_TypeError, + "%s() takes no arguments (%d given)", + self.fnname, len(args_w)) + elif self.nargs_expected == 1: + raise oefmt(space.w_TypeError, + "%s() takes exactly one argument (%d given)", + self.fnname, len(args_w)) + else: + raise oefmt(space.w_TypeError, + "%s() takes exactly %d arguments (%d given)", + self.fnname, self.nargs_expected, len(args_w)) + # + if self.locs is not None: + # This case is if there are structs as arguments or return values. + # If the result we want to present to the user is "returns struct", + # then internally allocate the struct and pass a pointer to it as + # a first argument. + if self.locs[0] == 'R': + w_result_cdata = self.ctype.fargs[0].newp(self.space.w_None) + args_w = [w_result_cdata] + args_w + self._prepare(args_w, 1) + self.ctype._call(self.fnptr, args_w) # returns w_None + assert isinstance(w_result_cdata, W_CDataPtrToStructOrUnion) + return w_result_cdata.structobj + else: + args_w = args_w[:] + self._prepare(args_w, 0) + # + return self.ctype._call(self.fnptr, args_w) + + def descr_repr(self, space): + return space.wrap("<FFIFunctionWrapper for %s()>" % (self.fnname,)) + + +W_FunctionWrapper.typedef = TypeDef( + 'FFIFunctionWrapper', + __repr__ = interp2app(W_FunctionWrapper.descr_repr), + __call__ = interp2app(W_FunctionWrapper.descr_call), + ) +W_FunctionWrapper.typedef.acceptable_as_base_class = False diff --git a/pypy/module/_file/test/test_file.py b/pypy/module/_file/test/test_file.py --- a/pypy/module/_file/test/test_file.py +++ b/pypy/module/_file/test/test_file.py @@ -280,6 +280,7 @@ # well as of _io.FileIO at least in CPython 3.3. This is # *not* the behavior of _io.FileIO in CPython 3.4 or 3.5; # see CPython's issue #21090. + import sys try: from posix import openpty, fdopen, write, close except ImportError: @@ -288,9 +289,18 @@ write(write_fd, 'Abc\n') close(write_fd) f = fdopen(read_fd) - s = f.read() - assert s == 'Abc\r\n' - raises(IOError, f.read) + # behavior on Linux: f.read() returns 'Abc\r\n', then the next time + # it raises IOError. Behavior on OS/X (Python 2.7.5): the close() + # above threw away the buffer, and f.read() always returns ''. + if sys.platform.startswith('linux'): + s = f.read() + assert s == 'Abc\r\n' + raises(IOError, f.read) + else: + s = f.read() + assert s == '' + s = f.read() + assert s == '' f.close() diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -427,6 +427,7 @@ 'PyThread_ReInitTLS', 'PyStructSequence_InitType', 'PyStructSequence_New', + 'PyStructSequence_UnnamedField', 'PyFunction_Type', 'PyMethod_Type', 'PyRange_Type', 'PyTraceBack_Type', diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py @@ -761,7 +761,6 @@ # py.test.raises(AttributeError, ffi.addressof, lib, 'unknown_var') py.test.raises(AttributeError, ffi.addressof, lib, "FOOBAR") - assert ffi.addressof(lib, 'FetchRectBottom') == lib.FetchRectBottom def test_defines__CFFI_(): # Check that we define the macro _CFFI_ automatically. @@ -777,3 +776,48 @@ #endif """) assert lib.CORRECT == 1 + +def test_unpack_args(): + ffi = FFI() + ffi.cdef("void foo0(void); void foo1(int); void foo2(int, int);") + lib = verify(ffi, "test_unpack_args", """ + void foo0(void) { } + void foo1(int x) { } + void foo2(int x, int y) { } + """) + assert 'foo0' in repr(lib.foo0) + assert 'foo1' in repr(lib.foo1) + assert 'foo2' in repr(lib.foo2) + lib.foo0() + lib.foo1(42) + lib.foo2(43, 44) + e1 = py.test.raises(TypeError, lib.foo0, 42) + e2 = py.test.raises(TypeError, lib.foo0, 43, 44) + e3 = py.test.raises(TypeError, lib.foo1) + e4 = py.test.raises(TypeError, lib.foo1, 43, 44) + e5 = py.test.raises(TypeError, lib.foo2) + e6 = py.test.raises(TypeError, lib.foo2, 42) + e7 = py.test.raises(TypeError, lib.foo2, 45, 46, 47) + assert str(e1.value) == "foo0() takes no arguments (1 given)" + assert str(e2.value) == "foo0() takes no arguments (2 given)" + assert str(e3.value) == "foo1() takes exactly one argument (0 given)" + assert str(e4.value) == "foo1() takes exactly one argument (2 given)" + assert str(e5.value) == "foo2() takes exactly 2 arguments (0 given)" + assert str(e6.value) == "foo2() takes exactly 2 arguments (1 given)" + assert str(e7.value) == "foo2() takes exactly 2 arguments (3 given)" + +def test_address_of_function(): + ffi = FFI() + ffi.cdef("long myfunc(long x);") + lib = verify(ffi, "test_addressof_function", """ + char myfunc(char x) { return (char)(x + 42); } + """) + assert lib.myfunc(5) == 47 + assert lib.myfunc(0xABC05) == 47 + assert not isinstance(lib.myfunc, ffi.CData) + assert ffi.typeof(lib.myfunc) == ffi.typeof("long(*)(long)") + addr = ffi.addressof(lib, 'myfunc') + assert addr(5) == 47 + assert addr(0xABC05) == 47 + assert isinstance(addr, ffi.CData) + assert ffi.typeof(addr) == ffi.typeof("long(*)(long)") diff --git a/rpython/jit/backend/test/test_ll_random.py b/rpython/jit/backend/test/test_ll_random.py --- a/rpython/jit/backend/test/test_ll_random.py +++ b/rpython/jit/backend/test/test_ll_random.py @@ -17,6 +17,10 @@ def __init__(self, *args, **kw): test_random.OperationBuilder.__init__(self, *args, **kw) self.vtable_counter = 0 + # note: rstrs and runicodes contain either new local strings, or + # constants. In other words, all BoxPtrs here were created earlier + # by the trace before, and so it should be kind of fine to mutate + # them with strsetitem/unicodesetitem. self.rstrs = [] self.runicodes = [] self.structure_types = [] @@ -484,6 +488,8 @@ class AbstractSetItemOperation(AbstractStringOperation): def produce_into(self, builder, r): v_string = self.get_string(builder, r) + if not isinstance(v_string, BoxPtr): + raise test_random.CannotProduceOperation # setitem(Const, ...) v_index = builder.get_index(len(v_string.getref(self.ptr).chars), r) v_target = ConstInt(r.random_integer() % self.max) builder.do(self.opnum, [v_string, v_index, v_target]) diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py @@ -4614,6 +4614,58 @@ """ self.optimize_strunicode_loop_extradescrs(ops, expected) + def test_str_equal_none3(self): + ops = """ + [] + p5 = newstr(0) + i0 = call(0, NULL, p5, descr=strequaldescr) + escape(i0) + jump() + """ + expected = """ + [] + escape(0) + jump() + """ + self.optimize_strunicode_loop_extradescrs(ops, expected) + + def test_str_equal_none4(self): + ops = """ + [p1] + p5 = newstr(0) + i0 = call(0, p5, p1, descr=strequaldescr) + escape(i0) + jump(p1) + """ + expected = """ + [p1] + # can't optimize more: p1 may be NULL! + i0 = call(0, s"", p1, descr=strequaldescr) + escape(i0) + jump(p1) + """ + self.optimize_strunicode_loop_extradescrs(ops, expected) + + def test_str_equal_none5(self): + ops = """ + [p1] + guard_nonnull(p1) [] + p5 = newstr(0) + i0 = call(0, p5, p1, descr=strequaldescr) + escape(i0) + jump(p1) + """ + expected = """ + [p1] + guard_nonnull(p1) [] + # p1 is not NULL, so the string comparison (p1=="") becomes: + i6 = strlen(p1) + i0 = int_eq(i6, 0) + escape(i0) + jump(p1) + """ + self.optimize_strunicode_loop_extradescrs(ops, expected) + def test_str_equal_nonnull1(self): ops = """ [p1] diff --git a/rpython/jit/metainterp/optimizeopt/vstring.py b/rpython/jit/metainterp/optimizeopt/vstring.py --- a/rpython/jit/metainterp/optimizeopt/vstring.py +++ b/rpython/jit/metainterp/optimizeopt/vstring.py @@ -667,10 +667,15 @@ l2box = v2.getstrlen(None, mode, None) if isinstance(l2box, ConstInt): if l2box.value == 0: - lengthbox = v1.getstrlen(self, mode, None) - seo = self.optimizer.send_extra_operation - seo(ResOperation(rop.INT_EQ, [lengthbox, CONST_0], resultbox)) - return True + if v1.is_nonnull(): + lengthbox = v1.getstrlen(self, mode, None) + else: + lengthbox = v1.getstrlen(None, mode, None) + if lengthbox is not None: + seo = self.optimizer.send_extra_operation + seo(ResOperation(rop.INT_EQ, [lengthbox, CONST_0], + resultbox)) + return True if l2box.value == 1: l1box = v1.getstrlen(None, mode, None) if isinstance(l1box, ConstInt) and l1box.value == 1: _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit