[pypy-commit] pypy cffi-handle-lifetime: Port callbacks to the new model, and make it run untranslated too.
arigo
noreply at buildbot.pypy.org
Mon Oct 12 09:28:53 CEST 2015
Author: Armin Rigo <arigo at tunes.org>
Branch: cffi-handle-lifetime
Changeset: r80117:9a7cc64655b1
Date: 2015-10-12 09:29 +0200
http://bitbucket.org/pypy/pypy/changeset/9a7cc64655b1/
Log: Port callbacks to the new model, and make it run untranslated too.
diff --git a/pypy/module/_cffi_backend/ccallback.py b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -1,14 +1,14 @@
"""
Callbacks.
"""
-import sys, os
+import sys, os, py
-from rpython.rlib import clibffi, jit, jit_libffi
+from rpython.rlib import clibffi, jit, jit_libffi, rgc, objectmodel
from rpython.rlib.objectmodel import keepalive_until_here
-from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
from pypy.interpreter.error import OperationError, oefmt
-from pypy.module._cffi_backend import cerrno, misc, handle
+from pypy.module._cffi_backend import cerrno, misc
from pypy.module._cffi_backend.cdataobj import W_CData
from pypy.module._cffi_backend.ctypefunc import SIZE_OF_FFI_ARG, W_CTypeFunc
from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveSigned
@@ -19,6 +19,22 @@
# ____________________________________________________________
+def make_callback(space, ctype, w_callable, w_error, w_onerror):
+ # Allocate a callback as a nonmovable W_CDataCallback instance, which
+ # we can cast to a plain VOIDP. As long as the object is not freed,
+ # we can cast the VOIDP back to a W_CDataCallback in reveal_callback().
+ cdata = objectmodel.instantiate(W_CDataCallback, nonmovable=True)
+ gcref = rgc.cast_instance_to_gcref(cdata)
+ raw_cdata = rgc.hide_nonmovable_gcref(gcref)
+ cdata.__init__(space, ctype, w_callable, w_error, w_onerror, raw_cdata)
+ return cdata
+
+def reveal_callback(raw_ptr):
+ addr = rffi.cast(llmemory.Address, raw_ptr)
+ gcref = rgc.reveal_gcref(addr)
+ return rgc.try_cast_gcref_to_instance(W_CDataCallback, gcref)
+
+
class Closure(object):
"""This small class is here to have a __del__ outside any cycle."""
@@ -37,7 +53,8 @@
_immutable_fields_ = ['key_pycode']
w_onerror = None
- def __init__(self, space, ctype, w_callable, w_error, w_onerror):
+ def __init__(self, space, ctype, w_callable, w_error, w_onerror,
+ raw_cdata):
raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
self._closure = Closure(raw_closure)
W_CData.__init__(self, space, raw_closure, ctype)
@@ -72,8 +89,6 @@
from pypy.module.thread.os_thread import setup_threads
setup_threads(space)
#
- handle_index = handle.get_handles(space).reserve_next_handle_index()
- #
cif_descr = self.getfunctype().cif_descr
if not cif_descr:
raise oefmt(space.w_NotImplementedError,
@@ -81,16 +96,13 @@
"return type or with '...'", self.getfunctype().name)
with self as ptr:
closure_ptr = rffi.cast(clibffi.FFI_CLOSUREP, ptr)
- unique_id = rffi.cast(rffi.VOIDP, handle_index)
+ unique_id = rffi.cast(rffi.VOIDP, raw_cdata)
res = clibffi.c_ffi_prep_closure(closure_ptr, cif_descr.cif,
invoke_callback,
unique_id)
if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
raise OperationError(space.w_SystemError,
space.wrap("libffi failed to build this callback"))
- #
- _current_space.space = space
- handle.get_handles(space).store_handle(handle_index, self)
def _repr_extra(self):
space = self.space
@@ -221,12 +233,6 @@
except OperationError, e:
_handle_applevel_exception(callback, e, ll_res, extra_line)
-class CurrentSpace:
- def _cleanup_(self):
- if hasattr(self, 'space'):
- del self.space
-_current_space = CurrentSpace()
-
def _invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
""" Callback specification.
ffi_cif - something ffi specific, don't care
@@ -236,10 +242,8 @@
(what the real callback is for example), casted to VOIDP
"""
ll_res = rffi.cast(rffi.CCHARP, ll_res)
- unique_id = rffi.cast(lltype.Signed, ll_userdata)
- space = _current_space.space
- callback = handle.get_handles(space).fetch_handle(unique_id)
- if callback is None or not isinstance(callback, W_CDataCallback):
+ callback = reveal_callback(ll_userdata)
+ if callback is None:
# oups!
try:
os.write(STDERR, "SystemError: invoking a callback "
@@ -251,6 +255,7 @@
misc._raw_memclear(ll_res, SIZE_OF_FFI_ARG)
return
#
+ space = callback.space
must_leave = False
try:
must_leave = space.threadlocals.try_enter_thread(space)
diff --git a/pypy/module/_cffi_backend/ffi_obj.py b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -294,9 +294,9 @@
CONSIDER_FN_AS_FNPTR)
space = self.space
if not space.is_none(w_python_callable):
- return ccallback.W_CDataCallback(space, w_ctype,
- w_python_callable, w_error,
- w_onerror)
+ return ccallback.make_callback(space, w_ctype,
+ w_python_callable, w_error,
+ w_onerror)
else:
# decorator mode: returns a single-argument function
return space.appexec([w_ctype, w_error, w_onerror],
diff --git a/pypy/module/_cffi_backend/func.py b/pypy/module/_cffi_backend/func.py
--- a/pypy/module/_cffi_backend/func.py
+++ b/pypy/module/_cffi_backend/func.py
@@ -24,8 +24,8 @@
@unwrap_spec(w_ctype=ctypeobj.W_CType)
def callback(space, w_ctype, w_callable, w_error=None, w_onerror=None):
- from pypy.module._cffi_backend.ccallback import W_CDataCallback
- return W_CDataCallback(space, w_ctype, w_callable, w_error, w_onerror)
+ from pypy.module._cffi_backend.ccallback import make_callback
+ return make_callback(space, w_ctype, w_callable, w_error, w_onerror)
# ____________________________________________________________
diff --git a/pypy/module/_cffi_backend/handle.py b/pypy/module/_cffi_backend/handle.py
--- a/pypy/module/_cffi_backend/handle.py
+++ b/pypy/module/_cffi_backend/handle.py
@@ -4,27 +4,20 @@
from pypy.interpreter.baseobjspace import W_Root
from pypy.module._cffi_backend import ctypeobj, ctypeptr, cdataobj
from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
-from rpython.rlib import rweaklist, objectmodel, jit
-from rpython.rtyper import annlowlevel
-
-
-class CffiHandles(rweaklist.RWeakListMixin):
- def __init__(self, space):
- self.initialize()
-
-def get_handles(space):
- return space.fromcache(CffiHandles)
+from rpython.rlib import rgc, objectmodel, jit
# ____________________________________________________________
@jit.dont_look_inside
def _newp_handle(space, w_ctype, w_x):
- if not objectmodel.we_are_translated():
- py.test.skip("can't test handles untranslated for now")
+ # Allocate a handle as a nonmovable W_CDataHandle instance, which
+ # we can cast to a plain CCHARP. As long as the object is not freed,
+ # we can cast the CCHARP back to a W_CDataHandle with reveal_gcref().
new_cdataobj = objectmodel.instantiate(cdataobj.W_CDataHandle,
nonmovable=True)
- gcref = annlowlevel.cast_instance_to_gcref(new_cdataobj)
- _cdata = rffi.cast(rffi.CCHARP, gcref)
+ gcref = rgc.cast_instance_to_gcref(new_cdataobj)
+ _cdata = rgc.hide_nonmovable_gcref(gcref)
+ _cdata = rffi.cast(rffi.CCHARP, _cdata)
cdataobj.W_CDataHandle.__init__(new_cdataobj, space, _cdata, w_ctype, w_x)
return new_cdataobj
@@ -36,10 +29,6 @@
"needs 'void *', got '%s'", w_ctype.name)
return _newp_handle(space, w_ctype, w_x)
- at jit.dont_look_inside
-def reveal_gcref(ptr):
- return rffi.cast(llmemory.GCREF, ptr)
-
@unwrap_spec(w_cdata=cdataobj.W_CData)
def from_handle(space, w_cdata):
ctype = w_cdata.ctype
@@ -49,15 +38,14 @@
"expected a 'cdata' object with a 'void *' out of "
"new_handle(), got '%s'", ctype.name)
with w_cdata as ptr:
- gcref = reveal_gcref(ptr)
+ addr = rffi.cast(llmemory.Address, ptr)
+ gcref = rgc.reveal_gcref(addr)
#
if not gcref:
raise oefmt(space.w_RuntimeError,
"cannot use from_handle() on NULL pointer")
- cd = annlowlevel.cast_gcref_to_instance(W_Root, gcref)
- # force an 'isinstance', to crash clearly if the handle is
- # dead or bogus
- if not isinstance(cd, cdataobj.W_CDataHandle):
+ cd = rgc.try_cast_gcref_to_instance(cdataobj.W_CDataHandle, gcref)
+ if cd is None:
raise oefmt(space.w_SystemError,
"ffi.from_handle(): dead or bogus object handle")
return cd.w_keepalive
diff --git a/pypy/module/_cffi_backend/test/test_handle.py b/pypy/module/_cffi_backend/test/test_handle.py
deleted file mode 100644
--- a/pypy/module/_cffi_backend/test/test_handle.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import random
-from pypy.module._cffi_backend.handle import CffiHandles
-
-
-class PseudoWeakRef(object):
- _content = 42
-
- def __call__(self):
- return self._content
-
-
-def test_cffi_handles_1():
- ch = CffiHandles(None)
- expected_content = {}
- for i in range(10000):
- index = ch.reserve_next_handle_index()
- assert 0 <= index < len(ch.handles)
- assert ch.handles[index]() is None
- pwr = PseudoWeakRef()
- expected_content[index] = pwr
- ch.handles[index] = pwr
- assert len(ch.handles) <= 16384
- for index, pwr in expected_content.items():
- assert ch.handles[index] is pwr
-
-def test_cffi_handles_2():
- ch = CffiHandles(None)
- expected_content = {}
- for i in range(10000):
- index = ch.reserve_next_handle_index()
- assert 0 <= index < len(ch.handles)
- assert ch.handles[index]() is None
- pwr = PseudoWeakRef()
- expected_content[index] = pwr
- ch.handles[index] = pwr
- #
- if len(expected_content) > 20:
- r = random.choice(list(expected_content))
- pwr = expected_content.pop(r)
- pwr._content = None
- #
- assert len(ch.handles) < 100
- for index, pwr in expected_content.items():
- assert ch.handles[index] is pwr
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -480,7 +480,7 @@
class _GcRef(object):
# implementation-specific: there should not be any after translation
- __slots__ = ['_x']
+ __slots__ = ['_x', '_handle']
def __init__(self, x):
self._x = x
def __hash__(self):
@@ -529,6 +529,48 @@
return None
try_cast_gcref_to_instance._annspecialcase_ = 'specialize:arg(0)'
+_ffi_cache = None
+def _fetch_ffi():
+ global _ffi_cache
+ if _ffi_cache is None:
+ try:
+ import _cffi_backend
+ _ffi_cache = _cffi_backend.FFI()
+ except (ImportError, AttributeError):
+ import py
+ py.test.skip("need CFFI >= 1.0")
+ return _ffi_cache
+
+ at jit.dont_look_inside
+def hide_nonmovable_gcref(gcref):
+ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+ if we_are_translated():
+ assert lltype.typeOf(gcref) == llmemory.GCREF
+ assert not can_move(gcref)
+ return rffi.cast(llmemory.Address, gcref)
+ else:
+ assert isinstance(gcref, _GcRef)
+ x = gcref._x
+ ffi = _fetch_ffi()
+ if not hasattr(x, '__handle'):
+ x.__handle = ffi.new_handle(x)
+ addr = int(ffi.cast("intptr_t", x.__handle))
+ return rffi.cast(llmemory.Address, addr)
+
+ at jit.dont_look_inside
+def reveal_gcref(addr):
+ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+ assert lltype.typeOf(addr) == llmemory.Address
+ if we_are_translated():
+ return rffi.cast(llmemory.GCREF, addr)
+ else:
+ addr = rffi.cast(lltype.Signed, addr)
+ if addr == 0:
+ return lltype.nullptr(llmemory.GCREF.TO)
+ ffi = _fetch_ffi()
+ x = ffi.from_handle(ffi.cast("void *", addr))
+ return _GcRef(x)
+
# ------------------- implementation -------------------
_cache_s_list_of_gcrefs = None
More information about the pypy-commit
mailing list