[pypy-commit] cffi default: There is no reason to restrict ffi.unpack() to primitives.

arigo pypy.commits at gmail.com
Sat Apr 16 18:17:53 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r2663:f8852464e468
Date: 2016-04-17 00:18 +0200
http://bitbucket.org/cffi/cffi/changeset/f8852464e468/

Log:	There is no reason to restrict ffi.unpack() to primitives.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -5586,7 +5586,7 @@
 {
     CDataObject *cd;
     CTypeDescrObject *ctitem;
-    Py_ssize_t i, length, itemsize, best_alignment;
+    Py_ssize_t i, length, itemsize;
     PyObject *result;
     char *src;
     int casenum;
@@ -5596,11 +5596,9 @@
                                      &CData_Type, &cd, &length))
         return NULL;
 
-    ctitem = cd->c_type->ct_itemdescr;
-    if (!(cd->c_type->ct_flags & (CT_ARRAY|CT_POINTER)) ||
-        !(ctitem->ct_flags & CT_PRIMITIVE_ANY)) {
+    if (!(cd->c_type->ct_flags & (CT_ARRAY|CT_POINTER))) {
         PyErr_Format(PyExc_TypeError,
-                     "expected a pointer to a primitive type, got '%s'",
+                     "expected a pointer or array, got '%s'",
                      cd->c_type->ct_name);
         return NULL;
     }
@@ -5620,6 +5618,7 @@
     }
 
     /* byte- and unicode strings */
+    ctitem = cd->c_type->ct_itemdescr;
     if (ctitem->ct_flags & CT_PRIMITIVE_CHAR) {
         if (ctitem->ct_size == sizeof(char))
             return PyBytes_FromStringAndSize(cd->c_data, length);
@@ -5630,17 +5629,34 @@
     }
 
     /* else, the result is a list.  This implementation should be
-       equivalent to, but on CPython much faster than, 'list(p[0:length])'.
+       equivalent to but much faster than '[p[i] for i in range(length)]'.
+       (Note that on PyPy, 'list(p[0:length])' should be equally fast,
+       but arguably, finding out that there *is* such an unexpected way
+       to write things down is the real problem.)
     */
-    result = PyList_New(length); if (result == NULL) return NULL;
+    result = PyList_New(length);
+    if (result == NULL)
+        return NULL;
 
     src = cd->c_data;
     itemsize = ctitem->ct_size;
-    best_alignment = ctitem->ct_length;
+    if (itemsize < 0) {
+        PyErr_Format(PyExc_ValueError, "'%s' points to items of unknown size",
+                     cd->c_type->ct_name);
+        return NULL;
+    }
+
+    /* Determine some common fast-paths for the loop below.  The case -1
+       is the fall-back, which always gives the right answer. */
+
+#define ALIGNMENT_CHECK(align)                          \
+        (((align) & ((align) - 1)) == 0 &&              \
+         (((uintptr_t)src) & ((align) - 1)) == 0)
 
     casenum = -1;
-    if ((best_alignment & (best_alignment - 1)) == 0 &&
-        (((uintptr_t)src) & (best_alignment - 1)) == 0) {
+
+    if ((ctitem->ct_flags & CT_PRIMITIVE_ANY) &&
+            ALIGNMENT_CHECK(ctitem->ct_length)) {
         /* Source data is fully aligned; we can directly read without
            memcpy().  The unaligned case is expected to be rare; in
            this situation it is ok to fall back to the general
@@ -5667,6 +5683,10 @@
             else if (itemsize == sizeof(float))  casenum = 8;
         }
     }
+    else if (ctitem->ct_flags & (CT_POINTER | CT_FUNCTIONPTR)) {
+        casenum = 10;    /* any pointer */
+    }
+#undef ALIGNMENT_CHECK
 
     for (i = 0; i < length; i++) {
         PyObject *x;
@@ -5685,6 +5705,7 @@
         case 7: x = PyLong_FromUnsignedLong(*(unsigned long *)src); break;
         case 8: x = PyFloat_FromDouble(*(float *)src); break;
         case 9: x = PyFloat_FromDouble(*(double *)src); break;
+        case 10: x = new_simple_cdata(*(char **)src, ctitem); break;
         }
         if (x == NULL) {
             Py_DECREF(result);
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -460,17 +460,19 @@
                                     from _cffi_backend.c */
 
 PyDoc_STRVAR(ffi_unpack_doc,
-"Unpack an array of primitive C data of the given length,\n"
+"Unpack an array of C data of the given length,\n"
 "returning a Python string/unicode/list.\n"
 "\n"
 "If 'cdata' is a pointer to 'char', returns a byte string.\n"
-"Unlike ffi.string(), it does not stop at the first null.\n"
+"It does not stop at the first null.  This is equivalent to:\n"
+"ffi.buffer(cdata, length)[:]\n"
 "\n"
 "If 'cdata' is a pointer to 'wchar_t', returns a unicode string.\n"
 "'length' is measured in wchar_t's; it is not the size in bytes.\n"
 "\n"
-"If 'cdata' is a pointer to some other integer or floating-point\n"
-"type, returns a list of 'length' integers or floats.");
+"If 'cdata' is a pointer to anything else, returns a list of\n"
+"'length' items.  This is a faster equivalent to:\n"
+"[cdata[i] for i in range(length)]");
 
 #define ffi_unpack  b_unpack     /* ffi_unpack() => b_unpack()
                                     from _cffi_backend.c */
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3563,8 +3563,30 @@
     py.test.raises(TypeError, unpack, p)
     py.test.raises(TypeError, unpack, b"foobar", 6)
     py.test.raises(TypeError, unpack, cast(BInt, 42), 1)
+    #
+    BPtr = new_pointer_type(BInt)
+    random_ptr = cast(BPtr, -424344)
+    other_ptr = cast(BPtr, 54321)
+    BArray = new_array_type(new_pointer_type(BPtr), None)
+    lst = unpack(newp(BArray, [random_ptr, other_ptr]), 2)
+    assert lst == [random_ptr, other_ptr]
+    #
     BFunc = new_function_type((BInt, BInt), BInt, False)
-    py.test.raises(TypeError, unpack, cast(new_pointer_type(BFunc), 42), 1)
+    BFuncPtr = new_pointer_type(BFunc)
+    lst = unpack(newp(new_array_type(BFuncPtr, None), 2), 2)
+    assert len(lst) == 2
+    assert not lst[0] and not lst[1]
+    assert typeof(lst[0]) is BFunc
+    #
+    BStruct = new_struct_type("foo")
+    BStructPtr = new_pointer_type(BStruct)
+    e = py.test.raises(ValueError, unpack, cast(BStructPtr, 42), 5)
+    assert str(e.value) == "'foo *' points to items of unknown size"
+    complete_struct_or_union(BStruct, [('a1', BInt, -1),
+                                       ('a2', BInt, -1)])
+    lst = unpack(newp(new_array_type(BStructPtr, None), [[4,5], [6,7]]), 2)
+    assert typeof(lst[0]) is BStruct
+    assert lst[0].a1 == 4 and lst[1].a2 == 7
     #
     py.test.raises(RuntimeError, unpack, cast(new_pointer_type(BChar), 0), 0)
     py.test.raises(RuntimeError, unpack, cast(new_pointer_type(BChar), 0), 10)
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -300,17 +300,19 @@
         return self._backend.string(cdata, maxlen)
 
     def unpack(self, cdata, length):
-        """Unpack an array of primitive C data of the given length,
+        """Unpack an array of C data of the given length, 
         returning a Python string/unicode/list.
 
         If 'cdata' is a pointer to 'char', returns a byte string.
-        Unlike ffi.string(), it does not stop at the first null.
+        It does not stop at the first null.  This is equivalent to:
+        ffi.buffer(cdata, length)[:]
 
         If 'cdata' is a pointer to 'wchar_t', returns a unicode string.
         'length' is measured in wchar_t's; it is not the size in bytes.
 
-        If 'cdata' is a pointer to some other integer or floating-point
-        type, returns a list of 'length' integers or floats.
+        If 'cdata' is a pointer to anything else, returns a list of
+        'length' items.  This is a faster equivalent to:
+        [cdata[i] for i in range(length)]
         """
         return self._backend.unpack(cdata, length)
 


More information about the pypy-commit mailing list