1. Pypy
  2. Untitled project
  3. pypy


pypy / pypy / module / _cffi_backend / ccallback.py

import os

from rpython.rlib import clibffi, rweakref, jit
from rpython.rlib.objectmodel import compute_unique_id, keepalive_until_here
from rpython.rtyper.lltypesystem import lltype, rffi

from pypy.interpreter.error import OperationError, operationerrfmt
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, BIG_ENDIAN, W_CTypeFunc
from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveSigned
from pypy.module._cffi_backend.ctypevoid import W_CTypeVoid

# ____________________________________________________________

class W_CDataCallback(W_CData):
    #_immutable_fields_ = ...
    ll_error = lltype.nullptr(rffi.CCHARP.TO)

    def __init__(self, space, ctype, w_callable, w_error):
        raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
        W_CData.__init__(self, space, raw_closure, ctype)
        if not space.is_true(space.callable(w_callable)):
            raise operationerrfmt(space.w_TypeError,
                                  "expected a callable object, not %s",
        self.w_callable = w_callable
        fresult = self.getfunctype().ctitem
        size = fresult.size
        if size > 0:
            if fresult.is_primitive_integer and size < SIZE_OF_FFI_ARG:
                size = SIZE_OF_FFI_ARG
            self.ll_error = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw',
        if not space.is_none(w_error):
            convert_from_object_fficallback(fresult, self.ll_error, w_error)
        self.unique_id = compute_unique_id(self)
        global_callback_mapping.set(self.unique_id, self)
        cif_descr = self.getfunctype().cif_descr
        if not cif_descr:
            raise OperationError(space.w_NotImplementedError,
                                 space.wrap("callbacks with '...'"))
        res = clibffi.c_ffi_prep_closure(self.get_closure(), cif_descr.cif,
                                         rffi.cast(rffi.VOIDP, self.unique_id))
        if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
            raise OperationError(space.w_SystemError,
                space.wrap("libffi failed to build this callback"))

    def get_closure(self):
        return rffi.cast(clibffi.FFI_CLOSUREP, self._cdata)

    def __del__(self):
        if self.ll_error:
            lltype.free(self.ll_error, flavor='raw')

    def _repr_extra(self):
        space = self.space
        return 'calling ' + space.str_w(space.repr(self.w_callable))

    def getfunctype(self):
        ctype = self.ctype
        if not isinstance(ctype, W_CTypeFunc):
            space = self.space
            raise OperationError(space.w_TypeError,
                                 space.wrap("expected a function ctype"))
        return ctype

    def invoke(self, ll_args):
        space = self.space
        ctype = self.getfunctype()
        args_w = []
        for i, farg in enumerate(ctype.fargs):
            ll_arg = rffi.cast(rffi.CCHARP, ll_args[i])
        return space.call(self.w_callable, space.newtuple(args_w))

    def convert_result(self, ll_res, w_res):
        fresult = self.getfunctype().ctitem
        convert_from_object_fficallback(fresult, ll_res, w_res)

    def print_error(self, operr, extra_line):
        space = self.space
        operr.write_unraisable(space, "callback ", self.w_callable,
                               with_traceback=True, extra_line=extra_line)

    def write_error_return_value(self, ll_res):
        fresult = self.getfunctype().ctitem
        if fresult.size > 0:
            misc._raw_memcopy(self.ll_error, ll_res, fresult.size)

global_callback_mapping = rweakref.RWeakValueDictionary(int, W_CDataCallback)

def convert_from_object_fficallback(fresult, ll_res, w_res):
    space = fresult.space
    small_result = fresult.size < SIZE_OF_FFI_ARG
    if small_result and isinstance(fresult, W_CTypeVoid):
        if not space.is_w(w_res, space.w_None):
            raise OperationError(space.w_TypeError,
                    space.wrap("callback with the return type 'void'"
                               " must return None"))
    if small_result and fresult.is_primitive_integer:
        # work work work around a libffi irregularity: for integer return
        # types we have to fill at least a complete 'ffi_arg'-sized result
        # buffer.
        if type(fresult) is W_CTypePrimitiveSigned:
            # It's probably fine to always zero-extend, but you never
            # know: maybe some code somewhere expects a negative
            # 'short' result to be returned into EAX as a 32-bit
            # negative number.  Better safe than sorry.  This code
            # is about that case.  Let's ignore this for enums.
            # do a first conversion only to detect overflows.  This
            # conversion produces stuff that is otherwise ignored.
            fresult.convert_from_object(ll_res, w_res)
            # manual inlining and tweaking of
            # W_CTypePrimitiveSigned.convert_from_object() in order
            # to write a whole 'ffi_arg'.
            value = misc.as_long(space, w_res)
            misc.write_raw_integer_data(ll_res, value, SIZE_OF_FFI_ARG)
            # zero extension: fill the '*result' with zeros, and (on big-
            # endian machines) correct the 'result' pointer to write to
            misc._raw_memclear(ll_res, SIZE_OF_FFI_ARG)
            if BIG_ENDIAN:
                diff = SIZE_OF_FFI_ARG - fresult.size
                ll_res = rffi.ptradd(ll_res, diff)
    fresult.convert_from_object(ll_res, w_res)

# ____________________________________________________________


def invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
    """ Callback specification.
    ffi_cif - something ffi specific, don't care
    ll_args - rffi.VOIDPP - pointer to array of pointers to args
    ll_restype - rffi.VOIDP - pointer to result
    ll_userdata - a special structure which holds necessary information
                  (what the real callback is for example), casted to VOIDP
    e = cerrno.get_real_errno()
    ll_res = rffi.cast(rffi.CCHARP, ll_res)
    unique_id = rffi.cast(lltype.Signed, ll_userdata)
    callback = global_callback_mapping.get(unique_id)
    if callback is None:
        # oups!
            os.write(STDERR, "SystemError: invoking a callback "
                             "that was already freed\n")
        except OSError:
        # In this case, we don't even know how big ll_res is.  Let's assume
        # it is just a 'ffi_arg', and store 0 there.
        misc._raw_memclear(ll_res, SIZE_OF_FFI_ARG)
    ec = None
        ec = cerrno.get_errno_container(callback.space)
        cerrno.save_errno_into(ec, e)
        extra_line = ''
            w_res = callback.invoke(ll_args)
            extra_line = "Trying to convert the result back to C:\n"
            callback.convert_result(ll_res, w_res)
        except OperationError, e:
            # got an app-level exception
            callback.print_error(e, extra_line)
    except Exception, e:
        # oups! last-level attempt to recover.
            os.write(STDERR, "SystemError: callback raised ")
            os.write(STDERR, str(e))
            os.write(STDERR, "\n")
        except OSError:
    if ec is not None: