[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