Author: Matti Picus <matti.pi...@gmail.com> Branch: unicode-utf8-py3 Changeset: r94791:ab11d063dfa4 Date: 2018-06-30 19:47 -0700 http://bitbucket.org/pypy/pypy/changeset/ab11d063dfa4/
Log: merge py3.5 into branch diff too long, truncating to 2000 out of 5287 lines diff --git a/pypy/doc/sandbox.rst b/pypy/doc/sandbox.rst --- a/pypy/doc/sandbox.rst +++ b/pypy/doc/sandbox.rst @@ -3,6 +3,11 @@ PyPy's sandboxing features ========================== +.. warning:: This is not actively maintained. You will likely have to fix + some issues yourself, or otherwise play around on your own. We provide + this documentation for historical reasions, it will not translate or + run on the latest PyPy code base. + Introduction ------------ diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -7,9 +7,9 @@ .. branch: cppyy-packaging -Upgrade to backend 0.6.0, support exception handling from wrapped functions, -update enum handling, const correctness for data members and associated tests, -support anonymous enums, support for function pointer arguments +Upgrade to backend 1.1.0, improved handling of templated methods and +functions (in particular automatic deduction of types), improved pythonization +interface, and a range of compatibility fixes for Python3 .. branch: socket_default_timeout_blockingness diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py --- a/pypy/interpreter/generator.py +++ b/pypy/interpreter/generator.py @@ -270,7 +270,7 @@ if self.frame is None: return # nothing to do in this case space = self.space - operr = get_generator_exit(space) + operr = None # note: w_yielded_from is always None if 'self.running' w_yf = self.w_yielded_from if w_yf is not None: @@ -278,6 +278,8 @@ self._gen_close_iter(space) except OperationError as e: operr = e + if operr is None: + operr = OperationError(space.w_GeneratorExit, space.w_None) try: self.send_error(operr) except OperationError as e: @@ -451,11 +453,6 @@ space.call_function(space.w_StopIteration, w_value)) -@specialize.memo() -def get_generator_exit(space): - return OperationError(space.w_GeneratorExit, - space.call_function(space.w_GeneratorExit)) - def gen_close_iter(space, w_yf): # This helper function is used by close() and throw() to # close a subiterator being delegated to by yield-from. diff --git a/pypy/interpreter/pyparser/automata.py b/pypy/interpreter/pyparser/automata.py --- a/pypy/interpreter/pyparser/automata.py +++ b/pypy/interpreter/pyparser/automata.py @@ -23,6 +23,10 @@ ERROR_STATE = chr(255) +# NB: all non-ascii bytes (>= 128) will be turned into 128 +NON_ASCII = chr(128) + + class DFA: # ____________________________________________________________ def __init__(self, states, accepts, start = 0): @@ -36,7 +40,10 @@ for key in state: if key == DEFAULT: continue - maximum = max(ord(key), maximum) + ordkey = ord(key) + if ordkey > 128: + raise ValueError("DFA does not support matching of specific non-ASCII character %r. Use NON_ASCII instead" % key) + maximum = max(ordkey, maximum) self.max_char = maximum + 1 defaults = [] @@ -73,7 +80,7 @@ for i in range(pos, len(inVec)): item = inVec[i] if ord(item) > 0x80: - item = "\x80" # NON_ASCII + item = NON_ASCII accept = self.accepts[crntState] crntState = self._next_state(item, crntState) if crntState != ERROR_STATE: @@ -105,6 +112,8 @@ i = pos for i in range(pos, len(inVec)): item = inVec[i] + if ord(item) > 0x80: + item = NON_ASCII accept = self.accepts[crntState] if accept: return i diff --git a/pypy/interpreter/pyparser/test/test_automata.py b/pypy/interpreter/pyparser/test/test_automata.py --- a/pypy/interpreter/pyparser/test/test_automata.py +++ b/pypy/interpreter/pyparser/test/test_automata.py @@ -1,4 +1,7 @@ -from pypy.interpreter.pyparser.automata import DFA, NonGreedyDFA, DEFAULT +# coding: utf-8 +import pytest + +from pypy.interpreter.pyparser.automata import DFA, NonGreedyDFA, DEFAULT, NON_ASCII def test_states(): d = DFA([{"\x00": 1}, {"\x01": 0}], [False, True]) @@ -27,3 +30,18 @@ d = NonGreedyDFA([{"a": 1}, {DEFAULT: 0}], [False, True]) assert d.recognize("a,a?ab") == 1 assert d.recognize("c") == -1 + +def test_nonascii(): + d = DFA([{"a": 1}, {NON_ASCII: 1}], [False, True]) + input = u"aüüüü".encode("utf-8") + assert d.recognize(input) == len(input) + assert d.recognize("c") == -1 + assert d.recognize("ü") == -1 + + d = NonGreedyDFA([{NON_ASCII: 0, "b": 1}, {"b": 0}], [False, True]) + input = u"üübbbb".encode("utf-8") + assert d.recognize(input) == len(u"üüb".encode("utf-8")) + assert d.recognize("c") == -1 + + pytest.raises(ValueError, DFA, [{"\x81": 2}], [True]) + diff --git a/pypy/interpreter/test/test_function.py b/pypy/interpreter/test/test_function.py --- a/pypy/interpreter/test/test_function.py +++ b/pypy/interpreter/test/test_function.py @@ -170,6 +170,7 @@ h = get_h() raises(ValueError, "f.__code__ = h.__code__") + @pytest.mark.skipif("config.option.runappdirect") def test_write_code_builtin_forbidden(self): def f(*args): return 42 @@ -339,7 +340,6 @@ assert meth() == obj def test_none_get_interaction(self): - skip("XXX issue #2083") assert type(None).__repr__(None) == 'None' def test_none_get_interaction_2(self): @@ -544,7 +544,9 @@ pass r = repr(ClsB().f) assert "ClsA.f of <" in r - assert "ClsB object at " in r + assert repr(type(ClsA.f)) == "<class 'function'>" + assert repr(type(ClsA().f)) == "<class 'method'>" + def test_method_call(self): class C(object): diff --git a/pypy/module/_cppyy/__init__.py b/pypy/module/_cppyy/__init__.py --- a/pypy/module/_cppyy/__init__.py +++ b/pypy/module/_cppyy/__init__.py @@ -1,7 +1,7 @@ from pypy.interpreter.mixedmodule import MixedModule class Module(MixedModule): - "This module brigdes the cppyy frontend with its backend, through PyPy.\n\ + "This module bridges the cppyy frontend with its backend, through PyPy.\n\ See http://cppyy.readthedocs.io/en/latest for full details." interpleveldefs = { @@ -14,17 +14,19 @@ '_set_function_generator': 'interp_cppyy.set_function_generator', '_register_class' : 'interp_cppyy.register_class', '_get_nullptr' : 'interp_cppyy.get_nullptr', - 'CPPInstanceBase' : 'interp_cppyy.W_CPPInstance', + 'CPPInstance' : 'interp_cppyy.W_CPPInstance', 'addressof' : 'interp_cppyy.addressof', '_bind_object' : 'interp_cppyy._bind_object', 'bind_object' : 'interp_cppyy.bind_object', 'move' : 'interp_cppyy.move', + '_pin_type' : 'interp_cppyy._pin_type', } appleveldefs = { '_post_import_startup' : 'pythonify._post_import_startup', + 'Template' : 'pythonify.CPPTemplate', 'add_pythonization' : 'pythonify.add_pythonization', - 'Template' : 'pythonify.CPPTemplate', + 'remove_pythonization' : 'pythonify.remove_pythonization', } def __init__(self, space, *args): diff --git a/pypy/module/_cppyy/capi/__init__.py b/pypy/module/_cppyy/capi/__init__.py --- a/pypy/module/_cppyy/capi/__init__.py +++ b/pypy/module/_cppyy/capi/__init__.py @@ -11,6 +11,3 @@ assert lltype.typeOf(ptr) == C_OBJECT address = rffi.cast(rffi.CCHARP, ptr) return rffi.cast(C_OBJECT, lltype.direct_ptradd(address, offset)) - -def exchange_address(ptr, cif_descr, index): - return rffi.ptradd(ptr, cif_descr.exchange_args[index]) diff --git a/pypy/module/_cppyy/capi/loadable_capi.py b/pypy/module/_cppyy/capi/loadable_capi.py --- a/pypy/module/_cppyy/capi/loadable_capi.py +++ b/pypy/module/_cppyy/capi/loadable_capi.py @@ -69,7 +69,8 @@ space = self.space cif_descr = self.cif_descr size = cif_descr.exchange_size - raw_string = rffi.cast(rffi.CCHARP, 0) # only ever have one in the CAPI + raw_string1 = rffi.cast(rffi.CCHARP, 0) + raw_string2 = rffi.cast(rffi.CCHARP, 0) # have max two in any CAPI buffer = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw') try: for i in range(len(args)): @@ -88,14 +89,18 @@ assert obj._voidp != rffi.cast(rffi.VOIDP, 0) data = rffi.cast(rffi.VOIDPP, data) data[0] = obj._voidp - else: # only other use is sring + else: # only other use is string assert obj.tc == 's' n = len(obj._string) - assert raw_string == rffi.cast(rffi.CCHARP, 0) - # XXX could use rffi.get_nonmovingbuffer_final_null() - raw_string = rffi.str2charp(obj._string) data = rffi.cast(rffi.CCHARPP, data) - data[0] = raw_string + if raw_string1 == rffi.cast(rffi.CCHARP, 0): + # XXX could use rffi.get_nonmovingbuffer_final_null() + raw_string1 = rffi.str2charp(obj._string) + data[0] = raw_string1 + else: + assert raw_string2 == rffi.cast(rffi.CCHARP, 0) + raw_string2 = rffi.str2charp(obj._string) + data[0] = raw_string2 jit_libffi.jit_ffi_call(cif_descr, rffi.cast(rffi.VOIDP, funcaddr), @@ -106,8 +111,10 @@ # immediate unwrapping, the round-trip is removed w_res = self.ctitem.copy_and_convert_to_object(resultdata) finally: - if raw_string != rffi.cast(rffi.CCHARP, 0): - rffi.free_charp(raw_string) + if raw_string1 != rffi.cast(rffi.CCHARP, 0): + rffi.free_charp(raw_string1) + if raw_string2 != rffi.cast(rffi.CCHARP, 0): + rffi.free_charp(raw_string2) lltype.free(buffer, flavor='raw') return w_res @@ -183,8 +190,7 @@ 'constructor' : ([c_method, c_object, c_int, c_voidp], c_object), 'call_o' : ([c_method, c_object, c_int, c_voidp, c_type], c_object), - 'function_address_from_index' : ([c_scope, c_index], c_voidp), # TODO: verify - 'function_address_from_method' : ([c_method], c_voidp), # id. + 'function_address' : ([c_method], c_voidp), # TODO: verify # handling of function argument buffer 'allocate_function_args' : ([c_int], c_voidp), @@ -207,6 +213,8 @@ 'num_bases' : ([c_type], c_int), 'base_name' : ([c_type, c_int], c_ccharp), 'is_subtype' : ([c_type, c_type], c_int), + 'smartptr_info' : ([c_ccharp, c_voidp, c_voidp], c_int), + 'add_smartptr_type' : ([c_ccharp], c_void), 'base_offset' : ([c_type, c_type, c_object, c_int], c_ptrdiff_t), @@ -214,30 +222,31 @@ 'num_methods' : ([c_scope], c_int), 'method_indices_from_name' : ([c_scope, c_ccharp], c_index_array), - 'method_name' : ([c_scope, c_index], c_ccharp), - 'method_mangled_name' : ([c_scope, c_index], c_ccharp), - 'method_result_type' : ([c_scope, c_index], c_ccharp), - 'method_num_args' : ([c_scope, c_index], c_int), - 'method_req_args' : ([c_scope, c_index], c_int), - 'method_arg_type' : ([c_scope, c_index, c_int], c_ccharp), - 'method_arg_default' : ([c_scope, c_index, c_int], c_ccharp), - 'method_signature' : ([c_scope, c_index, c_int], c_ccharp), - 'method_prototype' : ([c_scope, c_index, c_int], c_ccharp), + 'get_method' : ([c_scope, c_index], c_method), + + 'method_name' : ([c_method], c_ccharp), + 'method_full_name' : ([c_method], c_ccharp), + 'method_mangled_name' : ([c_method], c_ccharp), + 'method_result_type' : ([c_method], c_ccharp), + 'method_num_args' : ([c_method], c_int), + 'method_req_args' : ([c_method], c_int), + 'method_arg_type' : ([c_method, c_int], c_ccharp), + 'method_arg_default' : ([c_method, c_int], c_ccharp), + 'method_signature' : ([c_method, c_int], c_ccharp), + 'method_prototype' : ([c_scope, c_method, c_int], c_ccharp), 'is_const_method' : ([c_method], c_int), 'exists_method_template' : ([c_scope, c_ccharp], c_int), 'method_is_template' : ([c_scope, c_index], c_int), - 'method_num_template_args' : ([c_scope, c_index], c_int), - 'method_template_arg_name' : ([c_scope, c_index, c_index], c_ccharp), + 'get_method_template' : ([c_scope, c_ccharp, c_ccharp], c_method), - 'get_method' : ([c_scope, c_index], c_method), 'get_global_operator' : ([c_scope, c_scope, c_scope, c_ccharp], c_index), # method properties - 'is_public_method' : ([c_type, c_index], c_int), - 'is_constructor' : ([c_type, c_index], c_int), - 'is_destructor' : ([c_type, c_index], c_int), - 'is_staticmethod' : ([c_type, c_index], c_int), + 'is_public_method' : ([c_method], c_int), + 'is_constructor' : ([c_method], c_int), + 'is_destructor' : ([c_method], c_int), + 'is_staticmethod' : ([c_method], c_int), # data member reflection information 'num_datamembers' : ([c_scope], c_int), @@ -415,13 +424,9 @@ args = [_ArgH(cppmethod), _ArgH(cppobject), _ArgL(nargs), _ArgP(cargs), _ArgH(cppclass.handle)] return _cdata_to_cobject(space, call_capi(space, 'call_o', args)) -def c_function_address_from_index(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] +def c_function_address(space, cppmethod): return rffi.cast(C_FUNC_PTR, - _cdata_to_ptr(space, call_capi(space, 'function_address_from_index', args))) -def c_function_address_from_method(space, cppmethod): - return rffi.cast(C_FUNC_PTR, - _cdata_to_ptr(space, call_capi(space, 'function_address_from_method', [_ArgH(cppmethod)]))) + _cdata_to_ptr(space, call_capi(space, 'function_address', [_ArgH(cppmethod)]))) # handling of function argument buffer --------------------------------------- def c_allocate_function_args(space, size): @@ -479,6 +484,21 @@ if derived == base: return bool(1) return space.bool_w(call_capi(space, 'is_subtype', [_ArgH(derived.handle), _ArgH(base.handle)])) +def c_smartptr_info(space, name): + out_raw = lltype.malloc(rffi.ULONGP.TO, 1, flavor='raw', zero=True) + out_deref = lltype.malloc(rffi.ULONGP.TO, 1, flavor='raw', zero=True) + try: + args = [_ArgS(name), + _ArgP(rffi.cast(rffi.VOIDP, out_raw)), _ArgP(rffi.cast(rffi.VOIDP, out_deref))] + result = space.bool_w(call_capi(space, 'smartptr_info', args)) + raw = rffi.cast(C_TYPE, out_raw[0]) + deref = rffi.cast(C_METHOD, out_deref[0]) + finally: + lltype.free(out_deref, flavor='raw') + lltype.free(out_raw, flavor='raw') + return (result, raw, deref) +def c_add_smartptr_type(space, name): + return space.bool_w(call_capi(space, 'add_smartptr_type', [_ArgS(name)])) def _c_base_offset(space, derived_h, base_h, address, direction): args = [_ArgH(derived_h), _ArgH(base_h), _ArgH(address), _ArgL(direction)] @@ -510,30 +530,36 @@ c_free(space, rffi.cast(rffi.VOIDP, indices)) # c_free defined below return py_indices -def c_method_name(space, cppscope, index): +def c_get_method(space, cppscope, index): args = [_ArgH(cppscope.handle), _ArgL(index)] - return charp2str_free(space, call_capi(space, 'method_name', args)) -def c_method_result_type(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] - return charp2str_free(space, call_capi(space, 'method_result_type', args)) -def c_method_num_args(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] - return space.int_w(call_capi(space, 'method_num_args', args)) -def c_method_req_args(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] - return space.int_w(call_capi(space, 'method_req_args', args)) -def c_method_arg_type(space, cppscope, index, arg_index): - args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(arg_index)] + return rffi.cast(C_METHOD, space.uint_w(call_capi(space, 'get_method', args))) + +def c_method_name(space, cppmeth): + return charp2str_free(space, call_capi(space, 'method_name', [_ArgH(cppmeth)])) +def c_method_full_name(space, cppmeth): + return charp2str_free(space, call_capi(space, 'method_full_name', [_ArgH(cppmeth)])) +def c_method_mangled_name(space, cppmeth): + return charp2str_free(space, call_capi(space, 'method_mangled_name', [_ArgH(cppmeth)])) +def c_method_result_type(space, cppmeth): + return charp2str_free(space, call_capi(space, 'method_result_type', [_ArgH(cppmeth)])) +def c_method_num_args(space, cppmeth): + return space.int_w(call_capi(space, 'method_num_args', [_ArgH(cppmeth)])) +def c_method_req_args(space, cppmeth): + return space.int_w(call_capi(space, 'method_req_args', [_ArgH(cppmeth)])) +def c_method_arg_type(space, cppmeth, arg_index): + args = [_ArgH(cppmeth), _ArgL(arg_index)] return charp2str_free(space, call_capi(space, 'method_arg_type', args)) -def c_method_arg_default(space, cppscope, index, arg_index): - args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(arg_index)] +def c_method_arg_default(space, cppmeth, arg_index): + args = [_ArgH(cppmeth), _ArgL(arg_index)] return charp2str_free(space, call_capi(space, 'method_arg_default', args)) -def c_method_signature(space, cppscope, index, show_formalargs=True): - args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(show_formalargs)] +def c_method_signature(space, cppmeth, show_formalargs=True): + args = [_ArgH(cppmeth), _ArgL(show_formalargs)] return charp2str_free(space, call_capi(space, 'method_signature', args)) -def c_method_prototype(space, cppscope, index, show_formalargs=True): - args = [_ArgH(cppscope.handle), _ArgL(index), _ArgL(show_formalargs)] +def c_method_prototype(space, cppscope, cppmeth, show_formalargs=True): + args = [_ArgH(cppscope.handle), _ArgH(cppmeth), _ArgL(show_formalargs)] return charp2str_free(space, call_capi(space, 'method_prototype', args)) +def c_is_const_method(space, cppmeth): + return space.bool_w(call_capi(space, 'is_const_method', [_ArgH(cppmeth)])) def c_exists_method_template(space, cppscope, name): args = [_ArgH(cppscope.handle), _ArgS(name)] @@ -541,21 +567,10 @@ def c_method_is_template(space, cppscope, index): args = [_ArgH(cppscope.handle), _ArgL(index)] return space.bool_w(call_capi(space, 'method_is_template', args)) -def _c_method_num_template_args(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] - return space.int_w(call_capi(space, 'method_num_template_args', args)) -def c_template_args(space, cppscope, index): - nargs = _c_method_num_template_args(space, cppscope, index) - arg1 = _ArgH(cppscope.handle) - arg2 = _ArgL(index) - args = [c_resolve_name(space, charp2str_free(space, - call_capi(space, 'method_template_arg_name', [arg1, arg2, _ArgL(iarg)])) - ) for iarg in range(nargs)] - return args -def c_get_method(space, cppscope, index): - args = [_ArgH(cppscope.handle), _ArgL(index)] - return rffi.cast(C_METHOD, space.uint_w(call_capi(space, 'get_method', args))) +def c_get_method_template(space, cppscope, name, proto): + args = [_ArgH(cppscope.handle), _ArgS(name), _ArgS(proto)] + return rffi.cast(C_METHOD, space.uint_w(call_capi(space, 'get_method_template', args))) def c_get_global_operator(space, nss, lc, rc, op): if nss is not None: args = [_ArgH(nss.handle), _ArgH(lc.handle), _ArgH(rc.handle), _ArgS(op)] @@ -563,18 +578,14 @@ return rffi.cast(WLAVC_INDEX, -1) # method properties ---------------------------------------------------------- -def c_is_public_method(space, cppclass, index): - args = [_ArgH(cppclass.handle), _ArgL(index)] - return space.bool_w(call_capi(space, 'is_public_method', args)) -def c_is_constructor(space, cppclass, index): - args = [_ArgH(cppclass.handle), _ArgL(index)] - return space.bool_w(call_capi(space, 'is_constructor', args)) -def c_is_destructor(space, cppclass, index): - args = [_ArgH(cppclass.handle), _ArgL(index)] - return space.bool_w(call_capi(space, 'is_destructor', args)) -def c_is_staticmethod(space, cppclass, index): - args = [_ArgH(cppclass.handle), _ArgL(index)] - return space.bool_w(call_capi(space, 'is_staticmethod', args)) +def c_is_public_method(space, cppmeth): + return space.bool_w(call_capi(space, 'is_public_method', [_ArgH(cppmeth)])) +def c_is_constructor(space, cppmeth): + return space.bool_w(call_capi(space, 'is_constructor', [_ArgH(cppmeth)])) +def c_is_destructor(space, cppmeth): + return space.bool_w(call_capi(space, 'is_destructor', [_ArgH(cppmeth)])) +def c_is_staticmethod(space, cppmeth): + return space.bool_w(call_capi(space, 'is_staticmethod', [_ArgH(cppmeth)])) # data member reflection information ----------------------------------------- def c_num_datamembers(space, cppscope): @@ -676,7 +687,7 @@ space.setattr(w_pycppclass, space.newtext(m1), space.getattr(w_pycppclass, space.newtext(m2))) -def pythonize(space, name, w_pycppclass): +def pythonize(space, w_pycppclass, name): if name == "string": space.setattr(w_pycppclass, space.newtext("c_str"), _pythonizations["stdstring_c_str"]) _method_alias(space, w_pycppclass, "_cppyy_as_builtin", "c_str") diff --git a/pypy/module/_cppyy/converter.py b/pypy/module/_cppyy/converter.py --- a/pypy/module/_cppyy/converter.py +++ b/pypy/module/_cppyy/converter.py @@ -7,7 +7,7 @@ from rpython.rlib import rfloat, rawrefcount from pypy.module._rawffi.interp_rawffi import letter2tp -from pypy.module._rawffi.array import W_Array, W_ArrayInstance +from pypy.module._rawffi.array import W_ArrayInstance from pypy.module._cppyy import helper, capi, ffitypes @@ -68,6 +68,8 @@ pass # array type try: + if hasattr(space, "fake"): + raise NotImplementedError arr = space.interp_w(W_ArrayInstance, w_obj, can_be_None=True) if arr: return rffi.cast(rffi.VOIDP, space.uint_w(arr.getbuffer(space))) @@ -130,20 +132,6 @@ pass -class ArrayCache(object): - def __init__(self, space): - self.space = space - def __getattr__(self, name): - if name.startswith('array_'): - typecode = name[len('array_'):] - arr = self.space.interp_w(W_Array, letter2tp(self.space, typecode)) - setattr(self, name, arr) - return arr - raise AttributeError(name) - - def _freeze_(self): - return True - class ArrayTypeConverterMixin(object): _mixin_ = True _immutable_fields_ = ['size'] @@ -162,9 +150,7 @@ # read access, so no copy needed address_value = self._get_raw_address(space, w_obj, offset) address = rffi.cast(rffi.ULONG, address_value) - cache = space.fromcache(ArrayCache) - arr = getattr(cache, 'array_' + self.typecode) - return arr.fromaddress(space, address, self.size) + return W_ArrayInstance(space, letter2tp(space, self.typecode), self.size, address) def to_memory(self, space, w_obj, w_value, offset): # copy the full array (uses byte copy for now) @@ -205,17 +191,15 @@ # read access, so no copy needed address_value = self._get_raw_address(space, w_obj, offset) address = rffi.cast(rffi.ULONGP, address_value) - cache = space.fromcache(ArrayCache) - arr = getattr(cache, 'array_' + self.typecode) - return arr.fromaddress(space, address[0], self.size) + return W_ArrayInstance(space, letter2tp(space, self.typecode), self.size, address[0]) def to_memory(self, space, w_obj, w_value, offset): # copy only the pointer value rawobject = get_rawobject_nonnull(space, w_obj) - byteptr = rffi.cast(rffi.CCHARPP, capi.direct_ptradd(rawobject, offset)) + byteptr = rffi.cast(rffi.VOIDPP, capi.direct_ptradd(rawobject, offset)) buf = space.getarg_w('s*', w_value) try: - byteptr[0] = buf.get_raw_address() + byteptr[0] = rffi.cast(rffi.VOIDP, buf.get_raw_address()) except ValueError: raise oefmt(space.w_TypeError, "raw buffer interface not supported") @@ -337,6 +321,10 @@ address = rffi.cast(rffi.CCHARP, self._get_raw_address(space, w_obj, offset)) address[0] = self._unwrap_object(space, w_value) + +class UCharConverter(ffitypes.typeid(rffi.UCHAR), CharConverter): + pass + class FloatConverter(ffitypes.typeid(rffi.FLOAT), FloatTypeConverterMixin, TypeConverter): _immutable_fields_ = ['default'] @@ -398,12 +386,12 @@ arg = space.text_w(w_obj) x[0] = rffi.cast(rffi.LONG, rffi.str2charp(arg)) ba = rffi.cast(rffi.CCHARP, address) - ba[capi.c_function_arg_typeoffset(space)] = 'o' + ba[capi.c_function_arg_typeoffset(space)] = 'p' def from_memory(self, space, w_obj, w_pycppclass, offset): address = self._get_raw_address(space, w_obj, offset) charpptr = rffi.cast(rffi.CCHARPP, address) - return space.newbytes(rffi.charp2str(charpptr[0])) + return space.newtext(rffi.charp2str(charpptr[0])) def free_argument(self, space, arg, call_local): lltype.free(rffi.cast(rffi.CCHARPP, arg)[0], flavor='raw') @@ -420,7 +408,7 @@ strsize = self.size if charpptr[self.size-1] == '\0': strsize = self.size-1 # rffi will add \0 back - return space.newbytes(rffi.charpsize2str(charpptr, strsize)) + return space.newtext(rffi.charpsize2str(charpptr, strsize)) class VoidPtrConverter(TypeConverter): @@ -449,12 +437,12 @@ # returned as a long value for the address (INTPTR_T is not proper # per se, but rffi does not come with a PTRDIFF_T) address = self._get_raw_address(space, w_obj, offset) - ptrval = rffi.cast(rffi.ULONG, rffi.cast(rffi.VOIDPP, address)[0]) - if ptrval == 0: + ptrval = rffi.cast(rffi.ULONGP, address)[0] + if ptrval == rffi.cast(rffi.ULONG, 0): from pypy.module._cppyy import interp_cppyy return interp_cppyy.get_nullptr(space) - arr = space.interp_w(W_Array, letter2tp(space, 'P')) - return arr.fromaddress(space, ptrval, sys.maxint) + shape = letter2tp(space, 'P') + return W_ArrayInstance(space, shape, sys.maxint/shape.size, ptrval) def to_memory(self, space, w_obj, w_value, offset): address = rffi.cast(rffi.VOIDPP, self._get_raw_address(space, w_obj, offset)) @@ -504,8 +492,8 @@ def _unwrap_object(self, space, w_obj): from pypy.module._cppyy.interp_cppyy import W_CPPInstance if isinstance(w_obj, W_CPPInstance): - from pypy.module._cppyy.interp_cppyy import INSTANCE_FLAGS_IS_R_VALUE - if w_obj.flags & INSTANCE_FLAGS_IS_R_VALUE: + from pypy.module._cppyy.interp_cppyy import INSTANCE_FLAGS_IS_RVALUE + if w_obj.flags & INSTANCE_FLAGS_IS_RVALUE: # reject moves as all are explicit raise ValueError("lvalue expected") if capi.c_is_subtype(space, w_obj.clsdecl, self.clsdecl): @@ -514,7 +502,7 @@ obj_address = capi.direct_ptradd(rawobject, offset) return rffi.cast(capi.C_OBJECT, obj_address) raise oefmt(space.w_TypeError, - "cannot pass %T as %s", w_obj, self.clsdecl.name) + "cannot pass %T instance as %s", w_obj, self.clsdecl.name) def cffi_type(self, space): state = space.fromcache(ffitypes.State) @@ -534,11 +522,18 @@ class InstanceMoveConverter(InstanceRefConverter): def _unwrap_object(self, space, w_obj): # moving is same as by-ref, but have to check that move is allowed - from pypy.module._cppyy.interp_cppyy import W_CPPInstance, INSTANCE_FLAGS_IS_R_VALUE - if isinstance(w_obj, W_CPPInstance): - if w_obj.flags & INSTANCE_FLAGS_IS_R_VALUE: - w_obj.flags &= ~INSTANCE_FLAGS_IS_R_VALUE - return InstanceRefConverter._unwrap_object(self, space, w_obj) + from pypy.module._cppyy.interp_cppyy import W_CPPInstance, INSTANCE_FLAGS_IS_RVALUE + obj = space.interp_w(W_CPPInstance, w_obj) + if obj: + if obj.flags & INSTANCE_FLAGS_IS_RVALUE: + obj.flags &= ~INSTANCE_FLAGS_IS_RVALUE + try: + return InstanceRefConverter._unwrap_object(self, space, w_obj) + except Exception: + # TODO: if the method fails on some other converter, then the next + # overload can not be an rvalue anymore + obj.flags |= INSTANCE_FLAGS_IS_RVALUE + raise raise oefmt(space.w_ValueError, "object is not an rvalue") @@ -629,8 +624,7 @@ address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) assign = self.clsdecl.get_overload("__assign__") from pypy.module._cppyy import interp_cppyy - assign.call( - interp_cppyy.wrap_cppinstance(space, address, self.clsdecl, do_cast=False), [w_value]) + assign.call_impl(address, [w_value]) except Exception: InstanceConverter.to_memory(self, space, w_obj, w_value, offset) @@ -639,7 +633,6 @@ class StdStringRefConverter(InstancePtrConverter): _immutable_fields_ = ['cppclass', 'typecode'] - typecode = 'V' def __init__(self, space, extra): @@ -702,8 +695,7 @@ m = cppol.functions[i] if m.signature(False) == self.signature: x = rffi.cast(rffi.VOIDPP, address) - x[0] = rffi.cast(rffi.VOIDP, - capi.c_function_address_from_method(space, m.cppmethod)) + x[0] = rffi.cast(rffi.VOIDP, capi.c_function_address(space, m.cppmethod)) address = rffi.cast(capi.C_OBJECT, address) ba = rffi.cast(rffi.CCHARP, address) ba[capi.c_function_arg_typeoffset(space)] = 'p' @@ -714,6 +706,67 @@ "no overload found matching %s", self.signature) +class SmartPtrConverter(TypeConverter): + _immutable_fields = ['typecode', 'smartdecl', 'rawdecl', 'deref'] + typecode = 'V' + + def __init__(self, space, smartdecl, raw, deref): + from pypy.module._cppyy.interp_cppyy import W_CPPClassDecl, get_pythonized_cppclass + self.smartdecl = smartdecl + w_raw = get_pythonized_cppclass(space, raw) + self.rawdecl = space.interp_w(W_CPPClassDecl, + space.findattr(w_raw, space.newtext("__cppdecl__"))) + self.deref = deref + + def _unwrap_object(self, space, w_obj): + from pypy.module._cppyy.interp_cppyy import W_CPPInstance + if isinstance(w_obj, W_CPPInstance): + # w_obj could carry a 'hidden' smart ptr or be one, cover both cases + have_match = False + if w_obj.smartdecl and capi.c_is_subtype(space, w_obj.smartdecl, self.smartdecl): + # hidden case, do not derefence when getting obj address + have_match = True + rawobject = w_obj._rawobject # TODO: this direct access if fugly + offset = capi.c_base_offset(space, w_obj.smartdecl, self.smartdecl, rawobject, 1) + elif capi.c_is_subtype(space, w_obj.clsdecl, self.smartdecl): + # exposed smart pointer + have_match = True + rawobject = w_obj.get_rawobject() + offset = capi.c_base_offset(space, w_obj.clsdecl, self.smartdecl, rawobject, 1) + if have_match: + obj_address = capi.direct_ptradd(rawobject, offset) + return rffi.cast(capi.C_OBJECT, obj_address) + + raise oefmt(space.w_TypeError, + "cannot pass %T instance as %s", w_obj, self.rawdecl.name) + + def convert_argument(self, space, w_obj, address, call_local): + x = rffi.cast(rffi.VOIDPP, address) + x[0] = rffi.cast(rffi.VOIDP, self._unwrap_object(space, w_obj)) + address = rffi.cast(capi.C_OBJECT, address) + ba = rffi.cast(rffi.CCHARP, address) + ba[capi.c_function_arg_typeoffset(space)] = self.typecode + + def from_memory(self, space, w_obj, w_pycppclass, offset): + address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) + from pypy.module._cppyy import interp_cppyy + return interp_cppyy.wrap_cppinstance(space, address, + self.rawdecl, smartdecl=self.smartdecl, deref=self.deref, do_cast=False) + +class SmartPtrPtrConverter(SmartPtrConverter): + typecode = 'o' + + def from_memory(self, space, w_obj, w_pycppclass, offset): + self._is_abstract(space) + + def to_memory(self, space, w_obj, w_value, offset): + self._is_abstract(space) + + +class SmartPtrRefConverter(SmartPtrPtrConverter): + typecode = 'V' + + class MacroConverter(TypeConverter): def from_memory(self, space, w_obj, w_pycppclass, offset): # TODO: get the actual type info from somewhere ... @@ -729,44 +782,61 @@ # 1) full, exact match # 1a) const-removed match # 2) match of decorated, unqualified type - # 3) accept ref as pointer (for the stubs, const& can be - # by value, but that does not work for the ffi path) - # 4) generalized cases (covers basically all user classes) - # 5) void* or void converter (which fails on use) + # 3) generalized cases (covers basically all user classes) + # 3a) smart pointers + # 4) void* or void converter (which fails on use) name = capi.c_resolve_name(space, _name) - # 1) full, exact match + # full, exact match try: return _converters[name](space, default) except KeyError: pass - # 1a) const-removed match + # const-removed match try: return _converters[helper.remove_const(name)](space, default) except KeyError: pass - # 2) match of decorated, unqualified type + # match of decorated, unqualified type compound = helper.compound(name) clean_name = capi.c_resolve_name(space, helper.clean_type(name)) try: + return _converters[clean_name+compound](space, default) + except KeyError: + pass + + # arrays + try: # array_index may be negative to indicate no size or no size found array_size = helper.array_size(_name) # uses original arg + # TODO: using clean_name here drops const (e.g. const char[] will + # never be seen this way) return _a_converters[clean_name+compound](space, array_size) except KeyError: pass - # 3) TODO: accept ref as pointer - - # 4) generalized cases (covers basically all user classes) + # generalized cases (covers basically all user classes) from pypy.module._cppyy import interp_cppyy scope_decl = interp_cppyy.scope_byname(space, clean_name) if scope_decl: - # type check for the benefit of the annotator from pypy.module._cppyy.interp_cppyy import W_CPPClassDecl clsdecl = space.interp_w(W_CPPClassDecl, scope_decl, can_be_None=False) + + # check smart pointer type + check_smart = capi.c_smartptr_info(space, clean_name) + if check_smart[0]: + if compound == '': + return SmartPtrConverter(space, clsdecl, check_smart[1], check_smart[2]) + elif compound == '*': + return SmartPtrPtrConverter(space, clsdecl, check_smart[1], check_smart[2]) + elif compound == '&': + return SmartPtrRefConverter(space, clsdecl, check_smart[1], check_smart[2]) + # fall through: can still return smart pointer in non-smart way + + # type check for the benefit of the annotator if compound == "*": return InstancePtrConverter(space, clsdecl) elif compound == "&": @@ -786,7 +856,7 @@ if pos > 0: return FunctionPointerConverter(space, name[pos+2:]) - # 5) void* or void converter (which fails on use) + # void* or void converter (which fails on use) if 0 <= compound.find('*'): return VoidPtrConverter(space, default) # "user knows best" @@ -797,6 +867,7 @@ _converters["bool"] = BoolConverter _converters["char"] = CharConverter +_converters["unsigned char"] = UCharConverter _converters["float"] = FloatConverter _converters["const float&"] = ConstFloatRefConverter _converters["double"] = DoubleConverter @@ -886,6 +957,7 @@ "NOT_RPYTHON" array_info = ( ('b', rffi.sizeof(rffi.UCHAR), ("bool",)), # is debatable, but works ... + ('B', rffi.sizeof(rffi.UCHAR), ("unsigned char",)), ('h', rffi.sizeof(rffi.SHORT), ("short int", "short")), ('H', rffi.sizeof(rffi.USHORT), ("unsigned short int", "unsigned short")), ('i', rffi.sizeof(rffi.INT), ("int",)), @@ -901,9 +973,11 @@ for tcode, tsize, names in array_info: class ArrayConverter(ArrayTypeConverterMixin, TypeConverter): + _immutable_fields_ = ['typecode', 'typesize'] typecode = tcode typesize = tsize class PtrConverter(PtrTypeConverterMixin, TypeConverter): + _immutable_fields_ = ['typecode', 'typesize'] typecode = tcode typesize = tsize for name in names: @@ -912,6 +986,7 @@ # special case, const char* w/ size and w/o '\0' _a_converters["const char[]"] = CStringConverterWithSize + _a_converters["char[]"] = _a_converters["const char[]"] # debatable _build_array_converters() @@ -919,7 +994,6 @@ def _add_aliased_converters(): "NOT_RPYTHON" aliases = ( - ("char", "unsigned char"), # TODO: check ("char", "signed char"), # TODO: check ("const char*", "char*"), diff --git a/pypy/module/_cppyy/executor.py b/pypy/module/_cppyy/executor.py --- a/pypy/module/_cppyy/executor.py +++ b/pypy/module/_cppyy/executor.py @@ -5,7 +5,7 @@ from rpython.rtyper.lltypesystem import rffi, lltype from rpython.rlib import jit_libffi -from pypy.module._rawffi.interp_rawffi import unpack_simple_shape +from pypy.module._rawffi.interp_rawffi import letter2tp from pypy.module._rawffi.array import W_Array, W_ArrayInstance from pypy.module._cppyy import helper, capi, ffitypes @@ -26,7 +26,7 @@ NULL = lltype.nullptr(jit_libffi.FFI_TYPE_P.TO) -class FunctionExecutor(object): +class Executor(object): def __init__(self, space, extra): pass @@ -43,7 +43,7 @@ raise FastCallNotPossible -class PtrTypeExecutor(FunctionExecutor): +class PtrTypeExecutor(Executor): _immutable_fields_ = ['typecode'] typecode = 'P' @@ -56,14 +56,14 @@ raise NotImplementedError lresult = capi.c_call_l(space, cppmethod, cppthis, num_args, args) ptrval = rffi.cast(rffi.ULONG, lresult) - arr = space.interp_w(W_Array, unpack_simple_shape(space, space.newtext(self.typecode))) - if ptrval == 0: + if ptrval == rffi.cast(rffi.ULONG, 0): from pypy.module._cppyy import interp_cppyy return interp_cppyy.get_nullptr(space) - return arr.fromaddress(space, ptrval, sys.maxint) + shape = letter2tp(space, self.typecode) + return W_ArrayInstance(space, shape, sys.maxint/shape.size, ptrval) -class VoidExecutor(FunctionExecutor): +class VoidExecutor(Executor): def cffi_type(self, space): state = space.fromcache(ffitypes.State) return state.c_void @@ -96,7 +96,7 @@ _mixin_ = True def __init__(self, space, extra): - FunctionExecutor.__init__(self, space, extra) + Executor.__init__(self, space, extra) self.do_assign = False self.item = rffi.cast(self.c_type, 0) @@ -124,8 +124,7 @@ rffi.cast(self.c_ptrtype, rffi.cast(rffi.VOIDPP, result)[0])) -class CStringExecutor(FunctionExecutor): - +class CStringExecutor(Executor): def execute(self, space, cppmethod, cppthis, num_args, args): lresult = capi.c_call_l(space, cppmethod, cppthis, num_args, args) ccpresult = rffi.cast(rffi.CCHARP, lresult) @@ -139,8 +138,7 @@ return space.newtext(result) -class ConstructorExecutor(FunctionExecutor): - +class ConstructorExecutor(Executor): def execute(self, space, cppmethod, cpptype, num_args, args): from pypy.module._cppyy import interp_cppyy newthis = capi.c_constructor(space, cppmethod, cpptype, num_args, args) @@ -148,80 +146,77 @@ return space.newlong(rffi.cast(rffi.LONG, newthis)) # really want ptrdiff_t here -class InstancePtrExecutor(FunctionExecutor): - _immutable_fields_ = ['cppclass'] +class InstanceExecutor(Executor): + # For return of a C++ instance by pointer: MyClass* func() + _immutable_fields_ = ['clsdecl'] - def __init__(self, space, cppclass): - FunctionExecutor.__init__(self, space, cppclass) - self.cppclass = cppclass + def __init__(self, space, clsdecl): + Executor.__init__(self, space, clsdecl) + self.clsdecl = clsdecl + + def _wrap_result(self, space, obj): + from pypy.module._cppyy import interp_cppyy + return interp_cppyy.wrap_cppinstance(space, + obj, self.clsdecl, do_cast=False, python_owns=True, fresh=True) + + def execute(self, space, cppmethod, cppthis, num_args, args): + oresult = capi.c_call_o(space, cppmethod, cppthis, num_args, args, self.clsdecl) + return self._wrap_result(space, rffi.cast(capi.C_OBJECT, oresult)) + + +class InstancePtrExecutor(InstanceExecutor): + # For return of a C++ instance by pointer: MyClass* func() def cffi_type(self, space): state = space.fromcache(ffitypes.State) return state.c_voidp + def _wrap_result(self, space, obj): + from pypy.module._cppyy import interp_cppyy + return interp_cppyy.wrap_cppinstance(space, obj, self.clsdecl) + def execute(self, space, cppmethod, cppthis, num_args, args): - from pypy.module._cppyy import interp_cppyy - long_result = capi.c_call_l(space, cppmethod, cppthis, num_args, args) - ptr_result = rffi.cast(capi.C_OBJECT, long_result) - pyres = interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) - return pyres + lresult = capi.c_call_l(space, cppmethod, cppthis, num_args, args) + return self._wrap_result(space, rffi.cast(capi.C_OBJECT, lresult)) def execute_libffi(self, space, cif_descr, funcaddr, buffer): jit_libffi.jit_ffi_call(cif_descr, funcaddr, buffer) - result = rffi.ptradd(buffer, cif_descr.exchange_result) - from pypy.module._cppyy import interp_cppyy - ptr_result = rffi.cast(capi.C_OBJECT, rffi.cast(rffi.VOIDPP, result)[0]) - return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) + presult = rffi.ptradd(buffer, cif_descr.exchange_result) + obj = rffi.cast(capi.C_OBJECT, rffi.cast(rffi.VOIDPP, presult)[0]) + return self._wrap_result(space, obj) class InstancePtrPtrExecutor(InstancePtrExecutor): + # For return of a C++ instance by ptr-to-ptr or ptr-to-ref: MyClass*& func() def execute(self, space, cppmethod, cppthis, num_args, args): - from pypy.module._cppyy import interp_cppyy - voidp_result = capi.c_call_r(space, cppmethod, cppthis, num_args, args) - ref_address = rffi.cast(rffi.VOIDPP, voidp_result) - ptr_result = rffi.cast(capi.C_OBJECT, ref_address[0]) - return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) + presult = capi.c_call_r(space, cppmethod, cppthis, num_args, args) + ref = rffi.cast(rffi.VOIDPP, presult) + return self._wrap_result(space, rffi.cast(capi.C_OBJECT, ref[0])) def execute_libffi(self, space, cif_descr, funcaddr, buffer): from pypy.module._cppyy.interp_cppyy import FastCallNotPossible raise FastCallNotPossible -class InstanceExecutor(InstancePtrExecutor): - - def execute(self, space, cppmethod, cppthis, num_args, args): - from pypy.module._cppyy import interp_cppyy - long_result = capi.c_call_o(space, cppmethod, cppthis, num_args, args, self.cppclass) - ptr_result = rffi.cast(capi.C_OBJECT, long_result) - return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass, - do_cast=False, python_owns=True, fresh=True) - - def execute_libffi(self, space, cif_descr, funcaddr, buffer): - from pypy.module._cppyy.interp_cppyy import FastCallNotPossible - raise FastCallNotPossible - class StdStringExecutor(InstancePtrExecutor): - def execute(self, space, cppmethod, cppthis, num_args, args): cstr, cstr_len = capi.c_call_s(space, cppmethod, cppthis, num_args, args) pystr = rffi.charpsize2str(cstr, cstr_len) capi.c_free(space, rffi.cast(rffi.VOIDP, cstr)) - return space.newbytes(pystr) + return space.newbytes(pystr) def execute_libffi(self, space, cif_descr, funcaddr, buffer): from pypy.module._cppyy.interp_cppyy import FastCallNotPossible raise FastCallNotPossible class StdStringRefExecutor(InstancePtrExecutor): - - def __init__(self, space, cppclass): + def __init__(self, space, clsdecl): from pypy.module._cppyy import interp_cppyy - cppclass = interp_cppyy.scope_byname(space, capi.std_string_name) - InstancePtrExecutor.__init__(self, space, cppclass) + clsdecl = interp_cppyy.scope_byname(space, capi.std_string_name) + InstancePtrExecutor.__init__(self, space, clsdecl) class PyObjectExecutor(PtrTypeExecutor): - def wrap_result(self, space, lresult): space.getbuiltinmodule("cpyext") from pypy.module.cpyext.pyobject import PyObject, from_ref, make_ref, decref @@ -245,6 +240,41 @@ return self.wrap_result(space, rffi.cast(rffi.LONGP, result)[0]) +class SmartPointerExecutor(InstanceExecutor): + _immutable_fields_ = ['smartdecl', 'deref'] + + def __init__(self, space, smartdecl, raw, deref): + from pypy.module._cppyy.interp_cppyy import W_CPPClassDecl, get_pythonized_cppclass + w_raw = get_pythonized_cppclass(space, raw) + rawdecl = space.interp_w(W_CPPClassDecl, space.findattr(w_raw, space.newtext("__cppdecl__"))) + InstanceExecutor.__init__(self, space, rawdecl) + self.smartdecl = smartdecl + self.deref = deref + + def _wrap_result(self, space, obj): + from pypy.module._cppyy import interp_cppyy + return interp_cppyy.wrap_cppinstance(space, obj, self.clsdecl, + self.smartdecl, self.deref, do_cast=False, python_owns=True, fresh=True) + +class SmartPointerPtrExecutor(InstancePtrExecutor): + _immutable_fields_ = ['smartdecl', 'deref'] + + def __init__(self, space, smartdecl, raw, deref): + # TODO: share this with SmartPointerExecutor through in mixin + from pypy.module._cppyy.interp_cppyy import W_CPPClassDecl, get_pythonized_cppclass + w_raw = get_pythonized_cppclass(space, raw) + rawdecl = space.interp_w(W_CPPClassDecl, space.findattr(w_raw, space.newtext("__cppdecl__"))) + InstancePtrExecutor.__init__(self, space, rawdecl) + self.smartdecl = smartdecl + self.deref = deref + + def _wrap_result(self, space, obj): + from pypy.module._cppyy import interp_cppyy + # TODO: this is a pointer to a smart pointer, take ownership on the smart one? + return interp_cppyy.wrap_cppinstance(space, obj, self.clsdecl, + self.smartdecl, self.deref, do_cast=False) + + _executors = {} def get_executor(space, name): # Matching of 'name' to an executor factory goes through up to four levels: @@ -257,7 +287,7 @@ name = capi.c_resolve_name(space, name) - # 1) full, qualified match + # full, qualified match try: return _executors[name](space, None) except KeyError: @@ -266,13 +296,13 @@ compound = helper.compound(name) clean_name = capi.c_resolve_name(space, helper.clean_type(name)) - # 1a) clean lookup + # clean lookup try: return _executors[clean_name+compound](space, None) except KeyError: pass - # 2) drop '&': by-ref is pretty much the same as by-value, python-wise + # drop '&': by-ref is pretty much the same as by-value, python-wise if compound and compound[len(compound)-1] == '&': # TODO: this does not actually work with Reflex (?) try: @@ -280,19 +310,29 @@ except KeyError: pass - # 3) types/classes, either by ref/ptr or by value + # types/classes, either by ref/ptr or by value from pypy.module._cppyy import interp_cppyy cppclass = interp_cppyy.scope_byname(space, clean_name) if cppclass: # type check for the benefit of the annotator from pypy.module._cppyy.interp_cppyy import W_CPPClassDecl - cppclass = space.interp_w(W_CPPClassDecl, cppclass, can_be_None=False) + clsdecl = space.interp_w(W_CPPClassDecl, cppclass, can_be_None=False) + + # check smart pointer type + check_smart = capi.c_smartptr_info(space, clean_name) + if check_smart[0]: + if compound == '': + return SmartPointerExecutor(space, clsdecl, check_smart[1], check_smart[2]) + elif compound == '*' or compound == '&': + return SmartPointerPtrExecutor(space, clsdecl, check_smart[1], check_smart[2]) + # fall through: can still return smart pointer in non-smart way + if compound == '': - return InstanceExecutor(space, cppclass) + return InstanceExecutor(space, clsdecl) elif compound == '*' or compound == '&': - return InstancePtrExecutor(space, cppclass) + return InstancePtrExecutor(space, clsdecl) elif compound == '**' or compound == '*&': - return InstancePtrPtrExecutor(space, cppclass) + return InstancePtrPtrExecutor(space, clsdecl) elif "(anonymous)" in name: # special case: enum w/o a type name return _executors["internal_enum_type_t"](space, None) @@ -302,7 +342,7 @@ return _executors['void*'](space, None) # allow at least passing of the pointer # currently used until proper lazy instantiation available in interp_cppyy - return FunctionExecutor(space, None) + return Executor(space, None) _executors["void"] = VoidExecutor @@ -338,10 +378,10 @@ ) for c_type, stub, names in type_info: - class BasicExecutor(ffitypes.typeid(c_type), NumericExecutorMixin, FunctionExecutor): + class BasicExecutor(ffitypes.typeid(c_type), NumericExecutorMixin, Executor): _immutable_ = True c_stubcall = staticmethod(stub) - class BasicRefExecutor(ffitypes.typeid(c_type), NumericRefExecutorMixin, FunctionExecutor): + class BasicRefExecutor(ffitypes.typeid(c_type), NumericRefExecutorMixin, Executor): def cffi_type(self, space): state = space.fromcache(ffitypes.State) return state.c_voidp diff --git a/pypy/module/_cppyy/ffitypes.py b/pypy/module/_cppyy/ffitypes.py --- a/pypy/module/_cppyy/ffitypes.py +++ b/pypy/module/_cppyy/ffitypes.py @@ -74,18 +74,52 @@ # allow int to pass to char and make sure that str is of length 1 if space.isinstance_w(w_value, space.w_int): ival = space.c_int_w(w_value) + if ival < -128 or 127 < ival: + raise oefmt(space.w_ValueError, "char arg not in range(-128,128)") + + value = rffi.cast(rffi.CHAR, space.c_int_w(w_value)) + else: + if space.isinstance_w(w_value, space.w_text): + value = space.text_w(w_value) + else: + value = space.bytes_w(w_value) + if len(value) != 1: + raise oefmt(space.w_ValueError, + "char expected, got string of size %d", len(value)) + + value = rffi.cast(rffi.CHAR, value[0]) + return value # turn it into a "char" to the annotator + + def cffi_type(self, space): + state = space.fromcache(State) + return state.c_char + +class UCharTypeMixin(object): + _mixin_ = True + _immutable_fields_ = ['c_type', 'c_ptrtype'] + + c_type = rffi.UCHAR + c_ptrtype = rffi.CCHARP # there's no such thing as rffi.UCHARP + + def _wrap_object(self, space, obj): + return space.newbytes(obj) + + def _unwrap_object(self, space, w_value): + # allow int to pass to char and make sure that str is of length 1 + if space.isinstance_w(w_value, space.w_int): + ival = space.c_int_w(w_value) if ival < 0 or 256 <= ival: raise oefmt(space.w_ValueError, "char arg not in range(256)") value = rffi.cast(rffi.CHAR, space.c_int_w(w_value)) - elif space.isinstance_w(w_value, space.w_text): - value = space.text_w(w_value) else: - value = space.bytes_w(w_value) - - if len(value) != 1: - raise oefmt(space.w_ValueError, - "char expected, got string of size %d", len(value)) + if space.isinstance_w(w_value, space.w_text): + value = space.text_w(w_value) + else: + value = space.bytes_w(w_value) + if len(value) != 1: + raise oefmt(space.w_ValueError, + "unsigned char expected, got string of size %d", len(value)) value = rffi.cast(rffi.CHAR, value[0]) return value # turn it into a "char" to the annotator @@ -280,6 +314,7 @@ "NOT_RPYTHON" if c_type == bool: return BoolTypeMixin if c_type == rffi.CHAR: return CharTypeMixin + if c_type == rffi.UCHAR: return UCharTypeMixin if c_type == rffi.SHORT: return ShortTypeMixin if c_type == rffi.USHORT: return UShortTypeMixin if c_type == rffi.INT: return IntTypeMixin diff --git a/pypy/module/_cppyy/helper.py b/pypy/module/_cppyy/helper.py --- a/pypy/module/_cppyy/helper.py +++ b/pypy/module/_cppyy/helper.py @@ -1,3 +1,4 @@ +import sys from rpython.rlib import rstring @@ -116,6 +117,17 @@ # TODO: perhaps absorb or "pythonify" these operators? return cppname +if sys.hexversion < 0x3000000: + CPPYY__div__ = "__div__" + CPPYY__idiv__ = "__idiv__" + CPPYY__long__ = "__long__" + CPPYY__bool__ = "__nonzero__" +else: + CPPYY__div__ = "__truediv__" + CPPYY__idiv__ = "__itruediv__" + CPPYY__long__ = "__int__" + CPPYY__bool__ = "__bool__" + # _operator_mappings["[]"] = "__setitem__" # depends on return type # _operator_mappings["+"] = "__add__" # depends on # of args (see __pos__) # _operator_mappings["-"] = "__sub__" # id. (eq. __neg__) @@ -123,7 +135,7 @@ # _operator_mappings["[]"] = "__getitem__" # depends on return type _operator_mappings["()"] = "__call__" -_operator_mappings["/"] = "__div__" # __truediv__ in p3 +_operator_mappings["/"] = CPPYY__div__ _operator_mappings["%"] = "__mod__" _operator_mappings["**"] = "__pow__" # not C++ _operator_mappings["<<"] = "__lshift__" @@ -136,7 +148,7 @@ _operator_mappings["+="] = "__iadd__" _operator_mappings["-="] = "__isub__" _operator_mappings["*="] = "__imul__" -_operator_mappings["/="] = "__idiv__" # __itruediv__ in p3 +_operator_mappings["/="] = CPPYY__idiv__ _operator_mappings["%="] = "__imod__" _operator_mappings["**="] = "__ipow__" _operator_mappings["<<="] = "__ilshift__" @@ -154,7 +166,7 @@ # the following type mappings are "exact" _operator_mappings["const char*"] = "__str__" _operator_mappings["int"] = "__int__" -_operator_mappings["long"] = "__long__" # __int__ in p3 +_operator_mappings["long"] = CPPYY__long__ _operator_mappings["double"] = "__float__" # the following type mappings are "okay"; the assumption is that they @@ -163,13 +175,13 @@ _operator_mappings["char*"] = "__str__" _operator_mappings["short"] = "__int__" _operator_mappings["unsigned short"] = "__int__" -_operator_mappings["unsigned int"] = "__long__" # __int__ in p3 -_operator_mappings["unsigned long"] = "__long__" # id. -_operator_mappings["long long"] = "__long__" # id. -_operator_mappings["unsigned long long"] = "__long__" # id. +_operator_mappings["unsigned int"] = CPPYY__long__ +_operator_mappings["unsigned long"] = CPPYY__long__ +_operator_mappings["long long"] = CPPYY__long__ +_operator_mappings["unsigned long long"] = CPPYY__long__ _operator_mappings["float"] = "__float__" -_operator_mappings["bool"] = "__nonzero__" # __bool__ in p3 +_operator_mappings["bool"] = CPPYY__bool__ # the following are not python, but useful to expose _operator_mappings["->"] = "__follow__" diff --git a/pypy/module/_cppyy/include/capi.h b/pypy/module/_cppyy/include/capi.h --- a/pypy/module/_cppyy/include/capi.h +++ b/pypy/module/_cppyy/include/capi.h @@ -76,9 +76,7 @@ cppyy_object_t cppyy_call_o(cppyy_method_t method, cppyy_object_t self, int nargs, void* args, cppyy_type_t result_type); RPY_EXTERN - cppyy_funcaddr_t cppyy_function_address_from_index(cppyy_scope_t scope, cppyy_index_t idx); - RPY_EXTERN - cppyy_funcaddr_t cppyy_function_address_from_method(cppyy_method_t method); + cppyy_funcaddr_t cppyy_function_address(cppyy_method_t method); /* handling of function argument buffer ----------------------------------- */ RPY_EXTERN @@ -132,23 +130,28 @@ cppyy_index_t* cppyy_method_indices_from_name(cppyy_scope_t scope, const char* name); RPY_EXTERN - char* cppyy_method_name(cppyy_scope_t scope, cppyy_index_t idx); + cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx); + RPY_EXTERN - char* cppyy_method_mangled_name(cppyy_scope_t scope, cppyy_index_t idx); + char* cppyy_method_name(cppyy_method_t); RPY_EXTERN - char* cppyy_method_result_type(cppyy_scope_t scope, cppyy_index_t idx); + char* cppyy_method_full_name(cppyy_method_t); RPY_EXTERN - int cppyy_method_num_args(cppyy_scope_t scope, cppyy_index_t idx); + char* cppyy_method_mangled_name(cppyy_method_t); RPY_EXTERN - int cppyy_method_req_args(cppyy_scope_t scope, cppyy_index_t idx); + char* cppyy_method_result_type(cppyy_method_t); RPY_EXTERN - char* cppyy_method_arg_type(cppyy_scope_t scope, cppyy_index_t idx, int arg_index); + int cppyy_method_num_args(cppyy_method_t); RPY_EXTERN - char* cppyy_method_arg_default(cppyy_scope_t scope, cppyy_index_t idx, int arg_index); + int cppyy_method_req_args(cppyy_method_t); RPY_EXTERN - char* cppyy_method_signature(cppyy_scope_t scope, cppyy_index_t idx, int show_formalargs); + char* cppyy_method_arg_type(cppyy_method_t, int arg_index); RPY_EXTERN - char* cppyy_method_prototype(cppyy_scope_t scope, cppyy_index_t idx, int show_formalargs); + char* cppyy_method_arg_default(cppyy_method_t, int arg_index); + RPY_EXTERN + char* cppyy_method_signature(cppyy_method_t, int show_formalargs); + RPY_EXTERN + char* cppyy_method_prototype(cppyy_scope_t scope, cppyy_method_t idx, int show_formalargs); RPY_EXTERN int cppyy_is_const_method(cppyy_method_t); @@ -157,25 +160,21 @@ RPY_EXTERN int cppyy_method_is_template(cppyy_scope_t scope, cppyy_index_t idx); RPY_EXTERN - int cppyy_method_num_template_args(cppyy_scope_t scope, cppyy_index_t idx); - RPY_EXTERN - char* cppyy_method_template_arg_name(cppyy_scope_t scope, cppyy_index_t idx, cppyy_index_t iarg); + cppyy_method_t cppyy_get_method_template(cppyy_scope_t scope, const char* name, const char* proto); RPY_EXTERN - cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx); - RPY_EXTERN cppyy_index_t cppyy_get_global_operator( cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op); /* method properties ------------------------------------------------------ */ RPY_EXTERN - int cppyy_is_publicmethod(cppyy_type_t type, cppyy_index_t idx); + int cppyy_is_publicmethod(cppyy_method_t); RPY_EXTERN - int cppyy_is_constructor(cppyy_type_t type, cppyy_index_t idx); + int cppyy_is_constructor(cppyy_method_t); RPY_EXTERN - int cppyy_is_destructor(cppyy_type_t type, cppyy_index_t idx); + int cppyy_is_destructor(cppyy_method_t); RPY_EXTERN - int cppyy_is_staticmethod(cppyy_type_t type, cppyy_index_t idx); + int cppyy_is_staticmethod(cppyy_method_t); /* data member reflection information ------------------------------------- */ RPY_EXTERN diff --git a/pypy/module/_cppyy/interp_cppyy.py b/pypy/module/_cppyy/interp_cppyy.py --- a/pypy/module/_cppyy/interp_cppyy.py +++ b/pypy/module/_cppyy/interp_cppyy.py @@ -14,13 +14,21 @@ from pypy.module._cffi_backend import ctypefunc from pypy.module._cppyy import converter, executor, ffitypes, helper +CLASS_FLAGS_IS_PINNED = 0x0001 INSTANCE_FLAGS_PYTHON_OWNS = 0x0001 INSTANCE_FLAGS_IS_REF = 0x0002 -INSTANCE_FLAGS_IS_R_VALUE = 0x0004 +INSTANCE_FLAGS_IS_RVALUE = 0x0004 OVERLOAD_FLAGS_USE_FFI = 0x0001 +FUNCTION_IS_GLOBAL = 0x0001 +FUNCTION_IS_STATIC = 0x0001 +FUNCTION_IS_METHOD = 0x0002 +FUNCTION_IS_CONSTRUCTOR = 0x0004 +FUNCTION_IS_TEMPLATE = 0x0008 +FUNCTION_IS_SETITEM = 0x0010 + class FastCallNotPossible(Exception): pass @@ -100,9 +108,9 @@ state.cppscope_cache[final_scoped_name] = cppscope if not isns: - # build methods/data; TODO: also defer this for classes (a functional __dir__ + # build overloads/data; TODO: also defer this for classes (a functional __dir__ # and instrospection for help() is enough and allows more lazy loading) - cppscope._build_methods() + cppscope._build_overloads() cppscope._find_datamembers() return cppscope @@ -131,7 +139,7 @@ cppclass = space.interp_w(W_CPPClassDecl, w_cppclass) # add back-end specific method pythonizations (doing this on the wrapped # class allows simple aliasing of methods) - capi.pythonize(space, cppclass.name, w_pycppclass) + capi.pythonize(space, w_pycppclass, cppclass.name) state = space.fromcache(State) state.cppclass_registry[rffi.cast(rffi.LONG, cppclass.handle)] = w_pycppclass @@ -150,17 +158,18 @@ #----- -# Classes involved with methods and functions: +# Classes involved with methods and functions come at two levels: +# - overloads: user-facing collections of overloaded functions +# - wrappers: internal holders of the individual C++ methods # -# CPPMethod: base class wrapping a single function or method -# CPPConstructor: specialization for allocating a new object -# CPPFunction: specialization for free and static functions +# W_CPPOverload: instance methods (base class) +# W_CPPConstructorOverload: constructors +# W_CPPStaticOverload: free and static functions +# W_CPPTemplateOverload: templated methods +# W_CPPTemplateStaticOveload: templated free and static functions +# +# CPPMethod: a single function or method (base class) # CPPSetItem: specialization for Python's __setitem__ -# CPPTemplatedCall: trampoline to instantiate and bind templated functions -# W_CPPOverload, W_CPPConstructorOverload, W_CPPTemplateOverload: -# user-facing, app-level, collection of overloads, with specializations -# for constructors and templates -# W_CPPBoundMethod: instantiated template method # # All methods/functions derive from CPPMethod and are collected as overload # candidates in user-facing overload classes. Templated methods are a two-step @@ -173,15 +182,15 @@ also takes care of offset casting and recycling of known objects through the memory_regulator.""" - _attrs_ = ['space', 'scope', 'index', 'cppmethod', 'arg_defs', 'args_required', + _attrs_ = ['space', 'scope', 'cppmethod', 'arg_defs', 'args_required', 'converters', 'executor', '_funcaddr', 'cif_descr', 'uses_local'] - _immutable_ = True + _immutable_fields_ = ['scope', 'cppmethod', 'arg_defs', 'args_required', + 'converters', 'executor', 'uses_local'] - def __init__(self, space, declaring_scope, method_index, arg_defs, args_required): + def __init__(self, space, declaring_scope, cppmethod, arg_defs, args_required): self.space = space self.scope = declaring_scope - self.index = method_index - self.cppmethod = capi.c_get_method(self.space, self.scope, method_index) + self.cppmethod = cppmethod self.arg_defs = arg_defs self.args_required = args_required @@ -193,12 +202,6 @@ self._funcaddr = lltype.nullptr(capi.C_FUNC_PTR.TO) self.uses_local = False - @staticmethod - def unpack_cppthis(space, w_cppinstance, declaring_scope): - cppinstance = space.interp_w(W_CPPInstance, w_cppinstance) - cppinstance._nullcheck() - return cppinstance.get_cppthis(declaring_scope) - def _address_from_local_buffer(self, call_local, idx): if not call_local: return call_local @@ -266,11 +269,12 @@ def do_fast_call(self, cppthis, args_w, call_local): if self.cif_descr == lltype.nullptr(jit_libffi.CIF_DESCRIPTION): raise FastCallNotPossible + jit.promote(self) cif_descr = self.cif_descr buffer = lltype.malloc(rffi.CCHARP.TO, cif_descr.exchange_size, flavor='raw') try: # this pointer - data = capi.exchange_address(buffer, cif_descr, 0) + data = rffi.ptradd(buffer, cif_descr.exchange_args[0]) x = rffi.cast(rffi.LONGP, data) # LONGP needed for test_zjit.py x[0] = rffi.cast(rffi.LONG, cppthis) @@ -279,11 +283,11 @@ for i in range(len(args_w)): conv = self.converters[i] w_arg = args_w[i] - data = capi.exchange_address(buffer, cif_descr, i+1) + data = rffi.ptradd(buffer, cif_descr.exchange_args[i+1]) conv.convert_argument_libffi(self.space, w_arg, data, call_local) for j in range(i+1, len(self.arg_defs)): conv = self.converters[j] - data = capi.exchange_address(buffer, cif_descr, j+1) + data = rffi.ptradd(buffer, cif_descr.exchange_args[j+1]) conv.default_argument_libffi(self.space, data) assert self._funcaddr @@ -295,7 +299,7 @@ return w_res # from ctypefunc; have my own version for annotater purposes and to disable - # memory tracking (method live time is longer than the tests) + # memory tracking (method life time is longer than the tests) @jit.dont_look_inside def _rawallocate(self, builder): builder.space = self.space @@ -308,7 +312,7 @@ # allocate the buffer if we_are_translated(): rawmem = lltype.malloc(rffi.CCHARP.TO, builder.nb_bytes, - flavor='raw', track_allocation=False) + flavor='raw') rawmem = rffi.cast(jit_libffi.CIF_DESCRIPTION_P, rawmem) else: # gross overestimation of the length below, but too bad @@ -340,7 +344,7 @@ self.converters = [converter.get_converter(self.space, arg_type, arg_dflt) for arg_type, arg_dflt in self.arg_defs] self.executor = executor.get_executor( - self.space, capi.c_method_result_type(self.space, self.scope, self.index)) + self.space, capi.c_method_result_type(self.space, self.cppmethod)) for conv in self.converters: if conv.uses_local: @@ -350,8 +354,8 @@ # Each CPPMethod corresponds one-to-one to a C++ equivalent and cppthis # has been offset to the matching class. Hence, the libffi pointer is # uniquely defined and needs to be setup only once. - funcaddr = capi.c_function_address_from_index(self.space, self.scope, self.index) - if funcaddr and cppthis: # methods only for now + funcaddr = capi.c_function_address(self.space, self.cppmethod) + if funcaddr and cppthis: # TODO: methods only for now state = self.space.fromcache(ffitypes.State) # argument type specification (incl. cppthis) @@ -418,10 +422,10 @@ capi.c_deallocate_function_args(self.space, args) def signature(self, show_formalargs=True): - return capi.c_method_signature(self.space, self.scope, self.index, show_formalargs) + return capi.c_method_signature(self.space, self.cppmethod, show_formalargs) def prototype(self, show_formalargs=True): - return capi.c_method_prototype(self.space, self.scope, self.index, show_formalargs) + return capi.c_method_prototype(self.space, self.scope, self.cppmethod, show_formalargs) def priority(self): total_arg_priority = 0 @@ -431,8 +435,11 @@ @rgc.must_be_light_finalizer def __del__(self): - if self.cif_descr: - lltype.free(self.cif_descr, flavor='raw') + try: + if self.cif_descr: + lltype.free(self.cif_descr, flavor='raw') + except Exception: # TODO: happens for templates, why? + pass def __repr__(self): return "CPPMethod: %s" % self.prototype() @@ -441,80 +448,12 @@ assert 0, "you should never have a pre-built instance of this!" -class CPPFunction(CPPMethod): - """Global (namespaced) / static function dispatcher.""" - - _immutable_ = True - - @staticmethod - def unpack_cppthis(space, w_cppinstance, declaring_scope): - return capi.C_NULL_OBJECT - - def __repr__(self): - return "CPPFunction: %s" % self.prototype() - - -class CPPTemplatedCall(CPPMethod): - """Method dispatcher that first resolves the template instance.""" - - _attrs_ = ['space', 'templ_args'] - _immutable_ = True - - def __init__(self, space, templ_args, declaring_scope, method_index, arg_defs, args_required): - self.space = space - self.templ_args = templ_args - # TODO: might have to specialize for CPPTemplatedCall on CPPMethod/CPPFunction here - CPPMethod.__init__(self, space, declaring_scope, method_index, arg_defs, args_required) - - def call(self, cppthis, args_w, useffi): - assert lltype.typeOf(cppthis) == capi.C_OBJECT - for i in range(len(args_w)): - try: - s = self.space.text_w(args_w[i]) - except OperationError: - s = self.space.text_w(self.space.getattr(args_w[i], self.space.newtext('__name__'))) - s = capi.c_resolve_name(self.space, s) - if s != self.templ_args[i]: - raise oefmt(self.space.w_TypeError, - "non-matching template (got %s where %s expected)", - s, self.templ_args[i]) - return W_CPPBoundMethod(cppthis, self, useffi) - - def bound_call(self, cppthis, args_w, useffi): - return CPPMethod.call(self, cppthis, args_w, useffi) - - def __repr__(self): - return "CPPTemplatedCall: %s" % self.prototype() - - -class CPPConstructor(CPPMethod): - """Method dispatcher that constructs new objects. This method can not have - a fast path, as the allocation of the object is currently left to the - reflection layer only, since the C++ class may have an overloaded operator - new, disallowing malloc here.""" - - _immutable_ = True - - @staticmethod - def unpack_cppthis(space, w_cppinstance, declaring_scope): - return rffi.cast(capi.C_OBJECT, declaring_scope.handle) - - def call(self, cppthis, args_w, useffi): - # Note: this does not return a wrapped instance, just a pointer to the - # new instance; the overload must still wrap it before returning. Also, - # cppthis is declaring_scope.handle (as per unpack_cppthis(), above). - return CPPMethod.call(self, cppthis, args_w, useffi) - - def __repr__(self): - return "CPPConstructor: %s" % self.prototype() - - class CPPSetItem(CPPMethod): """Method dispatcher specific to Python's __setitem__ mapped onto C++'s operator[](int). The former function takes an extra argument to assign to the return type of the latter.""" - _immutable_ = True + _attrs_ = [] def call(self, cppthis, args_w, useffi): end = len(args_w)-1 @@ -528,46 +467,44 @@ class W_CPPOverload(W_Root): - """Dispatcher that is actually available at the app-level: it is a - collection of (possibly) overloaded methods or functions. It calls these - in order and deals with error handling and reporting.""" + """App-level dispatcher: controls a collection of (potentially) overloaded methods + or functions. Calls these in order and deals with error handling and reporting.""" - _attrs_ = ['space', 'scope', 'functions', 'flags'] + _attrs_ = ['space', 'scope', 'functions', 'flags', 'w_this'] _immutable_fields_ = ['scope', 'functions[*]'] - def __init__(self, space, declaring_scope, functions): - self.space = space - self.scope = declaring_scope - assert len(functions) + def __init__(self, space, declaring_scope, functions, flags = OVERLOAD_FLAGS_USE_FFI): + self.space = space + self.scope = declaring_scope from rpython.rlib import debug self.functions = debug.make_sure_not_resized(functions) - self.flags = 0 - self.flags |= OVERLOAD_FLAGS_USE_FFI + self.flags = flags + self.w_this = self.space.w_None - # allow user to determine ffi use rules per overload - def fget_useffi(self, space): - return space.newbool(bool(self.flags & OVERLOAD_FLAGS_USE_FFI)) + @unwrap_spec(args_w='args_w') + def descr_get(self, w_cppinstance, args_w): + if self.space.is_w(w_cppinstance, self.space.w_None): + return self # unbound, so no new instance needed + cppol = W_CPPOverload(self.space, self.scope, self.functions, self.flags) + cppol.w_this = w_cppinstance + return cppol # bound - @unwrap_spec(value=bool) - def fset_useffi(self, space, value): - if space.is_true(value): - self.flags |= OVERLOAD_FLAGS_USE_FFI + @unwrap_spec(args_w='args_w') + def call(self, args_w): + if self.space.is_w(self.w_this, self.space.w_None) and len(args_w): + w_this = args_w[0] + args_w = args_w[1:] else: - self.flags &= ~OVERLOAD_FLAGS_USE_FFI - - @jit.elidable_promote() - def is_static(self): - if isinstance(self.functions[0], CPPFunction): - return self.space.w_True - return self.space.w_False + w_this = self.w_this + cppinstance = self.space.interp_w(W_CPPInstance, w_this) + cppinstance._nullcheck() + if not capi.c_is_subtype(self.space, cppinstance.clsdecl, self.scope): + raise oefmt(self.space.w_TypeError, + "cannot pass %T instance as %s", w_this, self.scope.name) + return self.call_impl(cppinstance.get_cppthis(self.scope), args_w) @jit.unroll_safe - @unwrap_spec(args_w='args_w') - def call(self, w_cppinstance, args_w): - # instance handling is specific to the function type only, so take it out - # of the loop over function overloads - cppthis = self.functions[0].unpack_cppthis( - self.space, w_cppinstance, self.functions[0].scope) + def call_impl(self, cppthis, args_w): assert lltype.typeOf(cppthis) == capi.C_OBJECT # The following code tries out each of the functions in order. If @@ -625,38 +562,96 @@ sig += '\n'+self.functions[i].prototype() return self.space.newtext(sig) + # allow user to determine ffi use rules per overload + def fget_useffi(self, space): + return space.newbool(bool(self.flags & OVERLOAD_FLAGS_USE_FFI)) + + @unwrap_spec(value=bool) + def fset_useffi(self, space, value): + if space.is_true(value): + self.flags |= OVERLOAD_FLAGS_USE_FFI + else: + self.flags &= ~OVERLOAD_FLAGS_USE_FFI + + def fget_doc(self, space): + return self.prototype() + def __repr__(self): return "W_CPPOverload(%s)" % [f.prototype() for f in self.functions] W_CPPOverload.typedef = TypeDef( 'CPPOverload', - is_static = interp2app(W_CPPOverload.is_static), - call = interp2app(W_CPPOverload.call), + __get__ = interp2app(W_CPPOverload.descr_get), + __call__ = interp2app(W_CPPOverload.call), __useffi__ = GetSetProperty(W_CPPOverload.fget_useffi, W_CPPOverload.fset_useffi), - prototype = interp2app(W_CPPOverload.prototype), + __doc__ = GetSetProperty(W_CPPOverload.fget_doc) ) +# overload collection of static (class and free) functions; these differ +# from methods only in the handling of 'cppthis' +class W_CPPStaticOverload(W_CPPOverload): + _attrs_ = [] + + @unwrap_spec(args_w='args_w') + def descr_get(self, w_cppinstance, args_w): + if isinstance(w_cppinstance, W_CPPInstance): + # two possibilities: this is a static function called on an + # instance and w_this must not be set, or a free function rebound + # onto a class and w_this should be set + cppinstance = self.space.interp_w(W_CPPInstance, w_cppinstance) + if cppinstance.clsdecl.handle != self.scope.handle: + cppol = W_CPPStaticOverload(self.space, self.scope, self.functions, self.flags) + cppol.w_this = w_cppinstance + return cppol # bound + return self # unbound + + @unwrap_spec(args_w='args_w') + def call(self, args_w): + if not self.space.is_w(self.w_this, self.space.w_None): + # free function used as bound method, put self back into args_w + cppinstance = self.space.interp_w(W_CPPInstance, self.w_this) + cppinstance._nullcheck() + args_w = [self.w_this] + args_w + return self.call_impl(capi.C_NULL_OBJECT, args_w) + + def __repr__(self): + return "W_CPPStaticOverload(%s)" % [f.prototype() for f in self.functions] + +W_CPPStaticOverload.typedef = TypeDef( + 'CPPStaticOverload', + __get__ = interp2app(W_CPPStaticOverload.descr_get), + __call__ = interp2app(W_CPPStaticOverload.call), + __useffi__ = GetSetProperty(W_CPPStaticOverload.fget_useffi, W_CPPStaticOverload.fset_useffi), + __doc__ = GetSetProperty(W_CPPStaticOverload.fget_doc) +) + + class W_CPPConstructorOverload(W_CPPOverload): - @jit.elidable_promote() - def is_static(self): - return self.space.w_False + _attrs_ = [] - @jit.elidable_promote() - def unpack_cppthis(self, w_cppinstance): - return rffi.cast(capi.C_OBJECT, self.scope.handle) + @unwrap_spec(args_w='args_w') + def descr_get(self, w_cppinstance, args_w): + if self.space.is_w(w_cppinstance, self.space.w_None): + return self # unbound (TODO: probably useless) + cppol = W_CPPConstructorOverload(self.space, self.scope, self.functions, self.flags) + cppol.w_this = w_cppinstance + return cppol # bound - @jit.unroll_safe @unwrap_spec(args_w='args_w') - def call(self, w_cppinstance, args_w): + def call(self, args_w): # TODO: factor out the following: if capi.c_is_abstract(self.space, self.scope.handle): raise oefmt(self.space.w_TypeError, "cannot instantiate abstract class '%s'", self.scope.name) - w_result = W_CPPOverload.call(self, w_cppinstance, args_w) + if self.space.is_w(self.w_this, self.space.w_None) and len(args_w): + cppinstance = self.space.interp_w(W_CPPInstance, args_w[0]) + args_w = args_w[1:] + else: + cppinstance = self.space.interp_w(W_CPPInstance, self.w_this) + w_result = self.call_impl(rffi.cast(capi.C_OBJECT, self.scope.handle), args_w) newthis = rffi.cast(capi.C_OBJECT, self.space.uint_w(w_result)) - cppinstance = self.space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=True) if cppinstance is not None: cppinstance._rawobject = newthis memory_regulator.register(cppinstance) @@ -666,43 +661,224 @@ W_CPPConstructorOverload.typedef = TypeDef( 'CPPConstructorOverload', - is_static = interp2app(W_CPPConstructorOverload.is_static), - call = interp2app(W_CPPConstructorOverload.call), - prototype = interp2app(W_CPPConstructorOverload.prototype), + __get__ = interp2app(W_CPPConstructorOverload.descr_get), + __call__ = interp2app(W_CPPConstructorOverload.call), + __doc__ = GetSetProperty(W_CPPConstructorOverload.fget_doc) ) -class W_CPPTemplateOverload(W_CPPOverload): +class TemplateOverloadMixin(object): + """Mixin to instantiate templated methods/functions.""" + + _mixin_ = True + + def construct_template_args(self, w_tpArgs, args_w = None): + space = self.space + tmpl_args = '' + for i in range(space.len_w(w_tpArgs)): + w_tp = space.getitem(w_tpArgs, space.newint(i)) + if space.isinstance_w(w_tp, space.w_text): + s = space.text_w(w_tp) # string describing type + elif space.isinstance_w(w_tp, space.w_type): + try: + # cppyy bound types + s = space.text_w(space.getattr(w_tp, space.newtext('__cppname__'))) + if args_w: + # try to specialize the type match for the given object + cppinstance = self.space.interp_w(W_CPPInstance, args_w[i]) + if cppinstance.flags & INSTANCE_FLAGS_IS_RVALUE: + sugar = "&&" + elif cppinstance.flags & INSTANCE_FLAGS_IS_REF: + sugar = "*" + else: + sugar = "&" + s += sugar + except OperationError: + # generic python types + s = space.text_w(space.getattr(w_tp, space.newtext('__name__'))) + else: + # builtin types etc. + s = space.text_w(space.str(w_tp)) + # map python types -> C++ types + if s == 'str': s = 'std::string' + if i != 0: tmpl_args += ', ' + tmpl_args += s + return tmpl_args + + def find_method_template(self, name, proto = ''): + # find/instantiate new callable function + space = self.space + cppmeth = capi.c_get_method_template(space, self.scope, name, proto) + if not cppmeth: + raise oefmt(self.space.w_AttributeError, + "scope '%s' has no function %s", self.scope.name, name) + + funcs = [] + ftype = self.scope._make_cppfunction(name, cppmeth, funcs) + if ftype & FUNCTION_IS_STATIC: + cppol = W_CPPStaticOverload(space, self.scope, funcs[:], self.flags) + else: + cppol = W_CPPOverload(space, self.scope, funcs[:], self.flags) + return cppol + + def instantiate_and_call(self, name, args_w): + # try to match with run-time instantiations + for cppol in self.master.overloads.values(): + try: + return cppol.descr_get(self.w_this, []).call(args_w) + except Exception: + pass # completely ignore for now; have to see whether errors become confusing + + # if all failed, then try to deduce from argument types + w_types = self.space.newtuple([self.space.type(obj_w) for obj_w in args_w]) + proto = self.construct_template_args(w_types, args_w) + method = self.find_method_template(name, proto) + + # only cache result if the name retains the full template + fullname = capi.c_method_full_name(self.space, method.functions[0].cppmethod) + if 0 <= fullname.rfind('>'): + try: + existing = self.master.overloads[fullname] + allf = existing.functions + method.functions + if isinstance(existing, W_CPPStaticOverload): + cppol = W_CPPStaticOverload(self.space, self.scope, allf, self.flags) + else: + cppol = W_CPPOverload(self.space, self.scope, allf, self.flags) + self.master.overloads[fullname] = cppol + except KeyError: + self.master.overloads[fullname] = method + + return method.descr_get(self.w_this, []).call(args_w) + + def getitem_impl(self, name, args_w): + space = self.space + + if space.isinstance_w(args_w[0], space.w_tuple): + w_args = args_w[0] + else: + w_args = space.newtuple(args_w) + + tmpl_args = self.construct_template_args(w_args) + fullname = name+'<'+tmpl_args+'>' + try: + method = self.master.overloads[fullname] _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit