[pypy-commit] pypy ffi-backend: Re-add rlib/libffi.py, hopefully temporarily

arigo noreply at buildbot.pypy.org
Mon Aug 6 20:28:32 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: ffi-backend
Changeset: r56608:413414441174
Date: 2012-08-06 17:39 +0000
http://bitbucket.org/pypy/pypy/changeset/413414441174/

Log:	Re-add rlib/libffi.py, hopefully temporarily

diff --git a/pypy/rlib/libffi.py b/pypy/rlib/libffi.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/libffi.py
@@ -0,0 +1,558 @@
+from __future__ import with_statement
+
+from pypy.rpython.lltypesystem import rffi, lltype
+from pypy.rlib.objectmodel import specialize, enforceargs
+from pypy.rlib.rarithmetic import intmask, r_uint, r_singlefloat, r_longlong
+from pypy.rlib import jit
+from pypy.rlib import clibffi
+from pypy.rlib.clibffi import FUNCFLAG_CDECL, FUNCFLAG_STDCALL, \
+        AbstractFuncPtr, push_arg_as_ffiptr, c_ffi_call, FFI_TYPE_STRUCT
+from pypy.rlib.rdynload import dlopen, dlclose, dlsym, dlsym_byordinal
+from pypy.rlib.rdynload import DLLHANDLE
+
+import os
+
+class types(object):
+    """
+    This namespace contains the primitive types you can use to declare the
+    signatures of the ffi functions.
+
+    In general, the name of the types are closely related to the ones of the
+    C-level ffi_type_*: e.g, instead of ffi_type_sint you should use
+    libffi.types.sint.
+
+    However, you should not rely on a perfect correspondence: in particular,
+    the exact meaning of ffi_type_{slong,ulong} changes a lot between libffi
+    versions, so types.slong could be different than ffi_type_slong.
+    """
+
+    @classmethod
+    def _import(cls):
+        prefix = 'ffi_type_'
+        for key, value in clibffi.__dict__.iteritems():
+            if key.startswith(prefix):
+                name = key[len(prefix):]
+                setattr(cls, name, value)
+        cls.slong = clibffi.cast_type_to_ffitype(rffi.LONG)
+        cls.ulong = clibffi.cast_type_to_ffitype(rffi.ULONG)
+        cls.slonglong = clibffi.cast_type_to_ffitype(rffi.LONGLONG)
+        cls.ulonglong = clibffi.cast_type_to_ffitype(rffi.ULONGLONG)
+        cls.signed = clibffi.cast_type_to_ffitype(rffi.SIGNED)
+        cls.wchar_t = clibffi.cast_type_to_ffitype(lltype.UniChar)
+        del cls._import
+
+    @staticmethod
+    @jit.elidable
+    def getkind(ffi_type):
+        """Returns 'v' for void, 'f' for float, 'i' for signed integer,
+        and 'u' for unsigned integer.
+        """
+        if   ffi_type is types.void:    return 'v'
+        elif ffi_type is types.double:  return 'f'
+        elif ffi_type is types.float:   return 's'
+        elif ffi_type is types.pointer: return 'u'
+        #
+        elif ffi_type is types.schar:   return 'i'
+        elif ffi_type is types.uchar:   return 'u'
+        elif ffi_type is types.sshort:  return 'i'
+        elif ffi_type is types.ushort:  return 'u'
+        elif ffi_type is types.sint:    return 'i'
+        elif ffi_type is types.uint:    return 'u'
+        elif ffi_type is types.slong:   return 'i'
+        elif ffi_type is types.ulong:   return 'u'
+        #
+        elif ffi_type is types.sint8:   return 'i'
+        elif ffi_type is types.uint8:   return 'u'
+        elif ffi_type is types.sint16:  return 'i'
+        elif ffi_type is types.uint16:  return 'u'
+        elif ffi_type is types.sint32:  return 'i'
+        elif ffi_type is types.uint32:  return 'u'
+        ## (note that on 64-bit platforms, types.sint64 is types.slong and the
+        ## case is caught above)
+        elif ffi_type is types.sint64:  return 'I'
+        elif ffi_type is types.uint64:  return 'U'
+        #
+        elif types.is_struct(ffi_type): return 'S'
+        raise KeyError
+
+    @staticmethod
+    @jit.elidable
+    def is_struct(ffi_type):
+        return intmask(ffi_type.c_type) == FFI_TYPE_STRUCT
+
+types._import()
+
+# this was '_fits_into_long', which is not adequate, because long is
+# not necessary the type where we compute with. Actually meant is
+# the type 'Signed'.
+
+ at specialize.arg(0)
+def _fits_into_signed(TYPE):
+    if isinstance(TYPE, lltype.Ptr):
+        return True # pointers always fits into Signeds
+    if not isinstance(TYPE, lltype.Primitive):
+        return False
+    if TYPE is lltype.Void or TYPE is rffi.FLOAT or TYPE is rffi.DOUBLE:
+        return False
+    sz = rffi.sizeof(TYPE)
+    return sz <= rffi.sizeof(rffi.SIGNED)
+
+
+# ======================================================================
+
+IS_32_BIT = (r_uint.BITS == 32)
+
+ at specialize.memo()
+def _check_type(TYPE):
+    if isinstance(TYPE, lltype.Ptr):
+        if TYPE.TO._gckind != 'raw':
+            raise TypeError, "Can only push raw values to C, not 'gc'"
+        # XXX probably we should recursively check for struct fields here,
+        # lets just ignore that for now
+        if isinstance(TYPE.TO, lltype.Array) and 'nolength' not in TYPE.TO._hints:
+            raise TypeError, "Can only push to C arrays without length info"
+
+
+class ArgChain(object):
+    first = None
+    last = None
+    numargs = 0
+
+    @specialize.argtype(1)
+    def arg(self, val):
+        TYPE = lltype.typeOf(val)
+        _check_type(TYPE)
+        if _fits_into_signed(TYPE):
+            cls = IntArg
+            val = rffi.cast(rffi.SIGNED, val)
+        elif TYPE is rffi.DOUBLE:
+            cls = FloatArg
+        elif TYPE is rffi.LONGLONG or TYPE is rffi.ULONGLONG:
+            cls = LongLongArg
+            val = rffi.cast(rffi.LONGLONG, val)
+        elif TYPE is rffi.FLOAT:
+            cls = SingleFloatArg
+        else:
+            raise TypeError, 'Unsupported argument type: %s' % TYPE
+        self._append(cls(val))
+        return self
+
+    def arg_raw(self, val):
+        self._append(RawArg(val))
+
+    def _append(self, arg):
+        if self.first is None:
+            self.first = self.last = arg
+        else:
+            self.last.next = arg
+            self.last = arg
+        self.numargs += 1
+
+
+class AbstractArg(object):
+    next = None
+
+class IntArg(AbstractArg):
+    """ An argument holding an integer
+    """
+
+    def __init__(self, intval):
+        self.intval = intval
+
+    def push(self, func, ll_args, i):
+        func._push_int(self.intval, ll_args, i)
+
+
+class FloatArg(AbstractArg):
+    """ An argument holding a python float (i.e. a C double)
+    """
+
+    def __init__(self, floatval):
+        self.floatval = floatval
+
+    def push(self, func, ll_args, i):
+        func._push_float(self.floatval, ll_args, i)
+
+class RawArg(AbstractArg):
+    """ An argument holding a raw pointer to put inside ll_args
+    """
+
+    def __init__(self, ptrval):
+        self.ptrval = ptrval
+
+    def push(self, func, ll_args, i):
+        func._push_raw(self.ptrval, ll_args, i)
+
+class SingleFloatArg(AbstractArg):
+    """ An argument representing a C float
+    """
+
+    def __init__(self, singlefloatval):
+        self.singlefloatval = singlefloatval
+
+    def push(self, func, ll_args, i):
+        func._push_singlefloat(self.singlefloatval, ll_args, i)
+
+
+class LongLongArg(AbstractArg):
+    """ An argument representing a C long long
+    """
+
+    def __init__(self, longlongval):
+        self.longlongval = longlongval
+
+    def push(self, func, ll_args, i):
+        func._push_longlong(self.longlongval, ll_args, i)
+
+
+# ======================================================================
+
+
+class Func(AbstractFuncPtr):
+
+    _immutable_fields_ = ['funcsym']
+    argtypes = []
+    restype = clibffi.FFI_TYPE_NULL
+    flags = 0
+    funcsym = lltype.nullptr(rffi.VOIDP.TO)
+
+    def __init__(self, name, argtypes, restype, funcsym, flags=FUNCFLAG_CDECL,
+                 keepalive=None):
+        AbstractFuncPtr.__init__(self, name, argtypes, restype, flags)
+        self.keepalive = keepalive
+        self.funcsym = funcsym
+
+    # ========================================================================
+    # PUBLIC INTERFACE
+    # ========================================================================
+
+    @jit.unroll_safe
+    @specialize.arg(2, 3)
+    def call(self, argchain, RESULT, is_struct=False):
+        # WARNING!  This code is written carefully in a way that the JIT
+        # optimizer will see a sequence of calls like the following:
+        #
+        #    libffi_prepare_call
+        #    libffi_push_arg
+        #    libffi_push_arg
+        #    ...
+        #    libffi_call
+        #
+        # It is important that there is no other operation in the middle, else
+        # the optimizer will fail to recognize the pattern and won't turn it
+        # into a fast CALL.  Note that "arg = arg.next" is optimized away,
+        # assuming that argchain is completely virtual.
+        self = jit.promote(self)
+        if argchain.numargs != len(self.argtypes):
+            raise TypeError, 'Wrong number of arguments: %d expected, got %d' %\
+                (len(self.argtypes), argchain.numargs)
+        ll_args = self._prepare()
+        i = 0
+        arg = argchain.first
+        while arg:
+            arg.push(self, ll_args, i)
+            i += 1
+            arg = arg.next
+        #
+        if is_struct:
+            assert types.is_struct(self.restype)
+            res = self._do_call_raw(self.funcsym, ll_args)
+        elif _fits_into_signed(RESULT):
+            assert not types.is_struct(self.restype)
+            res = self._do_call_int(self.funcsym, ll_args)
+        elif RESULT is rffi.DOUBLE:
+            return self._do_call_float(self.funcsym, ll_args)
+        elif RESULT is rffi.FLOAT:
+            return self._do_call_singlefloat(self.funcsym, ll_args)
+        elif RESULT is rffi.LONGLONG or RESULT is rffi.ULONGLONG:
+            assert IS_32_BIT
+            res = self._do_call_longlong(self.funcsym, ll_args)
+        elif RESULT is lltype.Void:
+            return self._do_call_void(self.funcsym, ll_args)
+        else:
+            raise TypeError, 'Unsupported result type: %s' % RESULT
+        #
+        return rffi.cast(RESULT, res)
+
+    # END OF THE PUBLIC INTERFACE
+    # ------------------------------------------------------------------------
+
+    # JIT friendly interface
+    # the following methods are supposed to be seen opaquely by the optimizer
+
+    @jit.oopspec('libffi_prepare_call(self)')
+    def _prepare(self):
+        ll_args = lltype.malloc(rffi.VOIDPP.TO, len(self.argtypes), flavor='raw')
+        return ll_args
+
+
+    # _push_* and _do_call_* in theory could be automatically specialize()d by
+    # the annotator.  However, specialization doesn't work well with oopspec,
+    # so we specialize them by hand
+
+    @jit.oopspec('libffi_push_int(self, value, ll_args, i)')
+    @enforceargs( None, int,   None,    int) # fix the annotation for tests
+    def _push_int(self, value, ll_args, i):
+        self._push_arg(value, ll_args, i)
+
+    @jit.dont_look_inside
+    def _push_raw(self, value, ll_args, i):
+        ll_args[i] = value
+
+    @jit.oopspec('libffi_push_float(self, value, ll_args, i)')
+    @enforceargs(   None, float, None,    int) # fix the annotation for tests
+    def _push_float(self, value, ll_args, i):
+        self._push_arg(value, ll_args, i)
+
+    @jit.oopspec('libffi_push_singlefloat(self, value, ll_args, i)')
+    @enforceargs(None, r_singlefloat, None, int) # fix the annotation for tests
+    def _push_singlefloat(self, value, ll_args, i):
+        self._push_arg(value, ll_args, i)
+
+    @jit.oopspec('libffi_push_longlong(self, value, ll_args, i)')
+    @enforceargs(None, r_longlong, None, int) # fix the annotation for tests
+    def _push_longlong(self, value, ll_args, i):
+        self._push_arg(value, ll_args, i)
+
+    @jit.oopspec('libffi_call_int(self, funcsym, ll_args)')
+    def _do_call_int(self, funcsym, ll_args):
+        return self._do_call(funcsym, ll_args, rffi.SIGNED)
+
+    @jit.oopspec('libffi_call_float(self, funcsym, ll_args)')
+    def _do_call_float(self, funcsym, ll_args):
+        return self._do_call(funcsym, ll_args, rffi.DOUBLE)
+
+    @jit.oopspec('libffi_call_singlefloat(self, funcsym, ll_args)')
+    def _do_call_singlefloat(self, funcsym, ll_args):
+        return self._do_call(funcsym, ll_args, rffi.FLOAT)
+
+    @jit.dont_look_inside
+    def _do_call_raw(self, funcsym, ll_args):
+        # same as _do_call_int, but marked as jit.dont_look_inside
+        return self._do_call(funcsym, ll_args, rffi.SIGNED)
+
+    @jit.oopspec('libffi_call_longlong(self, funcsym, ll_args)')
+    def _do_call_longlong(self, funcsym, ll_args):
+        return self._do_call(funcsym, ll_args, rffi.LONGLONG)
+
+    @jit.oopspec('libffi_call_void(self, funcsym, ll_args)')
+    def _do_call_void(self, funcsym, ll_args):
+        return self._do_call(funcsym, ll_args, lltype.Void)
+
+    # ------------------------------------------------------------------------
+    # private methods
+
+    @specialize.argtype(1)
+    def _push_arg(self, value, ll_args, i):
+        # XXX: check the type is not translated?
+        argtype = self.argtypes[i]
+        c_size = intmask(argtype.c_size)
+        ll_buf = lltype.malloc(rffi.CCHARP.TO, c_size, flavor='raw')
+        push_arg_as_ffiptr(argtype, value, ll_buf)
+        ll_args[i] = ll_buf
+
+    @specialize.arg(3)
+    def _do_call(self, funcsym, ll_args, RESULT):
+        # XXX: check len(args)?
+        ll_result = lltype.nullptr(rffi.CCHARP.TO)
+        if self.restype != types.void:
+            ll_result = lltype.malloc(rffi.CCHARP.TO,
+                                      intmask(self.restype.c_size),
+                                      flavor='raw')
+        ffires = c_ffi_call(self.ll_cif,
+                            self.funcsym,
+                            rffi.cast(rffi.VOIDP, ll_result),
+                            rffi.cast(rffi.VOIDPP, ll_args))
+        if RESULT is not lltype.Void:
+            TP = lltype.Ptr(rffi.CArray(RESULT))
+            buf = rffi.cast(TP, ll_result)
+            if types.is_struct(self.restype):
+                assert RESULT == rffi.SIGNED
+                # for structs, we directly return the buffer and transfer the
+                # ownership
+                res = rffi.cast(RESULT, buf)
+            else:
+                res = buf[0]
+        else:
+            res = None
+        self._free_buffers(ll_result, ll_args)
+        clibffi.check_fficall_result(ffires, self.flags)
+        return res
+
+    def _free_buffers(self, ll_result, ll_args):
+        if ll_result:
+            self._free_buffer_maybe(rffi.cast(rffi.VOIDP, ll_result), self.restype)
+        for i in range(len(self.argtypes)):
+            argtype = self.argtypes[i]
+            self._free_buffer_maybe(ll_args[i], argtype)
+        lltype.free(ll_args, flavor='raw')
+
+    def _free_buffer_maybe(self, buf, ffitype):
+        # if it's a struct, the buffer is not freed and the ownership is
+        # already of the caller (in case of ll_args buffers) or transferred to
+        # it (in case of ll_result buffer)
+        if not types.is_struct(ffitype):
+            lltype.free(buf, flavor='raw')
+
+
+# ======================================================================
+
+
+# XXX: it partially duplicate the code in clibffi.py
+class CDLL(object):
+    def __init__(self, libname, mode=-1):
+        """Load the library, or raises DLOpenError."""
+        self.lib = rffi.cast(DLLHANDLE, 0)
+        with rffi.scoped_str2charp(libname) as ll_libname:
+            self.lib = dlopen(ll_libname, mode)
+
+    def __del__(self):
+        if self.lib:
+            dlclose(self.lib)
+            self.lib = rffi.cast(DLLHANDLE, 0)
+
+    def getpointer(self, name, argtypes, restype, flags=FUNCFLAG_CDECL):
+        return Func(name, argtypes, restype, dlsym(self.lib, name),
+                    flags=flags, keepalive=self)
+
+    def getpointer_by_ordinal(self, name, argtypes, restype,
+                              flags=FUNCFLAG_CDECL):
+        return Func('by_ordinal', argtypes, restype, 
+                    dlsym_byordinal(self.lib, name),
+                    flags=flags, keepalive=self)
+    def getaddressindll(self, name):
+        return dlsym(self.lib, name)
+
+if os.name == 'nt':
+    class WinDLL(CDLL):
+        def getpointer(self, name, argtypes, restype, flags=FUNCFLAG_STDCALL):
+            return Func(name, argtypes, restype, dlsym(self.lib, name),
+                        flags=flags, keepalive=self)
+        def getpointer_by_ordinal(self, name, argtypes, restype,
+                                  flags=FUNCFLAG_STDCALL):
+            return Func(name, argtypes, restype, dlsym_byordinal(self.lib, name),
+                        flags=flags, keepalive=self)
+
+# ======================================================================
+
+ at jit.oopspec('libffi_struct_getfield(ffitype, addr, offset)')
+def struct_getfield_int(ffitype, addr, offset):
+    """
+    Return the field of type ``ffitype`` at ``addr+offset``, widened to
+    lltype.Signed.
+    """
+    for TYPE, ffitype2 in clibffi.ffitype_map_int_or_ptr:
+        if ffitype is ffitype2:
+            value = _struct_getfield(TYPE, addr, offset)
+            return rffi.cast(lltype.Signed, value)
+    assert False, "cannot find the given ffitype"
+
+
+ at jit.oopspec('libffi_struct_setfield(ffitype, addr, offset, value)')
+def struct_setfield_int(ffitype, addr, offset, value):
+    """
+    Set the field of type ``ffitype`` at ``addr+offset``.  ``value`` is of
+    type lltype.Signed, and it's automatically converted to the right type.
+    """
+    for TYPE, ffitype2 in clibffi.ffitype_map_int_or_ptr:
+        if ffitype is ffitype2:
+            value = rffi.cast(TYPE, value)
+            _struct_setfield(TYPE, addr, offset, value)
+            return
+    assert False, "cannot find the given ffitype"
+
+
+ at jit.oopspec('libffi_struct_getfield(ffitype, addr, offset)')
+def struct_getfield_longlong(ffitype, addr, offset):
+    """
+    Return the field of type ``ffitype`` at ``addr+offset``, casted to
+    lltype.LongLong.
+    """
+    value = _struct_getfield(lltype.SignedLongLong, addr, offset)
+    return value
+
+ at jit.oopspec('libffi_struct_setfield(ffitype, addr, offset, value)')
+def struct_setfield_longlong(ffitype, addr, offset, value):
+    """
+    Set the field of type ``ffitype`` at ``addr+offset``.  ``value`` is of
+    type lltype.LongLong
+    """
+    _struct_setfield(lltype.SignedLongLong, addr, offset, value)
+
+
+ at jit.oopspec('libffi_struct_getfield(ffitype, addr, offset)')
+def struct_getfield_float(ffitype, addr, offset):
+    value = _struct_getfield(lltype.Float, addr, offset)
+    return value
+
+ at jit.oopspec('libffi_struct_setfield(ffitype, addr, offset, value)')
+def struct_setfield_float(ffitype, addr, offset, value):
+    _struct_setfield(lltype.Float, addr, offset, value)
+
+
+ at jit.oopspec('libffi_struct_getfield(ffitype, addr, offset)')
+def struct_getfield_singlefloat(ffitype, addr, offset):
+    value = _struct_getfield(lltype.SingleFloat, addr, offset)
+    return value
+
+ at jit.oopspec('libffi_struct_setfield(ffitype, addr, offset, value)')
+def struct_setfield_singlefloat(ffitype, addr, offset, value):
+    _struct_setfield(lltype.SingleFloat, addr, offset, value)
+
+
+ at specialize.arg(0)
+def _struct_getfield(TYPE, addr, offset):
+    """
+    Read the field of type TYPE at addr+offset.
+    addr is of type rffi.VOIDP, offset is an int.
+    """
+    addr = rffi.ptradd(addr, offset)
+    PTR_FIELD = lltype.Ptr(rffi.CArray(TYPE))
+    return rffi.cast(PTR_FIELD, addr)[0]
+
+
+ at specialize.arg(0)
+def _struct_setfield(TYPE, addr, offset, value):
+    """
+    Write the field of type TYPE at addr+offset.
+    addr is of type rffi.VOIDP, offset is an int.
+    """
+    addr = rffi.ptradd(addr, offset)
+    PTR_FIELD = lltype.Ptr(rffi.CArray(TYPE))
+    rffi.cast(PTR_FIELD, addr)[0] = value
+
+# ======================================================================
+
+# These specialize.call_location's should really be specialize.arg(0), however
+# you can't hash a pointer obj, which the specialize machinery wants to do.
+# Given the present usage of these functions, it's good enough.
+ at specialize.call_location()
+ at jit.oopspec("libffi_array_getitem(ffitype, width, addr, index, offset)")
+def array_getitem(ffitype, width, addr, index, offset):
+    for TYPE, ffitype2 in clibffi.ffitype_map:
+        if ffitype is ffitype2:
+            addr = rffi.ptradd(addr, index * width)
+            addr = rffi.ptradd(addr, offset)
+            return rffi.cast(rffi.CArrayPtr(TYPE), addr)[0]
+    assert False
+
+def array_getitem_T(TYPE, width, addr, index, offset):
+    addr = rffi.ptradd(addr, index * width)
+    addr = rffi.ptradd(addr, offset)
+    return rffi.cast(rffi.CArrayPtr(TYPE), addr)[0]
+
+ at specialize.call_location()
+ at jit.oopspec("libffi_array_setitem(ffitype, width, addr, index, offset, value)")
+def array_setitem(ffitype, width, addr, index, offset, value):
+    for TYPE, ffitype2 in clibffi.ffitype_map:
+        if ffitype is ffitype2:
+            addr = rffi.ptradd(addr, index * width)
+            addr = rffi.ptradd(addr, offset)
+            rffi.cast(rffi.CArrayPtr(TYPE), addr)[0] = value
+            return
+    assert False
+
+def array_setitem_T(TYPE, width, addr, index, offset, value):
+    addr = rffi.ptradd(addr, index * width)
+    addr = rffi.ptradd(addr, offset)
+    rffi.cast(rffi.CArrayPtr(TYPE), addr)[0] = value
diff --git a/pypy/rlib/test/test_libffi.py b/pypy/rlib/test/test_libffi.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/test/test_libffi.py
@@ -0,0 +1,610 @@
+import os
+
+import py
+
+from pypy.rlib.rarithmetic import r_singlefloat, r_longlong, r_ulonglong
+from pypy.rlib.test.test_clibffi import BaseFfiTest, make_struct_ffitype_e
+from pypy.rpython.lltypesystem import rffi, lltype
+from pypy.rpython.lltypesystem.ll2ctypes import ALLOCATED
+from pypy.rpython.llinterp import LLException
+from pypy.rlib.libffi import (CDLL, ArgChain, types,
+                              IS_32_BIT, array_getitem, array_setitem)
+from pypy.rlib.libffi import (struct_getfield_int, struct_setfield_int,
+                              struct_getfield_longlong, struct_setfield_longlong,
+                              struct_getfield_float, struct_setfield_float,
+                              struct_getfield_singlefloat, struct_setfield_singlefloat)
+
+class TestLibffiMisc(BaseFfiTest):
+
+    CDLL = CDLL
+
+    def test_argchain(self):
+        chain = ArgChain()
+        assert chain.numargs == 0
+        chain2 = chain.arg(42)
+        assert chain2 is chain
+        assert chain.numargs == 1
+        intarg = chain.first
+        assert chain.last is intarg
+        assert intarg.intval == 42
+        chain.arg(123.45)
+        assert chain.numargs == 2
+        assert chain.first is intarg
+        assert intarg.next is chain.last
+        floatarg = intarg.next
+        assert floatarg.floatval == 123.45
+
+    def test_wrong_args(self):
+        # so far the test passes but for the wrong reason :-), i.e. because
+        # .arg() only supports integers and floats
+        chain = ArgChain()
+        x = lltype.malloc(lltype.GcStruct('xxx'))
+        y = lltype.malloc(lltype.GcArray(rffi.SIGNED), 3)
+        z = lltype.malloc(lltype.Array(rffi.SIGNED), 4, flavor='raw')
+        py.test.raises(TypeError, "chain.arg(x)")
+        py.test.raises(TypeError, "chain.arg(y)")
+        py.test.raises(TypeError, "chain.arg(z)")
+        lltype.free(z, flavor='raw')
+
+    def test_library_open(self):
+        lib = self.get_libc()
+        del lib
+        assert not ALLOCATED
+
+    def test_library_get_func(self):
+        lib = self.get_libc()
+        ptr = lib.getpointer('fopen', [], types.void)
+        py.test.raises(KeyError, lib.getpointer, 'xxxxxxxxxxxxxxx', [], types.void)
+        del ptr
+        del lib
+        assert not ALLOCATED
+
+    def test_struct_fields(self):
+        longsize = 4 if IS_32_BIT else 8
+        POINT = lltype.Struct('POINT',
+                              ('x', rffi.LONG),
+                              ('y', rffi.SHORT),
+                              ('z', rffi.VOIDP),
+                              )
+        y_ofs = longsize
+        z_ofs = longsize*2
+        p = lltype.malloc(POINT, flavor='raw')
+        p.x = 42
+        p.y = rffi.cast(rffi.SHORT, -1)
+        p.z = rffi.cast(rffi.VOIDP, 0x1234)
+        addr = rffi.cast(rffi.VOIDP, p)
+        assert struct_getfield_int(types.slong, addr, 0) == 42
+        assert struct_getfield_int(types.sshort, addr, y_ofs) == -1
+        assert struct_getfield_int(types.pointer, addr, z_ofs) == 0x1234
+        #
+        struct_setfield_int(types.slong, addr, 0, 43)
+        struct_setfield_int(types.sshort, addr, y_ofs, 0x1234FFFE) # 0x1234 is masked out
+        struct_setfield_int(types.pointer, addr, z_ofs, 0x4321)
+        assert p.x == 43
+        assert p.y == -2
+        assert rffi.cast(rffi.LONG, p.z) == 0x4321
+        #
+        lltype.free(p, flavor='raw')
+
+    def test_array_fields(self):
+        POINT = lltype.Struct("POINT",
+            ("x", lltype.Float),
+            ("y", lltype.Float),
+        )
+        points = lltype.malloc(rffi.CArray(POINT), 2, flavor="raw")
+        points[0].x = 1.0
+        points[0].y = 2.0
+        points[1].x = 3.0
+        points[1].y = 4.0
+        points = rffi.cast(rffi.CArrayPtr(lltype.Char), points)
+        assert array_getitem(types.double, 16, points, 0, 0) == 1.0
+        assert array_getitem(types.double, 16, points, 0, 8) == 2.0
+        assert array_getitem(types.double, 16, points, 1, 0) == 3.0
+        assert array_getitem(types.double, 16, points, 1, 8) == 4.0
+        #
+        array_setitem(types.double, 16, points, 0, 0, 10.0)
+        array_setitem(types.double, 16, points, 0, 8, 20.0)
+        array_setitem(types.double, 16, points, 1, 0, 30.0)
+        array_setitem(types.double, 16, points, 1, 8, 40.0)
+        #
+        assert array_getitem(types.double, 16, points, 0, 0) == 10.0
+        assert array_getitem(types.double, 16, points, 0, 8) == 20.0
+        assert array_getitem(types.double, 16, points, 1, 0) == 30.0
+        assert array_getitem(types.double, 16, points, 1, 8) == 40.0
+        #
+        lltype.free(points, flavor="raw")
+
+
+    def test_struct_fields_longlong(self):
+        POINT = lltype.Struct('POINT',
+                              ('x', rffi.LONGLONG),
+                              ('y', rffi.ULONGLONG)
+                              )
+        y_ofs = 8
+        p = lltype.malloc(POINT, flavor='raw')
+        p.x = r_longlong(123)
+        p.y = r_ulonglong(456)
+        addr = rffi.cast(rffi.VOIDP, p)
+        assert struct_getfield_longlong(types.slonglong, addr, 0) == 123
+        assert struct_getfield_longlong(types.ulonglong, addr, y_ofs) == 456
+        #
+        v = rffi.cast(lltype.SignedLongLong, r_ulonglong(9223372036854775808))
+        struct_setfield_longlong(types.slonglong, addr, 0, v)
+        struct_setfield_longlong(types.ulonglong, addr, y_ofs, r_longlong(-1))
+        assert p.x == -9223372036854775808
+        assert rffi.cast(lltype.UnsignedLongLong, p.y) == 18446744073709551615
+        #
+        lltype.free(p, flavor='raw')
+
+    def test_struct_fields_float(self):
+        POINT = lltype.Struct('POINT',
+                              ('x', rffi.DOUBLE),
+                              ('y', rffi.DOUBLE)
+                              )
+        y_ofs = 8
+        p = lltype.malloc(POINT, flavor='raw')
+        p.x = 123.4
+        p.y = 567.8
+        addr = rffi.cast(rffi.VOIDP, p)
+        assert struct_getfield_float(types.double, addr, 0) == 123.4
+        assert struct_getfield_float(types.double, addr, y_ofs) == 567.8
+        #
+        struct_setfield_float(types.double, addr, 0, 321.0)
+        struct_setfield_float(types.double, addr, y_ofs, 876.5)
+        assert p.x == 321.0
+        assert p.y == 876.5
+        #
+        lltype.free(p, flavor='raw')
+
+    def test_struct_fields_singlefloat(self):
+        POINT = lltype.Struct('POINT',
+                              ('x', rffi.FLOAT),
+                              ('y', rffi.FLOAT)
+                              )
+        y_ofs = 4
+        p = lltype.malloc(POINT, flavor='raw')
+        p.x = r_singlefloat(123.4)
+        p.y = r_singlefloat(567.8)
+        addr = rffi.cast(rffi.VOIDP, p)
+        assert struct_getfield_singlefloat(types.double, addr, 0) == r_singlefloat(123.4)
+        assert struct_getfield_singlefloat(types.double, addr, y_ofs) == r_singlefloat(567.8)
+        #
+        struct_setfield_singlefloat(types.double, addr, 0, r_singlefloat(321.0))
+        struct_setfield_singlefloat(types.double, addr, y_ofs, r_singlefloat(876.5))
+        assert p.x == r_singlefloat(321.0)
+        assert p.y == r_singlefloat(876.5)
+        #
+        lltype.free(p, flavor='raw')
+
+    def test_windll(self):
+        if os.name != 'nt':
+            skip('Run only on windows')
+        from pypy.rlib.libffi import WinDLL
+        dll = WinDLL('Kernel32.dll')
+        sleep = dll.getpointer('Sleep',[types.uint], types.void)
+        chain = ArgChain()
+        chain.arg(10)
+        sleep.call(chain, lltype.Void, is_struct=False)
+
+class TestLibffiCall(BaseFfiTest):
+    """
+    Test various kind of calls through libffi.
+
+    The peculiarity of these tests is that they are run both directly (going
+    really through libffi) and by jit/metainterp/test/test_fficall.py, which
+    tests the call when JITted.
+
+    If you need to test a behaviour than it's not affected by JITing (e.g.,
+    typechecking), you should put your test in TestLibffiMisc.
+    """
+
+    CDLL = CDLL
+
+    @classmethod
+    def setup_class(cls):
+        from pypy.tool.udir import udir
+        from pypy.translator.tool.cbuild import ExternalCompilationInfo
+        from pypy.translator.tool.cbuild import STANDARD_DEFINES
+        from pypy.translator.platform import platform
+
+        BaseFfiTest.setup_class()
+        # prepare C code as an example, so we can load it and call
+        # it via rlib.libffi
+        c_file = udir.ensure("test_libffi", dir=1).join("foolib.c")
+        # automatically collect the C source from the docstrings of the tests
+        snippets = []
+        exports = []
+        for name in dir(cls):
+            if name.startswith('test_'):
+                meth = getattr(cls, name)
+                # the heuristic to determine it it's really C code could be
+                # improved: so far we just check that there is a '{' :-)
+                if meth.__doc__ is not None and '{' in meth.__doc__:
+                    snippets.append(meth.__doc__)
+                    import re
+                    for match in re.finditer(" ([A-Za-z_]+)\(", meth.__doc__):
+                        exports.append(match.group(1))
+        #
+        c_file.write(STANDARD_DEFINES + str(py.code.Source('\n'.join(snippets))))
+        eci = ExternalCompilationInfo(export_symbols=exports)
+        cls.libfoo_name = str(platform.compile([c_file], eci, 'x',
+                                               standalone=False))
+        cls.dll = cls.CDLL(cls.libfoo_name)
+
+    def teardown_class(cls):
+        if cls.dll:
+            cls.dll.__del__()
+            # Why doesn't this call cls.dll.__del__() ?
+            #del cls.dll
+
+    def get_libfoo(self):
+        return self.dll    
+
+    def call(self, funcspec, args, RESULT, is_struct=False, jitif=[]):
+        """
+        Call the specified function after constructing and ArgChain with the
+        arguments in ``args``.
+
+        The function is specified with ``funcspec``, which is a tuple of the
+        form (lib, name, argtypes, restype).
+
+        This method is overridden by metainterp/test/test_fficall.py in
+        order to do the call in a loop and JIT it. The optional arguments are
+        used only by that overridden method.
+
+        """
+        lib, name, argtypes, restype = funcspec
+        func = lib.getpointer(name, argtypes, restype)
+        chain = ArgChain()
+        for arg in args:
+            if isinstance(arg, tuple):
+                methname, arg = arg
+                meth = getattr(chain, methname)
+                meth(arg)
+            else:
+                chain.arg(arg)
+        return func.call(chain, RESULT, is_struct=is_struct)
+
+    # ------------------------------------------------------------------------
+
+    def test_very_simple(self):
+        """
+            int diff_xy(int x, Signed y)
+            {
+                return x - y;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'diff_xy', [types.sint, types.signed], types.sint)
+        res = self.call(func, [50, 8], lltype.Signed)
+        assert res == 42
+
+    def test_simple(self):
+        """
+            int sum_xy(int x, double y)
+            {
+                return (x + (int)y);
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy', [types.sint, types.double], types.sint)
+        res = self.call(func, [38, 4.2], lltype.Signed, jitif=["floats"])
+        assert res == 42
+
+    def test_float_result(self):
+        libm = self.get_libm()
+        func = (libm, 'pow', [types.double, types.double], types.double)
+        res = self.call(func, [2.0, 3.0], rffi.DOUBLE, jitif=["floats"])
+        assert res == 8.0
+
+    def test_cast_result(self):
+        """
+            unsigned char cast_to_uchar_and_ovf(int x)
+            {
+                return 200+(unsigned char)x;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'cast_to_uchar_and_ovf', [types.sint], types.uchar)
+        res = self.call(func, [0], rffi.UCHAR)
+        assert res == 200
+
+    def test_cast_argument(self):
+        """
+            int many_args(char a, int b)
+            {
+                return a+b;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'many_args', [types.uchar, types.sint], types.sint)
+        res = self.call(func, [chr(20), 22], rffi.SIGNED)
+        assert res == 42
+
+    def test_char_args(self):
+        """
+        char sum_args(char a, char b) {
+            return a + b;
+        }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_args', [types.schar, types.schar], types.schar)
+        res = self.call(func, [123, 43], rffi.CHAR)
+        assert res == chr(166)
+
+    def test_unsigned_short_args(self):
+        """
+            unsigned short sum_xy_us(unsigned short x, unsigned short y)
+            {
+                return x+y;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy_us', [types.ushort, types.ushort], types.ushort)
+        res = self.call(func, [32000, 8000], rffi.USHORT)
+        assert res == 40000
+
+
+    def test_pointer_as_argument(self):
+        """#include <stdlib.h>
+            Signed inc(Signed* x)
+            {
+                Signed oldval;
+                if (x == NULL)
+                    return -1;
+                oldval = *x;
+                *x = oldval+1;
+                return oldval;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'inc', [types.pointer], types.signed)
+        null = lltype.nullptr(rffi.SIGNEDP.TO)
+        res = self.call(func, [null], rffi.SIGNED)
+        assert res == -1
+        #
+        ptr_result = lltype.malloc(rffi.SIGNEDP.TO, 1, flavor='raw')
+        ptr_result[0] = 41
+        res = self.call(func, [ptr_result], rffi.SIGNED)
+        if self.__class__ is TestLibffiCall:
+            # the function was called only once
+            assert res == 41
+            assert ptr_result[0] == 42
+            lltype.free(ptr_result, flavor='raw')
+            # the test does not make sense when run with the JIT through
+            # meta_interp, because the __del__ are not properly called (hence
+            # we "leak" memory)
+            del libfoo
+            assert not ALLOCATED
+        else:
+            # the function as been called 9 times
+            assert res == 50
+            assert ptr_result[0] == 51
+            lltype.free(ptr_result, flavor='raw')
+
+    def test_return_pointer(self):
+        """
+            struct pair {
+                Signed a;
+                Signed b;
+            };
+
+            struct pair my_static_pair = {10, 20};
+
+            Signed* get_pointer_to_b()
+            {
+                return &my_static_pair.b;
+            }
+        """
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'get_pointer_to_b', [], types.pointer)
+        res = self.call(func, [], rffi.SIGNEDP)
+        assert res[0] == 20
+
+    def test_void_result(self):
+        """
+            int dummy;
+            void set_dummy(int val) { dummy = val; }
+            int get_dummy() { return dummy; }
+        """
+        libfoo = self.get_libfoo()
+        set_dummy = (libfoo, 'set_dummy', [types.sint], types.void)
+        get_dummy = (libfoo, 'get_dummy', [], types.sint)
+        #
+        initval = self.call(get_dummy, [], rffi.SIGNED)
+        #
+        res = self.call(set_dummy, [initval+1], lltype.Void)
+        assert res is None
+        #
+        res = self.call(get_dummy, [], rffi.SIGNED)
+        assert res == initval+1
+
+    def test_single_float_args(self):
+        """
+            float sum_xy_float(float x, float y)
+            {
+                return x+y;
+            }
+        """
+        from ctypes import c_float # this is used only to compute the expected result
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy_float', [types.float, types.float], types.float)
+        x = r_singlefloat(12.34)
+        y = r_singlefloat(56.78)
+        res = self.call(func, [x, y], rffi.FLOAT, jitif=["singlefloats"])
+        expected = c_float(c_float(12.34).value + c_float(56.78).value).value
+        assert float(res) == expected
+
+    def test_slonglong_args(self):
+        """
+            long long sum_xy_longlong(long long x, long long y)
+            {
+                return x+y;
+            }
+        """
+        maxint32 = 2147483647 # we cannot really go above maxint on 64 bits
+                              # (and we would not test anything, as there long
+                              # is the same as long long)
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy_longlong', [types.slonglong, types.slonglong],
+                types.slonglong)
+        if IS_32_BIT:
+            x = r_longlong(maxint32+1)
+            y = r_longlong(maxint32+2)
+        else:
+            x = maxint32+1
+            y = maxint32+2
+        res = self.call(func, [x, y], rffi.LONGLONG, jitif=["longlong"])
+        expected = maxint32*2 + 3
+        assert res == expected
+
+    def test_ulonglong_args(self):
+        """
+            unsigned long long sum_xy_ulonglong(unsigned long long x,
+                                                unsigned long long y)
+            {
+                return x+y;
+            }
+        """
+        maxint64 = 9223372036854775807 # maxint64+1 does not fit into a
+                                       # longlong, but it does into a
+                                       # ulonglong
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy_ulonglong', [types.ulonglong, types.ulonglong],
+                types.ulonglong)
+        x = r_ulonglong(maxint64+1)
+        y = r_ulonglong(2)
+        res = self.call(func, [x, y], rffi.ULONGLONG, jitif=["longlong"])
+        expected = maxint64 + 3
+        assert res == expected
+
+    def test_wrong_number_of_arguments(self):
+        from pypy.rpython.llinterp import LLException
+        libfoo = self.get_libfoo()
+        func = (libfoo, 'sum_xy', [types.sint, types.double], types.sint)
+
+        glob = globals()
+        loc = locals()
+        def my_raises(s):
+            try:
+                exec s in glob, loc
+            except TypeError:
+                pass
+            except LLException, e:
+                if str(e) != "<LLException 'TypeError'>":
+                    raise
+            else:
+                assert False, 'Did not raise'
+
+        my_raises("self.call(func, [38], rffi.SIGNED)") # one less
+        my_raises("self.call(func, [38, 12.3, 42], rffi.SIGNED)") # one more
+
+
+    def test_byval_argument(self):
+        """
+            struct Point {
+                Signed x;
+                Signed y;
+            };
+
+            Signed sum_point(struct Point p) {
+                return p.x + p.y;
+            }
+        """
+        libfoo = CDLL(self.libfoo_name)
+        ffi_point_struct = make_struct_ffitype_e(0, 0, [types.signed, types.signed])
+        ffi_point = ffi_point_struct.ffistruct
+        sum_point = (libfoo, 'sum_point', [ffi_point], types.signed)
+        #
+        ARRAY = rffi.CArray(rffi.SIGNED)
+        buf = lltype.malloc(ARRAY, 2, flavor='raw')
+        buf[0] = 30
+        buf[1] = 12
+        adr = rffi.cast(rffi.VOIDP, buf)
+        res = self.call(sum_point, [('arg_raw', adr)], rffi.SIGNED,
+                        jitif=["byval"])
+        assert res == 42
+        # check that we still have the ownership on the buffer
+        assert buf[0] == 30
+        assert buf[1] == 12
+        lltype.free(buf, flavor='raw')
+        lltype.free(ffi_point_struct, flavor='raw')
+
+    def test_byval_result(self):
+        """
+            struct Point make_point(Signed x, Signed y) {
+                struct Point p;
+                p.x = x;
+                p.y = y;
+                return p;
+            }
+        """
+        libfoo = CDLL(self.libfoo_name)
+        ffi_point_struct = make_struct_ffitype_e(0, 0, [types.signed, types.signed])
+        ffi_point = ffi_point_struct.ffistruct
+
+        libfoo = CDLL(self.libfoo_name)
+        make_point = (libfoo, 'make_point', [types.signed, types.signed], ffi_point)
+        #
+        PTR = lltype.Ptr(rffi.CArray(rffi.SIGNED))
+        p = self.call(make_point, [12, 34], PTR, is_struct=True,
+                      jitif=["byval"])
+        assert p[0] == 12
+        assert p[1] == 34
+        lltype.free(p, flavor='raw')
+        lltype.free(ffi_point_struct, flavor='raw')
+
+    if os.name == 'nt':
+        def test_stdcall_simple(self):
+            """
+            int __stdcall std_diff_xy(int x, Signed y)
+            {
+                return x - y;
+            }
+            """
+            libfoo = self.get_libfoo()
+            func = (libfoo, 'std_diff_xy', [types.sint, types.signed], types.sint)
+            try:
+                self.call(func, [50, 8], lltype.Signed)
+            except ValueError, e:
+                assert e.message == 'Procedure called with not enough ' + \
+                     'arguments (8 bytes missing) or wrong calling convention'
+            except LLException, e:
+                #jitted code raises this
+                assert str(e) == "<LLException 'StackCheckError'>"
+            else:
+                assert 0, 'wrong calling convention should have raised'
+
+        def test_by_ordinal(self):
+            """
+            int AAA_first_ordinal_function()
+            {
+                return 42;
+            }
+            """
+            libfoo = self.get_libfoo()
+            f_by_name = libfoo.getpointer('AAA_first_ordinal_function' ,[],
+                                          types.uint)
+            f_by_ordinal = libfoo.getpointer_by_ordinal(1 ,[], types.uint)
+            print dir(f_by_name)
+            assert f_by_name.funcsym == f_by_ordinal.funcsym
+
+        def test_by_ordinal2(self):
+            """
+            int __stdcall BBB_second_ordinal_function()
+            {
+                return 24;
+            }
+            """
+            from pypy.rlib.libffi import WinDLL
+            dll = WinDLL(self.libfoo_name)
+            f_by_name = dll.getpointer('BBB_second_ordinal_function' ,[],
+                                          types.uint)
+            f_by_ordinal = dll.getpointer_by_ordinal(2 ,[], types.uint)
+            print dir(f_by_name)
+            assert f_by_name.funcsym == f_by_ordinal.funcsym
+            chain = ArgChain()
+            assert 24 == f_by_ordinal.call(chain, lltype.Signed, is_struct=False)
+
+
+        


More information about the pypy-commit mailing list