Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r81816:6c7267731b67 Date: 2016-01-15 19:47 +0100 http://bitbucket.org/pypy/pypy/changeset/6c7267731b67/
Log: hg merge cffi-static-callback-embedding Add embedding support, bringing CFFI to version 1.5. diff --git a/lib-python/2.7/distutils/command/build_ext.py b/lib-python/2.7/distutils/command/build_ext.py --- a/lib-python/2.7/distutils/command/build_ext.py +++ b/lib-python/2.7/distutils/command/build_ext.py @@ -685,13 +685,17 @@ # the previous version of this code did. This should work for # CPython too. The point is that on PyPy with cpyext, the # config var 'SO' is just ".so" but we want to return - # ".pypy-VERSION.so" instead. - so_ext = _get_c_extension_suffix() + # ".pypy-VERSION.so" instead. Note a further tweak for cffi's + # embedding mode: if EXT_SUFFIX is also defined, use that + # directly. + so_ext = get_config_var('EXT_SUFFIX') if so_ext is None: - so_ext = get_config_var('SO') # fall-back - # extensions in debug_mode are named 'module_d.pyd' under windows - if os.name == 'nt' and self.debug: - so_ext = '_d.pyd' + so_ext = _get_c_extension_suffix() + if so_ext is None: + so_ext = get_config_var('SO') # fall-back + # extensions in debug_mode are named 'module_d.pyd' under windows + if os.name == 'nt' and self.debug: + so_ext = '_d.pyd' return os.path.join(*ext_path) + so_ext def get_export_symbols (self, ext): 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.4.2 +Version: 1.5.0 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.4.2" -__version_info__ = (1, 4, 2) +__version__ = "1.5.0" +__version_info__ = (1, 5, 0) # 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 @@ -146,8 +146,9 @@ ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) #define _cffi_convert_array_from_object \ ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_CPIDX 25 #define _cffi_call_python \ - ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[25]) + ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX]) #define _CFFI_NUM_EXPORTS 26 typedef struct _ctypedescr CTypeDescrObject; @@ -206,7 +207,8 @@ /********** end CPython-specific section **********/ #else _CFFI_UNUSED_FN -static void (*_cffi_call_python)(struct _cffi_externpy_s *, char *); +static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *); +# define _cffi_call_python _cffi_call_python_org #endif diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py --- a/lib_pypy/cffi/api.py +++ b/lib_pypy/cffi/api.py @@ -74,6 +74,7 @@ self._windows_unicode = None self._init_once_cache = {} self._cdef_version = None + self._embedding = None if hasattr(backend, 'set_ffi'): backend.set_ffi(self) for name in backend.__dict__: @@ -101,13 +102,21 @@ If 'packed' is specified as True, all structs declared inside this cdef are packed, i.e. laid out without any field alignment at all. """ + self._cdef(csource, override=override, packed=packed) + + def embedding_api(self, csource, packed=False): + self._cdef(csource, packed=packed, dllexport=True) + if self._embedding is None: + self._embedding = '' + + def _cdef(self, csource, override=False, **options): if not isinstance(csource, str): # unicode, on Python 2 if not isinstance(csource, basestring): raise TypeError("cdef() argument must be a string") csource = csource.encode('ascii') with self._lock: self._cdef_version = object() - self._parser.parse(csource, override=override, packed=packed) + self._parser.parse(csource, override=override, **options) self._cdefsources.append(csource) if override: for cache in self._function_caches: @@ -533,6 +542,31 @@ ('_UNICODE', '1')] kwds['define_macros'] = defmacros + def _apply_embedding_fix(self, kwds): + # must include an argument like "-lpython2.7" for the compiler + if '__pypy__' in sys.builtin_module_names: + if hasattr(sys, 'prefix'): + import os + libdir = os.path.join(sys.prefix, 'bin') + dirs = kwds.setdefault('library_dirs', []) + if libdir not in dirs: + dirs.append(libdir) + pythonlib = "pypy-c" + else: + if sys.platform == "win32": + template = "python%d%d" + if sys.flags.debug: + template = template + '_d' + else: + template = "python%d.%d" + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + if hasattr(sys, 'abiflags'): + pythonlib += sys.abiflags + libraries = kwds.setdefault('libraries', []) + if pythonlib not in libraries: + libraries.append(pythonlib) + def set_source(self, module_name, source, source_extension='.c', **kwds): if hasattr(self, '_assigned_source'): raise ValueError("set_source() cannot be called several times " @@ -592,14 +626,23 @@ recompile(self, module_name, source, c_file=filename, call_c_compiler=False, **kwds) - def compile(self, tmpdir='.', verbose=0): + def compile(self, tmpdir='.', verbose=0, target=None): + """The 'target' argument gives the final file name of the + compiled DLL. Use '*' to force distutils' choice, suitable for + regular CPython C API modules. Use a file name ending in '.*' + to ask for the system's default extension for dynamic libraries + (.so/.dll). + + The default is '*' when building a non-embedded C API extension, + and (module_name + '.*') when building an embedded library. + """ from .recompiler import recompile # if not hasattr(self, '_assigned_source'): raise ValueError("set_source() must be called before compile()") module_name, source, source_extension, kwds = self._assigned_source return recompile(self, module_name, source, tmpdir=tmpdir, - source_extension=source_extension, + target=target, source_extension=source_extension, compiler_verbose=verbose, **kwds) def init_once(self, func, tag): @@ -626,6 +669,32 @@ self._init_once_cache[tag] = (True, result) return result + def embedding_init_code(self, pysource): + if self._embedding: + raise ValueError("embedding_init_code() can only be called once") + # fix 'pysource' before it gets dumped into the C file: + # - remove empty lines at the beginning, so it starts at "line 1" + # - dedent, if all non-empty lines are indented + # - check for SyntaxErrors + import re + match = re.match(r'\s*\n', pysource) + if match: + pysource = pysource[match.end():] + lines = pysource.splitlines() or [''] + prefix = re.match(r'\s*', lines[0]).group() + for i in range(1, len(lines)): + line = lines[i] + if line.rstrip(): + while not line.startswith(prefix): + prefix = prefix[:-1] + i = len(prefix) + lines = [line[i:]+'\n' for line in lines] + pysource = ''.join(lines) + # + compile(pysource, "cffi_init", "exec") + # + self._embedding = pysource + def _load_backend_lib(backend, name, flags): if name is None: diff --git a/lib_pypy/cffi/cparser.py b/lib_pypy/cffi/cparser.py --- a/lib_pypy/cffi/cparser.py +++ b/lib_pypy/cffi/cparser.py @@ -220,8 +220,7 @@ self._included_declarations = set() self._anonymous_counter = 0 self._structnode2type = weakref.WeakKeyDictionary() - self._override = False - self._packed = False + self._options = None self._int_constants = {} self._recomplete = [] self._uses_new_feature = None @@ -281,16 +280,15 @@ msg = 'parse error\n%s' % (msg,) raise api.CDefError(msg) - def parse(self, csource, override=False, packed=False): - prev_override = self._override - prev_packed = self._packed + def parse(self, csource, override=False, packed=False, dllexport=False): + prev_options = self._options try: - self._override = override - self._packed = packed + self._options = {'override': override, + 'packed': packed, + 'dllexport': dllexport} self._internal_parse(csource) finally: - self._override = prev_override - self._packed = prev_packed + self._options = prev_options def _internal_parse(self, csource): ast, macros, csource = self._parse(csource) @@ -376,10 +374,13 @@ def _declare_function(self, tp, quals, decl): tp = self._get_type_pointer(tp, quals) - if self._inside_extern_python: - self._declare('extern_python ' + decl.name, tp) + if self._options['dllexport']: + tag = 'dllexport_python ' + elif self._inside_extern_python: + tag = 'extern_python ' else: - self._declare('function ' + decl.name, tp) + tag = 'function ' + self._declare(tag + decl.name, tp) def _parse_decl(self, decl): node = decl.type @@ -449,7 +450,7 @@ prevobj, prevquals = self._declarations[name] if prevobj is obj and prevquals == quals: return - if not self._override: + if not self._options['override']: raise api.FFIError( "multiple declarations of %s (for interactive usage, " "try cdef(xx, override=True))" % (name,)) @@ -728,7 +729,7 @@ if isinstance(tp, model.StructType) and tp.partial: raise NotImplementedError("%s: using both bitfields and '...;'" % (tp,)) - tp.packed = self._packed + tp.packed = self._options['packed'] if tp.completed: # must be re-completed: it is not opaque any more tp.completed = 0 self._recomplete.append(tp) diff --git a/lib_pypy/cffi/ffiplatform.py b/lib_pypy/cffi/ffiplatform.py --- a/lib_pypy/cffi/ffiplatform.py +++ b/lib_pypy/cffi/ffiplatform.py @@ -21,12 +21,14 @@ allsources.append(os.path.normpath(src)) return Extension(name=modname, sources=allsources, **kwds) -def compile(tmpdir, ext, compiler_verbose=0): +def compile(tmpdir, ext, compiler_verbose=0, target_extension=None, + embedding=False): """Compile a C extension module using distutils.""" saved_environ = os.environ.copy() try: - outputfilename = _build(tmpdir, ext, compiler_verbose) + outputfilename = _build(tmpdir, ext, compiler_verbose, + target_extension, embedding) outputfilename = os.path.abspath(outputfilename) finally: # workaround for a distutils bugs where some env vars can @@ -36,7 +38,32 @@ os.environ[key] = value return outputfilename -def _build(tmpdir, ext, compiler_verbose=0): +def _save_val(name): + import distutils.sysconfig + config_vars = distutils.sysconfig.get_config_vars() + return config_vars.get(name, Ellipsis) + +def _restore_val(name, value): + import distutils.sysconfig + config_vars = distutils.sysconfig.get_config_vars() + config_vars[name] = value + if value is Ellipsis: + del config_vars[name] + +def _win32_hack_for_embedding(): + from distutils.msvc9compiler import MSVCCompiler + if not hasattr(MSVCCompiler, '_remove_visual_c_ref_CFFI_BAK'): + MSVCCompiler._remove_visual_c_ref_CFFI_BAK = \ + MSVCCompiler._remove_visual_c_ref + MSVCCompiler._remove_visual_c_ref = lambda self,manifest_file: manifest_file + +def _win32_unhack_for_embedding(): + from distutils.msvc9compiler import MSVCCompiler + MSVCCompiler._remove_visual_c_ref = \ + MSVCCompiler._remove_visual_c_ref_CFFI_BAK + +def _build(tmpdir, ext, compiler_verbose=0, target_extension=None, + embedding=False): # XXX compact but horrible :-( from distutils.core import Distribution import distutils.errors, distutils.log @@ -49,18 +76,29 @@ options['build_temp'] = ('ffiplatform', tmpdir) # try: + if sys.platform == 'win32' and embedding: + _win32_hack_for_embedding() old_level = distutils.log.set_threshold(0) or 0 + old_SO = _save_val('SO') + old_EXT_SUFFIX = _save_val('EXT_SUFFIX') try: + if target_extension is not None: + _restore_val('SO', target_extension) + _restore_val('EXT_SUFFIX', target_extension) distutils.log.set_verbosity(compiler_verbose) dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() finally: distutils.log.set_threshold(old_level) + _restore_val('SO', old_SO) + _restore_val('EXT_SUFFIX', old_EXT_SUFFIX) + if sys.platform == 'win32' and embedding: + _win32_unhack_for_embedding() except (distutils.errors.CompileError, distutils.errors.LinkError) as e: raise VerificationError('%s: %s' % (e.__class__.__name__, e)) # - cmd_obj = dist.get_command_obj('build_ext') - [soname] = cmd_obj.get_outputs() return soname try: 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 @@ -3,6 +3,7 @@ from .cffi_opcode import * VERSION = "0x2601" +VERSION_EMBEDDED = "0x2701" class GlobalExpr: @@ -281,6 +282,29 @@ lines[i:i+1] = self._rel_readlines('parse_c_type.h') prnt(''.join(lines)) # + # if we have ffi._embedding != None, we give it here as a macro + # and include an extra file + base_module_name = self.module_name.split('.')[-1] + if self.ffi._embedding is not None: + prnt('#define _CFFI_MODULE_NAME "%s"' % (self.module_name,)) + prnt('#define _CFFI_PYTHON_STARTUP_CODE %s' % + (self._string_literal(self.ffi._embedding),)) + prnt('#ifdef PYPY_VERSION') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC _cffi_pypyinit_%s' % ( + base_module_name,)) + prnt('#elif PY_MAJOR_VERSION >= 3') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC PyInit_%s' % ( + base_module_name,)) + prnt('#else') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC init%s' % ( + base_module_name,)) + prnt('#endif') + lines = self._rel_readlines('_embedding.h') + prnt(''.join(lines)) + version = VERSION_EMBEDDED + else: + version = VERSION + # # then paste the C source given by the user, verbatim. prnt('/************************************************************/') prnt() @@ -365,17 +389,16 @@ prnt() # # the init function - base_module_name = self.module_name.split('.')[-1] prnt('#ifdef PYPY_VERSION') prnt('PyMODINIT_FUNC') prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,)) prnt('{') if self._num_externpy: prnt(' if (((intptr_t)p[0]) >= 0x0A03) {') - prnt(' _cffi_call_python = ' + prnt(' _cffi_call_python_org = ' '(void(*)(struct _cffi_externpy_s *, char *))p[1];') prnt(' }') - prnt(' p[0] = (const void *)%s;' % VERSION) + prnt(' p[0] = (const void *)%s;' % version) prnt(' p[1] = &_cffi_type_context;') prnt('}') # on Windows, distutils insists on putting init_cffi_xyz in @@ -394,14 +417,14 @@ prnt('PyInit_%s(void)' % (base_module_name,)) prnt('{') prnt(' return _cffi_init("%s", %s, &_cffi_type_context);' % ( - self.module_name, VERSION)) + self.module_name, version)) prnt('}') prnt('#else') prnt('PyMODINIT_FUNC') prnt('init%s(void)' % (base_module_name,)) prnt('{') prnt(' _cffi_init("%s", %s, &_cffi_type_context);' % ( - self.module_name, VERSION)) + self.module_name, version)) prnt('}') prnt('#endif') @@ -1123,7 +1146,10 @@ assert isinstance(tp, model.FunctionPtrType) self._do_collect_type(tp) - def _generate_cpy_extern_python_decl(self, tp, name): + def _generate_cpy_dllexport_python_collecttype(self, tp, name): + self._generate_cpy_extern_python_collecttype(tp, name) + + def _generate_cpy_extern_python_decl(self, tp, name, dllexport=False): prnt = self._prnt if isinstance(tp.result, model.VoidType): size_of_result = '0' @@ -1156,7 +1182,11 @@ size_of_a = 'sizeof(%s) > %d ? sizeof(%s) : %d' % ( tp.result.get_c_name(''), size_of_a, tp.result.get_c_name(''), size_of_a) - prnt('static %s' % tp.result.get_c_name(name_and_arguments)) + if dllexport: + tag = 'CFFI_DLLEXPORT' + else: + tag = 'static' + prnt('%s %s' % (tag, tp.result.get_c_name(name_and_arguments))) prnt('{') prnt(' char a[%s];' % size_of_a) prnt(' char *p = a;') @@ -1174,6 +1204,9 @@ prnt() self._num_externpy += 1 + def _generate_cpy_dllexport_python_decl(self, tp, name): + self._generate_cpy_extern_python_decl(tp, name, dllexport=True) + def _generate_cpy_extern_python_ctx(self, tp, name): if self.target_is_python: raise ffiplatform.VerificationError( @@ -1185,6 +1218,21 @@ self._lsts["global"].append( GlobalExpr(name, '&_cffi_externpy__%s' % name, type_op, name)) + def _generate_cpy_dllexport_python_ctx(self, tp, name): + self._generate_cpy_extern_python_ctx(tp, name) + + def _string_literal(self, s): + def _char_repr(c): + # escape with a '\' the characters '\', '"' or (for trigraphs) '?' + if c in '\\"?': return '\\' + c + if ' ' <= c < '\x7F': return c + if c == '\n': return '\\n' + return '\\%03o' % ord(c) + lines = [] + for line in s.splitlines(True): + lines.append('"%s"' % ''.join([_char_repr(c) for c in line])) + return ' \\\n'.join(lines) + # ---------- # emitting the opcodes for individual types @@ -1311,12 +1359,15 @@ def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True, c_file=None, source_extension='.c', extradir=None, - compiler_verbose=1, **kwds): + compiler_verbose=1, target=None, **kwds): if not isinstance(module_name, str): module_name = module_name.encode('ascii') if ffi._windows_unicode: ffi._apply_windows_unicode(kwds) if preamble is not None: + embedding = (ffi._embedding is not None) + if embedding: + ffi._apply_embedding_fix(kwds) if c_file is None: c_file, parts = _modname_to_file(tmpdir, module_name, source_extension) @@ -1325,13 +1376,40 @@ ext_c_file = os.path.join(*parts) else: ext_c_file = c_file - ext = ffiplatform.get_extension(ext_c_file, module_name, **kwds) + # + if target is None: + if embedding: + target = '%s.*' % module_name + else: + target = '*' + if target == '*': + target_module_name = module_name + target_extension = None # use default + else: + if target.endswith('.*'): + target = target[:-2] + if sys.platform == 'win32': + target += '.dll' + else: + target += '.so' + # split along the first '.' (not the last one, otherwise the + # preceeding dots are interpreted as splitting package names) + index = target.find('.') + if index < 0: + raise ValueError("target argument %r should be a file name " + "containing a '.'" % (target,)) + target_module_name = target[:index] + target_extension = target[index:] + # + ext = ffiplatform.get_extension(ext_c_file, target_module_name, **kwds) updated = make_c_source(ffi, module_name, preamble, c_file) if call_c_compiler: cwd = os.getcwd() try: os.chdir(tmpdir) - outputfilename = ffiplatform.compile('.', ext, compiler_verbose) + outputfilename = ffiplatform.compile('.', ext, compiler_verbose, + target_extension, + embedding=embedding) finally: os.chdir(cwd) return outputfilename diff --git a/pypy/doc/embedding.rst b/pypy/doc/embedding.rst --- a/pypy/doc/embedding.rst +++ b/pypy/doc/embedding.rst @@ -10,6 +10,15 @@ with a ``libpypy-c.so`` or ``pypy-c.dll`` file. This is the default in recent versions of PyPy. +.. note:: + + The interface described in this page is kept for backward compatibility. + From PyPy 4.1, it is recommended to use instead CFFI's `native embedding + support,`__ which gives a simpler approach that works on CPython as well + as PyPy. + +.. __: http://cffi.readthedocs.org/en/latest/embedding.html + The resulting shared library exports very few functions, however they are enough to accomplish everything you need, provided you follow a few principles. The API is: diff --git a/pypy/goal/targetpypystandalone.py b/pypy/goal/targetpypystandalone.py --- a/pypy/goal/targetpypystandalone.py +++ b/pypy/goal/targetpypystandalone.py @@ -84,13 +84,6 @@ from rpython.rlib.entrypoint import entrypoint_highlevel from rpython.rtyper.lltypesystem import rffi, lltype - w_pathsetter = space.appexec([], """(): - def f(path): - import sys - sys.path[:] = path - return f - """) - @entrypoint_highlevel('main', [rffi.CCHARP, rffi.INT], c_name='pypy_setup_home') def pypy_setup_home(ll_home, verbose): @@ -109,7 +102,10 @@ " not found in '%s' or in any parent directory" % home1) return rffi.cast(rffi.INT, 1) space.startup() - space.call_function(w_pathsetter, w_path) + space.appexec([w_path], """(path): + import sys + sys.path[:] = path + """) # import site try: space.setattr(space.getbuiltinmodule('sys'), @@ -149,6 +145,9 @@ return os_thread.setup_threads(space) os_thread.bootstrapper.acquire(space, None, None) + # XXX this doesn't really work. Don't use os.fork(), and + # if your embedder program uses fork(), don't use any PyPy + # code in the fork rthread.gc_thread_start() os_thread.bootstrapper.nbthreads += 1 os_thread.bootstrapper.release() 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 @@ -1,8 +1,9 @@ import sys from pypy.interpreter.mixedmodule import MixedModule -from rpython.rlib import rdynload, clibffi +from rpython.rlib import rdynload, clibffi, entrypoint +from rpython.rtyper.lltypesystem import rffi -VERSION = "1.4.2" +VERSION = "1.5.0" FFI_DEFAULT_ABI = clibffi.FFI_DEFAULT_ABI try: @@ -65,6 +66,10 @@ if has_stdcall: interpleveldefs['FFI_STDCALL'] = 'space.wrap(%d)' % FFI_STDCALL + def startup(self, space): + from pypy.module._cffi_backend import embedding + embedding.glob.space = space + def get_dict_rtld_constants(): found = {} @@ -78,3 +83,11 @@ for _name, _value in get_dict_rtld_constants().items(): Module.interpleveldefs[_name] = 'space.wrap(%d)' % _value + + +# write this entrypoint() here, to make sure it is registered early enough +@entrypoint.entrypoint_highlevel('main', [rffi.INT, rffi.VOIDP], + c_name='pypy_init_embedded_cffi_module') +def pypy_init_embedded_cffi_module(version, init_struct): + from pypy.module._cffi_backend import embedding + return embedding.pypy_init_embedded_cffi_module(version, init_struct) diff --git a/pypy/module/_cffi_backend/cffi1_module.py b/pypy/module/_cffi_backend/cffi1_module.py --- a/pypy/module/_cffi_backend/cffi1_module.py +++ b/pypy/module/_cffi_backend/cffi1_module.py @@ -9,18 +9,18 @@ VERSION_MIN = 0x2601 -VERSION_MAX = 0x26FF +VERSION_MAX = 0x27FF VERSION_EXPORT = 0x0A03 -initfunctype = lltype.Ptr(lltype.FuncType([rffi.VOIDPP], lltype.Void)) +INITFUNCPTR = lltype.Ptr(lltype.FuncType([rffi.VOIDPP], lltype.Void)) def load_cffi1_module(space, name, path, initptr): # This is called from pypy.module.cpyext.api.load_extension_module() from pypy.module._cffi_backend.call_python import get_ll_cffi_call_python - initfunc = rffi.cast(initfunctype, initptr) + initfunc = rffi.cast(INITFUNCPTR, initptr) with lltype.scoped_alloc(rffi.VOIDPP.TO, 16, zero=True) as p: p[0] = rffi.cast(rffi.VOIDP, VERSION_EXPORT) p[1] = rffi.cast(rffi.VOIDP, get_ll_cffi_call_python()) @@ -41,7 +41,8 @@ w_name = space.wrap(name) module = Module(space, w_name) - module.setdictvalue(space, '__file__', space.wrap(path)) + if path is not None: + module.setdictvalue(space, '__file__', space.wrap(path)) module.setdictvalue(space, 'ffi', space.wrap(ffi)) module.setdictvalue(space, 'lib', space.wrap(lib)) w_modules_dict = space.sys.get('modules') diff --git a/pypy/module/_cffi_backend/embedding.py b/pypy/module/_cffi_backend/embedding.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cffi_backend/embedding.py @@ -0,0 +1,146 @@ +import os +from rpython.rtyper.lltypesystem import lltype, rffi +from rpython.translator.tool.cbuild import ExternalCompilationInfo + +from pypy.interpreter.error import OperationError, oefmt + +# ____________________________________________________________ + + +EMBED_VERSION_MIN = 0xB011 +EMBED_VERSION_MAX = 0xB0FF + +STDERR = 2 +INITSTRUCTPTR = lltype.Ptr(lltype.Struct('CFFI_INIT', + ('name', rffi.CCHARP), + ('func', rffi.VOIDP), + ('code', rffi.CCHARP))) + +def load_embedded_cffi_module(space, version, init_struct): + from pypy.module._cffi_backend.cffi1_module import load_cffi1_module + declare_c_function() # translation-time hint only: + # declare _cffi_carefully_make_gil() + # + version = rffi.cast(lltype.Signed, version) + if not (EMBED_VERSION_MIN <= version <= EMBED_VERSION_MAX): + raise oefmt(space.w_ImportError, + "cffi embedded module has got unknown version tag %s", + hex(version)) + # + if space.config.objspace.usemodules.thread: + from pypy.module.thread import os_thread + os_thread.setup_threads(space) + # + name = rffi.charp2str(init_struct.name) + load_cffi1_module(space, name, None, init_struct.func) + code = rffi.charp2str(init_struct.code) + compiler = space.createcompiler() + pycode = compiler.compile(code, "<init code for '%s'>" % name, 'exec', 0) + w_globals = space.newdict(module=True) + space.setitem_str(w_globals, "__builtins__", space.wrap(space.builtin)) + pycode.exec_code(space, w_globals, w_globals) + + +class Global: + pass +glob = Global() + +def pypy_init_embedded_cffi_module(version, init_struct): + # called from __init__.py + name = "?" + try: + init_struct = rffi.cast(INITSTRUCTPTR, init_struct) + name = rffi.charp2str(init_struct.name) + # + space = glob.space + must_leave = False + try: + must_leave = space.threadlocals.try_enter_thread(space) + load_embedded_cffi_module(space, version, init_struct) + res = 0 + except OperationError, operr: + operr.write_unraisable(space, "initialization of '%s'" % name, + with_traceback=True) + space.appexec([], r"""(): + import sys + sys.stderr.write('pypy version: %s.%s.%s\n' % + sys.pypy_version_info[:3]) + sys.stderr.write('sys.path: %r\n' % (sys.path,)) + """) + res = -1 + if must_leave: + space.threadlocals.leave_thread(space) + except Exception, e: + # oups! last-level attempt to recover. + try: + os.write(STDERR, "From initialization of '") + os.write(STDERR, name) + os.write(STDERR, "':\n") + os.write(STDERR, str(e)) + os.write(STDERR, "\n") + except: + pass + res = -1 + return rffi.cast(rffi.INT, res) + +# ____________________________________________________________ + + +eci = ExternalCompilationInfo(separate_module_sources=[ +r""" +/* XXX Windows missing */ +#include <stdio.h> +#include <dlfcn.h> +#include <pthread.h> + +RPY_EXPORTED void rpython_startup_code(void); +RPY_EXPORTED int pypy_setup_home(char *, int); + +static unsigned char _cffi_ready = 0; +static const char *volatile _cffi_module_name; + +static void _cffi_init_error(const char *msg, const char *extra) +{ + fprintf(stderr, + "\nPyPy initialization failure when loading module '%s':\n%s%s\n", + _cffi_module_name, msg, extra); +} + +static void _cffi_init(void) +{ + Dl_info info; + char *home; + + rpython_startup_code(); + RPyGilAllocate(); + + if (dladdr(&_cffi_init, &info) == 0) { + _cffi_init_error("dladdr() failed: ", dlerror()); + return; + } + home = realpath(info.dli_fname, NULL); + if (pypy_setup_home(home, 1) != 0) { + _cffi_init_error("pypy_setup_home() failed", ""); + return; + } + _cffi_ready = 1; +} + +RPY_EXPORTED +int pypy_carefully_make_gil(const char *name) +{ + /* For CFFI: this initializes the GIL and loads the home path. + It can be called completely concurrently from unrelated threads. + It assumes that we don't hold the GIL before (if it exists), and we + don't hold it afterwards. + */ + static pthread_once_t once_control = PTHREAD_ONCE_INIT; + + _cffi_module_name = name; /* not really thread-safe, but better than + nothing */ + pthread_once(&once_control, _cffi_init); + return (int)_cffi_ready - 1; +} +"""]) + +declare_c_function = rffi.llexternal_use_eci(eci) 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 @@ -1,7 +1,7 @@ # ____________________________________________________________ import sys -assert __version__ == "1.4.2", ("This test_c.py file is for testing a version" +assert __version__ == "1.5.0", ("This test_c.py file is for testing a version" " of cffi that differs from the one that we" " get from 'import _cffi_backend'") if sys.version_info < (3,): diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py @@ -1353,8 +1353,8 @@ ffi = FFI(backend=self.Backend()) ffi.cdef("enum foo;") from cffi import __version_info__ - if __version_info__ < (1, 5): - py.test.skip("re-enable me in version 1.5") + if __version_info__ < (1, 6): + py.test.skip("re-enable me in version 1.6") e = py.test.raises(CDefError, ffi.cast, "enum foo", -1) assert str(e.value) == ( "'enum foo' has no values explicitly defined: refusing to guess " diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_version.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_version.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_version.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_version.py @@ -54,3 +54,10 @@ content = open(p).read() #v = BACKEND_VERSIONS.get(v, v) assert (('assert __version__ == "%s"' % v) in content) + +def test_embedding_h(): + parent = os.path.dirname(os.path.dirname(cffi.__file__)) + v = cffi.__version__ + p = os.path.join(parent, 'cffi', '_embedding.h') + content = open(p).read() + assert ('cffi version: %s"' % (v,)) in content diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py @@ -1719,3 +1719,10 @@ exec("from _test_import_from_lib.lib import *", d) assert (set(key for key in d if not key.startswith('_')) == set(['myfunc', 'MYFOO'])) + # + # also test "import *" on the module itself, which should be + # equivalent to "import ffi, lib" + d = {} + exec("from _test_import_from_lib import *", d) + assert (sorted([x for x in d.keys() if not x.startswith('__')]) == + ['ffi', 'lib']) diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py @@ -60,11 +60,16 @@ if (name.endswith('.so') or name.endswith('.pyd') or name.endswith('.dylib')): found_so = os.path.join(curdir, name) - # foo.cpython-34m.so => foo - name = name.split('.')[0] - # foo_d.so => foo (Python 2 debug builds) + # foo.so => foo + parts = name.split('.') + del parts[-1] + if len(parts) > 1 and parts[-1] != 'bar': + # foo.cpython-34m.so => foo, but foo.bar.so => foo.bar + del parts[-1] + name = '.'.join(parts) + # foo_d => foo (Python 2 debug builds) if name.endswith('_d') and hasattr(sys, 'gettotalrefcount'): - name = name.rsplit('_', 1)[0] + name = name[:-2] name += '.SO' if name.startswith('pycparser') and name.endswith('.egg'): continue # no clue why this shows up sometimes and not others @@ -209,6 +214,58 @@ 'Release': '?'}}) @chdir_to_tmp + def test_api_compile_explicit_target_1(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(target="foo.bar.*") + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'foo.bar.SO': None, + 'mod_name_in_package': {'mymod.c': None, + 'mymod.o': None}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'foo.bar.SO': None, + 'mod_name_in_package': {'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp + def test_api_compile_explicit_target_2(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(target=os.path.join("mod_name_in_package", "foo.bar.*")) + if sys.platform != 'win32': + sofile = self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.SO': None, + 'mymod.c': None, + 'mymod.o': None}}) + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'mod_name_in_package': {'foo.bar.SO': None, + 'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp + def test_api_compile_explicit_target_3(self): + ffi = cffi.FFI() + ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") + x = ffi.compile(target="foo.bar.baz") + if sys.platform != 'win32': + self.check_produced_files({ + 'foo.bar.baz': None, + 'mod_name_in_package': {'mymod.c': None, + 'mymod.o': None}}) + sofile = os.path.join(str(self.udir), 'foo.bar.baz') + assert os.path.isabs(x) and os.path.samefile(x, sofile) + else: + self.check_produced_files({ + 'foo.bar.baz': None, + 'mod_name_in_package': {'mymod.c': None}, + 'Release': '?'}) + + @chdir_to_tmp def test_api_distutils_extension_1(self): ffi = cffi.FFI() ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/__init__.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/__init__.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/__init__.py @@ -0,0 +1,1 @@ +# Generated by pypy/tool/import_cffi.py diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1-test.c @@ -0,0 +1,13 @@ +#include <stdio.h> + +extern int add1(int, int); + + +int main(void) +{ + int x, y; + x = add1(40, 2); + y = add1(100, -5); + printf("got: %d %d\n", x, y); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add1.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add1.py @@ -0,0 +1,34 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + import sys, time + sys.stdout.write("preparing") + for i in range(3): + sys.stdout.flush() + time.sleep(0.02) + sys.stdout.write(".") + sys.stdout.write("\n") + + from _add1_cffi import ffi + + int(ord("A")) # check that built-ins are there + + @ffi.def_extern() + def add1(x, y): + sys.stdout.write("adding %d and %d\n" % (x, y)) + sys.stdout.flush() + return x + y +""") + +ffi.set_source("_add1_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add2-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/add2-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add2-test.c @@ -0,0 +1,14 @@ +#include <stdio.h> + +extern int add1(int, int); +extern int add2(int, int, int); + + +int main(void) +{ + int x, y; + x = add1(40, 2); + y = add2(100, -5, -20); + printf("got: %d %d\n", x, y); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add2.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/add2.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add2.py @@ -0,0 +1,30 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add2(int, int, int); +""") + +ffi.embedding_init_code(r""" + import sys + sys.stdout.write("prepADD2\n") + + assert '_add2_cffi' in sys.modules + m = sys.modules['_add2_cffi'] + import _add2_cffi + ffi = _add2_cffi.ffi + + @ffi.def_extern() + def add2(x, y, z): + sys.stdout.write("adding %d and %d and %d\n" % (x, y, z)) + sys.stdout.flush() + return x + y + z +""") + +ffi.set_source("_add2_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add3.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/add3.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add3.py @@ -0,0 +1,25 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add3(int, int, int, int); +""") + +ffi.embedding_init_code(r""" + from _add3_cffi import ffi + import sys + + @ffi.def_extern() + def add3(x, y, z, t): + sys.stdout.write("adding %d, %d, %d, %d\n" % (x, y, z, t)) + sys.stdout.flush() + return x + y + z + t +""") + +ffi.set_source("_add3_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive-test.c @@ -0,0 +1,27 @@ +#include <stdio.h> + +#ifdef _MSC_VER +# define DLLIMPORT __declspec(dllimport) +#else +# define DLLIMPORT extern +#endif + +DLLIMPORT int add_rec(int, int); +DLLIMPORT int (*my_callback)(int); + +static int some_callback(int x) +{ + printf("some_callback(%d)\n", x); + fflush(stdout); + return add_rec(x, 9); +} + +int main(void) +{ + int x, y; + my_callback = some_callback; + x = add_rec(40, 2); + y = add_rec(100, -5); + printf("got: %d %d\n", x, y); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/add_recursive.py @@ -0,0 +1,34 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int (*my_callback)(int); + int add_rec(int, int); +""") + +ffi.embedding_init_code(r""" + from _add_recursive_cffi import ffi, lib + import sys + print("preparing REC") + sys.stdout.flush() + + @ffi.def_extern() + def add_rec(x, y): + print("adding %d and %d" % (x, y)) + sys.stdout.flush() + return x + y + + x = lib.my_callback(400) + print('<<< %d >>>' % (x,)) +""") + +ffi.set_source("_add_recursive_cffi", """ +/* use CFFI_DLLEXPORT: on windows, it expands to __declspec(dllexport), + which is needed to export a variable from a dll */ +CFFI_DLLEXPORT int (*my_callback)(int); +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/perf-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/perf-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/perf-test.c @@ -0,0 +1,86 @@ +#include <stdio.h> +#include <assert.h> +#include <sys/time.h> +#ifdef PTEST_USE_THREAD +# include <pthread.h> +# include <semaphore.h> +static sem_t done; +#endif + + +extern int add1(int, int); + + +static double time_delta(struct timeval *stop, struct timeval *start) +{ + return (stop->tv_sec - start->tv_sec) + + 1e-6 * (stop->tv_usec - start->tv_usec); +} + +static double measure(void) +{ + long long i, iterations; + int result; + struct timeval start, stop; + double elapsed; + + add1(0, 0); /* prepare off-line */ + + i = 0; + iterations = 1000; + result = gettimeofday(&start, NULL); + assert(result == 0); + + while (1) { + for (; i < iterations; i++) { + add1(((int)i) & 0xaaaaaa, ((int)i) & 0x555555); + } + result = gettimeofday(&stop, NULL); + assert(result == 0); + + elapsed = time_delta(&stop, &start); + assert(elapsed >= 0.0); + if (elapsed > 2.5) + break; + iterations = iterations * 3 / 2; + } + + return elapsed / (double)iterations; +} + +static void *start_routine(void *arg) +{ + double t = measure(); + printf("time per call: %.3g\n", t); + +#ifdef PTEST_USE_THREAD + int status = sem_post(&done); + assert(status == 0); +#endif + + return arg; +} + + +int main(void) +{ +#ifndef PTEST_USE_THREAD + start_routine(0); +#else + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + add1(0, 0); /* this is the main thread */ + + for (i = 0; i < PTEST_USE_THREAD; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + for (i = 0; i < PTEST_USE_THREAD; i++) { + status = sem_wait(&done); + assert(status == 0); + } +#endif + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/perf.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/perf.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/perf.py @@ -0,0 +1,22 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + from _perf_cffi import ffi + + @ffi.def_extern() + def add1(x, y): + return x + y +""") + +ffi.set_source("_perf_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py @@ -0,0 +1,151 @@ +# Generated by pypy/tool/import_cffi.py +import py +import sys, os, re +import shutil, subprocess, time +from pypy.module.test_lib_pypy.cffi_tests.udir import udir +import cffi + +if hasattr(sys, 'gettotalrefcount'): + py.test.skip("tried hard and failed to have these tests run " + "in a debug-mode python") + + +local_dir = os.path.dirname(os.path.abspath(__file__)) +_link_error = '?' + +def check_lib_python_found(tmpdir): + global _link_error + if _link_error == '?': + ffi = cffi.FFI() + kwds = {} + ffi._apply_embedding_fix(kwds) + ffi.set_source("_test_lib_python_found", "", **kwds) + try: + ffi.compile(tmpdir=tmpdir, verbose=True) + except cffi.VerificationError as e: + _link_error = e + else: + _link_error = None + if _link_error: + py.test.skip(str(_link_error)) + + +class EmbeddingTests: + _compiled_modules = {} + + def setup_method(self, meth): + check_lib_python_found(str(udir.ensure('embedding', dir=1))) + self._path = udir.join('embedding', meth.__name__) + if sys.platform == "win32": + self._compiled_modules.clear() # workaround + + def get_path(self): + return str(self._path.ensure(dir=1)) + + def _run(self, args, env=None): + print(args) + popen = subprocess.Popen(args, env=env, cwd=self.get_path(), + stdout=subprocess.PIPE, + universal_newlines=True) + output = popen.stdout.read() + err = popen.wait() + if err: + raise OSError("popen failed with exit code %r: %r" % ( + err, args)) + print(output.rstrip()) + return output + + def prepare_module(self, name): + if name not in self._compiled_modules: + path = self.get_path() + filename = '%s.py' % name + # NOTE: if you have an .egg globally installed with an older + # version of cffi, this will not work, because sys.path ends + # up with the .egg before the PYTHONPATH entries. I didn't + # find a solution to that: we could hack sys.path inside the + # script run here, but we can't hack it in the same way in + # execute(). + env = os.environ.copy() + env['PYTHONPATH'] = os.path.dirname(os.path.dirname(local_dir)) + output = self._run([sys.executable, os.path.join(local_dir, filename)], + env=env) + match = re.compile(r"\bFILENAME: (.+)").search(output) + assert match + dynamic_lib_name = match.group(1) + if sys.platform == 'win32': + assert dynamic_lib_name.endswith('_cffi.dll') + else: + assert dynamic_lib_name.endswith('_cffi.so') + self._compiled_modules[name] = dynamic_lib_name + return self._compiled_modules[name] + + def compile(self, name, modules, opt=False, threads=False, defines={}): + path = self.get_path() + filename = '%s.c' % name + shutil.copy(os.path.join(local_dir, filename), path) + shutil.copy(os.path.join(local_dir, 'thread-test.h'), path) + import distutils.ccompiler + curdir = os.getcwd() + try: + os.chdir(self.get_path()) + c = distutils.ccompiler.new_compiler() + print('compiling %s with %r' % (name, modules)) + extra_preargs = [] + if sys.platform == 'win32': + libfiles = [] + for m in modules: + m = os.path.basename(m) + assert m.endswith('.dll') + libfiles.append('Release\\%s.lib' % m[:-4]) + modules = libfiles + elif threads: + extra_preargs.append('-pthread') + objects = c.compile([filename], macros=sorted(defines.items()), debug=True) + c.link_executable(objects + modules, name, extra_preargs=extra_preargs) + finally: + os.chdir(curdir) + + def execute(self, name): + path = self.get_path() + env = os.environ.copy() + env['PYTHONPATH'] = os.path.dirname(os.path.dirname(local_dir)) + libpath = env.get('LD_LIBRARY_PATH') + if libpath: + libpath = path + ':' + libpath + else: + libpath = path + env['LD_LIBRARY_PATH'] = libpath + print('running %r in %r' % (name, path)) + executable_name = name + if sys.platform == 'win32': + executable_name = os.path.join(path, executable_name + '.exe') + popen = subprocess.Popen([executable_name], cwd=path, env=env, + stdout=subprocess.PIPE, + universal_newlines=True) + result = popen.stdout.read() + err = popen.wait() + if err: + raise OSError("%r failed with exit code %r" % (name, err)) + return result + + +class TestBasic(EmbeddingTests): + def test_basic(self): + add1_cffi = self.prepare_module('add1') + self.compile('add1-test', [add1_cffi]) + output = self.execute('add1-test') + assert output == ("preparing...\n" + "adding 40 and 2\n" + "adding 100 and -5\n" + "got: 42 95\n") + + def test_two_modules(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('add2-test', [add1_cffi, add2_cffi]) + output = self.execute('add2-test') + assert output == ("preparing...\n" + "adding 40 and 2\n" + "prepADD2\n" + "adding 100 and -5 and -20\n" + "got: 42 75\n") diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_performance.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_performance.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_performance.py @@ -0,0 +1,53 @@ +# Generated by pypy/tool/import_cffi.py +import sys +from pypy.module.test_lib_pypy.cffi_tests.embedding.test_basic import EmbeddingTests + +if sys.platform == 'win32': + import py + py.test.skip("written with POSIX functions") + + +class TestPerformance(EmbeddingTests): + def test_perf_single_threaded(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_1_thread(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '1'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_2_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '2'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_4_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '4'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) + + def test_perf_in_8_threads(self): + perf_cffi = self.prepare_module('perf') + self.compile('perf-test', [perf_cffi], opt=True, threads=True, + defines={'PTEST_USE_THREAD': '8'}) + output = self.execute('perf-test') + print('='*79) + print(output.rstrip()) + print('='*79) diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_recursive.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_recursive.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_recursive.py @@ -0,0 +1,16 @@ +# Generated by pypy/tool/import_cffi.py +from pypy.module.test_lib_pypy.cffi_tests.embedding.test_basic import EmbeddingTests + + +class TestRecursive(EmbeddingTests): + def test_recursive(self): + add_recursive_cffi = self.prepare_module('add_recursive') + self.compile('add_recursive-test', [add_recursive_cffi]) + output = self.execute('add_recursive-test') + assert output == ("preparing REC\n" + "some_callback(400)\n" + "adding 400 and 9\n" + "<<< 409 >>>\n" + "adding 40 and 2\n" + "adding 100 and -5\n" + "got: 42 95\n") diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_thread.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_thread.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_thread.py @@ -0,0 +1,62 @@ +# Generated by pypy/tool/import_cffi.py +from pypy.module.test_lib_pypy.cffi_tests.embedding.test_basic import EmbeddingTests + + +class TestThread(EmbeddingTests): + def test_first_calls_in_parallel(self): + add1_cffi = self.prepare_module('add1') + self.compile('thread1-test', [add1_cffi], threads=True) + for i in range(50): + output = self.execute('thread1-test') + assert output == ("starting\n" + "preparing...\n" + + "adding 40 and 2\n" * 10 + + "done\n") + + def _take_out(self, text, content): + assert content in text + i = text.index(content) + return text[:i] + text[i+len(content):] + + def test_init_different_modules_in_different_threads(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('thread2-test', [add1_cffi, add2_cffi], threads=True) + output = self.execute('thread2-test') + output = self._take_out(output, "preparing") + output = self._take_out(output, ".") + output = self._take_out(output, ".") + # at least the 3rd dot should be after everything from ADD2 + assert output == ("starting\n" + "prepADD2\n" + "adding 1000 and 200 and 30\n" + ".\n" + "adding 40 and 2\n" + "done\n") + + def test_alt_issue(self): + add1_cffi = self.prepare_module('add1') + add2_cffi = self.prepare_module('add2') + self.compile('thread2-test', [add1_cffi, add2_cffi], + threads=True, defines={'T2TEST_AGAIN_ADD1': '1'}) + output = self.execute('thread2-test') + output = self._take_out(output, "adding 40 and 2\n") + assert output == ("starting\n" + "preparing...\n" + "adding -1 and -1\n" + "prepADD2\n" + "adding 1000 and 200 and 30\n" + "done\n") + + def test_load_in_parallel_more(self): + add2_cffi = self.prepare_module('add2') + add3_cffi = self.prepare_module('add3') + self.compile('thread3-test', [add2_cffi, add3_cffi], threads=True) + for i in range(150): + output = self.execute('thread3-test') + for j in range(10): + output = self._take_out(output, "adding 40 and 2 and 100\n") + output = self._take_out(output, "adding 1000, 200, 30, 4\n") + assert output == ("starting\n" + "prepADD2\n" + "done\n") diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_tlocal.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_tlocal.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_tlocal.py @@ -0,0 +1,11 @@ +# Generated by pypy/tool/import_cffi.py +from pypy.module.test_lib_pypy.cffi_tests.embedding.test_basic import EmbeddingTests + + +class TestThreadLocal(EmbeddingTests): + def test_thread_local(self): + tlocal_cffi = self.prepare_module('tlocal') + self.compile('tlocal-test', [tlocal_cffi], threads=True) + for i in range(10): + output = self.execute('tlocal-test') + assert output == "done\n" diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/thread-test.h b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread-test.h new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread-test.h @@ -0,0 +1,62 @@ +/************************************************************/ +#ifndef _MSC_VER +/************************************************************/ + + +#include <pthread.h> +#include <semaphore.h> + + +/************************************************************/ +#else +/************************************************************/ + + +/* Very quick and dirty, just what I need for these tests. + Don't use directly in any real code! +*/ + +#include <Windows.h> +#include <assert.h> + +typedef HANDLE sem_t; +typedef HANDLE pthread_t; + +int sem_init(sem_t *sem, int pshared, unsigned int value) +{ + assert(pshared == 0); + assert(value == 0); + *sem = CreateSemaphore(NULL, 0, 999, NULL); + return *sem ? 0 : -1; +} + +int sem_post(sem_t *sem) +{ + return ReleaseSemaphore(*sem, 1, NULL) ? 0 : -1; +} + +int sem_wait(sem_t *sem) +{ + WaitForSingleObject(*sem, INFINITE); + return 0; +} + +DWORD WINAPI myThreadProc(LPVOID lpParameter) +{ + void *(* start_routine)(void *) = (void *(*)(void *))lpParameter; + start_routine(NULL); + return 0; +} + +int pthread_create(pthread_t *thread, void *attr, + void *start_routine(void *), void *arg) +{ + assert(arg == NULL); + *thread = CreateThread(NULL, 0, myThreadProc, start_routine, 0, NULL); + return *thread ? 0 : -1; +} + + +/************************************************************/ +#endif +/************************************************************/ diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/thread1-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread1-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread1-test.c @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <assert.h> +#include "thread-test.h" + +#define NTHREADS 10 + + +extern int add1(int, int); + +static sem_t done; + + +static void *start_routine(void *arg) +{ + int x, status; + x = add1(40, 2); + assert(x == 42); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + for (i = 0; i < NTHREADS; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + for (i = 0; i < NTHREADS; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/thread2-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread2-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread2-test.c @@ -0,0 +1,57 @@ +#include <stdio.h> +#include <assert.h> +#include "thread-test.h" + +extern int add1(int, int); +extern int add2(int, int, int); + +static sem_t done; + + +static void *start_routine_1(void *arg) +{ + int x, status; + x = add1(40, 2); + assert(x == 42); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +static void *start_routine_2(void *arg) +{ + int x, status; +#ifdef T2TEST_AGAIN_ADD1 + add1(-1, -1); +#endif + x = add2(1000, 200, 30); + assert(x == 1230); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + status = pthread_create(&th, NULL, start_routine_1, NULL); + assert(status == 0); + status = pthread_create(&th, NULL, start_routine_2, NULL); + assert(status == 0); + + for (i = 0; i < 2; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/thread3-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread3-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/thread3-test.c @@ -0,0 +1,55 @@ +#include <stdio.h> +#include <assert.h> +#include "thread-test.h" + +extern int add2(int, int, int); +extern int add3(int, int, int, int); + +static sem_t done; + + +static void *start_routine_2(void *arg) +{ + int x, status; + x = add2(40, 2, 100); + assert(x == 142); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +static void *start_routine_3(void *arg) +{ + int x, status; + x = add3(1000, 200, 30, 4); + assert(x == 1234); + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + printf("starting\n"); + fflush(stdout); + for (i = 0; i < 10; i++) { + status = pthread_create(&th, NULL, start_routine_2, NULL); + assert(status == 0); + status = pthread_create(&th, NULL, start_routine_3, NULL); + assert(status == 0); + } + for (i = 0; i < 20; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal-test.c b/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal-test.c new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal-test.c @@ -0,0 +1,47 @@ +#include <stdio.h> +#include <assert.h> +#include "thread-test.h" + +#define NTHREADS 10 + + +extern int add1(int, int); + +static sem_t done; + + +static void *start_routine(void *arg) +{ + int i, x, expected, status; + + expected = add1(40, 2); + assert((expected % 1000) == 42); + + for (i=0; i<10; i++) { + x = add1(50, i); + assert(x == expected + 8 + i); + } + + status = sem_post(&done); + assert(status == 0); + + return arg; +} + +int main(void) +{ + pthread_t th; + int i, status = sem_init(&done, 0, 0); + assert(status == 0); + + for (i = 0; i < NTHREADS; i++) { + status = pthread_create(&th, NULL, start_routine, NULL); + assert(status == 0); + } + for (i = 0; i < NTHREADS; i++) { + status = sem_wait(&done); + assert(status == 0); + } + printf("done\n"); + return 0; +} diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal.py new file mode 100644 --- /dev/null +++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/tlocal.py @@ -0,0 +1,34 @@ +# Generated by pypy/tool/import_cffi.py +import cffi + +ffi = cffi.FFI() + +ffi.embedding_api(""" + int add1(int, int); +""") + +ffi.embedding_init_code(r""" + from _tlocal_cffi import ffi + import itertools + try: + import thread + g_seen = itertools.count().next + except ImportError: + import _thread as thread # py3 + g_seen = itertools.count().__next__ + tloc = thread._local() + + @ffi.def_extern() + def add1(x, y): + try: + num = tloc.num + except AttributeError: + num = tloc.num = g_seen() * 1000 + return x + y + num +""") + +ffi.set_source("_tlocal_cffi", """ +""") + +fn = ffi.compile(verbose=True) +print('FILENAME: %s' % (fn,)) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit