[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