Commits

wlav committed 5bb835b

first attempt at pythonizations in rpython

Comments (0)

Files changed (9)

pypy/module/cppyy/__init__.py

         'load_reflection_info'   : 'pythonify.load_reflection_info',
         'add_pythonization'      : 'pythonify.add_pythonization',
     }
+
+    def __init__(self, space, *args):
+        "NOT_RPYTHON"
+        MixedModule.__init__(self, space, *args)
+
+        # 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
+        capi.register_pythonizations(space)

pypy/module/cppyy/capi/__init__.py

 
 identify  = backend.identify
 pythonize = backend.pythonize
+register_pythonizations = backend.register_pythonizations
+
 ts_reflect = backend.ts_reflect
 ts_call    = backend.ts_call
 ts_memory  = backend.ts_memory

pypy/module/cppyy/capi/cint_capi.py

-import py, os
+import py, os, sys
+
+from pypy.interpreter.error import OperationError
+from pypy.interpreter.gateway import interp2app, unwrap_spec
+from pypy.interpreter.typedef import TypeDef
 
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 from pypy.rpython.lltypesystem import rffi
 from pypy.rlib import libffi, rdynload
 
+from pypy.module.itertools import interp_itertools
+
+
 __all__ = ['identify', 'eci', 'c_load_dictionary']
 
 pkgpath = py.path.local(__file__).dirpath().join(os.pardir)
     _cintdll = rdynload.dlopen(ll_libname, rdynload.RTLD_GLOBAL | rdynload.RTLD_NOW)
 with rffi.scoped_str2charp('libCore.so') as ll_libname:
     _coredll = rdynload.dlopen(ll_libname, rdynload.RTLD_GLOBAL | rdynload.RTLD_NOW)
+#with rffi.scoped_str2charp('libTree.so') as ll_libname:
+#    _coredll = rdynload.dlopen(ll_libname, rdynload.RTLD_GLOBAL | rdynload.RTLD_NOW)
 
 eci = ExternalCompilationInfo(
     separate_module_files=[srcpath.join("cintcwrapper.cxx")],
     include_dirs=[incpath] + rootincpath,
     includes=["cintcwrapper.h"],
     library_dirs=rootlibpath,
+#    link_extra=["-lCore", "-lCint", "-lTree"],
     link_extra=["-lCore", "-lCint"],
     use_cpp_linker=True,
 )
         raise rdynload.DLOpenError(err)
     return libffi.CDLL(name)       # should return handle to already open file
 
-# CINT-specific pythonizations     
+
+# CINT-specific pythonizations ===============================================
+
+### TTree --------------------------------------------------------------------
+_ttree_Branch = rffi.llexternal(
+    "cppyy_ttree_Branch",
+    [rffi.VOIDP, rffi.CCHARP, rffi.CCHARP, rffi.VOIDP, rffi.INT, rffi.INT], rffi.LONG,
+    threadsafe=False,
+    compilation_info=eci)
+
+@unwrap_spec(args_w='args_w')
+def ttree_Branch(space, w_self, args_w):
+    """Pythonized version of TTree::Branch(): takes proxy objects and by-passes
+    the CINT-manual layer."""
+
+    from pypy.module.cppyy import interp_cppyy
+    tree_class = interp_cppyy.scope_byname(space, "TTree")
+
+    # sigs to modify (and by-pass CINT):
+    #  1. (const char*, const char*, T**,               Int_t=32000, Int_t=99)
+    #  2. (const char*, T**,                            Int_t=32000, Int_t=99)
+    argc = len(args_w)
+
+    # basic error handling of wrong arguments is best left to the original call,
+    # so that error messages etc. remain consistent in appearance: the following
+    # block may raise TypeError or IndexError to break out anytime
+
+    try:
+        if argc < 2 or 5 < argc:
+            raise TypeError("wrong number of arguments")
+
+        tree = space.interp_w(interp_cppyy.W_CPPInstance, w_self, can_be_None=True)
+        if (tree is None) or (tree.cppclass != tree_class):
+            raise TypeError("not a TTree")
+
+        # first argument must always always be cont char*
+        branchname = space.str_w(args_w[0])
+
+        # if args_w[1] is a classname, then case 1, else case 2
+        try:
+            classname = space.str_w(args_w[1])
+            addr_idx  = 2
+            w_address = args_w[addr_idx]
+        except OperationError:
+            addr_idx  = 1
+            w_address = args_w[addr_idx]
+
+        bufsize, splitlevel = 32000, 99
+        if addr_idx+1 < argc: bufsize = space.c_int_w(args_w[addr_idx+1])
+        if addr_idx+2 < argc: splitlevel = space.c_int_w(args_w[addr_idx+2])
+
+        # now retrieve the W_CPPInstance and build other stub arguments
+        cppinstance = space.interp_w(interp_cppyy.W_CPPInstance, w_address)
+        address = rffi.cast(rffi.VOIDP, cppinstance.get_rawobject())
+        klassname = cppinstance.cppclass.name
+        vtree = rffi.cast(rffi.VOIDP, tree.get_rawobject())
+
+        # call the helper stub to by-pass CINT
+        vbranch = _ttree_Branch(vtree, branchname, klassname, address, bufsize, splitlevel)
+        branch_class = interp_cppyy.scope_byname(space, "TBranch")
+        space = tree.space    # holds the class cache in State
+        w_branch = interp_cppyy.wrap_cppobject(
+            space, space.w_None, branch_class, vbranch, isref=False, python_owns=False)
+        return w_branch
+    except (OperationError, TypeError, IndexError), e:
+        pass
+
+    # return control back to the original, unpythonized overload
+    return tree_class.get_overload("Branch").call(w_self, args_w)
+
+class W_TTreeIter(interp_itertools.W_Count):
+    def __init__(self, space, w_firstval, w_step):
+        interp_itertools.W_Count.__init__(self, space, w_firstval, w_step)
+        self.w_tree = space.w_None
+
+    def set_tree(self, space, w_tree):
+        self.w_tree = w_tree
+        try:
+            space.getattr(self.w_tree, space.wrap("_pythonized"))
+        except OperationError:
+            from pypy.module.cppyy import interp_cppyy
+            tree = space.interp_w(interp_cppyy.W_CPPInstance, self.w_tree)
+            self.space = space = tree.space       # holds the class cache in State
+            w_branches = space.call_method(self.w_tree, "GetListOfBranches")
+            for i in range(space.int_w(space.call_method(w_branches, "GetEntriesFast"))):
+                w_branch = space.call_method(w_branches, "At", space.wrap(i))
+                w_name = space.call_method(w_branch, "GetName")
+                w_klassname = space.call_method(w_branch, "GetClassName")
+                klass = interp_cppyy.scope_byname(space, space.str_w(w_klassname))
+                w_obj = klass.construct()
+                space.call_method(w_branch, "SetObject", w_obj)
+                # cache the object and define this tree pythonized
+                space.setattr(self.w_tree, w_name, w_obj)
+                space.setattr(self.w_tree, space.wrap("_pythonized"), space.w_True)
+
+    def iter_w(self):
+        return self.space.wrap(self)
+
+    def next_w(self):
+        w_bytes_read = self.space.call_method(self.w_tree, "GetEntry", self.w_c)
+        if not self.space.is_true(w_bytes_read):
+            raise OperationError(self.space.w_StopIteration, self.space.w_None)
+        w_c = self.w_c
+        self.w_c = self.space.add(w_c, self.w_step)
+        return w_c
+
+W_TTreeIter.typedef = TypeDef(
+    'TTreeIter',
+    __iter__ = interp2app(W_TTreeIter.iter_w),
+    next = interp2app(W_TTreeIter.next_w),
+)
+
+@unwrap_spec(args_w='args_w')
+def ttree_iter(space, w_self, args_w):
+    """Allow iteration over TTree's. Also initializes branch data members and
+    sets addresses, if needed."""
+    w_treeiter = W_TTreeIter(space, space.wrap(0), space.wrap(1))
+    w_treeiter.set_tree(space, w_self)
+    return w_treeiter
+
+# setup pythonizations for later use at run-time
+_pythonizations = {}
+def register_pythonizations(space):
+    "NOT_RPYTHON"
+
+    ### TTree
+    _pythonizations['ttree_Branch'] = space.wrap(interp2app(ttree_Branch))
+    _pythonizations['ttree_iter']   = space.wrap(interp2app(ttree_iter))
+
+# callback coming in when app-level bound classes have been created
 def pythonize(space, name, w_pycppclass):
 
