Author: Armin Rigo <[email protected]>
Branch: cffi-static-callback
Changeset: r80680:b2f90804d8eb
Date: 2015-11-15 13:17 +0100
http://bitbucket.org/pypy/pypy/changeset/b2f90804d8eb/
Log: in-progress
diff --git a/pypy/module/_cffi_backend/call_python.py
b/pypy/module/_cffi_backend/call_python.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_cffi_backend/call_python.py
@@ -0,0 +1,155 @@
+import os
+from rpython.rlib.objectmodel import specialize, instantiate
+from rpython.rlib.rarithmetic import intmask
+from rpython.rlib import jit
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.annlowlevel import llhelper
+
+from pypy.interpreter.error import oefmt
+from pypy.interpreter.gateway import interp2app
+from pypy.module._cffi_backend import parse_c_type
+from pypy.module._cffi_backend import cerrno
+from pypy.module._cffi_backend import cffi_opcode
+from pypy.module._cffi_backend import realize_c_type
+from pypy.module._cffi_backend.realize_c_type import getop, getarg
+
+
+STDERR = 2
+CALLPY_FN = lltype.FuncType([parse_c_type.PCALLPY, rffi.CCHARP],
+ lltype.Void)
+
+
+def get_printable_location(callpython):
+ with callpython as ptr:
+ callpy = rffi.cast(parse_c_type.PCALLPY, ptr)
+ return 'cffi_call_python ' + rffi.charp2str(callpy.g_name)
+
+jitdriver = jit.JitDriver(name='cffi_call_python',
+ greens=['callpython'],
+ reds=['ll_args'],
+ get_printable_location=get_printable_location)
+
+def py_invoke_callpython(callpython, ll_args):
+ jitdriver.jit_merge_point(callpython=callpython, ll_args=ll_args)
+ # the same buffer is used both for passing arguments and the result value
+ callpython.do_invoke(ll_args, ll_args)
+
+
+def _cffi_call_python(ll_callpy, ll_args):
+ """Invoked by the helpers generated from CFFI_CALL_PYTHON in the cdef.
+
+ 'callpy' is a static structure that describes which of the
+ CFFI_CALL_PYTHON is called. It has got fields 'name' and
+ 'type_index' describing the function, and more reserved fields
+ that are initially zero. These reserved fields are set up by
+ ffi.call_python(), which invokes init_call_python() below.
+
+ 'args' is a pointer to an array of 8-byte entries. Each entry
+ contains an argument. If an argument is less than 8 bytes, only
+ the part at the beginning of the entry is initialized. If an
+ argument is 'long double' or a struct/union, then it is passed
+ by reference.
+
+ 'args' is also used as the place to write the result to. In all
+ cases, 'args' is at least 8 bytes in size.
+ """
+ from pypy.module._cffi_backend.ccallback import reveal_callback
+
+ cerrno._errno_after(rffi.RFFI_ERR_ALL | rffi.RFFI_ALT_ERRNO)
+
+ if not ll_callpy.c_reserved1:
+ # Not initialized! We don't have a space at all, so just
+ # write the error to the file descriptor stderr. (xxx cpython's
+ # cffi writes it to sys.stderr)
+ try:
+ funcname = rffi.charp2str(ll_callpy.c_name)
+ msg = ("CFFI_CALL_PYTHON: function %s() called, but no code was "
+ "attached to it yet with ffi.call_python('%s'). "
+ "Returning 0.\n" % (funcname, funcname))
+ os.write(STDERR, msg)
+ except:
+ pass
+ for i in range(intmask(ll_callpy.c_size_of_result)):
+ ll_args[i] = '\x00'
+ else:
+ callpython = reveal_callback(ll_callpy.c_reserved1)
+ space = callpython.space
+ must_leave = False
+ try:
+ must_leave = space.threadlocals.try_enter_thread(space)
+ py_invoke_callpython(callpython, ll_args)
+ #
+ except Exception, e:
+ # oups! last-level attempt to recover.
+ try:
+ os.write(STDERR, "SystemError: call_python function raised ")
+ os.write(STDERR, str(e))
+ os.write(STDERR, "\n")
+ except:
+ pass
+ callpython.write_error_return_value(ll_res)
+ if must_leave:
+ space.threadlocals.leave_thread(space)
+
+ cerrno._errno_before(rffi.RFFI_ERR_ALL | rffi.RFFI_ALT_ERRNO)
+
+
+def get_ll_cffi_call_python():
+ return llhelper(lltype.Ptr(CALLPY_FN), _cffi_call_python)
+
+
+class Cache:
+ def __init__(self, space):
+ self.cache_dict = {}
+
+
+def callpy_deco(space, w_ffi, w_python_callable, w_name, w_error, w_onerror):
+ from pypy.module._cffi_backend.ffi_obj import W_FFIObject
+ from pypy.module._cffi_backend.ccallback import W_CallPython
+
+ ffi = space.interp_w(W_FFIObject, w_ffi)
+
+ if space.is_w(w_name, space.w_None):
+ XXX
+ else:
+ name = space.str_w(w_name)
+
+ ctx = ffi.ctxobj.ctx
+ index = parse_c_type.search_in_globals(ctx, name)
+ if index < 0:
+ raise callpy_not_found(ffi, name)
+
+ g = ctx.c_globals[index]
+ if getop(g.c_type_op) != cffi_opcode.OP_CALL_PYTHON:
+ raise callpy_not_found(ffi, name)
+
+ w_ct = realize_c_type.realize_c_type(ffi, ctx.c_types, getarg(g.c_type_op))
+
+ # make a W_CallPython instance, which is nonmovable; then cast it
+ # to a raw pointer and assign it to the field 'reserved1' of the
+ # callpy object from C. We must make sure to keep it alive forever,
+ # or at least until ffi.call_python() is used again to change the
+ # binding. Note that the W_CallPython is never exposed to the user.
+ callpy = rffi.cast(parse_c_type.PCALLPY, g.c_address)
+ callpython = instantiate(W_CallPython, nonmovable=True)
+ callpython.__init__(space, rffi.cast(rffi.CCHARP, callpy), w_ct,
+ w_python_callable, w_error, w_onerror)
+
+ key = rffi.cast(lltype.Signed, callpy)
+ space.fromcache(Cache).cache_dict[key] = callpython
+ callpy.c_reserved1 = rffi.cast(rffi.CCHARP, callpython.hide_object())
+
+ # return a cdata of type function-pointer, equal to the one
+ # obtained by reading 'lib.bar' (see lib_obj.py)
+ ptr = lltype.direct_fieldptr(g, 'c_size_or_direct_fn')
+ return w_ct.convert_to_object(rffi.cast(rffi.CCHARP, ptr))
+
+
+def callpy_not_found(ffi, name):
+ raise oefmt(ffi.w_FFIError,
+ "ffi.call_python('%s'): name not found as a "
+ "CFFI_CALL_PYTHON line from the cdef", name)
+
[email protected]()
+def get_generic_decorator(space):
+ return space.wrap(interp2app(callpy_deco))
diff --git a/pypy/module/_cffi_backend/ccallback.py
b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -3,7 +3,7 @@
"""
import sys, os, py
-from rpython.rlib import clibffi, jit, jit_libffi, rgc, objectmodel
+from rpython.rlib import clibffi, jit, rgc, objectmodel
from rpython.rlib.objectmodel import keepalive_until_here
from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
@@ -25,46 +25,38 @@
# we can cast to a plain VOIDP. As long as the object is not freed,
# we can cast the VOIDP back to a W_CDataCallback in reveal_callback().
cdata = objectmodel.instantiate(W_CDataCallback, nonmovable=True)
- gcref = rgc.cast_instance_to_gcref(cdata)
- raw_cdata = rgc.hide_nonmovable_gcref(gcref)
- cdata.__init__(space, ctype, w_callable, w_error, w_onerror, raw_cdata)
+ cdata.__init__(space, ctype, w_callable, w_error, w_onerror)
return cdata
def reveal_callback(raw_ptr):
addr = rffi.cast(llmemory.Address, raw_ptr)
gcref = rgc.reveal_gcref(addr)
- return rgc.try_cast_gcref_to_instance(W_CDataCallback, gcref)
+ return rgc.try_cast_gcref_to_instance(W_CallPython, gcref)
class Closure(object):
"""This small class is here to have a __del__ outside any cycle."""
- ll_error = lltype.nullptr(rffi.CCHARP.TO) # set manually
-
def __init__(self, ptr):
self.ptr = ptr
def __del__(self):
clibffi.closureHeap.free(rffi.cast(clibffi.FFI_CLOSUREP, self.ptr))
- if self.ll_error:
- lltype.free(self.ll_error, flavor='raw')
-class W_CDataCallback(W_CData):
- _immutable_fields_ = ['key_pycode']
+class W_CallPython(W_CData):
+ """Base class for W_CDataCallback, also used from call_python.py.
+ """
w_onerror = None
+ decode_args_from_libffi = False
- def __init__(self, space, ctype, w_callable, w_error, w_onerror,
- raw_cdata):
- raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
- self._closure = Closure(raw_closure)
- W_CData.__init__(self, space, raw_closure, ctype)
+ def __init__(self, space, cdata, ctype, w_callable, w_error, w_onerror):
+ W_CData.__init__(self, space, cdata, ctype)
#
if not space.is_true(space.callable(w_callable)):
raise oefmt(space.w_TypeError,
"expected a callable object, not %T", w_callable)
self.w_callable = w_callable
- self.key_pycode = space._try_fetch_pycode(w_callable)
if not space.is_none(w_onerror):
if not space.is_true(space.callable(w_onerror)):
raise oefmt(space.w_TypeError,
@@ -74,40 +66,20 @@
#
fresult = self.getfunctype().ctitem
size = fresult.size
- if size > 0:
- if fresult.is_primitive_integer and size < SIZE_OF_FFI_ARG:
- size = SIZE_OF_FFI_ARG
- self._closure.ll_error = lltype.malloc(rffi.CCHARP.TO, size,
- flavor='raw', zero=True)
- if not space.is_none(w_error):
- convert_from_object_fficallback(fresult, self._closure.ll_error,
- w_error)
+ if fresult.is_primitive_integer and size < SIZE_OF_FFI_ARG:
+ size = SIZE_OF_FFI_ARG
+ with lltype.scoped_alloc(rffi.CCHARP.TO, size, zero=True) as ll_error:
+ if not space.is_none(w_error):
+ convert_from_object_fficallback(fresult, ll_error, w_error,
+ self.decode_args_from_libffi)
+ self.error_string = rffi.charpsize2str(ll_error, size)
#
# We must setup the GIL here, in case the callback is invoked in
# some other non-Pythonic thread. This is the same as cffi on
- # CPython.
+ # CPython, or ctypes.
if space.config.translation.thread:
from pypy.module.thread.os_thread import setup_threads
setup_threads(space)
- #
- cif_descr = self.getfunctype().cif_descr
- if not cif_descr:
- raise oefmt(space.w_NotImplementedError,
- "%s: callback with unsupported argument or "
- "return type or with '...'", self.getfunctype().name)
- with self as ptr:
- closure_ptr = rffi.cast(clibffi.FFI_CLOSUREP, ptr)
- unique_id = rffi.cast(rffi.VOIDP, raw_cdata)
- res = clibffi.c_ffi_prep_closure(closure_ptr, cif_descr.cif,
- invoke_callback,
- unique_id)
- if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
- raise OperationError(space.w_SystemError,
- space.wrap("libffi failed to build this callback"))
-
- def _repr_extra(self):
- space = self.space
- return 'calling ' + space.str_w(space.repr(self.w_callable))
def getfunctype(self):
ctype = self.ctype
@@ -117,43 +89,118 @@
space.wrap("expected a function ctype"))
return ctype
+ def hide_object(self):
+ gcref = rgc.cast_instance_to_gcref(self)
+ return rgc.hide_nonmovable_gcref(gcref)
+
+ def _repr_extra(self):
+ space = self.space
+ return 'calling ' + space.str_w(space.repr(self.w_callable))
+
+ def write_error_return_value(self, ll_res):
+ error_string = self.error_string
+ for i in range(len(error_string)):
+ ll_res[i] = error_string[i]
+
+ def do_invoke(self, ll_res, ll_args):
+ space = self.space
+ extra_line = ''
+ try:
+ w_args = self.prepare_args_tuple(ll_args)
+ w_res = space.call(self.w_callable, w_args)
+ extra_line = "Trying to convert the result back to C:\n"
+ self.convert_result(ll_res, w_res)
+ except OperationError, e:
+ self.handle_applevel_exception(e, ll_res, extra_line)
+
@jit.unroll_safe
- def invoke(self, ll_args):
+ def prepare_args_tuple(self, ll_args):
space = self.space
ctype = self.getfunctype()
ctype = jit.promote(ctype)
args_w = []
+ decode_args_from_libffi = self.decode_args_from_libffi
for i, farg in enumerate(ctype.fargs):
- ll_arg = rffi.cast(rffi.CCHARP, ll_args[i])
+ if decode_args_from_libffi:
+ ll_arg = rffi.cast(rffi.CCHARP, ll_args[i])
+ else:
+ ll_arg = rffi.ptradd(rffi.cast(rffi.CCHARP, ll_args), 8 * i)
+ if farg.is_indirect_arg_for_call_python:
+ ll_arg = rffi.cast(rffi.CCHARPP, ll_arg)[0]
args_w.append(farg.convert_to_object(ll_arg))
- return space.call(self.w_callable, space.newtuple(args_w))
+ return space.newtuple(args_w)
def convert_result(self, ll_res, w_res):
fresult = self.getfunctype().ctitem
- convert_from_object_fficallback(fresult, ll_res, w_res)
+ convert_from_object_fficallback(fresult, ll_res, w_res,
+ self.decode_args_from_libffi)
def print_error(self, operr, extra_line):
space = self.space
operr.write_unraisable(space, "cffi callback ", self.w_callable,
with_traceback=True, extra_line=extra_line)
- def write_error_return_value(self, ll_res):
- fresult = self.getfunctype().ctitem
- if fresult.size > 0:
- misc._raw_memcopy(self._closure.ll_error, ll_res, fresult.size)
- keepalive_until_here(self) # to keep self._closure.ll_error alive
+ @jit.dont_look_inside
+ def handle_applevel_exception(self, e, ll_res, extra_line):
+ space = self.space
+ self.write_error_return_value(ll_res)
+ if self.w_onerror is None:
+ self.print_error(e, extra_line)
+ else:
+ try:
+ e.normalize_exception(space)
+ w_t = e.w_type
+ w_v = e.get_w_value(space)
+ w_tb = space.wrap(e.get_traceback())
+ w_res = space.call_function(self.w_onerror, w_t, w_v, w_tb)
+ if not space.is_none(w_res):
+ self.convert_result(ll_res, w_res)
+ except OperationError, e2:
+ # double exception! print a double-traceback...
+ self.print_error(e, extra_line) # original traceback
+ e2.write_unraisable(space, '', with_traceback=True,
+ extra_line="\nDuring the call to 'onerror', "
+ "another exception occurred:\n\n")
-def convert_from_object_fficallback(fresult, ll_res, w_res):
+class W_CDataCallback(W_CallPython):
+ _immutable_fields_ = ['key_pycode']
+ decode_args_from_libffi = True
+
+ def __init__(self, space, ctype, w_callable, w_error, w_onerror):
+ raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
+ self._closure = Closure(raw_closure)
+ W_CallPython.__init__(self, space, raw_closure, ctype,
+ w_callable, w_error, w_onerror)
+ self.key_pycode = space._try_fetch_pycode(w_callable)
+ #
+ cif_descr = self.getfunctype().cif_descr
+ if not cif_descr:
+ raise oefmt(space.w_NotImplementedError,
+ "%s: callback with unsupported argument or "
+ "return type or with '...'", self.getfunctype().name)
+ with self as ptr:
+ closure_ptr = rffi.cast(clibffi.FFI_CLOSUREP, ptr)
+ unique_id = rffi.cast(rffi.VOIDP, self.hide_object())
+ res = clibffi.c_ffi_prep_closure(closure_ptr, cif_descr.cif,
+ invoke_callback,
+ unique_id)
+ if rffi.cast(lltype.Signed, res) != clibffi.FFI_OK:
+ raise OperationError(space.w_SystemError,
+ space.wrap("libffi failed to build this callback"))
+
+
+def convert_from_object_fficallback(fresult, ll_res, w_res,
+ encode_result_for_libffi):
space = fresult.space
- small_result = fresult.size < SIZE_OF_FFI_ARG
- if small_result and isinstance(fresult, W_CTypeVoid):
+ if isinstance(fresult, W_CTypeVoid):
if not space.is_w(w_res, space.w_None):
raise OperationError(space.w_TypeError,
space.wrap("callback with the return type 'void'"
" must return None"))
return
#
+ small_result = encode_result_for_libffi and fresult.size < SIZE_OF_FFI_ARG
if small_result and fresult.is_primitive_integer:
# work work work around a libffi irregularity: for integer return
# types we have to fill at least a complete 'ffi_arg'-sized result
@@ -191,29 +238,6 @@
STDERR = 2
[email protected]_look_inside
-def _handle_applevel_exception(callback, e, ll_res, extra_line):
- space = callback.space
- callback.write_error_return_value(ll_res)
- if callback.w_onerror is None:
- callback.print_error(e, extra_line)
- else:
- try:
- e.normalize_exception(space)
- w_t = e.w_type
- w_v = e.get_w_value(space)
- w_tb = space.wrap(e.get_traceback())
- w_res = space.call_function(callback.w_onerror,
- w_t, w_v, w_tb)
- if not space.is_none(w_res):
- callback.convert_result(ll_res, w_res)
- except OperationError, e2:
- # double exception! print a double-traceback...
- callback.print_error(e, extra_line) # original traceback
- e2.write_unraisable(space, '', with_traceback=True,
- extra_line="\nDuring the call to 'onerror', "
- "another exception occurred:\n\n")
-
def get_printable_location(key_pycode):
if key_pycode is None:
return 'cffi_callback <?>'
@@ -226,13 +250,7 @@
def py_invoke_callback(callback, ll_res, ll_args):
jitdriver.jit_merge_point(callback=callback, ll_res=ll_res,
ll_args=ll_args)
- extra_line = ''
- try:
- w_res = callback.invoke(ll_args)
- extra_line = "Trying to convert the result back to C:\n"
- callback.convert_result(ll_res, w_res)
- except OperationError, e:
- _handle_applevel_exception(callback, e, ll_res, extra_line)
+ callback.do_invoke(ll_res, ll_args)
def _invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
""" Callback specification.
diff --git a/pypy/module/_cffi_backend/cffi1_module.py
b/pypy/module/_cffi_backend/cffi1_module.py
--- a/pypy/module/_cffi_backend/cffi1_module.py
+++ b/pypy/module/_cffi_backend/cffi1_module.py
@@ -17,12 +17,12 @@
def load_cffi1_module(space, name, path, initptr):
# This is called from pypy.module.cpyext.api.load_extension_module()
- from pypy.module._cffi_backend.call_python import get_cffi_call_python
+ from pypy.module._cffi_backend.call_python import get_ll_cffi_call_python
initfunc = rffi.cast(initfunctype, initptr)
with lltype.scoped_alloc(rffi.VOIDPP.TO, 16, zero=True) as p:
p[0] = rffi.cast(rffi.VOIDP, VERSION_EXPORT)
- p[1] = rffi.cast(rffi.VOIDP, get_cffi_call_python())
+ p[1] = rffi.cast(rffi.VOIDP, get_ll_cffi_call_python())
initfunc(p)
version = rffi.cast(lltype.Signed, p[0])
if not (VERSION_MIN <= version <= VERSION_MAX):
diff --git a/pypy/module/_cffi_backend/ctypeobj.py
b/pypy/module/_cffi_backend/ctypeobj.py
--- a/pypy/module/_cffi_backend/ctypeobj.py
+++ b/pypy/module/_cffi_backend/ctypeobj.py
@@ -22,6 +22,7 @@
cast_anything = False
is_primitive_integer = False
is_nonfunc_pointer_or_array = False
+ is_indirect_arg_for_call_python = False
kind = "?"
def __init__(self, space, size, name, name_position):
diff --git a/pypy/module/_cffi_backend/ctypeprim.py
b/pypy/module/_cffi_backend/ctypeprim.py
--- a/pypy/module/_cffi_backend/ctypeprim.py
+++ b/pypy/module/_cffi_backend/ctypeprim.py
@@ -424,6 +424,7 @@
class W_CTypePrimitiveLongDouble(W_CTypePrimitiveFloat):
_attrs_ = []
+ is_indirect_arg_for_call_python = True
@jit.dont_look_inside
def extra_repr(self, cdata):
diff --git a/pypy/module/_cffi_backend/ctypestruct.py
b/pypy/module/_cffi_backend/ctypestruct.py
--- a/pypy/module/_cffi_backend/ctypestruct.py
+++ b/pypy/module/_cffi_backend/ctypestruct.py
@@ -18,6 +18,7 @@
class W_CTypeStructOrUnion(W_CType):
_immutable_fields_ = ['alignment?', '_fields_list?[*]', '_fields_dict?',
'_custom_field_pos?', '_with_var_array?']
+ is_indirect_arg_for_call_python = True
# three possible states:
# - "opaque": for opaque C structs; self.size < 0.
diff --git a/pypy/module/_cffi_backend/ffi_obj.py
b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -279,6 +279,30 @@
return cbuffer.buffer(self.space, w_cdata, size)
+ @unwrap_spec(w_name=WrappedDefault(None),
+ w_error=WrappedDefault(None),
+ w_onerror=WrappedDefault(None))
+ def descr_call_python(self, w_name, w_error, w_onerror):
+ """\
+A decorator. Attaches the decorated Python function to the C code
+generated for the CFFI_CALL_PYTHON function of the same name. Calling
+the C function will then invoke the Python function.
+
+Optional arguments: 'name' is the name of the C function, if
+different from the Python function; and 'error' and 'onerror'
+handle what occurs if the Python function raises an exception
+(see the docs for details)."""
+ #
+ # returns a single-argument function
+ space = self.space
+ w_ffi = space.wrap(self)
+ w_decorator = call_python.get_generic_decorator(space)
+ return space.appexec([w_decorator, w_ffi, w_name, w_error, w_onerror],
+ """(decorator, ffi, name, error, onerror):
+ return lambda python_callable: decorator(ffi, python_callable,
+ name, error, onerror)""")
+
+
@unwrap_spec(w_python_callable=WrappedDefault(None),
w_error=WrappedDefault(None),
w_onerror=WrappedDefault(None))
@@ -633,7 +657,7 @@
addressof = interp2app(W_FFIObject.descr_addressof),
alignof = interp2app(W_FFIObject.descr_alignof),
buffer = interp2app(W_FFIObject.descr_buffer),
- #call_python = interp2app(W_FFIObject.descr_call_python),
+ call_python = interp2app(W_FFIObject.descr_call_python),
callback = interp2app(W_FFIObject.descr_callback),
cast = interp2app(W_FFIObject.descr_cast),
dlclose = interp2app(W_FFIObject.descr_dlclose),
diff --git a/pypy/module/_cffi_backend/parse_c_type.py
b/pypy/module/_cffi_backend/parse_c_type.py
--- a/pypy/module/_cffi_backend/parse_c_type.py
+++ b/pypy/module/_cffi_backend/parse_c_type.py
@@ -71,11 +71,11 @@
('error_location', rffi.SIZE_T),
('error_message', rffi.CCHARP))
-CALLPY_S = rffi.CStruct('_cffi_callpy_s',
- ('name', rffi.CCHARP),
- ('size_of_result', rffi.SIZE_T),
- ('reserved1', rffi.VOIDP),
- ('reserved2', rffi.VOIDP))
+PCALLPY = rffi.CStructPtr('_cffi_callpy_s',
+ ('name', rffi.CCHARP),
+ ('size_of_result', rffi.SIZE_T),
+ ('reserved1', rffi.CCHARP),
+ ('reserved2', rffi.CCHARP))
GETCONST_S = rffi.CStruct('_cffi_getconst_s',
('value', rffi.ULONGLONG),
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit