Source

pypy / pypy / module / _cffi_backend / cdataobj.py

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
413
414
415
import operator
from pypy.interpreter.error import OperationError, operationerrfmt
from pypy.interpreter.baseobjspace import Wrappable
from pypy.interpreter.gateway import interp2app, unwrap_spec
from pypy.interpreter.typedef import TypeDef, make_weakref_descr
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.rlib.objectmodel import keepalive_until_here, specialize
from rpython.rlib import objectmodel, rgc
from rpython.tool.sourcetools import func_with_new_name

from pypy.module._cffi_backend import misc


class W_CData(Wrappable):
    _attrs_ = ['space', '_cdata', 'ctype', '_lifeline_']
    _immutable_fields_ = ['_cdata', 'ctype']
    _cdata = lltype.nullptr(rffi.CCHARP.TO)

    def __init__(self, space, cdata, ctype):
        from pypy.module._cffi_backend import ctypeprim
        assert lltype.typeOf(cdata) == rffi.CCHARP
        assert isinstance(ctype, ctypeprim.W_CType)
        self.space = space
        self._cdata = cdata    # don't forget keepalive_until_here!
        self.ctype = ctype

    def _repr_extra(self):
        extra = self.ctype.extra_repr(self._cdata)
        keepalive_until_here(self)
        return extra

    def _repr_extra_owning(self):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
        ctype = self.ctype
        if isinstance(ctype, W_CTypePointer):
            num_bytes = ctype.ctitem.size
        else:
            num_bytes = self._sizeof()
        return 'owning %d bytes' % num_bytes

    def repr(self):
        extra2 = self._repr_extra()
        extra1 = ''
        if not isinstance(self, W_CDataNewOwning):
            # it's slightly confusing to get "<cdata 'struct foo' 0x...>"
            # because the struct foo is not owned.  Trying to make it
            # clearer, write in this case "<cdata 'struct foo &' 0x...>".
            from pypy.module._cffi_backend import ctypestruct
            if isinstance(self.ctype, ctypestruct.W_CTypeStructOrUnion):
                extra1 = ' &'
        return self.space.wrap("<cdata '%s%s' %s>" % (
            self.ctype.name, extra1, extra2))

    def bool(self):
        return self.space.wrap(bool(self._cdata))

    def int(self):
        w_result = self.ctype.int(self._cdata)
        keepalive_until_here(self)
        return w_result

    def long(self):
        w_result = self.int()
        space = self.space
        if space.is_w(space.type(w_result), space.w_int):
            w_result = space.newlong(space.int_w(w_result))
        return w_result

    def float(self):
        w_result = self.ctype.float(self._cdata)
        keepalive_until_here(self)
        return w_result

    def len(self):
        from pypy.module._cffi_backend import ctypearray
        space = self.space
        if isinstance(self.ctype, ctypearray.W_CTypeArray):
            return space.wrap(self.get_array_length())
        raise operationerrfmt(space.w_TypeError,
                              "cdata of type '%s' has no len()",
                              self.ctype.name)

    def _make_comparison(name):
        op = getattr(operator, name)
        requires_ordering = name not in ('eq', 'ne')
        #
        def _cmp(self, w_other):
            from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitive
            space = self.space
            cdata1 = self._cdata
            other = space.interpclass_w(w_other)
            if isinstance(other, W_CData):
                cdata2 = other._cdata
            else:
                return space.w_NotImplemented

            if requires_ordering:
                if (isinstance(self.ctype, W_CTypePrimitive) or
                    isinstance(other.ctype, W_CTypePrimitive)):
                    raise OperationError(space.w_TypeError,
                        space.wrap("cannot do comparison on a primitive cdata"))
                cdata1 = rffi.cast(lltype.Unsigned, cdata1)
                cdata2 = rffi.cast(lltype.Unsigned, cdata2)
            return space.newbool(op(cdata1, cdata2))
        #
        return func_with_new_name(_cmp, name)

    lt = _make_comparison('lt')
    le = _make_comparison('le')
    eq = _make_comparison('eq')
    ne = _make_comparison('ne')
    gt = _make_comparison('gt')
    ge = _make_comparison('ge')

    def hash(self):
        h = (objectmodel.compute_identity_hash(self.ctype) ^
             rffi.cast(lltype.Signed, self._cdata))
        return self.space.wrap(h)

    def getitem(self, w_index):
        space = self.space
        if space.isinstance_w(w_index, space.w_slice):
            w_o = self._do_getslice(w_index)
        else:
            i = space.getindex_w(w_index, space.w_IndexError)
            ctype = self.ctype._check_subscript_index(self, i)
            w_o = self._do_getitem(ctype, i)
        keepalive_until_here(self)
        return w_o

    def _do_getitem(self, ctype, i):
        ctitem = ctype.ctitem
        return ctitem.convert_to_object(
            rffi.ptradd(self._cdata, i * ctitem.size))

    def setitem(self, w_index, w_value):
        space = self.space
        if space.isinstance_w(w_index, space.w_slice):
            self._do_setslice(w_index, w_value)
        else:
            i = space.getindex_w(w_index, space.w_IndexError)
            ctype = self.ctype._check_subscript_index(self, i)
            ctitem = ctype.ctitem
            ctitem.convert_from_object(
                rffi.ptradd(self._cdata, i * ctitem.size),
                w_value)
        keepalive_until_here(self)

    def _do_getslicearg(self, w_slice):
        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
        from pypy.objspace.std.sliceobject import W_SliceObject
        assert isinstance(w_slice, W_SliceObject)
        space = self.space
        #
        if space.is_w(w_slice.w_start, space.w_None):
            raise OperationError(space.w_IndexError,
                                 space.wrap("slice start must be specified"))
        start = space.int_w(w_slice.w_start)
        #
        if space.is_w(w_slice.w_stop, space.w_None):
            raise OperationError(space.w_IndexError,
                                 space.wrap("slice stop must be specified"))
        stop = space.int_w(w_slice.w_stop)
        #
        if not space.is_w(w_slice.w_step, space.w_None):
            raise OperationError(space.w_IndexError,
                                 space.wrap("slice with step not supported"))
        #
        if start > stop:
            raise OperationError(space.w_IndexError,
                                 space.wrap("slice start > stop"))
        #
        ctype = self.ctype._check_slice_index(self, start, stop)
        assert isinstance(ctype, W_CTypePointer)
        #
        return ctype, start, stop - start

    def _do_getslice(self, w_slice):
        ctptr, start, length = self._do_getslicearg(w_slice)
        #
        space = self.space
        ctarray = ctptr.cache_array_type
        if ctarray is None:
            from pypy.module._cffi_backend import newtype
            ctarray = newtype.new_array_type(space, ctptr, space.w_None)
            ctptr.cache_array_type = ctarray
        #
        p = rffi.ptradd(self._cdata, start * ctarray.ctitem.size)
        return W_CDataSliced(space, p, ctarray, length)

    def _do_setslice(self, w_slice, w_value):
        ctptr, start, length = self._do_getslicearg(w_slice)
        ctitem = ctptr.ctitem
        ctitemsize = ctitem.size
        cdata = rffi.ptradd(self._cdata, start * ctitemsize)
        #
        if isinstance(w_value, W_CData):
            from pypy.module._cffi_backend import ctypearray
            ctv = w_value.ctype
            if (isinstance(ctv, ctypearray.W_CTypeArray) and
                ctv.ctitem is ctitem and
                w_value.get_array_length() == length):
                # fast path: copying from exactly the correct type
                s = w_value._cdata
                for i in range(ctitemsize * length):
                    cdata[i] = s[i]
                keepalive_until_here(w_value)
                return
        #
        space = self.space
        w_iter = space.iter(w_value)
        for i in range(length):
            try:
                w_item = space.next(w_iter)
            except OperationError, e:
                if not e.match(space, space.w_StopIteration):
                    raise
                raise operationerrfmt(space.w_ValueError,
                                      "need %d values to unpack, got %d",
                                      length, i)
            ctitem.convert_from_object(cdata, w_item)
            cdata = rffi.ptradd(cdata, ctitemsize)
        try:
            space.next(w_iter)
        except OperationError, e:
            if not e.match(space, space.w_StopIteration):
                raise
        else:
            raise operationerrfmt(space.w_ValueError,
                                  "got more than %d values to unpack", length)

    def _add_or_sub(self, w_other, sign):
        space = self.space
        i = sign * space.getindex_w(w_other, space.w_OverflowError)
        return self.ctype.add(self._cdata, i)

    def add(self, w_other):
        return self._add_or_sub(w_other, +1)

    def sub(self, w_other):
        space = self.space
        ob = space.interpclass_w(w_other)
        if isinstance(ob, W_CData):
            from pypy.module._cffi_backend import ctypeptr, ctypearray
            ct = ob.ctype
            if isinstance(ct, ctypearray.W_CTypeArray):
                ct = ct.ctptr
            #
            if (ct is not self.ctype or
                   not isinstance(ct, ctypeptr.W_CTypePointer) or
                   ct.ctitem.size <= 0):
                raise operationerrfmt(space.w_TypeError,
                    "cannot subtract cdata '%s' and cdata '%s'",
                    self.ctype.name, ct.name)
            #
            diff = (rffi.cast(lltype.Signed, self._cdata) -
                    rffi.cast(lltype.Signed, ob._cdata)) // ct.ctitem.size
            return space.wrap(diff)
        #
        return self._add_or_sub(w_other, -1)

    def getcfield(self, w_attr):
        return self.ctype.getcfield(self.space.str_w(w_attr))

    def getattr(self, w_attr):
        w_res = self.getcfield(w_attr).read(self._cdata)
        keepalive_until_here(self)
        return w_res

    def setattr(self, w_attr, w_value):
        self.getcfield(w_attr).write(self._cdata, w_value)
        keepalive_until_here(self)

    def call(self, args_w):
        w_result = self.ctype.call(self._cdata, args_w)
        keepalive_until_here(self)
        return w_result

    def iter(self):
        return self.ctype.iter(self)

    @specialize.argtype(1)
    def write_raw_integer_data(self, source):
        misc.write_raw_integer_data(self._cdata, source, self.ctype.size)
        keepalive_until_here(self)

    def write_raw_float_data(self, source):
        misc.write_raw_float_data(self._cdata, source, self.ctype.size)
        keepalive_until_here(self)

    def convert_to_object(self):
        w_obj = self.ctype.convert_to_object(self._cdata)
        keepalive_until_here(self)
        return w_obj

    def get_array_length(self):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        assert isinstance(ctype, ctypearray.W_CTypeArray)
        length = ctype.length
        assert length >= 0
        return length

    def _sizeof(self):
        return self.ctype.size


class W_CDataMem(W_CData):
    """This is the base class used for cdata objects that own and free
    their memory.  Used directly by the results of cffi.cast('int', x)
    or other primitive explicitly-casted types.  It is further subclassed
    by W_CDataNewOwning."""
    _attrs_ = []

    def __init__(self, space, size, ctype):
        cdata = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw', zero=True)
        W_CData.__init__(self, space, cdata, ctype)

    @rgc.must_be_light_finalizer
    def __del__(self):
        lltype.free(self._cdata, flavor='raw')


class W_CDataNewOwning(W_CDataMem):
    """This is the class used for the cata objects created by newp()."""
    _attrs_ = []

    def _repr_extra(self):
        return self._repr_extra_owning()


class W_CDataNewOwningLength(W_CDataNewOwning):
    """Subclass with an explicit length, for allocated instances of
    the C type 'foo[]'."""
    _attrs_ = ['length']
    _immutable_fields_ = ['length']

    def __init__(self, space, size, ctype, length):
        W_CDataNewOwning.__init__(self, space, size, ctype)
        self.length = length

    def _sizeof(self):
        from pypy.module._cffi_backend import ctypearray
        ctype = self.ctype
        assert isinstance(ctype, ctypearray.W_CTypeArray)
        return self.length * ctype.ctitem.size

    def get_array_length(self):
        return self.length


class W_CDataPtrToStructOrUnion(W_CData):
    """This subclass is used for the pointer returned by new('struct foo').
    It has a strong reference to a W_CDataNewOwning that really owns the
    struct, which is the object returned by the app-level expression 'p[0]'.
    But it is not itself owning any memory, although its repr says so;
    it is merely a co-owner."""
    _attrs_ = ['structobj']
    _immutable_fields_ = ['structobj']

    def __init__(self, space, cdata, ctype, structobj):
        W_CData.__init__(self, space, cdata, ctype)
        self.structobj = structobj

    def _repr_extra(self):
        return self._repr_extra_owning()

    def _do_getitem(self, ctype, i):
        assert i == 0
        return self.structobj


class W_CDataSliced(W_CData):
    """Subclass with an explicit length, for slices."""
    _attrs_ = ['length']
    _immutable_fields_ = ['length']

    def __init__(self, space, cdata, ctype, length):
        W_CData.__init__(self, space, cdata, ctype)
        self.length = length

    def _repr_extra(self):
        return "sliced length %d" % (self.length,)

    def get_array_length(self):
        return self.length


W_CData.typedef = TypeDef(
    'CData',
    __module__ = '_cffi_backend',
    __repr__ = interp2app(W_CData.repr),
    __bool__ = interp2app(W_CData.bool),
    __int__ = interp2app(W_CData.int),
    __long__ = interp2app(W_CData.long),
    __float__ = interp2app(W_CData.float),
    __len__ = interp2app(W_CData.len),
    __lt__ = interp2app(W_CData.lt),
    __le__ = interp2app(W_CData.le),
    __eq__ = interp2app(W_CData.eq),
    __ne__ = interp2app(W_CData.ne),
    __gt__ = interp2app(W_CData.gt),
    __ge__ = interp2app(W_CData.ge),
    __hash__ = interp2app(W_CData.hash),
    __getitem__ = interp2app(W_CData.getitem),
    __setitem__ = interp2app(W_CData.setitem),
    __add__ = interp2app(W_CData.add),
    __sub__ = interp2app(W_CData.sub),
    __getattr__ = interp2app(W_CData.getattr),
    __setattr__ = interp2app(W_CData.setattr),
    __call__ = interp2app(W_CData.call),
    __iter__ = interp2app(W_CData.iter),
    __weakref__ = make_weakref_descr(W_CData),
    )
W_CData.typedef.acceptable_as_base_class = False