-    if name[0:8] == "TVectorT":
+    if name == 'TFile':
+        space.setattr(w_pycppclass, space.wrap("__getattr__"),
+                      space.getattr(w_pycppclass, space.wrap("Get")))
+
+    elif name == 'TTree':
+        space.setattr(w_pycppclass, space.wrap("_unpythonized_Branch"),
+                      space.getattr(w_pycppclass, space.wrap("Branch")))
+        space.setattr(w_pycppclass, space.wrap("Branch"), _pythonizations["ttree_Branch"])
+        space.setattr(w_pycppclass, space.wrap("__iter__"), _pythonizations["ttree_iter"])
+
+    elif name[0:8] == "TVectorT":    # TVectorT<> template
         space.setattr(w_pycppclass, space.wrap("__len__"),
                       space.getattr(w_pycppclass, space.wrap("GetNoElements")))

pypy/module/cppyy/capi/reflex_capi.py

 def c_load_dictionary(name):
     return libffi.CDLL(name)
 
+
 # Reflex-specific pythonizations
+def register_pythonizations(space):
+    "NOT_RPYTHON"
+    pass
+
 def pythonize(space, name, w_pycppclass):
     pass

pypy/module/cppyy/converter.py

         try:
             buf = space.buffer_w(w_obj)
             x[0] = rffi.cast(rffi.VOIDP, buf.get_raw_address())
-            ba[capi.c_function_arg_typeoffset()] = 'o'
         except (OperationError, ValueError):
             x[0] = rffi.cast(rffi.VOIDP, get_rawobject(space, w_obj))
-            ba[capi.c_function_arg_typeoffset()] = 'a'
+        ba[capi.c_function_arg_typeoffset()] = 'o'
 
     def convert_argument_libffi(self, space, w_obj, argchain, call_local):
         argchain.arg(get_rawobject(space, w_obj))

pypy/module/cppyy/include/cintcwrapper.h

 extern "C" {
 #endif // ifdef __cplusplus
 
+    /* misc helpers */
     void* cppyy_load_dictionary(const char* lib_name);
 
+    /* pythonization helpers */
+    cppyy_object_t cppyy_ttree_Branch(
+        void* vtree, const char* branchname, const char* classname,
+        void* addobj, int bufsize, int splitlevel);
+
 #ifdef __cplusplus
 }
 #endif // ifdef __cplusplus

pypy/module/cppyy/interp_cppyy.py

     def __eq__(self, other):
         return self.handle == other.handle
 
+    def __ne__(self, other):
+        return self.handle != other.handle
+
 
 # For now, keep namespaces and classes separate as namespaces are extensible
 # with info from multiple dictionaries and do not need to bother with meta
     _immutable_ = True
     kind = "class"
 
+    def __init__(self, space, name, opaque_handle):
+        W_CPPScope.__init__(self, space, name, opaque_handle)
+        self.default_constructor = None
+
     def _make_cppfunction(self, pyname, index):
+        default_constructor = False
         num_args = capi.c_method_num_args(self, index)
         args_required = capi.c_method_req_args(self, index)
         arg_defs = []
             arg_defs.append((arg_type, arg_dflt))
         if capi.c_is_constructor(self, index):
             cls = CPPConstructor
+            if args_required == 0:
+                default_constructor = True
         elif capi.c_is_staticmethod(self, index):
             cls = CPPFunction
         elif pyname == "__setitem__":
             cls = CPPSetItem
         else:
             cls = CPPMethod
-        return cls(self.space, self, index, arg_defs, args_required)
+        cppfunction = cls(self.space, self, index, arg_defs, args_required)
+        if default_constructor:
+            self.default_constructor = cppfunction
+        return cppfunction
 
     def _find_datamembers(self):
         num_datamembers = capi.c_num_datamembers(self)
             datamember = W_CPPDataMember(self.space, self, type_name, offset, is_static)
             self.datamembers[datamember_name] = datamember
 
+    def construct(self):
+        if self.default_constructor is not None:
+            return self.default_constructor.call(capi.C_NULL_OBJECT, [])
+        raise self.missing_attribute_error("default_constructor")
+
     def find_overload(self, name):
         raise self.missing_attribute_error(name)
 
     return w_pycppclass
 
 def wrap_new_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns):
+    rawobject = rffi.cast(capi.C_OBJECT, rawobject)
     if space.is_w(w_pycppclass, space.w_None):
         w_pycppclass = get_pythonized_cppclass(space, cppclass.handle)
     w_cppinstance = space.allocate_instance(W_CPPInstance, w_pycppclass)
     return w_cppinstance
 
 def wrap_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns):
+    rawobject = rffi.cast(capi.C_OBJECT, rawobject)
     obj = memory_regulator.retrieve(rawobject)
     if obj is not None and obj.cppclass is cppclass:
         return obj
     return wrap_new_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns)
 
 def wrap_cppobject(space, w_pycppclass, cppclass, rawobject, isref, python_owns):
+    rawobject = rffi.cast(capi.C_OBJECT, rawobject)
     if rawobject:
         actual = capi.c_actual_class(cppclass, rawobject)
         if actual != cppclass.handle:

pypy/module/cppyy/src/cintcwrapper.cxx

 #include "TMethod.h"
 #include "TMethodArg.h"
 
+// for pythonization
+#include "TTree.h"
+#include "TBranch.h"
+
 #include "Api.h"
 
 #include <assert.h>
         return (void*)1;
     return (void*)0;
 }
+
+
+/* pythonization helpers -------------------------------------------------- */
+cppyy_object_t cppyy_ttree_Branch(void* vtree, const char* branchname, const char* classname,
+        void* addobj, int bufsize, int splitlevel) {
+    // this little song-and-dance is to by-pass the handwritten Branch methods
+    TBranch* b = ((TTree*)vtree)->Bronch(branchname, classname, (void*)&addobj, bufsize, splitlevel);
+    b->SetObject(addobj);
+    return (cppyy_object_t)b;
+}

pypy/module/cppyy/test/test_cint.py

         assert len(v) == N
         for j in v:
              assert round(v[int(math.sqrt(j)+0.5)]-j, 5) == 0.
+
+
+class AppTestCINTTTree:
+    def setup_class(cls):
+        cls.space = space
+        cls.w_N = space.wrap(5)
+        cls.w_M = space.wrap(10)
+        cls.w_fname = space.wrap("test.root")
+        cls.w_tname = space.wrap("test")
+        cls.w_title = space.wrap("test tree")
+        cls.space.appexec([], """():
+            import cppyy""")
+
+    def test01_write_stdvector( self ):
+        """Test writing of a single branched TTree with an std::vector<double>"""
+
+        from cppyy import gbl               # bootstraps, only needed for tests
+        from cppyy.gbl import TFile, TTree
+        from cppyy.gbl.std import vector
+
+        f = TFile(self.fname, "RECREATE")
+        t = TTree(self.tname, self.title)
+        t._python_owns = False
+
+        v = vector("double")()
+        raises(TypeError, TTree.Branch, None, "mydata", v.__class__.__name__, v)
+        raises(TypeError, TTree.Branch, v, "mydata", v.__class__.__name__, v)
+
+        t.Branch("mydata", v.__class__.__name__, v)
+
+        for i in range(self.N):
+            for j in range(self.M):
+                v.push_back(i*self.M+j)
+            t.Fill()
+            v.clear()
+        f.Write()
+        f.Close()
+
+    def test02_read_stdvector(self):
+        """Test reading of a single branched TTree with an std::vector<double>"""
+
+        from cppyy import gbl               # bootstraps, only needed for tests
+        from cppyy.gbl import TFile
+
+        f = TFile(self.fname)
+        mytree = f.Get(self.tname)
+
+        i = 0
+        for event in mytree:
+            for entry in mytree.mydata:
+                assert i == int(entry)
+                i += 1
+        assert i == self.N * self.M
+
+        f.Close()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.