Source

pypy / pypy / module / _cffi_backend / ctypefunc.py

The branch 'ffi-backend' does not exist.
Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
"""
Function pointers.
"""

from pypy.interpreter.error import OperationError, operationerrfmt
from pypy.rpython.lltypesystem import lltype, llmemory, rffi
from pypy.rlib import jit, clibffi
from pypy.rlib.objectmodel import we_are_translated, instantiate
from pypy.rlib.objectmodel import keepalive_until_here

from pypy.module._cffi_backend.ctypeobj import W_CType
from pypy.module._cffi_backend.ctypeptr import W_CTypePtrBase
from pypy.module._cffi_backend.ctypevoid import W_CTypeVoid
from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion
from pypy.module._cffi_backend import ctypeprim, ctypestruct, ctypearray
from pypy.module._cffi_backend import cdataobj, cerrno


class W_CTypeFunc(W_CTypePtrBase):

    def __init__(self, space, fargs, fresult, ellipsis):
        self.cif_descr = lltype.nullptr(CIF_DESCRIPTION)
        extra = self._compute_extra_text(fargs, fresult, ellipsis)
        size = rffi.sizeof(rffi.VOIDP)
        W_CTypePtrBase.__init__(self, space, size, extra, 2, fresult,
                                could_cast_anything=False)
        self.fargs = fargs
        self.ellipsis = bool(ellipsis)
        # fresult is stored in self.ctitem

        if not ellipsis:
            # Functions with '...' varargs are stored without a cif_descr
            # at all.  The cif is computed on every call from the actual
            # types passed in.  For all other functions, the cif_descr
            # is computed here.
            CifDescrBuilder(fargs, fresult).rawallocate(self)

    def new_ctypefunc_completing_argtypes(self, args_w):
        space = self.space
        nargs_declared = len(self.fargs)
        fvarargs = [None] * len(args_w)
        fvarargs[:nargs_declared] = self.fargs
        for i in range(nargs_declared, len(args_w)):
            w_obj = args_w[i]
            if isinstance(w_obj, cdataobj.W_CData):
                ct = w_obj.ctype
                if isinstance(ct, ctypearray.W_CTypeArray):
                    ct = ct.ctptr
            else:
                raise operationerrfmt(space.w_TypeError,
                             "argument %d passed in the variadic part "
                             "needs to be a cdata object (got %s)",
                             i + 1, space.type(w_obj).getname(space))
            fvarargs[i] = ct
        ctypefunc = instantiate(W_CTypeFunc)
        ctypefunc.space = space
        ctypefunc.fargs = fvarargs
        ctypefunc.ctitem = self.ctitem
        CifDescrBuilder(fvarargs, self.ctitem).rawallocate(ctypefunc)
        return ctypefunc

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

    def _compute_extra_text(self, fargs, fresult, ellipsis):
        argnames = ['(*)(']
        for i, farg in enumerate(fargs):
            if i > 0:
                argnames.append(', ')
            argnames.append(farg.name)
        if ellipsis:
            if len(fargs) > 0:
                argnames.append(', ')
            argnames.append('...')
        argnames.append(')')
        return ''.join(argnames)


    def call(self, funcaddr, args_w):
        space = self.space
        cif_descr = self.cif_descr
        nargs_declared = len(self.fargs)

        if cif_descr:
            # regular case: this function does not take '...' arguments
            if len(args_w) != nargs_declared:
                raise operationerrfmt(space.w_TypeError,
                                      "'%s' expects %d arguments, got %d",
                                      self.name, nargs_declared, len(args_w))
        else:
            # call of a variadic function
            if len(args_w) < nargs_declared:
                raise operationerrfmt(space.w_TypeError,
                                  "'%s' expects at least %d arguments, got %d",
                                      self.name, nargs_declared, len(args_w))
            self = self.new_ctypefunc_completing_argtypes(args_w)
            cif_descr = self.cif_descr

        size = cif_descr.exchange_size
        mustfree_count_plus_1 = 0
        buffer = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw')
        try:
            buffer_array = rffi.cast(rffi.VOIDPP, buffer)
            for i in range(len(args_w)):
                data = rffi.ptradd(buffer, cif_descr.exchange_args[i])
                buffer_array[i] = data
                w_obj = args_w[i]
                argtype = self.fargs[i]
                #
                # special-case for strings.  xxx should avoid copying
                if argtype.is_char_ptr_or_array:
                    try:
                        s = space.str_w(w_obj)
                    except OperationError, e:
                        if not e.match(space, space.w_TypeError):
                            raise
                    else:
                        raw_string = rffi.str2charp(s)
                        rffi.cast(rffi.CCHARPP, data)[0] = raw_string
                        # set the "must free" flag to 1
                        set_mustfree_flag(data, 1)
                        mustfree_count_plus_1 = i + 1
                        continue   # skip the convert_from_object()

                    # set the "must free" flag to 0
                    set_mustfree_flag(data, 0)
                #
                if argtype.is_unichar_ptr_or_array:
                    try:
                        space.unicode_w(w_obj)
                    except OperationError:
                        if not e.match(space, space.w_TypeError):
                            raise
                    else:
                        # passing a unicode raises NotImplementedError for now
                        raise OperationError(space.w_NotImplementedError,
                                    space.wrap("automatic unicode-to-"
                                               "'wchar_t *' conversion"))
                #
                argtype.convert_from_object(data, w_obj)
            resultdata = rffi.ptradd(buffer, cif_descr.exchange_result)

            cerrno.restore_errno()
            clibffi.c_ffi_call(cif_descr.cif,
                               rffi.cast(rffi.VOIDP, funcaddr),
                               resultdata,
                               buffer_array)
            cerrno.save_errno()

            if isinstance(self.ctitem, W_CTypeVoid):
                w_res = space.w_None
            elif isinstance(self.ctitem, W_CTypeStructOrUnion):
                w_res = self.ctitem.copy_and_convert_to_object(resultdata)
            else:
                w_res = self.ctitem.convert_to_object(resultdata)
        finally:
            for i in range(mustfree_count_plus_1):
                argtype = self.fargs[i]
                if argtype.is_char_ptr_or_array:
                    data = rffi.ptradd(buffer, cif_descr.exchange_args[i])
                    if get_mustfree_flag(data):
                        raw_string = rffi.cast(rffi.CCHARPP, data)[0]
                        lltype.free(raw_string, flavor='raw')
            lltype.free(buffer, flavor='raw')
        return w_res

def get_mustfree_flag(data):
    return ord(rffi.ptradd(data, -1)[0])

def set_mustfree_flag(data, flag):
    rffi.ptradd(data, -1)[0] = chr(flag)

def _get_abi(space, name):
    abi = getattr(clibffi, name)
    assert isinstance(abi, int)
    return space.wrap(abi)

# ____________________________________________________________

# The "cif" is a block of raw memory describing how to do a call via libffi.
# It starts with a block of memory of type FFI_CIF, which is used by libffi
# itself.  Following it, we find _cffi_backend-specific information:
#
#  - 'exchange_size': an integer that tells how big a buffer we must
#    allocate for the call; this buffer should start with an array of
#    pointers to the actual argument values.
#
#  - 'exchange_result': the offset in that buffer for the result of the call.
#
#  - 'exchange_args[nargs]': the offset in that buffer for each argument.
#
# Following this, we have other data structures for libffi (with direct
# pointers from the FFI_CIF to these data structures):
#
#  - the argument types, as an array of 'ffi_type *'.
#
#  - optionally, the result's and the arguments' ffi type data
#    (this is used only for 'struct' ffi types; in other cases the
#    'ffi_type *' just points to static data like 'ffi_type_sint32').

FFI_CIF = clibffi.FFI_CIFP.TO
FFI_TYPE = clibffi.FFI_TYPE_P.TO
FFI_TYPE_P = clibffi.FFI_TYPE_P
FFI_TYPE_PP = clibffi.FFI_TYPE_PP
SIZE_OF_FFI_ARG = 8     # good enough

CIF_DESCRIPTION = lltype.Struct(
    'CIF_DESCRIPTION',
    ('cif', FFI_CIF),
    ('exchange_size', lltype.Signed),
    ('exchange_result', lltype.Signed),
    ('exchange_args', rffi.CArray(lltype.Signed)))

CIF_DESCRIPTION_P = lltype.Ptr(CIF_DESCRIPTION)

# We attach (lazily or not) to the classes or instances a 'ffi_type' attribute
W_CType.ffi_type = lltype.nullptr(FFI_TYPE_P.TO)
W_CTypePtrBase.ffi_type = clibffi.ffi_type_pointer
W_CTypeVoid.ffi_type = clibffi.ffi_type_void

def _settype(ctype, ffi_type):
    ctype.ffi_type = ffi_type
    return ffi_type


class CifDescrBuilder(object):
    rawmem = lltype.nullptr(rffi.CCHARP.TO)

    def __init__(self, fargs, fresult):
        self.fargs = fargs
        self.fresult = fresult

    def fb_alloc(self, size):
        size = llmemory.raw_malloc_usage(size)
        if not self.bufferp:
            self.nb_bytes += size
            return lltype.nullptr(rffi.CCHARP.TO)
        else:
            result = self.bufferp
            self.bufferp = rffi.ptradd(result, size)
            return result


    def fb_fill_type(self, ctype):
        if ctype.ffi_type:   # common case: the ffi_type was already computed
            return ctype.ffi_type

        space = self.space
        size = ctype.size
        if size < 0:
            raise operationerrfmt(space.w_TypeError,
                                  "ctype '%s' has incomplete type",
                                  ctype.name)

        if isinstance(ctype, ctypestruct.W_CTypeStruct):

            # We can't pass a struct that was completed by verify().
            # Issue: assume verify() is given "struct { long b; ...; }".
            # Then it will complete it in the same way whether it is actually
            # "struct { long a, b; }" or "struct { double a; long b; }".
            # But on 64-bit UNIX, these two structs are passed by value
            # differently: e.g. on x86-64, "b" ends up in register "rsi" in
            # the first case and "rdi" in the second case.
            if ctype.custom_field_pos:
                raise OperationError(space.w_TypeError,
                                     space.wrap(
                   "cannot pass as an argument a struct that was completed "
                   "with verify() (see pypy/module/_cffi_backend/ctypefunc.py "
                   "for details)"))

            # allocate an array of (n + 1) ffi_types
            n = len(ctype.fields_list)
            elements = self.fb_alloc(rffi.sizeof(FFI_TYPE_P) * (n + 1))
            elements = rffi.cast(FFI_TYPE_PP, elements)

            # fill it with the ffi types of the fields
            for i, cf in enumerate(ctype.fields_list):
                if cf.is_bitfield():
                    raise OperationError(space.w_NotImplementedError,
                        space.wrap("cannot pass as argument a struct "
                                   "with bit fields"))
                ffi_subtype = self.fb_fill_type(cf.ctype)
                if elements:
                    elements[i] = ffi_subtype

            # zero-terminate the array
            if elements:
                elements[n] = lltype.nullptr(FFI_TYPE_P.TO)

            # allocate and fill an ffi_type for the struct itself
            ffistruct = self.fb_alloc(rffi.sizeof(FFI_TYPE))
            ffistruct = rffi.cast(FFI_TYPE_P, ffistruct)
            if ffistruct:
                rffi.setintfield(ffistruct, 'c_size', size)
                rffi.setintfield(ffistruct, 'c_alignment', ctype.alignof())
                rffi.setintfield(ffistruct, 'c_type', clibffi.FFI_TYPE_STRUCT)
                ffistruct.c_elements = elements

            return ffistruct

        elif isinstance(ctype, ctypeprim.W_CTypePrimitiveSigned):
            # compute lazily once the ffi_type
            if   size == 1: return _settype(ctype, clibffi.ffi_type_sint8)
            elif size == 2: return _settype(ctype, clibffi.ffi_type_sint16)
            elif size == 4: return _settype(ctype, clibffi.ffi_type_sint32)
            elif size == 8: return _settype(ctype, clibffi.ffi_type_sint64)

        elif (isinstance(ctype, ctypeprim.W_CTypePrimitiveCharOrWChar) or
              isinstance(ctype, ctypeprim.W_CTypePrimitiveUnsigned)):
            if   size == 1: return _settype(ctype, clibffi.ffi_type_uint8)
            elif size == 2: return _settype(ctype, clibffi.ffi_type_uint16)
            elif size == 4: return _settype(ctype, clibffi.ffi_type_uint32)
            elif size == 8: return _settype(ctype, clibffi.ffi_type_uint64)

        elif isinstance(ctype, ctypeprim.W_CTypePrimitiveFloat):
            if   size == 4: return _settype(ctype, clibffi.ffi_type_float)
            elif size == 8: return _settype(ctype, clibffi.ffi_type_double)

        raise operationerrfmt(space.w_NotImplementedError,
                              "ctype '%s' (size %d) not supported as argument"
                              " or return value",
                              ctype.name, size)


    def fb_build(self):
        nargs = len(self.fargs)

        # start with a cif_description (cif and exchange_* fields)
        self.fb_alloc(llmemory.sizeof(CIF_DESCRIPTION, nargs))

        # next comes an array of 'ffi_type*', one per argument
        atypes = self.fb_alloc(rffi.sizeof(FFI_TYPE_P) * nargs)
        self.atypes = rffi.cast(FFI_TYPE_PP, atypes)

        # next comes the result type data
        self.rtype = self.fb_fill_type(self.fresult)

        # next comes each argument's type data
        for i, farg in enumerate(self.fargs):
            atype = self.fb_fill_type(farg)
            if self.atypes:
                self.atypes[i] = atype


    def align_arg(self, n):
        return (n + 7) & ~7

    def fb_build_exchange(self, cif_descr):
        nargs = len(self.fargs)

        # first, enough room for an array of 'nargs' pointers
        exchange_offset = rffi.sizeof(rffi.CCHARP) * nargs
        exchange_offset = self.align_arg(exchange_offset)
        cif_descr.exchange_result = exchange_offset

        # then enough room for the result --- which means at least
        # sizeof(ffi_arg), according to the ffi docs (this is 8).
        exchange_offset += max(rffi.getintfield(self.rtype, 'c_size'),
                               SIZE_OF_FFI_ARG)

        # loop over args
        for i, farg in enumerate(self.fargs):
            if farg.is_char_ptr_or_array:
                exchange_offset += 1   # for the "must free" flag
            exchange_offset = self.align_arg(exchange_offset)
            cif_descr.exchange_args[i] = exchange_offset
            exchange_offset += rffi.getintfield(self.atypes[i], 'c_size')

        # store the exchange data size
        cif_descr.exchange_size = exchange_offset


    @jit.dont_look_inside
    def rawallocate(self, ctypefunc):
        space = ctypefunc.space
        self.space = space

        # compute the total size needed in the CIF_DESCRIPTION buffer
        self.nb_bytes = 0
        self.bufferp = lltype.nullptr(rffi.CCHARP.TO)
        self.fb_build()

        # allocate the buffer
        if we_are_translated():
            rawmem = lltype.malloc(rffi.CCHARP.TO, self.nb_bytes,
                                   flavor='raw')
            rawmem = rffi.cast(CIF_DESCRIPTION_P, rawmem)
        else:
            # gross overestimation of the length below, but too bad
            rawmem = lltype.malloc(CIF_DESCRIPTION_P.TO, self.nb_bytes,
                                   flavor='raw')

        # the buffer is automatically managed from the W_CTypeFunc instance
        ctypefunc.cif_descr = rawmem

        # call again fb_build() to really build the libffi data structures
        self.bufferp = rffi.cast(rffi.CCHARP, rawmem)
        self.fb_build()
        assert self.bufferp == rffi.ptradd(rffi.cast(rffi.CCHARP, rawmem),
                                           self.nb_bytes)

        # fill in the 'exchange_*' fields
        self.fb_build_exchange(ctypefunc.cif_descr)

        # call libffi's ffi_prep_cif() function
        res = clibffi.c_ffi_prep_cif(rawmem.cif, clibffi.FFI_DEFAULT_ABI,
                                     len(self.fargs),
                                     self.rtype, self.atypes)
        if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
            raise OperationError(space.w_SystemError,
                space.wrap("libffi failed to build this function type"))