Author: Armin Rigo <ar...@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) -@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 + +@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) + +@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 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit