Author: Ronan Lamy <ronan.l...@gmail.com> Branch: py3.5 Changeset: r91988:b4c040585955 Date: 2017-07-28 15:33 +0100 http://bitbucket.org/pypy/pypy/changeset/b4c040585955/
Log: hg merge default diff too long, truncating to 2000 out of 19088 lines diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -27,16 +27,17 @@ ^pypy/module/cpyext/test/.+\.manifest$ ^pypy/module/test_lib_pypy/ctypes_tests/.+\.o$ ^pypy/module/test_lib_pypy/ctypes_tests/_ctypes_test\.o$ -^pypy/module/cppyy/src/.+\.o$ -^pypy/module/cppyy/bench/.+\.so$ -^pypy/module/cppyy/bench/.+\.root$ -^pypy/module/cppyy/bench/.+\.d$ -^pypy/module/cppyy/src/.+\.errors$ -^pypy/module/cppyy/test/.+_rflx\.cpp$ -^pypy/module/cppyy/test/.+\.so$ -^pypy/module/cppyy/test/.+\.rootmap$ -^pypy/module/cppyy/test/.+\.exe$ -^pypy/module/cppyy/test/.+_cint.h$ +^pypy/module/_cppyy/src/.+\.o$ +^pypy/module/_cppyy/bench/.+\.so$ +^pypy/module/_cppyy/bench/.+\.root$ +^pypy/module/_cppyy/bench/.+\.d$ +^pypy/module/_cppyy/src/.+\.errors$ +^pypy/module/_cppyy/test/.+_rflx\.cpp$ +^pypy/module/_cppyy/test/.+\.so$ +^pypy/module/_cppyy/test/.+\.rootmap$ +^pypy/module/_cppyy/test/.+\.exe$ +^pypy/module/_cppyy/test/.+_cint.h$ +^pypy/module/_cppyy/.+/*\.pcm$ ^pypy/module/test_lib_pypy/cffi_tests/__pycache__.+$ ^pypy/doc/.+\.html$ ^pypy/doc/config/.+\.rst$ @@ -93,6 +94,3 @@ ^release/ ^rpython/_cache$ -pypy/module/cppyy/.+/*\.pcm - - diff --git a/lib_pypy/_tkinter/tklib_build.py b/lib_pypy/_tkinter/tklib_build.py --- a/lib_pypy/_tkinter/tklib_build.py +++ b/lib_pypy/_tkinter/tklib_build.py @@ -22,12 +22,27 @@ linklibs = ['tcl', 'tk'] libdirs = [] else: - for _ver in ['', '8.6', '8.5', '']: + # On some Linux distributions, the tcl and tk libraries are + # stored in /usr/include, so we must check this case also + libdirs = [] + found = False + for _ver in ['', '8.6', '8.5']: incdirs = ['/usr/include/tcl' + _ver] linklibs = ['tcl' + _ver, 'tk' + _ver] - libdirs = [] if os.path.isdir(incdirs[0]): + found = True break + if not found: + for _ver in ['8.6', '8.5', '']: + incdirs = [] + linklibs = ['tcl' + _ver, 'tk' + _ver] + if os.path.isfile(''.join(['/usr/lib/lib', linklibs[1], '.so'])): + found = True + break + if not found: + sys.stderr.write("*** TCL libraries not found! Falling back...\n") + incdirs = [] + linklibs = ['tcl', 'tk'] config_ffi = FFI() config_ffi.cdef(""" 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 @@ -95,6 +95,7 @@ #define _cffi_from_c_ulong PyLong_FromUnsignedLong #define _cffi_from_c_longlong PyLong_FromLongLong #define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong #define _cffi_to_c_double PyFloat_AsDouble #define _cffi_to_c_float PyFloat_AsDouble diff --git a/lib_pypy/cffi/_embedding.h b/lib_pypy/cffi/_embedding.h --- a/lib_pypy/cffi/_embedding.h +++ b/lib_pypy/cffi/_embedding.h @@ -1,7 +1,12 @@ /***** Support code for embedding *****/ -#if defined(_MSC_VER) +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(_WIN32) # define CFFI_DLLEXPORT __declspec(dllexport) #elif defined(__GNUC__) # define CFFI_DLLEXPORT __attribute__((visibility("default"))) @@ -525,3 +530,7 @@ #undef cffi_compare_and_swap #undef cffi_write_barrier #undef cffi_read_barrier + +#ifdef __cplusplus +} +#endif 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 @@ -412,6 +412,9 @@ prnt(' }') prnt(' p[0] = (const void *)0x%x;' % self._version) prnt(' p[1] = &_cffi_type_context;') + prnt('#if PY_MAJOR_VERSION >= 3') + prnt(' return NULL;') + prnt('#endif') prnt('}') # on Windows, distutils insists on putting init_cffi_xyz in # 'export_symbols', so instead of fighting it, just give up and @@ -578,7 +581,7 @@ def _convert_expr_from_c(self, tp, var, context): if isinstance(tp, model.BasePrimitiveType): - if tp.is_integer_type(): + if tp.is_integer_type() and tp.name != '_Bool': return '_cffi_from_c_int(%s, %s)' % (var, tp.name) elif isinstance(tp, model.UnknownFloatType): return '_cffi_from_c_double(%s)' % (var,) 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 @@ -296,7 +296,7 @@ def _convert_expr_from_c(self, tp, var, context): if isinstance(tp, model.PrimitiveType): - if tp.is_integer_type(): + if tp.is_integer_type() and tp.name != '_Bool': return '_cffi_from_c_int(%s, %s)' % (var, tp.name) elif tp.name != 'long double': return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) @@ -872,6 +872,7 @@ #define _cffi_from_c_ulong PyLong_FromUnsignedLong #define _cffi_from_c_longlong PyLong_FromLongLong #define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong #define _cffi_to_c_double PyFloat_AsDouble #define _cffi_to_c_float PyFloat_AsDouble diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -39,7 +39,7 @@ "thread", "itertools", "pyexpat", "cpyext", "array", "binascii", "_multiprocessing", '_warnings', "_collections", "_multibytecodec", "_continuation", "_cffi_backend", - "_csv", "_pypyjson", "_posixsubprocess", # "cppyy", "micronumpy" + "_csv", "_pypyjson", "_posixsubprocess", # "_cppyy", "micronumpy" "_jitlog", ]) @@ -71,8 +71,8 @@ if name in translation_modules: translation_modules.remove(name) - if "cppyy" in working_modules: - working_modules.remove("cppyy") # not tested on win32 + if "_cppyy" in working_modules: + working_modules.remove("_cppyy") # not tested on win32 # The _locale module is needed by site.py on Windows default_modules.add("_locale") @@ -81,8 +81,8 @@ working_modules.remove('fcntl') # LOCK_NB not defined working_modules.remove("_minimal_curses") working_modules.remove("termios") - if "cppyy" in working_modules: - working_modules.remove("cppyy") # depends on ctypes + if "_cppyy" in working_modules: + working_modules.remove("_cppyy") # depends on ctypes #if sys.platform.startswith("linux"): # _mach = os.popen('uname -m', 'r').read().strip() @@ -94,7 +94,7 @@ '_multiprocessing': [('objspace.usemodules.time', True), ('objspace.usemodules.thread', True)], 'cpyext': [('objspace.usemodules.array', True)], - 'cppyy': [('objspace.usemodules.cpyext', True)], + '_cppyy': [('objspace.usemodules.cpyext', True)], 'faulthandler': [('objspace.usemodules._vmprof', True)], } module_suggests = { diff --git a/pypy/doc/cppyy.rst b/pypy/doc/cppyy.rst deleted file mode 100644 --- a/pypy/doc/cppyy.rst +++ /dev/null @@ -1,672 +0,0 @@ -cppyy: C++ bindings for PyPy -============================ - -The cppyy module delivers dynamic Python-C++ bindings. -It is designed for automation, high performance, scale, interactivity, and -handling all of modern C++ (11, 14, etc.). -It is based on `Cling`_ which, through `LLVM`_/`clang`_, provides C++ -reflection and interactivity. -Reflection information is extracted from C++ header files. -Cppyy itself is built into PyPy (an alternative exists for CPython), but -it requires a `backend`_, installable through pip, to interface with Cling. - -.. _Cling: https://root.cern.ch/cling -.. _LLVM: http://llvm.org/ -.. _clang: http://clang.llvm.org/ -.. _backend: https://pypi.python.org/pypi/PyPy-cppyy-backend - - -Installation ------------- - -This assumes PyPy2.7 v5.7 or later; earlier versions use a Reflex-based cppyy -module, which is no longer supported. -Both the tooling and user-facing Python codes are very backwards compatible, -however. -Further dependencies are cmake (for general build), Python2.7 (for LLVM), and -a modern C++ compiler (one that supports at least C++11). - -Assuming you have a recent enough version of PyPy installed, use pip to -complete the installation of cppyy:: - - $ MAKE_NPROCS=4 pypy-c -m pip install --verbose PyPy-cppyy-backend - -Set the number of parallel builds ('4' in this example, through the MAKE_NPROCS -environment variable) to a number appropriate for your machine. -The building process may take quite some time as it includes a customized -version of LLVM as part of Cling, which is why --verbose is recommended so that -you can see the build progress. - -The default installation will be under -$PYTHONHOME/site-packages/cppyy_backend/lib, -which needs to be added to your dynamic loader path (LD_LIBRARY_PATH). -If you need the dictionary and class map generation tools (used in the examples -below), you need to add $PYTHONHOME/site-packages/cppyy_backend/bin to your -executable path (PATH). - - -Basic bindings example ----------------------- - -These examples assume that cppyy_backend is pointed to by the environment -variable CPPYYHOME, and that CPPYYHOME/lib is added to LD_LIBRARY_PATH and -CPPYYHOME/bin to PATH. - -Let's first test with a trivial example whether all packages are properly -installed and functional. -Create a C++ header file with some class in it (all functions are made inline -for convenience; if you have out-of-line code, link with it as appropriate):: - - $ cat MyClass.h - class MyClass { - public: - MyClass(int i = -99) : m_myint(i) {} - - int GetMyInt() { return m_myint; } - void SetMyInt(int i) { m_myint = i; } - - public: - int m_myint; - }; - -Then, generate the bindings using ``genreflex`` (installed under -cppyy_backend/bin in site_packages), and compile the code:: - - $ genreflex MyClass.h - $ g++ -std=c++11 -fPIC -rdynamic -O2 -shared -I$CPPYYHOME/include MyClass_rflx.cpp -o libMyClassDict.so -L$CPPYYHOME/lib -lCling - -Next, make sure that the library can be found through the dynamic lookup path -(the ``LD_LIBRARY_PATH`` environment variable on Linux, ``PATH`` on Windows), -for example by adding ".". -Now you're ready to use the bindings. -Since the bindings are designed to look pythonistic, it should be -straightforward:: - - $ pypy-c - >>>> import cppyy - >>>> cppyy.load_reflection_info("libMyClassDict.so") - <CPPLibrary object at 0xb6fd7c4c> - >>>> myinst = cppyy.gbl.MyClass(42) - >>>> print myinst.GetMyInt() - 42 - >>>> myinst.SetMyInt(33) - >>>> print myinst.m_myint - 33 - >>>> myinst.m_myint = 77 - >>>> print myinst.GetMyInt() - 77 - >>>> help(cppyy.gbl.MyClass) # shows that normal python introspection works - -That's all there is to it! - - -Automatic class loader ----------------------- - -There is one big problem in the code above, that prevents its use in a (large -scale) production setting: the explicit loading of the reflection library. -Clearly, if explicit load statements such as these show up in code downstream -from the ``MyClass`` package, then that prevents the ``MyClass`` author from -repackaging or even simply renaming the dictionary library. - -The solution is to make use of an automatic class loader, so that downstream -code never has to call ``load_reflection_info()`` directly. -The class loader makes use of so-called rootmap files, which ``genreflex`` -can produce. -These files contain the list of available C++ classes and specify the library -that needs to be loaded for their use (as an aside, this listing allows for a -cross-check to see whether reflection info is generated for all classes that -you expect). -By convention, the rootmap files should be located next to the reflection info -libraries, so that they can be found through the normal shared library search -path. -They can be concatenated together, or consist of a single rootmap file per -library. -For example:: - - $ genreflex MyClass.h --rootmap=libMyClassDict.rootmap --rootmap-lib=libMyClassDict.so - $ g++ -std=c++11 -fPIC -rdynamic -O2 -shared -I$CPPYYHOME/include MyClass_rflx.cpp -o libMyClassDict.so -L$CPPYYHOME/lib -lCling - -where the first option (``--rootmap``) specifies the output file name, and the -second option (``--rootmap-lib``) the name of the reflection library where -``MyClass`` will live. -It is necessary to provide that name explicitly, since it is only in the -separate linking step where this name is fixed. -If the second option is not given, the library is assumed to be libMyClass.so, -a name that is derived from the name of the header file. - -With the rootmap file in place, the above example can be rerun without explicit -loading of the reflection info library:: - - $ pypy-c - >>>> import cppyy - >>>> myinst = cppyy.gbl.MyClass(42) - >>>> print myinst.GetMyInt() - 42 - >>>> # etc. ... - -As a caveat, note that the class loader is currently limited to classes only. - - -Advanced example ----------------- - -The following snippet of C++ is very contrived, to allow showing that such -pathological code can be handled and to show how certain features play out in -practice:: - - $ cat MyAdvanced.h - #include <string> - - class Base1 { - public: - Base1(int i) : m_i(i) {} - virtual ~Base1() {} - int m_i; - }; - - class Base2 { - public: - Base2(double d) : m_d(d) {} - virtual ~Base2() {} - double m_d; - }; - - class C; - - class Derived : public virtual Base1, public virtual Base2 { - public: - Derived(const std::string& name, int i, double d) : Base1(i), Base2(d), m_name(name) {} - virtual C* gimeC() { return (C*)0; } - std::string m_name; - }; - - Base2* BaseFactory(const std::string& name, int i, double d) { - return new Derived(name, i, d); - } - -This code is still only in a header file, with all functions inline, for -convenience of the example. -If the implementations live in a separate source file or shared library, the -only change needed is to link those in when building the reflection library. - -If you were to run ``genreflex`` like above in the basic example, you will -find that not all classes of interest will be reflected, nor will be the -global factory function. -In particular, ``std::string`` will be missing, since it is not defined in -this header file, but in a header file that is included. -In practical terms, general classes such as ``std::string`` should live in a -core reflection set, but for the moment assume we want to have it in the -reflection library that we are building for this example. - -The ``genreflex`` script can be steered using a so-called `selection file`_ -(see "Generating Reflex Dictionaries") -which is a simple XML file specifying, either explicitly or by using a -pattern, which classes, variables, namespaces, etc. to select from the given -header file. -With the aid of a selection file, a large project can be easily managed: -simply ``#include`` all relevant headers into a single header file that is -handed to ``genreflex``. -In fact, if you hand multiple header files to ``genreflex``, then a selection -file is almost obligatory: without it, only classes from the last header will -be selected. -Then, apply a selection file to pick up all the relevant classes. -For our purposes, the following rather straightforward selection will do -(the name ``lcgdict`` for the root is historical, but required):: - - $ cat MyAdvanced.xml - <lcgdict> - <class pattern="Base?" /> - <class name="Derived" /> - <class name="std::string" /> - <function name="BaseFactory" /> - </lcgdict> - -.. _selection file: https://root.cern.ch/how/how-use-reflex - -Now the reflection info can be generated and compiled:: - - $ genreflex MyAdvanced.h --selection=MyAdvanced.xml - $ g++ -std=c++11 -fPIC -rdynamic -O2 -shared -I$CPPYYHOME/include MyAdvanced_rflx.cpp -o libAdvExDict.so -L$CPPYYHOME/lib -lCling - -and subsequently be used from PyPy:: - - >>>> import cppyy - >>>> cppyy.load_reflection_info("libAdvExDict.so") - <CPPLibrary object at 0x00007fdb48fc8120> - >>>> d = cppyy.gbl.BaseFactory("name", 42, 3.14) - >>>> type(d) - <class '__main__.Derived'> - >>>> isinstance(d, cppyy.gbl.Base1) - True - >>>> isinstance(d, cppyy.gbl.Base2) - True - >>>> d.m_i, d.m_d - (42, 3.14) - >>>> d.m_name == "name" - True - >>>> - -Again, that's all there is to it! - -A couple of things to note, though. -If you look back at the C++ definition of the ``BaseFactory`` function, -you will see that it declares the return type to be a ``Base2``, yet the -bindings return an object of the actual type ``Derived``? -This choice is made for a couple of reasons. -First, it makes method dispatching easier: if bound objects are always their -most derived type, then it is easy to calculate any offsets, if necessary. -Second, it makes memory management easier: the combination of the type and -the memory address uniquely identifies an object. -That way, it can be recycled and object identity can be maintained if it is -entered as a function argument into C++ and comes back to PyPy as a return -value. -Last, but not least, casting is decidedly unpythonistic. -By always providing the most derived type known, casting becomes unnecessary. -For example, the data member of ``Base2`` is simply directly available. -Note also that the unreflected ``gimeC`` method of ``Derived`` does not -preclude its use. -It is only the ``gimeC`` method that is unusable as long as class ``C`` is -unknown to the system. - - -Features --------- - -The following is not meant to be an exhaustive list, since cppyy is still -under active development. -Furthermore, the intention is that every feature is as natural as possible on -the python side, so if you find something missing in the list below, simply -try it out. -It is not always possible to provide exact mapping between python and C++ -(active memory management is one such case), but by and large, if the use of a -feature does not strike you as obvious, it is more likely to simply be a bug. -That is a strong statement to make, but also a worthy goal. -For the C++ side of the examples, refer to this :doc:`example code <cppyy_example>`, which was -bound using:: - - $ genreflex example.h --deep --rootmap=libexampleDict.rootmap --rootmap-lib=libexampleDict.so - $ g++ -std=c++11 -fPIC -rdynamic -O2 -shared -I$CPPYYHOME/include example_rflx.cpp -o libexampleDict.so -L$CPPYYHOME/lib -lCling - -* **abstract classes**: Are represented as python classes, since they are - needed to complete the inheritance hierarchies, but will raise an exception - if an attempt is made to instantiate from them. - Example:: - - >>>> from cppyy.gbl import AbstractClass, ConcreteClass - >>>> a = AbstractClass() - Traceback (most recent call last): - File "<console>", line 1, in <module> - TypeError: cannot instantiate abstract class 'AbstractClass' - >>>> issubclass(ConcreteClass, AbstractClass) - True - >>>> c = ConcreteClass() - >>>> isinstance(c, AbstractClass) - True - >>>> - -* **arrays**: Supported for builtin data types only, as used from module - ``array``. - Out-of-bounds checking is limited to those cases where the size is known at - compile time (and hence part of the reflection info). - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> from array import array - >>>> c = ConcreteClass() - >>>> c.array_method(array('d', [1., 2., 3., 4.]), 4) - 1 2 3 4 - >>>> - -* **builtin data types**: Map onto the expected equivalent python types, with - the caveat that there may be size differences, and thus it is possible that - exceptions are raised if an overflow is detected. - -* **casting**: Is supposed to be unnecessary. - Object pointer returns from functions provide the most derived class known - in the hierarchy of the object being returned. - This is important to preserve object identity as well as to make casting, - a pure C++ feature after all, superfluous. - Example:: - - >>>> from cppyy.gbl import AbstractClass, ConcreteClass - >>>> c = ConcreteClass() - >>>> ConcreteClass.show_autocast.__doc__ - 'AbstractClass* ConcreteClass::show_autocast()' - >>>> d = c.show_autocast() - >>>> type(d) - <class '__main__.ConcreteClass'> - >>>> - - However, if need be, you can perform C++-style reinterpret_casts (i.e. - without taking offsets into account), by taking and rebinding the address - of an object:: - - >>>> from cppyy import addressof, bind_object - >>>> e = bind_object(addressof(d), AbstractClass) - >>>> type(e) - <class '__main__.AbstractClass'> - >>>> - -* **classes and structs**: Get mapped onto python classes, where they can be - instantiated as expected. - If classes are inner classes or live in a namespace, their naming and - location will reflect that. - Example:: - - >>>> from cppyy.gbl import ConcreteClass, Namespace - >>>> ConcreteClass == Namespace.ConcreteClass - False - >>>> n = Namespace.ConcreteClass.NestedClass() - >>>> type(n) - <class '__main__.Namespace::ConcreteClass::NestedClass'> - >>>> - -* **data members**: Public data members are represented as python properties - and provide read and write access on instances as expected. - Private and protected data members are not accessible. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> c = ConcreteClass() - >>>> c.m_int - 42 - >>>> - -* **default arguments**: C++ default arguments work as expected, but python - keywords are not supported. - It is technically possible to support keywords, but for the C++ interface, - the formal argument names have no meaning and are not considered part of the - API, hence it is not a good idea to use keywords. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> c = ConcreteClass() # uses default argument - >>>> c.m_int - 42 - >>>> c = ConcreteClass(13) - >>>> c.m_int - 13 - >>>> - -* **doc strings**: The doc string of a method or function contains the C++ - arguments and return types of all overloads of that name, as applicable. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> print ConcreteClass.array_method.__doc__ - void ConcreteClass::array_method(int*, int) - void ConcreteClass::array_method(double*, int) - >>>> - -* **enums**: Are translated as ints with no further checking. - -* **functions**: Work as expected and live in their appropriate namespace - (which can be the global one, ``cppyy.gbl``). - -* **inheritance**: All combinations of inheritance on the C++ (single, - multiple, virtual) are supported in the binding. - However, new python classes can only use single inheritance from a bound C++ - class. - Multiple inheritance would introduce two "this" pointers in the binding. - This is a current, not a fundamental, limitation. - The C++ side will not see any overridden methods on the python side, as - cross-inheritance is planned but not yet supported. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> help(ConcreteClass) - Help on class ConcreteClass in module __main__: - - class ConcreteClass(AbstractClass) - | Method resolution order: - | ConcreteClass - | AbstractClass - | cppyy.CPPObject - | __builtin__.CPPInstance - | __builtin__.object - | - | Methods defined here: - | - | ConcreteClass(self, *args) - | ConcreteClass::ConcreteClass(const ConcreteClass&) - | ConcreteClass::ConcreteClass(int) - | ConcreteClass::ConcreteClass() - | - etc. .... - -* **memory**: C++ instances created by calling their constructor from python - are owned by python. - You can check/change the ownership with the _python_owns flag that every - bound instance carries. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> c = ConcreteClass() - >>>> c._python_owns # True: object created in Python - True - >>>> - -* **methods**: Are represented as python methods and work as expected. - They are first class objects and can be bound to an instance. - Virtual C++ methods work as expected. - To select a specific virtual method, do like with normal python classes - that override methods: select it from the class that you need, rather than - calling the method on the instance. - To select a specific overload, use the __dispatch__ special function, which - takes the name of the desired method and its signature (which can be - obtained from the doc string) as arguments. - -* **namespaces**: Are represented as python classes. - Namespaces are more open-ended than classes, so sometimes initial access may - result in updates as data and functions are looked up and constructed - lazily. - Thus the result of ``dir()`` on a namespace shows the classes available, - even if they may not have been created yet. - It does not show classes that could potentially be loaded by the class - loader. - Once created, namespaces are registered as modules, to allow importing from - them. - Namespace currently do not work with the class loader. - Fixing these bootstrap problems is on the TODO list. - The global namespace is ``cppyy.gbl``. - -* **NULL**: Is represented as ``cppyy.gbl.nullptr``. - In C++11, the keyword ``nullptr`` is used to represent ``NULL``. - For clarity of intent, it is recommended to use this instead of ``None`` - (or the integer ``0``, which can serve in some cases), as ``None`` is better - understood as ``void`` in C++. - -* **operator conversions**: If defined in the C++ class and a python - equivalent exists (i.e. all builtin integer and floating point types, as well - as ``bool``), it will map onto that python conversion. - Note that ``char*`` is mapped onto ``__str__``. - Example:: - - >>>> from cppyy.gbl import ConcreteClass - >>>> print ConcreteClass() - Hello operator const char*! - >>>> - -* **operator overloads**: If defined in the C++ class and if a python - equivalent is available (not always the case, think e.g. of ``operator||``), - then they work as expected. - Special care needs to be taken for global operator overloads in C++: first, - make sure that they are actually reflected, especially for the global - overloads for ``operator==`` and ``operator!=`` of STL vector iterators in - the case of gcc (note that they are not needed to iterate over a vector). - Second, make sure that reflection info is loaded in the proper order. - I.e. that these global overloads are available before use. - -* **pointers**: For builtin data types, see arrays. - For objects, a pointer to an object and an object looks the same, unless - the pointer is a data member. - In that case, assigning to the data member will cause a copy of the pointer - and care should be taken about the object's life time. - If a pointer is a global variable, the C++ side can replace the underlying - object and the python side will immediately reflect that. - -* **PyObject***: Arguments and return types of ``PyObject*`` can be used, and - passed on to CPython API calls. - Since these CPython-like objects need to be created and tracked (this all - happens through ``cpyext``) this interface is not particularly fast. - -* **static data members**: Are represented as python property objects on the - class and the meta-class. - Both read and write access is as expected. - -* **static methods**: Are represented as python's ``staticmethod`` objects - and can be called both from the class as well as from instances. - -* **strings**: The std::string class is considered a builtin C++ type and - mixes quite well with python's str. - Python's str can be passed where a ``const char*`` is expected, and an str - will be returned if the return type is ``const char*``. - -* **templated classes**: Are represented in a meta-class style in python. - This may look a little bit confusing, but conceptually is rather natural. - For example, given the class ``std::vector<int>``, the meta-class part would - be ``std.vector``. - Then, to get the instantiation on ``int``, do ``std.vector(int)`` and to - create an instance of that class, do ``std.vector(int)()``:: - - >>>> import cppyy - >>>> cppyy.load_reflection_info('libexampleDict.so') - >>>> cppyy.gbl.std.vector # template metatype - <cppyy.CppyyTemplateType object at 0x00007fcdd330f1a0> - >>>> cppyy.gbl.std.vector(int) # instantiates template -> class - <class '__main__.std::vector<int>'> - >>>> cppyy.gbl.std.vector(int)() # instantiates class -> object - <__main__.std::vector<int> object at 0x00007fe480ba4bc0> - >>>> - - Note that templates can be build up by handing actual types to the class - instantiation (as done in this vector example), or by passing in the list of - template arguments as a string. - The former is a lot easier to work with if you have template instantiations - using classes that themselves are templates in the arguments (think e.g a - vector of vectors). - All template classes must already exist in the loaded reflection info, they - do not work (yet) with the class loader. - - For compatibility with other bindings generators, use of square brackets - instead of parenthesis to instantiate templates is supported as well. - -* **templated functions**: Automatically participate in overloading and are - used in the same way as other global functions. - -* **templated methods**: For now, require an explicit selection of the - template parameters. - This will be changed to allow them to participate in overloads as expected. - -* **typedefs**: Are simple python references to the actual classes to which - they refer. - -* **unary operators**: Are supported if a python equivalent exists, and if the - operator is defined in the C++ class. - -You can always find more detailed examples and see the full of supported -features by looking at the tests in pypy/module/cppyy/test. - -If a feature or reflection info is missing, this is supposed to be handled -gracefully. -In fact, there are unit tests explicitly for this purpose (even as their use -becomes less interesting over time, as the number of missing features -decreases). -Only when a missing feature is used, should there be an exception. -For example, if no reflection info is available for a return type, then a -class that has a method with that return type can still be used. -Only that one specific method can not be used. - - -Templates ---------- - -Templates can be automatically instantiated, assuming the appropriate header -files have been loaded or are accessible to the class loader. -This is the case for example for all of STL. -For example:: - - $ cat MyTemplate.h - #include <vector> - - class MyClass { - public: - MyClass(int i = -99) : m_i(i) {} - MyClass(const MyClass& s) : m_i(s.m_i) {} - MyClass& operator=(const MyClass& s) { m_i = s.m_i; return *this; } - ~MyClass() {} - int m_i; - }; - -Run the normal ``genreflex`` and compilation steps:: - - $ genreflex MyTemplate.h --selection=MyTemplate.xml - $ g++ -std=c++11 -fPIC -rdynamic -O2 -shared -I$CPPYYHOME/include MyTemplate_rflx.cpp -o libTemplateDict.so -L$CPPYYHOME/lib -lCling - -Subsequent use should be as expected. -Note the meta-class style of "instantiating" the template:: - - >>>> import cppyy - >>>> cppyy.load_reflection_info("libTemplateDict.so") - >>>> std = cppyy.gbl.std - >>>> MyClass = cppyy.gbl.MyClass - >>>> v = std.vector(MyClass)() - >>>> v += [MyClass(1), MyClass(2), MyClass(3)] - >>>> for m in v: - .... print m.m_i, - .... - 1 2 3 - >>>> - -The arguments to the template instantiation can either be a string with the -full list of arguments, or the explicit classes. -The latter makes for easier code writing if the classes passed to the -instantiation are themselves templates. - - -The fast lane -------------- - -By default, cppyy will use direct function pointers through `CFFI`_ whenever -possible. If this causes problems for you, you can disable it by setting the -CPPYY_DISABLE_FASTPATH environment variable. - -.. _CFFI: https://cffi.readthedocs.io/en/latest/ - - -CPython -------- - -Most of the ideas in cppyy come originally from the `PyROOT`_ project, which -contains a CPython-based cppyy.py module (with similar dependencies as the -one that comes with PyPy). -A standalone pip-installable version is planned, but for now you can install -ROOT through your favorite distribution installer (available in the science -section). - -.. _PyROOT: https://root.cern.ch/pyroot - -There are a couple of minor differences between the two versions of cppyy -(the CPython version has a few more features). -Work is on-going to integrate the nightly tests of both to make sure their -feature sets are equalized. - - -Python3 -------- - -The CPython version of cppyy supports Python3, assuming your packager has -build the backend for it. -The cppyy module has not been tested with the `Py3k`_ version of PyPy. -Note that the generated reflection information (from ``genreflex``) is fully -independent of Python, and does not need to be rebuild when switching versions -or interpreters. - -.. _Py3k: https://bitbucket.org/pypy/pypy/src/py3k - - -.. toctree:: - :hidden: - - cppyy_example diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst --- a/pypy/doc/cpython_differences.rst +++ b/pypy/doc/cpython_differences.rst @@ -337,6 +337,8 @@ - ``frozenset`` (empty frozenset only) + - unbound method objects (for Python 2 only) + This change requires some changes to ``id`` as well. ``id`` fulfills the following condition: ``x is y <=> id(x) == id(y)``. Therefore ``id`` of the above types will return a value that is computed from the argument, and can diff --git a/pypy/doc/extending.rst b/pypy/doc/extending.rst --- a/pypy/doc/extending.rst +++ b/pypy/doc/extending.rst @@ -61,29 +61,23 @@ .. _libffi: http://sourceware.org/libffi/ -Cling and cppyy ---------------- +cppyy +----- -The builtin :doc:`cppyy <cppyy>` module uses reflection information, provided by -`Cling`_ (which needs to be `installed separately`_), of C/C++ code to -automatically generate bindings at runtime. -In Python, classes and functions are always runtime structures, so when they -are generated matters not for performance. -However, if the backend itself is capable of dynamic behavior, it is a much -better functional match, allowing tighter integration and more natural -language mappings. +For C++, `cppyy`_ is an automated bindings generator available for both +PyPy and CPython. +``cppyy`` relies on declarations from C++ header files to dynamically +construct Python equivalent classes, functions, variables, etc. +It is designed for use by large scale programs and supports modern C++. +With PyPy, it leverages the built-in ``_cppyy`` module, allowing the JIT to +remove most of the cross-language overhead. -The :doc:`cppyy <cppyy>` module is written in RPython, thus PyPy's JIT is able to remove -most cross-language call overhead. +To install, run ``pip install cppyy``. +Further details are available in the `full documentation`_. -:doc:Full details are `available here <cppyy>`. +.. _cppyy: http://cppyy.readthedocs.org/ +.. _`full documentation`: http://cppyy.readthedocs.org/ -.. _installed separately: https://pypi.python.org/pypi/PyPy-cppyy-backend -.. _Cling: https://root.cern.ch/cling - -.. toctree:: - - cppyy RPython Mixed Modules --------------------- diff --git a/pypy/doc/how-to-release.rst b/pypy/doc/how-to-release.rst --- a/pypy/doc/how-to-release.rst +++ b/pypy/doc/how-to-release.rst @@ -40,6 +40,9 @@ sure things are ported back to the trunk and to the branch as necessary. +* Maybe bump the SOABI number in module/imp/importing. This has many + implications, so make sure the PyPy community agrees to the change. + * Update and write documentation * update pypy/doc/contributor.rst (and possibly LICENSE) diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -25,3 +25,8 @@ .. branch: cpyext-hash_notimpl If ``tp_hash`` is ``PyObject_HashNotImplemented``, set ``obj.__dict__['__hash__']`` to None + +.. branch: cppyy-packaging + +Renaming of ``cppyy`` to ``_cppyy``. +The former is now an external package installable with ``pip install cppyy``. 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 @@ -3838,6 +3838,7 @@ assert result == samples for i in range(len(samples)): assert result[i] == p[i] and type(result[i]) is type(p[i]) + assert (type(result[i]) is bool) == (type(samples[i]) is bool) # BInt = new_primitive_type("int") py.test.raises(TypeError, unpack, p) diff --git a/pypy/module/cppyy/__init__.py b/pypy/module/_cppyy/__init__.py rename from pypy/module/cppyy/__init__.py rename to pypy/module/_cppyy/__init__.py --- a/pypy/module/cppyy/__init__.py +++ b/pypy/module/_cppyy/__init__.py @@ -33,11 +33,11 @@ # pythonization functions may be written in RPython, but the interp2app # code generation is not, so give it a chance to run now - from pypy.module.cppyy import capi + from pypy.module._cppyy import capi capi.register_pythonizations(space) def startup(self, space): - from pypy.module.cppyy import capi + from pypy.module._cppyy import capi capi.verify_backend(space) # may raise ImportError space.call_method(self, '_init_pythonify') diff --git a/pypy/module/cppyy/backend/create_cppyy_package.py b/pypy/module/_cppyy/backend/create_cppyy_package.py rename from pypy/module/cppyy/backend/create_cppyy_package.py rename to pypy/module/_cppyy/backend/create_cppyy_package.py diff --git a/pypy/module/cppyy/bench/Makefile b/pypy/module/_cppyy/bench/Makefile rename from pypy/module/cppyy/bench/Makefile rename to pypy/module/_cppyy/bench/Makefile diff --git a/pypy/module/cppyy/bench/bench02.cxx b/pypy/module/_cppyy/bench/bench02.cxx rename from pypy/module/cppyy/bench/bench02.cxx rename to pypy/module/_cppyy/bench/bench02.cxx diff --git a/pypy/module/cppyy/bench/bench02.h b/pypy/module/_cppyy/bench/bench02.h rename from pypy/module/cppyy/bench/bench02.h rename to pypy/module/_cppyy/bench/bench02.h diff --git a/pypy/module/cppyy/bench/bench02.xml b/pypy/module/_cppyy/bench/bench02.xml rename from pypy/module/cppyy/bench/bench02.xml rename to pypy/module/_cppyy/bench/bench02.xml diff --git a/pypy/module/cppyy/bench/hsimple.C b/pypy/module/_cppyy/bench/hsimple.C rename from pypy/module/cppyy/bench/hsimple.C rename to pypy/module/_cppyy/bench/hsimple.C diff --git a/pypy/module/cppyy/bench/hsimple.py b/pypy/module/_cppyy/bench/hsimple.py rename from pypy/module/cppyy/bench/hsimple.py rename to pypy/module/_cppyy/bench/hsimple.py diff --git a/pypy/module/cppyy/bench/hsimple_rflx.py b/pypy/module/_cppyy/bench/hsimple_rflx.py rename from pypy/module/cppyy/bench/hsimple_rflx.py rename to pypy/module/_cppyy/bench/hsimple_rflx.py diff --git a/pypy/module/cppyy/capi/__init__.py b/pypy/module/_cppyy/capi/__init__.py rename from pypy/module/cppyy/capi/__init__.py rename to pypy/module/_cppyy/capi/__init__.py --- a/pypy/module/cppyy/capi/__init__.py +++ b/pypy/module/_cppyy/capi/__init__.py @@ -9,10 +9,10 @@ # the selection of the desired backend (default is Reflex). # choose C-API access method: -from pypy.module.cppyy.capi.loadable_capi import * -#from pypy.module.cppyy.capi.builtin_capi import * +from pypy.module._cppyy.capi.loadable_capi import * +#from pypy.module._cppyy.capi.builtin_capi import * -from pypy.module.cppyy.capi.capi_types import C_OBJECT,\ +from pypy.module._cppyy.capi.capi_types import C_OBJECT,\ C_NULL_TYPE, C_NULL_OBJECT def direct_ptradd(ptr, offset): diff --git a/pypy/module/cppyy/capi/builtin_capi.py b/pypy/module/_cppyy/capi/builtin_capi.py rename from pypy/module/cppyy/capi/builtin_capi.py rename to pypy/module/_cppyy/capi/builtin_capi.py --- a/pypy/module/cppyy/capi/builtin_capi.py +++ b/pypy/module/_cppyy/capi/builtin_capi.py @@ -4,7 +4,7 @@ import cling_capi as backend -from pypy.module.cppyy.capi.capi_types import C_SCOPE, C_TYPE, C_OBJECT,\ +from pypy.module._cppyy.capi.capi_types import C_SCOPE, C_TYPE, C_OBJECT,\ C_METHOD, C_INDEX, C_INDEX_ARRAY, WLAVC_INDEX, C_FUNC_PTR identify = backend.identify diff --git a/pypy/module/cppyy/capi/capi_types.py b/pypy/module/_cppyy/capi/capi_types.py rename from pypy/module/cppyy/capi/capi_types.py rename to pypy/module/_cppyy/capi/capi_types.py diff --git a/pypy/module/cppyy/capi/cling_capi.py b/pypy/module/_cppyy/capi/cling_capi.py rename from pypy/module/cppyy/capi/cling_capi.py rename to pypy/module/_cppyy/capi/cling_capi.py --- a/pypy/module/cppyy/capi/cling_capi.py +++ b/pypy/module/_cppyy/capi/cling_capi.py @@ -11,7 +11,7 @@ from rpython.rlib import jit, libffi, rdynload from pypy.module._rawffi.array import W_ArrayInstance -from pypy.module.cppyy.capi.capi_types import C_OBJECT +from pypy.module._cppyy.capi.capi_types import C_OBJECT __all__ = ['identify', 'std_string_name', 'eci', 'c_load_dictionary'] @@ -99,7 +99,7 @@ def stdstring_c_str(space, w_self): """Return a python string taking into account \0""" - from pypy.module.cppyy import interp_cppyy + from pypy.module._cppyy import interp_cppyy cppstr = space.interp_w(interp_cppyy.W_CPPInstance, w_self, can_be_None=False) return space.wrap(c_stdstring2charp(space, cppstr._rawobject)) @@ -112,12 +112,12 @@ W_AbstractSeqIterObject.__init__(self, w_vector) # TODO: this should live in rpythonize.py or something so that the # imports can move to the top w/o getting circles - from pypy.module.cppyy import interp_cppyy + from pypy.module._cppyy import interp_cppyy assert isinstance(w_vector, interp_cppyy.W_CPPInstance) vector = space.interp_w(interp_cppyy.W_CPPInstance, w_vector) self.overload = vector.cppclass.get_overload("__getitem__") - from pypy.module.cppyy import capi + from pypy.module._cppyy import capi v_type = capi.c_stdvector_valuetype(space, vector.cppclass.name) v_size = capi.c_stdvector_valuesize(space, vector.cppclass.name) @@ -131,7 +131,7 @@ self.data = rffi.cast(rffi.VOIDP, space.uint_w(arr.getbuffer(space))) - from pypy.module.cppyy import converter + from pypy.module._cppyy import converter self.converter = converter.get_converter(space, v_type, '') self.len = space.uint_w(vector.cppclass.get_overload("size").call(w_vector, [])) self.stride = v_size @@ -143,7 +143,7 @@ self.w_seq = None raise OperationError(space.w_StopIteration, space.w_None) try: - from pypy.module.cppyy import capi # TODO: refector + from pypy.module._cppyy import capi # TODO: refector offset = capi.direct_ptradd(rffi.cast(C_OBJECT, self.data), self.index*self.stride) w_item = self.converter.from_memory(space, space.w_None, space.w_None, offset) except OperationError as e: @@ -186,7 +186,7 @@ _method_alias(space, w_pycppclass, "__str__", "c_str") if "vector" in name[:11]: # len('std::vector') == 11 - from pypy.module.cppyy import capi + from pypy.module._cppyy import capi v_type = capi.c_stdvector_valuetype(space, name) if v_type: space.setattr(w_pycppclass, space.wrap("value_type"), space.wrap(v_type)) diff --git a/pypy/module/_cppyy/capi/loadable_capi.py b/pypy/module/_cppyy/capi/loadable_capi.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/capi/loadable_capi.py @@ -0,0 +1,629 @@ +from rpython.rtyper.lltypesystem import rffi, lltype +from rpython.rlib.rarithmetic import intmask +from rpython.rlib import jit, jit_libffi, libffi, rdynload, objectmodel +from rpython.rlib.rarithmetic import r_singlefloat +from rpython.tool import leakfinder + +from pypy.interpreter.gateway import interp2app +from pypy.interpreter.error import oefmt + +from pypy.module._cffi_backend import ctypefunc, ctypeprim, cdataobj, misc +from pypy.module._cffi_backend import newtype +from pypy.module._cppyy import ffitypes + +from pypy.module._cppyy.capi.capi_types import C_SCOPE, C_TYPE, C_OBJECT,\ + C_METHOD, C_INDEX, C_INDEX_ARRAY, WLAVC_INDEX, C_FUNC_PTR + + +reflection_library = 'libcppyy_backend.so' + +def identify(): + return 'loadable_capi' + +# this is not technically correct, but will do for now +std_string_name = 'std::basic_string<char>' + +class _Arg: # poor man's union + _immutable_ = True + def __init__(self, tc, h = 0, l = -1, s = '', p = rffi.cast(rffi.VOIDP, 0)): + self.tc = tc + self._handle = h + self._long = l + self._string = s + self._voidp = p + +class _ArgH(_Arg): + _immutable_ = True + def __init__(self, val): + _Arg.__init__(self, 'h', h = val) + +class _ArgL(_Arg): + _immutable_ = True + def __init__(self, val): + _Arg.__init__(self, 'l', l = val) + +class _ArgS(_Arg): + _immutable_ = True + def __init__(self, val): + _Arg.__init__(self, 's', s = val) + +class _ArgP(_Arg): + _immutable_ = True + def __init__(self, val): + _Arg.__init__(self, 'p', p = val) + +# For the loadable CAPI, the calls start and end in RPython. Therefore, the standard +# _call of W_CTypeFunc, which expects wrapped objects, does not quite work: some +# vars (e.g. void* equivalent) can not be wrapped, and others (such as rfloat) risk +# rounding problems. This W_RCTypeFun then, takes args, instead of args_w. Note that +# rcall() is a new method, so as to not interfere with the base class call and _call +# when rtyping. It is also called directly (see call_capi below). +class W_RCTypeFunc(ctypefunc.W_CTypeFunc): + @jit.unroll_safe + def rcall(self, funcaddr, args): + assert self.cif_descr + self = jit.promote(self) + # no checking of len(args) needed, as calls in this context are not dynamic + + # The following code is functionally similar to W_CTypeFunc._call, but its + # implementation is tailored to the restricted use (include memory handling) + # of the CAPI calls. + space = self.space + cif_descr = self.cif_descr + size = cif_descr.exchange_size + raw_string = rffi.cast(rffi.CCHARP, 0) # only ever have one in the CAPI + buffer = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw') + try: + for i in range(len(args)): + data = rffi.ptradd(buffer, cif_descr.exchange_args[i]) + obj = args[i] + argtype = self.fargs[i] + # the following is clumsy, but the data types used as arguments are + # very limited, so it'll do for now + if obj.tc == 'l': + assert isinstance(argtype, ctypeprim.W_CTypePrimitiveSigned) + misc.write_raw_signed_data(data, rffi.cast(rffi.LONG, obj._long), argtype.size) + elif obj.tc == 'h': + assert isinstance(argtype, ctypeprim.W_CTypePrimitiveUnsigned) + misc.write_raw_unsigned_data(data, rffi.cast(rffi.ULONG, obj._handle), argtype.size) + elif obj.tc == 'p': + assert obj._voidp != rffi.cast(rffi.VOIDP, 0) + data = rffi.cast(rffi.VOIDPP, data) + data[0] = obj._voidp + else: # only other use is sring + assert obj.tc == 's' + n = len(obj._string) + assert raw_string == rffi.cast(rffi.CCHARP, 0) + # XXX could use rffi.get_nonmovingbuffer_final_null() + raw_string = rffi.str2charp(obj._string) + data = rffi.cast(rffi.CCHARPP, data) + data[0] = raw_string + + jit_libffi.jit_ffi_call(cif_descr, + rffi.cast(rffi.VOIDP, funcaddr), + buffer) + + resultdata = rffi.ptradd(buffer, cif_descr.exchange_result) + # this wrapping is unnecessary, but the assumption is that given the + # immediate unwrapping, the round-trip is removed + w_res = self.ctitem.copy_and_convert_to_object(resultdata) + finally: + if raw_string != rffi.cast(rffi.CCHARP, 0): + rffi.free_charp(raw_string) + lltype.free(buffer, flavor='raw') + return w_res + +class State(object): + def __init__(self, space): + self.library = None + self.capi_calls = {} + + nt = newtype # module from _cffi_backend + state = space.fromcache(ffitypes.State) # factored out common types + + # TODO: the following need to match up with the globally defined C_XYZ low-level + # types (see capi/__init__.py), but by using strings here, that isn't guaranteed + c_opaque_ptr = state.c_ulong + + c_scope = c_opaque_ptr + c_type = c_scope + c_object = c_opaque_ptr + c_method = c_opaque_ptr + c_index = state.c_long + c_index_array = state.c_voidp + + c_void = state.c_void + c_char = state.c_char + c_uchar = state.c_uchar + c_short = state.c_short + c_int = state.c_int + c_long = state.c_long + c_llong = state.c_llong + c_ullong = state.c_ullong + c_float = state.c_float + c_double = state.c_double + c_ldouble = state.c_ldouble + + c_ccharp = state.c_ccharp + c_voidp = state.c_voidp + + c_size_t = nt.new_primitive_type(space, 'size_t') + c_ptrdiff_t = nt.new_primitive_type(space, 'ptrdiff_t') + + self.capi_call_ifaces = { + # name to opaque C++ scope representation + 'num_scopes' : ([c_scope], c_int), + 'scope_name' : ([c_scope, c_int], c_ccharp), + + 'resolve_name' : ([c_ccharp], c_ccharp), + 'get_scope' : ([c_ccharp], c_scope), + 'actual_class' : ([c_type, c_object], c_type), + + # memory management + 'allocate' : ([c_type], c_object), + 'deallocate' : ([c_type, c_object], c_void), + 'destruct' : ([c_type, c_object], c_void), + + # method/function dispatching + 'call_v' : ([c_method, c_object, c_int, c_voidp], c_void), + 'call_b' : ([c_method, c_object, c_int, c_voidp], c_uchar), + 'call_c' : ([c_method, c_object, c_int, c_voidp], c_char), + + 'call_h' : ([c_method, c_object, c_int, c_voidp], c_short), + 'call_i' : ([c_method, c_object, c_int, c_voidp], c_int), + 'call_l' : ([c_method, c_object, c_int, c_voidp], c_long), + 'call_ll' : ([c_method, c_object, c_int, c_voidp], c_llong), + 'call_f' : ([c_method, c_object, c_int, c_voidp], c_float), + 'call_d' : ([c_method, c_object, c_int, c_voidp], c_double), + 'call_ld' : ([c_method, c_object, c_int, c_voidp], c_ldouble), + + 'call_r' : ([c_method, c_object, c_int, c_voidp], c_voidp), + # call_s actually takes an size_t* as last parameter, but this will do + 'call_s' : ([c_method, c_object, c_int, c_voidp, c_voidp], c_ccharp), + + 'constructor' : ([c_method, c_object, c_int, c_voidp], c_object), + 'call_o' : ([c_method, c_object, c_int, c_voidp, c_type], c_object), + + 'get_function_address' : ([c_scope, c_index], c_voidp), # TODO: verify + + # handling of function argument buffer + 'allocate_function_args' : ([c_int], c_voidp), + 'deallocate_function_args' : ([c_voidp], c_void), + 'function_arg_sizeof' : ([], c_size_t), + 'function_arg_typeoffset' : ([], c_size_t), + + # scope reflection information + 'is_namespace' : ([c_scope], c_int), + 'is_template' : ([c_ccharp], c_int), + 'is_abstract' : ([c_type], c_int), + 'is_enum' : ([c_ccharp], c_int), + + # type/class reflection information + 'final_name' : ([c_type], c_ccharp), + 'scoped_final_name' : ([c_type], c_ccharp), + 'has_complex_hierarchy' : ([c_type], c_int), + 'num_bases' : ([c_type], c_int), + 'base_name' : ([c_type, c_int], c_ccharp), + 'is_subtype' : ([c_type, c_type], c_int), + + 'base_offset' : ([c_type, c_type, c_object, c_int], c_ptrdiff_t), + + # method/function reflection information + 'num_methods' : ([c_scope], c_int), + 'method_index_at' : ([c_scope, c_int], c_index), + 'method_indices_from_name' : ([c_scope, c_ccharp], c_index_array), + + 'method_name' : ([c_scope, c_index], c_ccharp), + 'method_result_type' : ([c_scope, c_index], c_ccharp), + 'method_num_args' : ([c_scope, c_index], c_int), + 'method_req_args' : ([c_scope, c_index], c_int), + 'method_arg_type' : ([c_scope, c_index, c_int], c_ccharp), + 'method_arg_default' : ([c_scope, c_index, c_int], c_ccharp), + 'method_signature' : ([c_scope, c_index], c_ccharp), + + 'method_is_template' : ([c_scope, c_index], c_int), + 'method_num_template_args' : ([c_scope, c_index], c_int), + 'method_template_arg_name' : ([c_scope, c_index, c_index], c_ccharp), + + 'get_method' : ([c_scope, c_index], c_method), + 'get_global_operator' : ([c_scope, c_scope, c_scope, c_ccharp], c_index), + + # method properties + 'is_constructor' : ([c_type, c_index], c_int), + 'is_staticmethod' : ([c_type, c_index], c_int), + + # data member reflection information + 'num_datamembers' : ([c_scope], c_int), + 'datamember_name' : ([c_scope, c_int], c_ccharp), + 'datamember_type' : ([c_scope, c_int], c_ccharp), + 'datamember_offset' : ([c_scope, c_int], c_ptrdiff_t), + + 'datamember_index' : ([c_scope, c_ccharp], c_int), + + # data member properties + 'is_publicdata' : ([c_scope, c_int], c_int), + 'is_staticdata' : ([c_scope, c_int], c_int), + + # misc helpers + 'strtoll' : ([c_ccharp], c_llong), + 'strtoull' : ([c_ccharp], c_ullong), + 'free' : ([c_voidp], c_void), + + 'charp2stdstring' : ([c_ccharp, c_size_t], c_object), + #stdstring2charp actually takes an size_t* as last parameter, but this will do + 'stdstring2charp' : ([c_object, c_voidp], c_ccharp), + 'stdstring2stdstring' : ([c_object], c_object), + + 'stdvector_valuetype' : ([c_ccharp], c_ccharp), + 'stdvector_valuesize' : ([c_ccharp], c_size_t), + + } + + # size/offset are backend-specific but fixed after load + self.c_sizeof_farg = 0 + self.c_offset_farg = 0 + + +def load_reflection_library(space): + state = space.fromcache(State) + if state.library is None: + from pypy.module._cffi_backend.libraryobj import W_Library + state.library = W_Library(space, reflection_library, rdynload.RTLD_LOCAL | rdynload.RTLD_LAZY) + if state.library: + # fix constants + state.c_sizeof_farg = _cdata_to_size_t(space, call_capi(space, 'function_arg_sizeof', [])) + state.c_offset_farg = _cdata_to_size_t(space, call_capi(space, 'function_arg_typeoffset', [])) + return state.library + +def verify_backend(space): + try: + load_reflection_library(space) + except Exception: + if objectmodel.we_are_translated(): + raise oefmt(space.w_ImportError, + "missing reflection library %s", reflection_library) + return False + return True + +def call_capi(space, name, args): + state = space.fromcache(State) + try: + c_call = state.capi_calls[name] + except KeyError: + if state.library is None: + load_reflection_library(space) + iface = state.capi_call_ifaces[name] + cfunc = W_RCTypeFunc(space, iface[0], iface[1], False) + c_call = state.library.load_function(cfunc, 'cppyy_'+name) + # TODO: there must be a better way to trick the leakfinder ... + if not objectmodel.we_are_translated(): + leakfinder.remember_free(c_call.ctype.cif_descr._obj0) + state.capi_calls[name] = c_call + with c_call as ptr: + return c_call.ctype.rcall(ptr, args) + +def _cdata_to_cobject(space, w_cdata): + return rffi.cast(C_OBJECT, space.uint_w(w_cdata)) + +def _cdata_to_size_t(space, w_cdata): + return rffi.cast(rffi.SIZE_T, space.uint_w(w_cdata)) + +def _cdata_to_ptrdiff_t(space, w_cdata): + return rffi.cast(rffi.LONG, space.int_w(w_cdata)) + +def _cdata_to_ptr(space, w_cdata): # TODO: this is both a hack and dreadfully slow + w_cdata = space.interp_w(cdataobj.W_CData, w_cdata, can_be_None=False) + ptr = w_cdata.unsafe_escaping_ptr() + return rffi.cast(rffi.VOIDP, ptr) + +def _cdata_to_ccharp(space, w_cdata): + ptr = _cdata_to_ptr(space, w_cdata) # see above ... something better? + return rffi.cast(rffi.CCHARP, ptr) + +def c_load_dictionary(name): + return libffi.CDLL(name) + +# name to opaque C++ scope representation ------------------------------------ +def c_num_scopes(space, cppscope): + return space.int_w(call_capi(space, 'num_scopes', [_ArgH(cppscope.handle)])) +def c_scope_name(space, cppscope, iscope): + args = [_ArgH(cppscope.handle), _ArgL(iscope)] + return charp2str_free(space, call_capi(space, 'scope_name', args)) + +def c_resolve_name(space, name): + return charp2str_free(space, call_capi(space, 'resolve_name', [_ArgS(name)])) +def c_get_scope_opaque(space, name): + return rffi.cast(C_SCOPE, space.uint_w(call_capi(space, 'get_scope', [_ArgS(name)]))) +def c_actual_class(space, cppclass, cppobj): + args = [_ArgH(cppclass.handle), _ArgH(cppobj)] + return rffi.cast(C_TYPE, space.uint_w(call_capi(space, 'actual_class', args))) + +# memory management ---------------------------------------------------------- +def c_allocate(space, cppclass): + return _cdata_to_cobject(space, call_capi(space, 'allocate', [_ArgH(cppclass.handle)])) +def c_deallocate(space, cppclass, cppobject): + call_capi(space, 'deallocate', [_ArgH(cppclass.handle), _ArgH(cppobject)]) +def c_destruct(space, cppclass, cppobject): + call_capi(space, 'destruct', [_ArgH(cppclass.handle), _ArgH(cppobject)]) + +# method/function dispatching ------------------------------------------------ +def c_call_v(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + call_capi(space, 'call_v', args) +def c_call_b(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.UCHAR, space.c_uint_w(call_capi(space, 'call_b', args))) +def c_call_c(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.CHAR, space.bytes_w(call_capi(space, 'call_c', args))[0]) +def c_call_h(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.SHORT, space.int_w(call_capi(space, 'call_h', args))) +def c_call_i(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.INT, space.c_int_w(call_capi(space, 'call_i', args))) +def c_call_l(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.LONG, space.int_w(call_capi(space, 'call_l', args))) +def c_call_ll(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.LONGLONG, space.r_longlong_w(call_capi(space, 'call_ll', args))) +def c_call_f(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.FLOAT, r_singlefloat(space.float_w(call_capi(space, 'call_f', args)))) +def c_call_d(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.DOUBLE, space.float_w(call_capi(space, 'call_d', args))) +def c_call_ld(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return rffi.cast(rffi.LONGDOUBLE, space.float_w(call_capi(space, 'call_ld', args))) + +def c_call_r(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return _cdata_to_ptr(space, call_capi(space, 'call_r', args)) +def c_call_s(space, cppmethod, cppobject, nargs, cargs): + length = lltype.malloc(rffi.SIZE_TP.TO, 1, flavor='raw') + try: + w_cstr = call_capi(space, 'call_s', + [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs), + _ArgP(rffi.cast(rffi.VOIDP, length))]) + cstr_len = intmask(length[0]) + finally: + lltype.free(length, flavor='raw') + return _cdata_to_ccharp(space, w_cstr), cstr_len + +def c_constructor(space, cppmethod, cppobject, nargs, cargs): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs)] + return _cdata_to_cobject(space, call_capi(space, 'constructor', args)) +def c_call_o(space, cppmethod, cppobject, nargs, cargs, cppclass): + args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs), _ArgH(cppclass.handle)] + return _cdata_to_cobject(space, call_capi(space, 'call_o', args)) + +def c_get_function_address(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return rffi.cast(C_FUNC_PTR, + _cdata_to_ptr(space, call_capi(space, 'get_function_address', args))) + +# handling of function argument buffer --------------------------------------- +def c_allocate_function_args(space, size): + return _cdata_to_ptr(space, call_capi(space, 'allocate_function_args', [_ArgL(size)])) +def c_deallocate_function_args(space, cargs): + call_capi(space, 'deallocate_function_args', [_ArgP(cargs)]) +def c_function_arg_sizeof(space): + state = space.fromcache(State) + return state.c_sizeof_farg +def c_function_arg_typeoffset(space): + state = space.fromcache(State) + return state.c_offset_farg + +# scope reflection information ----------------------------------------------- +def c_is_namespace(space, scope): + return space.bool_w(call_capi(space, 'is_namespace', [_ArgH(scope)])) +def c_is_template(space, name): + return space.bool_w(call_capi(space, 'is_template', [_ArgS(name)])) +def c_is_abstract(space, cpptype): + return space.bool_w(call_capi(space, 'is_abstract', [_ArgH(cpptype)])) +def c_is_enum(space, name): + return space.bool_w(call_capi(space, 'is_enum', [_ArgS(name)])) + +# type/class reflection information ------------------------------------------ +def c_final_name(space, cpptype): + return charp2str_free(space, call_capi(space, 'final_name', [_ArgH(cpptype)])) +def c_scoped_final_name(space, cpptype): + return charp2str_free(space, call_capi(space, 'scoped_final_name', [_ArgH(cpptype)])) +def c_has_complex_hierarchy(space, handle): + return space.bool_w(call_capi(space, 'has_complex_hierarchy', [_ArgH(handle)])) +def c_num_bases(space, cppclass): + return space.int_w(call_capi(space, 'num_bases', [_ArgH(cppclass.handle)])) +def c_base_name(space, cppclass, base_index): + args = [_ArgH(cppclass.handle), _ArgL(base_index)] + return charp2str_free(space, call_capi(space, 'base_name', args)) +def c_is_subtype(space, derived, base): + jit.promote(base) + if derived == base: + return bool(1) + return space.bool_w(call_capi(space, 'is_subtype', [_ArgH(derived.handle), _ArgH(base.handle)])) + +def _c_base_offset(space, derived_h, base_h, address, direction): + args = [_ArgH(derived_h), _ArgH(base_h), _ArgH(address), _ArgL(direction)] + return _cdata_to_ptrdiff_t(space, call_capi(space, 'base_offset', args)) +def c_base_offset(space, derived, base, address, direction): + if derived == base: + return rffi.cast(rffi.LONG, 0) + return _c_base_offset(space, derived.handle, base.handle, address, direction) +def c_base_offset1(space, derived_h, base, address, direction): + return _c_base_offset(space, derived_h, base.handle, address, direction) + +# method/function reflection information ------------------------------------- +def c_num_methods(space, cppscope): + args = [_ArgH(cppscope.handle)] + return space.int_w(call_capi(space, 'num_methods', args)) +def c_method_index_at(space, cppscope, imethod): + args = [_ArgH(cppscope.handle), _ArgL(imethod)] + return space.int_w(call_capi(space, 'method_index_at', args)) +def c_method_indices_from_name(space, cppscope, name): + args = [_ArgH(cppscope.handle), _ArgS(name)] + indices = rffi.cast(C_INDEX_ARRAY, + _cdata_to_ptr(space, call_capi(space, 'method_indices_from_name', args))) + if not indices: + return [] + py_indices = [] + i = 0 + index = indices[i] + while index != -1: + i += 1 + py_indices.append(index) + index = indices[i] + c_free(space, rffi.cast(rffi.VOIDP, indices)) # c_free defined below + return py_indices + +def c_method_name(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return charp2str_free(space, call_capi(space, 'method_name', args)) +def c_method_result_type(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return charp2str_free(space, call_capi(space, 'method_result_type', args)) +def c_method_num_args(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return space.int_w(call_capi(space, 'method_num_args', args)) +def c_method_req_args(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return space.int_w(call_capi(space, 'method_req_args', args)) +def c_method_arg_type(space, cppscope, index, arg_index): + args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(arg_index)] + return charp2str_free(space, call_capi(space, 'method_arg_type', args)) +def c_method_arg_default(space, cppscope, index, arg_index): + args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(arg_index)] + return charp2str_free(space, call_capi(space, 'method_arg_default', args)) +def c_method_signature(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return charp2str_free(space, call_capi(space, 'method_signature', args)) + +def c_method_is_template(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return space.bool_w(call_capi(space, 'method_is_template', args)) +def _c_method_num_template_args(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return space.int_w(call_capi(space, 'method_num_template_args', args)) +def c_template_args(space, cppscope, index): + nargs = _c_method_num_template_args(space, cppscope, index) + arg1 = _ArgH(cppscope.handle) + arg2 = _ArgL(index) + args = [c_resolve_name(space, charp2str_free(space, + call_capi(space, 'method_template_arg_name', [arg1, arg2, _ArgL(iarg)])) + ) for iarg in range(nargs)] + return args + +def c_get_method(space, cppscope, index): + args = [_ArgH(cppscope.handle), _ArgL(index)] + return rffi.cast(C_METHOD, space.uint_w(call_capi(space, 'get_method', args))) +def c_get_global_operator(space, nss, lc, rc, op): + if nss is not None: + args = [_ArgH(nss.handle), _ArgH(lc.handle), _ArgH(rc.handle), _ArgS(op)] + return rffi.cast(WLAVC_INDEX, space.int_w(call_capi(space, 'get_global_operator', args))) + return rffi.cast(WLAVC_INDEX, -1) + +# method properties ---------------------------------------------------------- +def c_is_constructor(space, cppclass, index): + args = [_ArgH(cppclass.handle), _ArgL(index)] + return space.bool_w(call_capi(space, 'is_constructor', args)) +def c_is_staticmethod(space, cppclass, index): + args = [_ArgH(cppclass.handle), _ArgL(index)] + return space.bool_w(call_capi(space, 'is_staticmethod', args)) + +# data member reflection information ----------------------------------------- +def c_num_datamembers(space, cppscope): + return space.int_w(call_capi(space, 'num_datamembers', [_ArgH(cppscope.handle)])) +def c_datamember_name(space, cppscope, datamember_index): + args = [_ArgH(cppscope.handle), _ArgL(datamember_index)] + return charp2str_free(space, call_capi(space, 'datamember_name', args)) +def c_datamember_type(space, cppscope, datamember_index): + args = [_ArgH(cppscope.handle), _ArgL(datamember_index)] + return charp2str_free(space, call_capi(space, 'datamember_type', args)) +def c_datamember_offset(space, cppscope, datamember_index): + args = [_ArgH(cppscope.handle), _ArgL(datamember_index)] + return _cdata_to_ptrdiff_t(space, call_capi(space, 'datamember_offset', args)) + +def c_datamember_index(space, cppscope, name): + args = [_ArgH(cppscope.handle), _ArgS(name)] + return space.int_w(call_capi(space, 'datamember_index', args)) + +# data member properties ----------------------------------------------------- +def c_is_publicdata(space, cppscope, datamember_index): + args = [_ArgH(cppscope.handle), _ArgL(datamember_index)] + return space.bool_w(call_capi(space, 'is_publicdata', args)) +def c_is_staticdata(space, cppscope, datamember_index): + args = [_ArgH(cppscope.handle), _ArgL(datamember_index)] + return space.bool_w(call_capi(space, 'is_staticdata', args)) + +# misc helpers --------------------------------------------------------------- +def c_strtoll(space, svalue): + return space.r_longlong_w(call_capi(space, 'strtoll', [_ArgS(svalue)])) +def c_strtoull(space, svalue): + return space.r_ulonglong_w(call_capi(space, 'strtoull', [_ArgS(svalue)])) +def c_free(space, voidp): + call_capi(space, 'free', [_ArgP(voidp)]) + +def charp2str_free(space, cdata): + charp = rffi.cast(rffi.CCHARP, _cdata_to_ptr(space, cdata)) + pystr = rffi.charp2str(charp) + c_free(space, rffi.cast(rffi.VOIDP, charp)) + return pystr + +def c_charp2stdstring(space, svalue, sz): + return _cdata_to_cobject(space, call_capi(space, 'charp2stdstring', + [_ArgS(svalue), _ArgH(rffi.cast(rffi.ULONG, sz))])) +def c_stdstring2charp(space, cppstr): + sz = lltype.malloc(rffi.SIZE_TP.TO, 1, flavor='raw') + try: + w_cstr = call_capi(space, 'stdstring2charp', + [_ArgH(cppstr), _ArgP(rffi.cast(rffi.VOIDP, sz))]) + cstr_len = intmask(sz[0]) + finally: + lltype.free(sz, flavor='raw') + return rffi.charpsize2str(_cdata_to_ccharp(space, w_cstr), cstr_len) +def c_stdstring2stdstring(space, cppobject): + return _cdata_to_cobject(space, call_capi(space, 'stdstring2stdstring', [_ArgH(cppobject)])) + +def c_stdvector_valuetype(space, pystr): + return charp2str_free(space, call_capi(space, 'stdvector_valuetype', [_ArgS(pystr)])) + +def c_stdvector_valuetype(space, pystr): + return charp2str_free(space, call_capi(space, 'stdvector_valuetype', [_ArgS(pystr)])) +def c_stdvector_valuesize(space, pystr): + return _cdata_to_size_t(space, call_capi(space, 'stdvector_valuesize', [_ArgS(pystr)])) + + +# TODO: factor these out ... +# pythonizations +def stdstring_c_str(space, w_self): + """Return a python string taking into account \0""" + + from pypy.module._cppyy import interp_cppyy + cppstr = space.interp_w(interp_cppyy.W_CPPInstance, w_self, can_be_None=False) + return space.newtext(c_stdstring2charp(space, cppstr._rawobject)) + +# setup pythonizations for later use at run-time +_pythonizations = {} +def register_pythonizations(space): + "NOT_RPYTHON" + + allfuncs = [ + + ### std::string + stdstring_c_str, + + ] + + for f in allfuncs: + _pythonizations[f.__name__] = interp2app(f).spacebind(space) + +def _method_alias(space, w_pycppclass, m1, m2): + space.setattr(w_pycppclass, space.newtext(m1), + space.getattr(w_pycppclass, space.newtext(m2))) + +def pythonize(space, name, w_pycppclass): + if name == "string": + space.setattr(w_pycppclass, space.newtext("c_str"), _pythonizations["stdstring_c_str"]) + _method_alias(space, w_pycppclass, "_cppyy_as_builtin", "c_str") + _method_alias(space, w_pycppclass, "__str__", "c_str") diff --git a/pypy/module/_cppyy/converter.py b/pypy/module/_cppyy/converter.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/converter.py @@ -0,0 +1,893 @@ +import sys + +from pypy.interpreter.error import OperationError, oefmt + +from rpython.rtyper.lltypesystem import rffi, lltype +from rpython.rlib.rarithmetic import r_singlefloat, r_longfloat +from rpython.rlib import rfloat + +from pypy.module._rawffi.interp_rawffi import letter2tp +from pypy.module._rawffi.array import W_Array, W_ArrayInstance + +from pypy.module._cppyy import helper, capi, ffitypes + +# Converter objects are used to translate between RPython and C++. They are +# defined by the type name for which they provide conversion. Uses are for +# function arguments, as well as for read and write access to data members. +# All type conversions are fully checked. +# +# Converter instances are greated by get_converter(<type name>), see below. +# The name given should be qualified in case there is a specialised, exact +# match for the qualified type. + + +def get_rawobject(space, w_obj): + from pypy.module._cppyy.interp_cppyy import W_CPPInstance + cppinstance = space.interp_w(W_CPPInstance, w_obj, can_be_None=True) + if cppinstance: + rawobject = cppinstance.get_rawobject() + assert lltype.typeOf(rawobject) == capi.C_OBJECT + return rawobject + return capi.C_NULL_OBJECT + +def set_rawobject(space, w_obj, address): + from pypy.module._cppyy.interp_cppyy import W_CPPInstance + cppinstance = space.interp_w(W_CPPInstance, w_obj, can_be_None=True) + if cppinstance: + assert lltype.typeOf(cppinstance._rawobject) == capi.C_OBJECT + cppinstance._rawobject = rffi.cast(capi.C_OBJECT, address) + +def get_rawobject_nonnull(space, w_obj): + from pypy.module._cppyy.interp_cppyy import W_CPPInstance + cppinstance = space.interp_w(W_CPPInstance, w_obj, can_be_None=True) + if cppinstance: + cppinstance._nullcheck() + rawobject = cppinstance.get_rawobject() + assert lltype.typeOf(rawobject) == capi.C_OBJECT + return rawobject + return capi.C_NULL_OBJECT + +def is_nullpointer_specialcase(space, w_obj): + # 0, None, and nullptr may serve as "NULL", check for any of them + + # integer 0 + try: + return space.int_w(w_obj) == 0 + except Exception: + pass + # None or nullptr + from pypy.module._cppyy import interp_cppyy + return space.is_true(space.is_(w_obj, space.w_None)) or \ + space.is_true(space.is_(w_obj, interp_cppyy.get_nullptr(space))) + +def get_rawbuffer(space, w_obj): + # raw buffer + try: + buf = space.getarg_w('s*', w_obj) + return rffi.cast(rffi.VOIDP, buf.get_raw_address()) + except Exception: + pass + # array type + try: + arr = space.interp_w(W_ArrayInstance, w_obj, can_be_None=True) + if arr: + return rffi.cast(rffi.VOIDP, space.uint_w(arr.getbuffer(space))) + except Exception: + pass + # pre-defined NULL + if is_nullpointer_specialcase(space, w_obj): + return rffi.cast(rffi.VOIDP, 0) + raise TypeError("not an addressable buffer") + + +class TypeConverter(object): + _immutable_fields_ = ['cffi_name', 'uses_local', 'name'] + + cffi_name = None + uses_local = False + name = "" + + def __init__(self, space, extra): + pass + + def _get_raw_address(self, space, w_obj, offset): + rawobject = get_rawobject_nonnull(space, w_obj) + assert lltype.typeOf(rawobject) == capi.C_OBJECT + if rawobject: + fieldptr = capi.direct_ptradd(rawobject, offset) + else: + fieldptr = rffi.cast(capi.C_OBJECT, offset) + return fieldptr + + def _is_abstract(self, space): + raise oefmt(space.w_TypeError, + "no converter available for '%s'", self.name) + + def cffi_type(self, space): + from pypy.module._cppyy.interp_cppyy import FastCallNotPossible + raise FastCallNotPossible + + def convert_argument(self, space, w_obj, address, call_local): + self._is_abstract(space) + + def convert_argument_libffi(self, space, w_obj, address, call_local): + from pypy.module._cppyy.interp_cppyy import FastCallNotPossible + raise FastCallNotPossible + + def default_argument_libffi(self, space, address): + from pypy.module._cppyy.interp_cppyy import FastCallNotPossible + raise FastCallNotPossible + + def from_memory(self, space, w_obj, w_pycppclass, offset): + self._is_abstract(space) + + def to_memory(self, space, w_obj, w_value, offset): + self._is_abstract(space) + + def finalize_call(self, space, w_obj, call_local): + pass + + def free_argument(self, space, arg, call_local): + pass + + +class ArrayCache(object): + def __init__(self, space): + self.space = space + def __getattr__(self, name): + if name.startswith('array_'): + typecode = name[len('array_'):] + arr = self.space.interp_w(W_Array, letter2tp(self.space, typecode)) + setattr(self, name, arr) + return arr + raise AttributeError(name) + + def _freeze_(self): + return True + +class ArrayTypeConverterMixin(object): + _mixin_ = True + _immutable_fields_ = ['size'] + + def __init__(self, space, array_size): + if array_size <= 0: + self.size = sys.maxint + else: + self.size = array_size + + def cffi_type(self, space): + state = space.fromcache(ffitypes.State) + return state.c_voidp + + def from_memory(self, space, w_obj, w_pycppclass, offset): + # read access, so no copy needed + address_value = self._get_raw_address(space, w_obj, offset) + address = rffi.cast(rffi.ULONG, address_value) + cache = space.fromcache(ArrayCache) + arr = getattr(cache, 'array_' + self.typecode) + return arr.fromaddress(space, address, self.size) + + def to_memory(self, space, w_obj, w_value, offset): + # copy the full array (uses byte copy for now) + address = rffi.cast(rffi.CCHARP, self._get_raw_address(space, w_obj, offset)) + buf = space.getarg_w('s*', w_value) + # TODO: report if too many items given? + for i in range(min(self.size*self.typesize, buf.getlength())): + address[i] = buf.getitem(i) + + +class PtrTypeConverterMixin(object): + _mixin_ = True + _immutable_fields_ = ['size'] + + def __init__(self, space, array_size): + self.size = sys.maxint + + def cffi_type(self, space): + state = space.fromcache(ffitypes.State) + return state.c_voidp + + def convert_argument(self, space, w_obj, address, call_local): + w_tc = space.findattr(w_obj, space.newtext('typecode')) + if w_tc is not None and space.text_w(w_tc) != self.typecode: + raise oefmt(space.w_TypeError, + "expected %s pointer type, but received %s", + self.typecode, space.text_w(w_tc)) + x = rffi.cast(rffi.VOIDPP, address) + try: + x[0] = rffi.cast(rffi.VOIDP, get_rawbuffer(space, w_obj)) + except TypeError: + raise oefmt(space.w_TypeError, + "raw buffer interface not supported") + ba = rffi.cast(rffi.CCHARP, address) + ba[capi.c_function_arg_typeoffset(space)] = 'o' + + def from_memory(self, space, w_obj, w_pycppclass, offset): + # read access, so no copy needed + address_value = self._get_raw_address(space, w_obj, offset) + address = rffi.cast(rffi.ULONGP, address_value) + cache = space.fromcache(ArrayCache) + arr = getattr(cache, 'array_' + self.typecode) + return arr.fromaddress(space, address[0], self.size) + + def to_memory(self, space, w_obj, w_value, offset): + # copy only the pointer value + rawobject = get_rawobject_nonnull(space, w_obj) + byteptr = rffi.cast(rffi.CCHARPP, capi.direct_ptradd(rawobject, offset)) + buf = space.getarg_w('s*', w_value) + try: + byteptr[0] = buf.get_raw_address() + except ValueError: + raise oefmt(space.w_TypeError, + "raw buffer interface not supported") + + +class NumericTypeConverterMixin(object): + _mixin_ = True + + def convert_argument_libffi(self, space, w_obj, address, call_local): + x = rffi.cast(self.c_ptrtype, address) + x[0] = self._unwrap_object(space, w_obj) + + def default_argument_libffi(self, space, address): + x = rffi.cast(self.c_ptrtype, address) + x[0] = self.default + + def from_memory(self, space, w_obj, w_pycppclass, offset): + address = self._get_raw_address(space, w_obj, offset) + rffiptr = rffi.cast(self.c_ptrtype, address) + return self._wrap_object(space, rffiptr[0]) + + def to_memory(self, space, w_obj, w_value, offset): + address = self._get_raw_address(space, w_obj, offset) + rffiptr = rffi.cast(self.c_ptrtype, address) + rffiptr[0] = self._unwrap_object(space, w_value) + +class ConstRefNumericTypeConverterMixin(NumericTypeConverterMixin): + _mixin_ = True + _immutable_fields_ = ['uses_local'] + + uses_local = True + + def cffi_type(self, space): + state = space.fromcache(ffitypes.State) + return state.c_voidp _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit