Author: Antonio Cuni <anto.c...@gmail.com> Branch: Changeset: r94128:f902cda5d7ea Date: 2018-03-24 11:05 +0100 http://bitbucket.org/pypy/pypy/changeset/f902cda5d7ea/
Log: Merge the second part of the cpyext-fast-typecheck branch. Despite the name, this implements a very different feature :) This heavily refactors and simplify W_PyCWrapperObject, which is used to call all C slots from Python. Instead of taking a generic callback to call, we create a specialized subclass for each kind of slot. In particular, this lets us to avoid creating a full tuple (and possibly a dict) to contain the wrapped arguments. The end result is a hugh speedup in some of the antocuni/cpyext- benchmarks microbenchmarks; in particular, compared to default: - len(Foo()) is 9x faster - Foo()[0] is 5.7x faster - np.__getitem__ is ~50% faster - np.mean is ~20% faster diff --git a/pypy/module/cpyext/methodobject.py b/pypy/module/cpyext/methodobject.py --- a/pypy/module/cpyext/methodobject.py +++ b/pypy/module/cpyext/methodobject.py @@ -45,6 +45,18 @@ from pypy.module.cpyext.object import _dealloc _dealloc(space, py_obj) +def w_kwargs_from_args(space, __args__): + w_kwargs = None + if __args__.keywords: + # CCC: we should probably have a @jit.look_inside_iff if the + # keyword count is constant, as we do in Arguments.unpack + w_kwargs = space.newdict() + for i in range(len(__args__.keywords)): + key = __args__.keywords[i] + w_obj = __args__.keywords_w[i] + space.setitem(w_kwargs, space.newtext(key), w_obj) + return w_kwargs + class W_PyCFunctionObject(W_Root): _immutable_fields_ = ["flags"] @@ -103,15 +115,7 @@ def call_keywords(self, space, w_self, __args__): func = rffi.cast(PyCFunctionKwArgs, self.ml.c_ml_meth) py_args = tuple_from_args_w(space, __args__.arguments_w) - w_kwargs = None - if __args__.keywords: - # CCC: we should probably have a @jit.look_inside_iff if the - # keyword count is constant, as we do in Arguments.unpack - w_kwargs = space.newdict() - for i in range(len(__args__.keywords)): - key = __args__.keywords[i] - w_obj = __args__.keywords_w[i] - space.setitem(w_kwargs, space.newtext(key), w_obj) + w_kwargs = w_kwargs_from_args(space, __args__) try: return generic_cpy_call(space, func, w_self, py_args, w_kwargs) finally: @@ -213,14 +217,15 @@ (self.name, self.w_objclass.getname(self.space))) +class W_PyCWrapperObject(W_Root): + """ + Abstract class; for concrete subclasses, see slotdefs.py + """ + _immutable_fields_ = ['offset[*]'] -class W_PyCWrapperObject(W_Root): - def __init__(self, space, pto, method_name, wrapper_func, - wrapper_func_kwds, doc, func, offset=None): + def __init__(self, space, pto, method_name, doc, func, offset): self.space = space self.method_name = method_name - self.wrapper_func = wrapper_func - self.wrapper_func_kwds = wrapper_func_kwds self.doc = doc self.func = func self.offset = offset @@ -229,10 +234,17 @@ assert isinstance(w_type, W_TypeObject) self.w_objclass = w_type - def call(self, space, w_self, w_args, w_kw): + def descr_call(self, space, w_self, __args__): + return self.call(space, w_self, __args__) + + def call(self, space, w_self, __args__): + raise NotImplementedError + + @jit.unroll_safe + def get_func_to_call(self): func_to_call = self.func if self.offset: - pto = as_pyobj(space, self.w_objclass) + pto = as_pyobj(self.space, self.w_objclass) # make ptr the equivalent of this, using the offsets #func_to_call = rffi.cast(rffi.VOIDP, ptr.c_tp_as_number.c_nb_multiply) if pto: @@ -246,31 +258,33 @@ assert False, "failed to convert w_type %s to PyObject" % str( self.w_objclass) assert func_to_call - if self.wrapper_func is None: - assert self.wrapper_func_kwds is not None - return self.wrapper_func_kwds(space, w_self, w_args, func_to_call, - w_kw) - if space.is_true(w_kw): - raise oefmt(space.w_TypeError, + return func_to_call + + def check_args(self, __args__, arity): + length = len(__args__.arguments_w) + if length != arity: + raise oefmt(self.space.w_TypeError, "expected %d arguments, got %d", + arity, length) + if __args__.keywords: + raise oefmt(self.space.w_TypeError, "wrapper %s doesn't take any keyword arguments", self.method_name) - return self.wrapper_func(space, w_self, w_args, func_to_call) + + def check_argsv(self, __args__, min, max): + length = len(__args__.arguments_w) + if not min <= length <= max: + raise oefmt(self.space.w_TypeError, "expected %d-%d arguments, got %d", + min, max, length) + if __args__.keywords: + raise oefmt(self.space.w_TypeError, + "wrapper %s doesn't take any keyword arguments", + self.method_name) def descr_method_repr(self): return self.space.newtext("<slot wrapper '%s' of '%s' objects>" % (self.method_name, self.w_objclass.name)) -@jit.dont_look_inside -def cwrapper_descr_call(space, w_self, __args__): - self = space.interp_w(W_PyCWrapperObject, w_self) - args_w, kw_w = __args__.unpack() - w_args = space.newtuple(args_w[1:]) - w_self = args_w[0] - w_kw = space.newdict() - for key, w_obj in kw_w.items(): - space.setitem(w_kw, space.newtext(key), w_obj) - return self.call(space, w_self, w_args, w_kw) def cmethod_descr_get(space, w_function, w_obj, w_cls=None): asking_for_bound = (space.is_none(w_cls) or @@ -323,7 +337,7 @@ W_PyCWrapperObject.typedef = TypeDef( 'wrapper_descriptor', - __call__ = interp2app(cwrapper_descr_call), + __call__ = interp2app(W_PyCWrapperObject.descr_call), __get__ = interp2app(cmethod_descr_get), __name__ = interp_attrproperty('method_name', cls=W_PyCWrapperObject, wrapfn="newtext_or_none"), diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -19,6 +19,8 @@ from pypy.module.cpyext.state import State from pypy.module.cpyext import userslot from pypy.module.cpyext.buffer import CBuffer, CPyBuffer, fq +from pypy.module.cpyext.methodobject import (W_PyCWrapperObject, tuple_from_args_w, + w_kwargs_from_args) from pypy.interpreter.error import OperationError, oefmt from pypy.interpreter.argument import Arguments from rpython.rlib.unroll import unrolling_iterable @@ -38,29 +40,6 @@ Py_GT = 4 Py_GE = 5 - -def check_num_args(space, w_ob, n): - from pypy.module.cpyext.tupleobject import PyTuple_CheckExact - if not PyTuple_CheckExact(space, w_ob): - raise oefmt(space.w_SystemError, - "PyArg_UnpackTuple() argument list is not a tuple") - if n == space.len_w(w_ob): - return - raise oefmt(space.w_TypeError, - "expected %d arguments, got %d", - n, space.len_w(w_ob)) - -def check_num_argsv(space, w_ob, low, high): - from pypy.module.cpyext.tupleobject import PyTuple_CheckExact - if not PyTuple_CheckExact(space, w_ob): - raise oefmt(space.w_SystemError, - "PyArg_UnpackTuple() argument list is not a tuple") - if low <=space.len_w(w_ob) <= high: - return - raise oefmt(space.w_TypeError, - "expected %d-%d arguments, got %d", - low, high, space.len_w(w_ob)) - @not_rpython def llslot(space, func): return func.api_func.get_llhelper(space) @@ -71,337 +50,413 @@ get_llhelper = v_func.value.api_func.get_llhelper return ctx.appcall(get_llhelper, v_space) +# NOTE: the following wrap_* are subclasses of W_PyCWrapperObject, even if +# they don't follow the usual W_* naming convention for subclasses of W_Root: +# we do this because we automatically generate most of the slots from the +# CPython code copy&pasted inside slotdefs_str, and thus we need to keep the +# same names as they are used in C. -def wrap_init(space, w_self, w_args, func, w_kwargs): - func_init = rffi.cast(initproc, func) - res = generic_cpy_call(space, func_init, w_self, w_args, w_kwargs) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return None +class wrap_init(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_init = rffi.cast(initproc, func) + py_args = tuple_from_args_w(space, __args__.arguments_w) + w_kwargs = w_kwargs_from_args(space, __args__) + res = generic_cpy_call(space, func_init, w_self, py_args, w_kwargs) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return None -def wrap_unaryfunc(space, w_self, w_args, func): - func_unary = rffi.cast(unaryfunc, func) - check_num_args(space, w_args, 0) - return generic_cpy_call(space, func_unary, w_self) +class wrap_unaryfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 0) + func = self.get_func_to_call() + func_unary = rffi.cast(unaryfunc, func) + return generic_cpy_call(space, func_unary, w_self) -def wrap_binaryfunc(space, w_self, w_args, func): - func_binary = rffi.cast(binaryfunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - return generic_cpy_call(space, func_binary, w_self, args_w[0]) +class wrap_binaryfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_binary = rffi.cast(binaryfunc, func) + w_x = __args__.arguments_w[0] + return generic_cpy_call(space, func_binary, w_self, w_x) def _get_ob_type(space, w_obj): # please ensure that w_obj stays alive ob_type = as_pyobj(space, space.type(w_obj)) return rffi.cast(PyTypeObjectPtr, ob_type) -def wrap_binaryfunc_l(space, w_self, w_args, func): - func_binary = rffi.cast(binaryfunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - ob_type = _get_ob_type(space, w_self) - if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and - not space.issubtype_w(space.type(args_w[0]), space.type(w_self))): - return space.w_NotImplemented - return generic_cpy_call(space, func_binary, w_self, args_w[0]) +class wrap_binaryfunc_l(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_binary = rffi.cast(binaryfunc, func) + w_value = __args__.arguments_w[0] + ob_type = _get_ob_type(space, w_self) + if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and + not space.issubtype_w(space.type(w_value), space.type(w_self))): + return space.w_NotImplemented + return generic_cpy_call(space, func_binary, w_self, w_value) -def wrap_binaryfunc_r(space, w_self, w_args, func): - func_binary = rffi.cast(binaryfunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - ob_type = _get_ob_type(space, w_self) - if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and - not space.issubtype_w(space.type(args_w[0]), space.type(w_self))): - return space.w_NotImplemented - return generic_cpy_call(space, func_binary, args_w[0], w_self) +class wrap_binaryfunc_r(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_binary = rffi.cast(binaryfunc, func) + w_value = __args__.arguments_w[0] + ob_type = _get_ob_type(space, w_self) + if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and + not space.issubtype_w(space.type(w_value), space.type(w_self))): + return space.w_NotImplemented + return generic_cpy_call(space, func_binary, w_value, w_self) -def wrap_ternaryfunc(space, w_self, w_args, func): - # The third argument is optional - func_ternary = rffi.cast(ternaryfunc, func) - check_num_argsv(space, w_args, 1, 2) - args_w = space.fixedview(w_args) - arg3 = space.w_None - if len(args_w) > 1: - arg3 = args_w[1] - return generic_cpy_call(space, func_ternary, w_self, args_w[0], arg3) +class wrap_ternaryfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + # The third argument is optional + self.check_argsv(__args__, 1, 2) + func = self.get_func_to_call() + func_ternary = rffi.cast(ternaryfunc, func) + w_arg0 = __args__.arguments_w[0] + if len(__args__.arguments_w) == 2: + w_arg1 = __args__.arguments_w[1] + else: + w_arg1 = space.w_None + return generic_cpy_call(space, func_ternary, w_self, w_arg0, w_arg1) -def wrap_ternaryfunc_r(space, w_self, w_args, func): - # The third argument is optional - func_ternary = rffi.cast(ternaryfunc, func) - check_num_argsv(space, w_args, 1, 2) - args_w = space.fixedview(w_args) - ob_type = _get_ob_type(space, w_self) - if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and - not space.issubtype_w(space.type(args_w[0]), space.type(w_self))): - return space.w_NotImplemented - arg3 = space.w_None - if len(args_w) > 1: - arg3 = args_w[1] - return generic_cpy_call(space, func_ternary, args_w[0], w_self, arg3) +class wrap_ternaryfunc_r(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + # The third argument is optional + self.check_argsv(__args__, 1, 2) + func = self.get_func_to_call() + func_ternary = rffi.cast(ternaryfunc, func) + w_arg0 = __args__.arguments_w[0] + if len(__args__.arguments_w) == 2: + w_arg1 = __args__.arguments_w[1] + else: + w_arg1 = space.w_None + ob_type = _get_ob_type(space, w_self) + if (not ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and + not space.issubtype_w(space.type(w_arg0), space.type(w_self))): + return space.w_NotImplemented + return generic_cpy_call(space, func_ternary, w_arg0, w_self, w_arg1) +class wrap_inquirypred(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 0) + func = self.get_func_to_call() + func_inquiry = rffi.cast(inquiry, func) + res = generic_cpy_call(space, func_inquiry, w_self) + res = rffi.cast(lltype.Signed, res) + if res == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.newbool(bool(res)) -def wrap_inquirypred(space, w_self, w_args, func): - func_inquiry = rffi.cast(inquiry, func) - check_num_args(space, w_args, 0) - res = generic_cpy_call(space, func_inquiry, w_self) - res = rffi.cast(lltype.Signed, res) - if res == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.newbool(bool(res)) +class wrap_getattr(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(getattrfunc, func) + w_name = __args__.arguments_w[0] + name_ptr = rffi.str2charp(space.text_w(w_name)) + try: + return generic_cpy_call(space, func_target, w_self, name_ptr) + finally: + rffi.free_charp(name_ptr) -def wrap_getattr(space, w_self, w_args, func): - func_target = rffi.cast(getattrfunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - name_ptr = rffi.str2charp(space.text_w(args_w[0])) - try: - return generic_cpy_call(space, func_target, w_self, name_ptr) - finally: - rffi.free_charp(name_ptr) +class wrap_getattro(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(getattrofunc, func) + w_name = __args__.arguments_w[0] + return generic_cpy_call(space, func_target, w_self, w_name) -def wrap_getattro(space, w_self, w_args, func): - func_target = rffi.cast(getattrofunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - return generic_cpy_call(space, func_target, w_self, args_w[0]) +class wrap_setattr(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 2) + func = self.get_func_to_call() + func_target = rffi.cast(setattrofunc, func) + w_name = __args__.arguments_w[0] + w_value = __args__.arguments_w[1] + # XXX "Carlo Verre hack"? + res = generic_cpy_call(space, func_target, w_self, w_name, w_value) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_setattr(space, w_self, w_args, func): - func_target = rffi.cast(setattrofunc, func) - check_num_args(space, w_args, 2) - w_name, w_value = space.fixedview(w_args) - # XXX "Carlo Verre hack"? - res = generic_cpy_call(space, func_target, w_self, w_name, w_value) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_delattr(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(setattrofunc, func) + w_name = __args__.arguments_w[0] + # XXX "Carlo Verre hack"? + res = generic_cpy_call(space, func_target, w_self, w_name, None) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_delattr(space, w_self, w_args, func): - func_target = rffi.cast(setattrofunc, func) - check_num_args(space, w_args, 1) - w_name, = space.fixedview(w_args) - # XXX "Carlo Verre hack"? - res = generic_cpy_call(space, func_target, w_self, w_name, None) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_descr_get(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_target = rffi.cast(descrgetfunc, func) + length = len(__args__.arguments_w) + if length == 1: + w_obj = __args__.arguments_w[0] + w_type = None + elif length == 2: + w_obj = __args__.arguments_w[0] + w_type = __args__.arguments_w[1] + else: + raise oefmt(space.w_TypeError, + "expected 1 or 2 arguments, got %d", len(__args__.arguments_w)) + if w_obj is space.w_None: + w_obj = None + if w_type is space.w_None: + w_type = None + if w_obj is None and w_type is None: + raise oefmt(space.w_TypeError, "__get__(None, None) is invalid") + return generic_cpy_call(space, func_target, w_self, w_obj, w_type) -def wrap_descr_get(space, w_self, w_args, func): - func_target = rffi.cast(descrgetfunc, func) - args_w = space.fixedview(w_args) - if len(args_w) == 1: - w_obj, = args_w - w_type = None - elif len(args_w) == 2: - w_obj, w_type = args_w - else: - raise oefmt(space.w_TypeError, - "expected 1 or 2 arguments, got %d", len(args_w)) - if w_obj is space.w_None: - w_obj = None - if w_type is space.w_None: - w_type = None - if w_obj is None and w_type is None: - raise oefmt(space.w_TypeError, "__get__(None, None) is invalid") - return generic_cpy_call(space, func_target, w_self, w_obj, w_type) +class wrap_descr_set(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 2) + func = self.get_func_to_call() + func_target = rffi.cast(descrsetfunc, func) + w_obj = __args__.arguments_w[0] + w_value = __args__.arguments_w[1] + res = generic_cpy_call(space, func_target, w_self, w_obj, w_value) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_descr_set(space, w_self, w_args, func): - func_target = rffi.cast(descrsetfunc, func) - check_num_args(space, w_args, 2) - w_obj, w_value = space.fixedview(w_args) - res = generic_cpy_call(space, func_target, w_self, w_obj, w_value) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_descr_delete(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(descrsetfunc, func) + w_obj = __args__.arguments_w[0] + res = generic_cpy_call(space, func_target, w_self, w_obj, None) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_descr_delete(space, w_self, w_args, func): - func_target = rffi.cast(descrsetfunc, func) - check_num_args(space, w_args, 1) - w_obj, = space.fixedview(w_args) - res = generic_cpy_call(space, func_target, w_self, w_obj, None) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_call(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_target = rffi.cast(ternaryfunc, func) + py_args = tuple_from_args_w(space, __args__.arguments_w) + w_kwargs = w_kwargs_from_args(space, __args__) + return generic_cpy_call(space, func_target, w_self, py_args, w_kwargs) -def wrap_call(space, w_self, w_args, func, w_kwds): - func_target = rffi.cast(ternaryfunc, func) - return generic_cpy_call(space, func_target, w_self, w_args, w_kwds) +class wrap_ssizessizeobjargproc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 3) + func = self.get_func_to_call() + func_target = rffi.cast(ssizessizeobjargproc, func) + i = space.int_w(space.index(__args__.arguments_w[0])) + j = space.int_w(space.index(__args__.arguments_w[1])) + w_y = __args__.arguments_w[2] + res = generic_cpy_call(space, func_target, w_self, i, j, w_y) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_ssizessizeobjargproc(space, w_self, w_args, func): - func_target = rffi.cast(ssizessizeobjargproc, func) - check_num_args(space, w_args, 3) - args_w = space.fixedview(w_args) - i = space.int_w(space.index(args_w[0])) - j = space.int_w(space.index(args_w[1])) - w_y = args_w[2] - res = generic_cpy_call(space, func_target, w_self, i, j, w_y) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_lenfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 0) + func = self.get_func_to_call() + func_len = rffi.cast(lenfunc, func) + res = generic_cpy_call(space, func_len, w_self) + if widen(res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.newint(res) -def wrap_lenfunc(space, w_self, w_args, func): - func_len = rffi.cast(lenfunc, func) - check_num_args(space, w_args, 0) - res = generic_cpy_call(space, func_len, w_self) - if widen(res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.newint(res) +class wrap_sq_item(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(ssizeargfunc, func) + w_index = __args__.arguments_w[0] + index = space.int_w(space.index(w_index)) + return generic_cpy_call(space, func_target, w_self, index) -def wrap_sq_item(space, w_self, w_args, func): - func_target = rffi.cast(ssizeargfunc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - index = space.int_w(space.index(args_w[0])) - return generic_cpy_call(space, func_target, w_self, index) +class wrap_sq_setitem(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 2) + func = self.get_func_to_call() + func_target = rffi.cast(ssizeobjargproc, func) + w_index = __args__.arguments_w[0] + w_value = __args__.arguments_w[1] + index = space.int_w(space.index(w_index)) + res = generic_cpy_call(space, func_target, w_self, index, w_value) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) -def wrap_sq_setitem(space, w_self, w_args, func): - func_target = rffi.cast(ssizeobjargproc, func) - check_num_args(space, w_args, 2) - args_w = space.fixedview(w_args) - index = space.int_w(space.index(args_w[0])) - res = generic_cpy_call(space, func_target, w_self, index, args_w[1]) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) - -def wrap_sq_delitem(space, w_self, w_args, func): - func_target = rffi.cast(ssizeobjargproc, func) - check_num_args(space, w_args, 1) - args_w = space.fixedview(w_args) - index = space.int_w(space.index(args_w[0])) - null = rffi.cast(PyObject, 0) - res = generic_cpy_call(space, func_target, w_self, index, null) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) +class wrap_sq_delitem(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(ssizeobjargproc, func) + w_index = __args__.arguments_w[0] + index = space.int_w(space.index(w_index)) + null = rffi.cast(PyObject, 0) + res = generic_cpy_call(space, func_target, w_self, index, null) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) # Warning, confusing function name (like CPython). Used only for sq_contains. -def wrap_objobjproc(space, w_self, w_args, func): - func_target = rffi.cast(objobjproc, func) - check_num_args(space, w_args, 1) - w_value, = space.fixedview(w_args) - res = generic_cpy_call(space, func_target, w_self, w_value) - res = rffi.cast(lltype.Signed, res) - if res == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.newbool(bool(res)) +class wrap_objobjproc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(objobjproc, func) + w_value = __args__.arguments_w[0] + res = generic_cpy_call(space, func_target, w_self, w_value) + res = rffi.cast(lltype.Signed, res) + if res == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.newbool(bool(res)) -def wrap_objobjargproc(space, w_self, w_args, func): - func_target = rffi.cast(objobjargproc, func) - check_num_args(space, w_args, 2) - w_key, w_value = space.fixedview(w_args) - res = generic_cpy_call(space, func_target, w_self, w_key, w_value) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.w_None +class wrap_objobjargproc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 2) + func = self.get_func_to_call() + func_target = rffi.cast(objobjargproc, func) + w_key = __args__.arguments_w[0] + w_value = __args__.arguments_w[1] + res = generic_cpy_call(space, func_target, w_self, w_key, w_value) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.w_None -def wrap_delitem(space, w_self, w_args, func): - func_target = rffi.cast(objobjargproc, func) - check_num_args(space, w_args, 1) - w_key, = space.fixedview(w_args) - null = rffi.cast(PyObject, 0) - res = generic_cpy_call(space, func_target, w_self, w_key, null) - if rffi.cast(lltype.Signed, res) == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.w_None +class wrap_delitem(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(objobjargproc, func) + w_key = __args__.arguments_w[0] + null = rffi.cast(PyObject, 0) + res = generic_cpy_call(space, func_target, w_self, w_key, null) + if rffi.cast(lltype.Signed, res) == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.w_None -def wrap_ssizessizeargfunc(space, w_self, w_args, func): - func_target = rffi.cast(ssizessizeargfunc, func) - check_num_args(space, w_args, 2) - args_w = space.fixedview(w_args) - start = space.int_w(args_w[0]) - end = space.int_w(args_w[1]) - return generic_cpy_call(space, func_target, w_self, start, end) +class wrap_ssizessizeargfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 2) + func = self.get_func_to_call() + func_target = rffi.cast(ssizessizeargfunc, func) + start = space.int_w(__args__.arguments_w[0]) + end = space.int_w(__args__.arguments_w[1]) + return generic_cpy_call(space, func_target, w_self, start, end) -def wrap_next(space, w_self, w_args, func): - from pypy.module.cpyext.api import generic_cpy_call_expect_null - func_target = rffi.cast(iternextfunc, func) - check_num_args(space, w_args, 0) - w_res = generic_cpy_call_expect_null(space, func_target, w_self) - if not w_res and not PyErr_Occurred(space): - raise OperationError(space.w_StopIteration, space.w_None) - return w_res +class wrap_next(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + from pypy.module.cpyext.api import generic_cpy_call_expect_null + self.check_args(__args__, 0) + func = self.get_func_to_call() + func_target = rffi.cast(iternextfunc, func) + w_res = generic_cpy_call_expect_null(space, func_target, w_self) + if not w_res and not PyErr_Occurred(space): + raise OperationError(space.w_StopIteration, space.w_None) + return w_res -def wrap_hashfunc(space, w_self, w_args, func): - func_target = rffi.cast(hashfunc, func) - check_num_args(space, w_args, 0) - res = generic_cpy_call(space, func_target, w_self) - if res == -1: - space.fromcache(State).check_and_raise_exception(always=True) - return space.newint(res) +class wrap_hashfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 0) + func = self.get_func_to_call() + func_target = rffi.cast(hashfunc, func) + res = generic_cpy_call(space, func_target, w_self) + if res == -1: + space.fromcache(State).check_and_raise_exception(always=True) + return space.newint(res) -def wrap_getreadbuffer(space, w_self, w_args, func): - func_target = rffi.cast(readbufferproc, func) - py_type = _get_ob_type(space, w_self) - rbp = rffi.cast(rffi.VOIDP, 0) - if py_type.c_tp_as_buffer: - rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) - with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr: - index = rffi.cast(Py_ssize_t, 0) - size = generic_cpy_call(space, func_target, w_self, index, ptr) - if size < 0: - space.fromcache(State).check_and_raise_exception(always=True) - view = CPyBuffer(space, ptr[0], size, w_self, - releasebufferproc=rbp) - fq.register_finalizer(view) - return space.newbuffer(CBuffer(view)) +class wrap_getreadbuffer(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_target = rffi.cast(readbufferproc, func) + py_type = _get_ob_type(space, w_self) + rbp = rffi.cast(rffi.VOIDP, 0) + if py_type.c_tp_as_buffer: + rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) + with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr: + index = rffi.cast(Py_ssize_t, 0) + size = generic_cpy_call(space, func_target, w_self, index, ptr) + if size < 0: + space.fromcache(State).check_and_raise_exception(always=True) + view = CPyBuffer(space, ptr[0], size, w_self, + releasebufferproc=rbp) + fq.register_finalizer(view) + return space.newbuffer(CBuffer(view)) -def wrap_getwritebuffer(space, w_self, w_args, func): - func_target = rffi.cast(readbufferproc, func) - py_type = _get_ob_type(space, w_self) - rbp = rffi.cast(rffi.VOIDP, 0) - if py_type.c_tp_as_buffer: - rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) - with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr: - index = rffi.cast(Py_ssize_t, 0) - size = generic_cpy_call(space, func_target, w_self, index, ptr) - if size < 0: - space.fromcache(State).check_and_raise_exception(always=True) - view = CPyBuffer(space, ptr[0], size, w_self, readonly=False, - releasebufferproc=rbp) - fq.register_finalizer(view) - return space.newbuffer(CBuffer(view)) +class wrap_getwritebuffer(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_target = rffi.cast(readbufferproc, func) + py_type = _get_ob_type(space, w_self) + rbp = rffi.cast(rffi.VOIDP, 0) + if py_type.c_tp_as_buffer: + rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) + with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr: + index = rffi.cast(Py_ssize_t, 0) + size = generic_cpy_call(space, func_target, w_self, index, ptr) + if size < 0: + space.fromcache(State).check_and_raise_exception(always=True) + view = CPyBuffer(space, ptr[0], size, w_self, readonly=False, + releasebufferproc=rbp) + fq.register_finalizer(view) + return space.newbuffer(CBuffer(view)) -def wrap_getbuffer(space, w_self, w_args, func): - func_target = rffi.cast(getbufferproc, func) - py_type = _get_ob_type(space, w_self) - rbp = rffi.cast(rffi.VOIDP, 0) - if py_type.c_tp_as_buffer: - rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) - with lltype.scoped_alloc(Py_buffer) as pybuf: - _flags = 0 - if space.len_w(w_args) > 0: - _flags = space.int_w(space.listview(w_args)[0]) - flags = rffi.cast(rffi.INT_real,_flags) - size = generic_cpy_call(space, func_target, w_self, pybuf, flags) - if widen(size) < 0: - space.fromcache(State).check_and_raise_exception(always=True) - ptr = pybuf.c_buf - size = pybuf.c_len - ndim = widen(pybuf.c_ndim) - shape = None - if pybuf.c_shape: - shape = [pybuf.c_shape[i] for i in range(ndim)] - strides = None - if pybuf.c_strides: - strides = [pybuf.c_strides[i] for i in range(ndim)] - if pybuf.c_format: - format = rffi.charp2str(pybuf.c_format) - else: - format = 'B' - # the CPython docs mandates that you do an incref whenever you call - # bf_getbuffer; so, we pass needs_decref=True to ensure that we don't - # leak we release the buffer: - # https://docs.python.org/3.5/c-api/typeobj.html#c.PyBufferProcs.bf_getbuffer - buf = CPyBuffer(space, ptr, size, w_self, format=format, - ndim=ndim, shape=shape, strides=strides, - itemsize=pybuf.c_itemsize, - readonly=widen(pybuf.c_readonly), - needs_decref=True, - releasebufferproc = rbp) - fq.register_finalizer(buf) - return buf.wrap(space) + +class wrap_getbuffer(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + func = self.get_func_to_call() + func_target = rffi.cast(getbufferproc, func) + py_type = _get_ob_type(space, w_self) + rbp = rffi.cast(rffi.VOIDP, 0) + if py_type.c_tp_as_buffer: + rbp = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer) + with lltype.scoped_alloc(Py_buffer) as pybuf: + _flags = 0 + if len(__args__.arguments_w) > 0: + _flags = space.int_w(__args__.arguments_w[0]) + flags = rffi.cast(rffi.INT_real,_flags) + size = generic_cpy_call(space, func_target, w_self, pybuf, flags) + if widen(size) < 0: + space.fromcache(State).check_and_raise_exception(always=True) + ptr = pybuf.c_buf + size = pybuf.c_len + ndim = widen(pybuf.c_ndim) + shape = None + if pybuf.c_shape: + shape = [pybuf.c_shape[i] for i in range(ndim)] + strides = None + if pybuf.c_strides: + strides = [pybuf.c_strides[i] for i in range(ndim)] + if pybuf.c_format: + format = rffi.charp2str(pybuf.c_format) + else: + format = 'B' + # the CPython docs mandates that you do an incref whenever you call + # bf_getbuffer; so, we pass needs_decref=True to ensure that we don't + # leak we release the buffer: + # https://docs.python.org/3.5/c-api/typeobj.html#c.PyBufferProcs.bf_getbuffer + buf = CPyBuffer(space, ptr, size, w_self, format=format, + ndim=ndim, shape=shape, strides=strides, + itemsize=pybuf.c_itemsize, + readonly=widen(pybuf.c_readonly), + needs_decref=True, + releasebufferproc = rbp) + fq.register_finalizer(buf) + return buf.wrap(space) def get_richcmp_func(OP_CONST): - def inner(space, w_self, w_args, func): - func_target = rffi.cast(richcmpfunc, func) - check_num_args(space, w_args, 1) - w_other, = space.fixedview(w_args) - return generic_cpy_call(space, func_target, - w_self, w_other, rffi.cast(rffi.INT_real, OP_CONST)) - return inner + class wrap_richcmp(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(richcmpfunc, func) + w_other = __args__.arguments_w[0] + return generic_cpy_call(space, func_target, + w_self, w_other, rffi.cast(rffi.INT_real, OP_CONST)) + return wrap_richcmp richcmp_eq = get_richcmp_func(Py_EQ) richcmp_ne = get_richcmp_func(Py_NE) @@ -410,17 +465,19 @@ richcmp_gt = get_richcmp_func(Py_GT) richcmp_ge = get_richcmp_func(Py_GE) -def wrap_cmpfunc(space, w_self, w_args, func): - func_target = rffi.cast(cmpfunc, func) - check_num_args(space, w_args, 1) - w_other, = space.fixedview(w_args) +class wrap_cmpfunc(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + self.check_args(__args__, 1) + func = self.get_func_to_call() + func_target = rffi.cast(cmpfunc, func) + w_other = __args__.arguments_w[0] - if not space.issubtype_w(space.type(w_self), space.type(w_other)): - raise oefmt(space.w_TypeError, - "%T.__cmp__(x,y) requires y to be a '%T', not a '%T'", - w_self, w_self, w_other) + if not space.issubtype_w(space.type(w_self), space.type(w_other)): + raise oefmt(space.w_TypeError, + "%T.__cmp__(x,y) requires y to be a '%T', not a '%T'", + w_self, w_self, w_other) - return space.newint(generic_cpy_call(space, func_target, w_self, w_other)) + return space.newint(generic_cpy_call(space, func_target, w_self, w_other)) SLOT_FACTORIES = {} def slot_factory(tp_name): @@ -804,9 +861,10 @@ missing_wrappers = ['wrap_indexargfunc', 'wrap_delslice', 'wrap_coercefunc'] for name in missing_wrappers: assert name not in globals() - def missing_wrapper(space, w_self, w_args, func): - print "cpyext: missing slot wrapper " + name - raise NotImplementedError("Slot wrapper " + name) + class missing_wrapper(W_PyCWrapperObject): + def call(self, space, w_self, __args__): + print "cpyext: missing slot wrapper " + name + raise NotImplementedError("Slot wrapper " + name) missing_wrapper.__name__ = name globals()[name] = missing_wrapper @@ -836,13 +894,12 @@ PyWrapperFlag_KEYWORDS = 1 class TypeSlot: - def __init__(self, method_name, slot_name, function, wrapper1, wrapper2, doc): + def __init__(self, method_name, slot_name, function, wrapper, doc): self.method_name = method_name self.slot_name = slot_name self.slot_names = tuple(("c_" + slot_name).split(".")) self.slot_func = function - self.wrapper_func = wrapper1 - self.wrapper_func_kwds = wrapper2 + self.wrapper_class = wrapper self.doc = doc # adapted from typeobject.c @@ -863,13 +920,7 @@ function = getattr(userslot, FUNCTION or '!missing', None) assert FLAGS == 0 or FLAGS == PyWrapperFlag_KEYWORDS - if FLAGS: - wrapper1 = None - wrapper2 = wrapper - else: - wrapper1 = wrapper - wrapper2 = None - return TypeSlot(NAME, SLOT, function, wrapper1, wrapper2, DOC) + return TypeSlot(NAME, SLOT, function, wrapper, DOC) def TPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC): return FLSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC, 0) @@ -1158,7 +1209,7 @@ x.slot_func.api_func if x.slot_func else None) for x in slotdefs]) slotdefs_for_wrappers = unrolling_iterable( - [(x.method_name, x.slot_names, x.wrapper_func, x.wrapper_func_kwds, x.doc) + [(x.method_name, x.slot_names, x.wrapper_class, x.doc) for x in slotdefs]) if __name__ == "__main__": diff --git a/pypy/module/cpyext/test/test_cpyext.py b/pypy/module/cpyext/test/test_cpyext.py --- a/pypy/module/cpyext/test/test_cpyext.py +++ b/pypy/module/cpyext/test/test_cpyext.py @@ -636,7 +636,8 @@ Py_ssize_t refcnt_after; Py_INCREF(true_obj); Py_INCREF(true_obj); - PyBool_Check(true_obj); + if (!PyBool_Check(true_obj)) + Py_RETURN_NONE; refcnt_after = true_obj->ob_refcnt; Py_DECREF(true_obj); Py_DECREF(true_obj); diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -796,6 +796,36 @@ assert module.tp_init(list, x, ("hi",)) is None assert x == ["h", "i"] + def test_mp_subscript(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], prologue=''' + static PyObject* + mp_subscript(PyObject *self, PyObject *key) + { + return Py_BuildValue("i", 42); + } + PyMappingMethods tp_as_mapping; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''', more_init = ''' + Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT; + Foo_Type.tp_as_mapping = &tp_as_mapping; + tp_as_mapping.mp_subscript = (binaryfunc)mp_subscript; + if (PyType_Ready(&Foo_Type) < 0) INITERROR; + ''') + obj = module.new_obj() + assert obj[100] == 42 + raises(TypeError, "obj.__getitem__(100, 101)") + raises(TypeError, "obj.__getitem__(100, a=42)") + def test_mp_ass_subscript(self): module = self.import_extension('foo', [ ("new_obj", "METH_NOARGS", @@ -859,6 +889,84 @@ res = "foo" in obj assert res is True + def test_sq_ass_slice(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], prologue=''' + static int + sq_ass_slice(PyObject *self, Py_ssize_t a, Py_ssize_t b, PyObject *o) + { + int expected = (a == 10 && b == 20 && + PyInt_Check(o) && PyInt_AsLong(o) == 42); + if (!expected) { + PyErr_SetString(PyExc_ValueError, "test failed"); + return -1; + } + return 0; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''', more_init=''' + Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_ass_slice = sq_ass_slice; + if (PyType_Ready(&Foo_Type) < 0) INITERROR; + ''') + obj = module.new_obj() + obj[10:20] = 42 + raises(ValueError, "obj[10:20] = 43") + raises(ValueError, "obj[11:20] = 42") + raises(ValueError, "obj[10:21] = 42") + + def test_sq_ass_item(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], prologue=''' + static int + sq_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) + { + int expected; + if (o == NULL) // delitem + expected = (i == 12); + else // setitem + expected = (i == 10 && PyInt_Check(o) && PyInt_AsLong(o) == 42); + if (!expected) { + PyErr_SetString(PyExc_ValueError, "test failed"); + return -1; + } + return 0; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''', more_init=''' + Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_ass_item = sq_ass_item; + if (PyType_Ready(&Foo_Type) < 0) INITERROR; + ''') + obj = module.new_obj() + obj[10] = 42 + raises(ValueError, "obj[10] = 43") + raises(ValueError, "obj[11] = 42") + del obj[12] + raises(ValueError, "del obj[13]") + def test_tp_iter(self): module = self.import_extension('foo', [ ("tp_iter", "METH_VARARGS", diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -25,8 +25,8 @@ Py_TPPYPYFLAGS_FLOAT_SUBCLASS, ) from pypy.module.cpyext.methodobject import (W_PyCClassMethodObject, - W_PyCWrapperObject, PyCFunction_NewEx, PyCFunction, PyMethodDef, - W_PyCMethodObject, W_PyCFunctionObject) + PyCFunction_NewEx, PyCFunction, PyMethodDef, + W_PyCMethodObject, W_PyCFunctionObject, W_PyCWrapperObject) from pypy.module.cpyext.modsupport import convert_method_defs from pypy.module.cpyext.pyobject import ( PyObject, make_ref, from_ref, get_typedescr, make_typedescr, @@ -311,7 +311,7 @@ def add_operators(space, dict_w, pto): from pypy.module.cpyext.object import PyObject_HashNotImplemented hash_not_impl = llslot(space, PyObject_HashNotImplemented) - for method_name, slot_names, wrapper_func, wrapper_func_kwds, doc in slotdefs_for_wrappers: + for method_name, slot_names, wrapper_class, doc in slotdefs_for_wrappers: if method_name in dict_w: continue offset = [rffi.offsetof(lltype.typeOf(pto).TO, slot_names[0])] @@ -336,10 +336,11 @@ func_voidp = rffi.cast(rffi.VOIDP, func) if not func: continue - if wrapper_func is None and wrapper_func_kwds is None: + if wrapper_class is None: continue - w_obj = W_PyCWrapperObject(space, pto, method_name, wrapper_func, - wrapper_func_kwds, doc, func_voidp, offset=offset) + + assert issubclass(wrapper_class, W_PyCWrapperObject) + w_obj = wrapper_class(space, pto, method_name, doc, func_voidp, offset=offset[:]) dict_w[method_name] = w_obj if pto.c_tp_doc: dict_w['__doc__'] = space.newtext( _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit