[pypy-svn] r26273 - in pypy/dist/pypy/rpython/rctypes: . test

arigo at codespeak.net arigo at codespeak.net
Mon Apr 24 17:56:02 CEST 2006


Author: arigo
Date: Mon Apr 24 17:56:00 2006
New Revision: 26273

Modified:
   pypy/dist/pypy/rpython/rctypes/afunc.py
   pypy/dist/pypy/rpython/rctypes/implementation.py
   pypy/dist/pypy/rpython/rctypes/test/test_rfunc.py
Log:
rctypes: annotation of user-defined RPython-level callback functions.


Modified: pypy/dist/pypy/rpython/rctypes/afunc.py
==============================================================================
--- pypy/dist/pypy/rpython/rctypes/afunc.py	(original)
+++ pypy/dist/pypy/rpython/rctypes/afunc.py	Mon Apr 24 17:56:00 2006
@@ -1,7 +1,7 @@
 from pypy.annotation.model import SomeCTypesObject
 from pypy.annotation import model as annmodel
 from pypy.rpython.error import TyperError
-from pypy.rpython.extregistry import ExtRegistryEntry
+from pypy.rpython.rctypes.implementation import CTypesEntry
 from pypy.rpython.lltypesystem import lltype
 
 import ctypes
@@ -10,7 +10,7 @@
 CFuncPtrType = type(ctypes.CFUNCTYPE(None))
 
 
-class CallEntry(ExtRegistryEntry):
+class CallEntry(CTypesEntry):
     """Annotation and rtyping of calls to external functions
     declared with ctypes.
     """
@@ -33,6 +33,33 @@
         s_result = SomeCTypesObject(result_ctype, SomeCTypesObject.OWNSMEMORY)
         return s_result.return_annotation()
 
+    def object_seen(self, bookkeeper):
+        "Called when the annotator sees this ctypes function object."
+        # if the function is a Python callback, emulate a call to it
+        # so that the callback is properly annotated
+        if hasattr(self.instance, 'callback'):
+            callback = self.instance.callback
+            argtypes = self.instance.argtypes
+            restype  = self.instance.restype
+            s_callback = bookkeeper.immutablevalue(callback)
+            # the input arg annotations, which are automatically unwrapped
+            args_s = [bookkeeper.valueoftype(ctype).return_annotation()
+                      for ctype in argtypes]
+            uniquekey = (callback, argtypes, restype)
+            s_res = bookkeeper.emulate_pbc_call(uniquekey, s_callback, args_s)
+            # check the result type
+            if restype is None:
+                s_expected = annmodel.s_None
+            else:
+                s_expected = bookkeeper.valueoftype(restype)
+            # can also return the unwrapped version of the ctype,
+            # e.g. an int instead of a c_int
+            s_orelse = s_expected.return_annotation()
+            assert s_expected.contains(s_res) or s_orelse.contains(s_res), (
+                "%r should return a %s but returned %s" % (callback,
+                                                           restype,
+                                                           s_res))
+
     def specialize_call(self, hop):
         from pypy.rpython.rctypes.rmodel import CTypesValueRepr
         cfuncptr = self.instance

Modified: pypy/dist/pypy/rpython/rctypes/implementation.py
==============================================================================
--- pypy/dist/pypy/rpython/rctypes/implementation.py	(original)
+++ pypy/dist/pypy/rpython/rctypes/implementation.py	Mon Apr 24 17:56:00 2006
@@ -2,20 +2,103 @@
 from pypy.annotation.model import SomeCTypesObject
 from pypy.rpython import extregistry
 from pypy.rpython.extregistry import ExtRegistryEntry
+import ctypes
 
+# rctypes version of ctypes.CFUNCTYPE.
+# It's required to work around three limitations of CFUNCTYPE:
+#
+#   * There is no PY_ version to make callbacks for CPython, which
+#     expects the callback to follow the usual conventions (NULL = error).
+#
+#   * The wrapped callback is not exposed in any special attribute, so
+#     if rctypes sees a CFunctionType object it can't find the Python callback
+#
+#   * I would expect a return type of py_object to mean that if the
+#     callback Python function returns a py_object, the C caller sees the
+#     PyObject* inside.  Wrong: it sees the py_object wrapper itself.  For
+#     consistency -- and also because unwrapping the py_object manually is
+#     not allowed annotation-wise -- we change the semantics here under
+#     the nose of the annotator.
+
+_c_callback_functype_cache = {}
+def CALLBACK_FUNCTYPE(restype, *argtypes, **flags):
+    if 'callconv' in flags:
+        callconv = flags.pop('callconv')
+    else:
+        callconv = ctypes.CDLL
+    assert not flags, "unknown keyword arguments %r" % (flags.keys(),)
+    try:
+        return _c_callback_functype_cache[(restype, argtypes)]
+    except KeyError:
+        class CallbackFunctionType(ctypes._CFuncPtr):
+            _argtypes_ = argtypes
+            _restype_ = restype
+            _flags_ = callconv._FuncPtr._flags_
+
+            def __new__(cls, callback):
+                assert callable(callback)
+                if issubclass(restype, ctypes.py_object):
+                    def func(*args, **kwds):
+                        w_res = callback(*args, **kwds)
+                        assert isinstance(w_res, py_object)
+                        return w_res.value
+                else:
+                    func = callback
+                res = super(CallbackFunctionType, cls).__new__(cls, func)
+                res.callback = callback
+                return res
 
-class CTypesCallEntry(ExtRegistryEntry):
+        _c_callback_functype_cache[(restype, argtypes)] = CallbackFunctionType
+        return CallbackFunctionType
+
+# ____________________________________________________________
+
+class CTypesEntry(ExtRegistryEntry):
+
+    def compute_annotation(self):
+        self.ctype_object_discovered()
+        return super(CTypesEntry, self).compute_annotation()
+
+    def ctype_object_discovered(self):
+        if self.instance is None:
+            return
+        from pypy.annotation.bookkeeper import getbookkeeper
+        bookkeeper = getbookkeeper()
+        if bookkeeper is None:
+            return
+        # follow all dependent ctypes objects in order to discover
+        # all callback functions
+        memo = {}
+        def recfind(o):
+            if id(o) in memo:
+                return
+            memo[id(o)] = o
+            if isinstance(o, dict):
+                for x in o.itervalues():
+                    recfind(x)
+            elif extregistry.is_registered(o):
+                entry = extregistry.lookup(o)
+                if isinstance(entry, CTypesEntry):
+                    entry.object_seen(bookkeeper)
+        recfind(self.instance._objects)
+
+    def object_seen(self, bookkeeper):
+        """To be overriden for ctypes objects whose mere presence influences
+        annotation, e.g. callback functions."""
+
+
+class CTypesCallEntry(CTypesEntry):
     "Annotation and rtyping of calls to ctypes types."
 
     def compute_result_annotation(self, *args_s, **kwds_s):
         ctype = self.instance    # the ctype is the called object
         return SomeCTypesObject(ctype, SomeCTypesObject.OWNSMEMORY)
 
-
-class CTypesObjEntry(ExtRegistryEntry):
+class CTypesObjEntry(CTypesEntry):
     "Annotation and rtyping of ctypes instances."
 
     def compute_annotation(self):
+        self.ctype_object_discovered()
         ctype = self.type
         return SomeCTypesObject(ctype, SomeCTypesObject.OWNSMEMORY)
 

Modified: pypy/dist/pypy/rpython/rctypes/test/test_rfunc.py
==============================================================================
--- pypy/dist/pypy/rpython/rctypes/test/test_rfunc.py	(original)
+++ pypy/dist/pypy/rpython/rctypes/test/test_rfunc.py	Mon Apr 24 17:56:00 2006
@@ -4,17 +4,18 @@
 
 import py
 import sys
-import pypy.rpython.rctypes.implementation
+from pypy.rpython.rctypes.implementation import CALLBACK_FUNCTYPE
 from pypy.annotation.annrpython import RPythonAnnotator
+from pypy.translator.translator import TranslationContext, graphof
 from pypy.rpython.test.test_llinterp import interpret
 from pypy.translator.c.test.test_genc import compile
 from pypy import conftest
 from pypy.rpython.rstr import string_repr
 from pypy.rpython.lltypesystem import lltype
 
-from ctypes import cdll, pythonapi, _FUNCFLAG_PYTHONAPI
+from ctypes import cdll, pythonapi, PyDLL, _FUNCFLAG_PYTHONAPI
 from ctypes import c_int, c_long, c_char_p, c_char, create_string_buffer
-from ctypes import POINTER, py_object, byref
+from ctypes import POINTER, py_object, byref, Structure
 from pypy.rpython.rctypes.tool import util      # ctypes.util from 0.9.9.6
 
 # __________ the standard C library __________
@@ -54,6 +55,14 @@
 ctime.restype = c_char_p
 #ctimes.argtypes: omitted for this test
 
+IntIntCallback = CALLBACK_FUNCTYPE(c_int, c_int)
+def mycallback(n):
+    return n+3
+callback = IntIntCallback(mycallback)
+
+PyIntIntCallback = CALLBACK_FUNCTYPE(c_int, c_int, callconv=PyDLL)
+pycallback = PyIntIntCallback(mycallback)
+
 
 def test_labs(n=6):
     assert labs(n) == abs(n)
@@ -97,6 +106,10 @@
     s2 = ctime(byref(c_long(N)))
     assert s1.strip() == s2.strip()
 
+def test_callback():
+    assert callback(100) == 103
+    assert pycallback(100) == 103
+
 class Test_annotation:
     def test_annotate_labs(self):
         a = RPythonAnnotator()
@@ -132,6 +145,41 @@
         if conftest.option.view:
             a.translator.view()
 
+    def test_annotate_callback(self):
+        def fn(n):
+            return callback(n)
+        t = TranslationContext()
+        a = t.buildannotator()
+        s = a.build_types(fn, [int])
+        assert s.knowntype == int
+        if conftest.option.view:
+            a.translator.view()
+        graph = graphof(t, mycallback)
+        [v1] = graph.getargs()
+        v2 = graph.getreturnvar()
+        assert a.binding(v1).knowntype == int
+        assert a.binding(v2).knowntype == int
+
+    def test_annotation_indirectly_found_callback(self):
+        class S(Structure):
+            _fields_ = [('vtable', IntIntCallback*5),
+                        ('z', c_int)]
+        s = S(z=3)
+        s.vtable[2] = callback
+        def fn():
+            return s.z
+        t = TranslationContext()
+        a = t.buildannotator()
+        s = a.build_types(fn, [])
+        assert s.knowntype == int
+        if conftest.option.view:
+            a.translator.view()
+        graph = graphof(t, mycallback)
+        [v1] = graph.getargs()
+        v2 = graph.getreturnvar()
+        assert a.binding(v1).knowntype == int
+        assert a.binding(v2).knowntype == int
+
 class Test_specialization:
     def test_specialize_labs(self):
         res = interpret(test_labs, [-11])



More information about the Pypy-commit mailing list