Author: Wim Lavrijsen <wlavrij...@lbl.gov> Branch: cppyy-packaging Changeset: r92725:1ab7076e5b66 Date: 2017-10-11 15:39 -0700 http://bitbucket.org/pypy/pypy/changeset/1ab7076e5b66/
Log: basic std::move implementation 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 @@ -17,6 +17,7 @@ 'addressof' : 'interp_cppyy.addressof', '_bind_object' : 'interp_cppyy._bind_object', 'bind_object' : 'interp_cppyy.bind_object', + 'move' : 'interp_cppyy.move', } appleveldefs = { 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 @@ -4,7 +4,7 @@ from rpython.rtyper.lltypesystem import rffi, lltype from rpython.rlib.rarithmetic import r_singlefloat, r_longfloat -from rpython.rlib import rfloat +from rpython.rlib import rfloat, rawrefcount from pypy.module._rawffi.interp_rawffi import letter2tp from pypy.module._rawffi.array import W_Array, W_ArrayInstance @@ -495,6 +495,10 @@ def _unwrap_object(self, space, w_obj): from pypy.module._cppyy.interp_cppyy import W_CPPClass if isinstance(w_obj, W_CPPClass): + from pypy.module._cppyy.interp_cppyy import INSTANCE_FLAGS_IS_R_VALUE + if w_obj.flags & INSTANCE_FLAGS_IS_R_VALUE: + # reject moves as all are explicit + raise ValueError("lvalue expected") if capi.c_is_subtype(space, w_obj.clsdecl, self.clsdecl): rawobject = w_obj.get_rawobject() offset = capi.c_base_offset(space, w_obj.clsdecl, self.clsdecl, rawobject, 1) @@ -518,6 +522,17 @@ x = rffi.cast(rffi.VOIDPP, address) x[0] = rffi.cast(rffi.VOIDP, self._unwrap_object(space, w_obj)) +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_CPPClass, INSTANCE_FLAGS_IS_R_VALUE + if isinstance(w_obj, W_CPPClass): + 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) + raise oefmt(space.w_ValueError, "object is not an rvalue") + + class InstanceConverter(InstanceRefConverter): def convert_argument_libffi(self, space, w_obj, address, call_local): @@ -719,6 +734,8 @@ return InstancePtrConverter(space, clsdecl) elif compound == "&": return InstanceRefConverter(space, clsdecl) + elif compound == "&&": + return InstanceMoveConverter(space, clsdecl) elif compound == "**": return InstancePtrPtrConverter(space, clsdecl) elif compound == "": 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 @@ -15,6 +15,10 @@ from pypy.module._cppyy import converter, executor, ffitypes, helper +INSTANCE_FLAGS_PYTHON_OWNS = 0x0001 +INSTANCE_FLAGS_IS_REF = 0x0002 +INSTANCE_FLAGS_IS_R_VALUE = 0x0004 + class FastCallNotPossible(Exception): pass @@ -1001,9 +1005,9 @@ class W_CPPClass(W_Root): - _attrs_ = ['space', 'clsdecl', '_rawobject', 'isref', 'python_owns', + _attrs_ = ['space', 'clsdecl', '_rawobject', 'flags', 'finalizer_registered'] - _immutable_fields_ = ['clsdecl', 'isref'] + _immutable_fields_ = ['clsdecl'] finalizer_registered = False @@ -1014,35 +1018,42 @@ assert not isref or rawobject self._rawobject = rawobject assert not isref or not python_owns - self.isref = isref - self.python_owns = python_owns - self._opt_register_finalizer() + self.flags = 0 + if isref: + self.flags |= INSTANCE_FLAGS_IS_REF + if python_owns: + self.flags |= INSTANCE_FLAGS_PYTHON_OWNS + self._opt_register_finalizer() def _opt_register_finalizer(self): - if self.python_owns and not self.finalizer_registered \ - and not hasattr(self.space, "fake"): + if not self.finalizer_registered and not hasattr(self.space, "fake"): + assert self.flags & INSTANCE_FLAGS_PYTHON_OWNS self.register_finalizer(self.space) self.finalizer_registered = True def _nullcheck(self): - if not self._rawobject or (self.isref and not self.get_rawobject()): + if not self._rawobject or \ + ((self.flags & INSTANCE_FLAGS_IS_REF) and not self.get_rawobject()): raise oefmt(self.space.w_ReferenceError, "trying to access a NULL pointer") # allow user to determine ownership rules on a per object level def fget_python_owns(self, space): - return space.newbool(self.python_owns) + return space.newbool(self.flags & INSTANCE_FLAGS_PYTHON_OWNS) @unwrap_spec(value=bool) def fset_python_owns(self, space, value): - self.python_owns = space.is_true(value) - self._opt_register_finalizer() + if space.is_true(value): + self.flags |= INSTANCE_FLAGS_PYTHON_OWNS + self._opt_register_finalizer() + else: + self.flags &= ~INSTANCE_FLAGS_PYTHON_OWNS def get_cppthis(self, calling_scope): return self.clsdecl.get_cppthis(self, calling_scope) def get_rawobject(self): - if not self.isref: + if not (self.flags & INSTANCE_FLAGS_IS_REF): return self._rawobject else: ptrptr = rffi.cast(rffi.VOIDPP, self._rawobject) @@ -1104,7 +1115,8 @@ return self.space.not_(self.instance__eq__(w_other)) def instance__nonzero__(self): - if not self._rawobject or (self.isref and not self.get_rawobject()): + if not self._rawobject or \ + ((self.flags & INSTANCE_FLAGS_IS_REF) and not self.get_rawobject()): return self.space.w_False return self.space.w_True @@ -1130,13 +1142,13 @@ (self.clsdecl.name, rffi.cast(rffi.ULONG, self.get_rawobject()))) def destruct(self): - if self._rawobject and not self.isref: + if self._rawobject and not (self.flags & INSTANCE_FLAGS_IS_REF): memory_regulator.unregister(self) capi.c_destruct(self.space, self.clsdecl, self._rawobject) self._rawobject = capi.C_NULL_OBJECT def _finalize_(self): - if self.python_owns: + if self.flags & INSTANCE_FLAGS_PYTHON_OWNS: self.destruct() W_CPPClass.typedef = TypeDef( @@ -1272,3 +1284,9 @@ "no such class: %s", space.text_w(w_pycppclass)) return _bind_object(space, w_obj, w_clsdecl, owns, cast) +def move(space, w_obj): + """Casts the given instance into an C++-style rvalue.""" + obj = space.interp_w(W_CPPClass, w_obj, can_be_None=True) + if obj: + obj.flags |= INSTANCE_FLAGS_IS_R_VALUE + return w_obj diff --git a/pypy/module/_cppyy/pythonify.py b/pypy/module/_cppyy/pythonify.py --- a/pypy/module/_cppyy/pythonify.py +++ b/pypy/module/_cppyy/pythonify.py @@ -434,6 +434,9 @@ # pre-create std to allow direct importing gbl.std = make_cppnamespace(gbl, 'std', _cppyy._scope_byname('std')) + # add move cast + gbl.std.move = _cppyy.move + # install a type for enums to refer to # TODO: this is correct for C++98, not for C++11 and in general there will # be the same issue for all typedef'd builtin types diff --git a/pypy/module/_cppyy/test/Makefile b/pypy/module/_cppyy/test/Makefile --- a/pypy/module/_cppyy/test/Makefile +++ b/pypy/module/_cppyy/test/Makefile @@ -1,5 +1,6 @@ dicts = advancedcppDict.so \ advancedcpp2Dict.so \ + cpp11featuresDict.so \ crossingDict.so \ datatypesDict.so \ example01Dict.so \ diff --git a/pypy/module/_cppyy/test/cpp11features.cxx b/pypy/module/_cppyy/test/cpp11features.cxx new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/test/cpp11features.cxx @@ -0,0 +1,18 @@ +#if __cplusplus >= 201103L + +#include "cpp11features.h" + + +// for std::shared_ptr<> testing +int TestSharedPtr::s_counter = 0; + +std::shared_ptr<TestSharedPtr> create_shared_ptr_instance() { + return std::shared_ptr<TestSharedPtr>(new TestSharedPtr); +} + + +// for move ctors etc. +int TestMoving1::s_move_counter = 0; +int TestMoving2::s_move_counter = 0; + +#endif // c++11 and later diff --git a/pypy/module/_cppyy/test/cpp11features.h b/pypy/module/_cppyy/test/cpp11features.h new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/test/cpp11features.h @@ -0,0 +1,45 @@ +#if __cplusplus >= 201103L + +#include <memory> + + +//=========================================================================== +class TestSharedPtr { // for std::shared_ptr<> testing +public: + static int s_counter; + +public: + TestSharedPtr() { ++s_counter; } + TestSharedPtr(const TestSharedPtr&) { ++s_counter; } + ~TestSharedPtr() { --s_counter; } +}; + +std::shared_ptr<TestSharedPtr> create_shared_ptr_instance(); + + +//=========================================================================== +class TestMoving1 { // for move ctors etc. +public: + static int s_move_counter; + +public: + TestMoving1() {} + TestMoving1(TestMoving1&&) { ++s_move_counter; } + TestMoving1(const TestMoving1&) {} + TestMoving1& operator=(TestMoving1&&) { ++s_move_counter; return *this; } + TestMoving1& operator=(TestMoving1&) { return *this; } +}; + +class TestMoving2 { // note opposite method order from TestMoving1 +public: + static int s_move_counter; + +public: + TestMoving2() {} + TestMoving2(const TestMoving2&) {} + TestMoving2(TestMoving2&& other) { ++s_move_counter; } + TestMoving2& operator=(TestMoving2&) { return *this; } + TestMoving2& operator=(TestMoving2&&) { ++s_move_counter; return *this; } +}; + +#endif // c++11 and later diff --git a/pypy/module/_cppyy/test/cpp11features.xml b/pypy/module/_cppyy/test/cpp11features.xml new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/test/cpp11features.xml @@ -0,0 +1,6 @@ +<lcgdict> + + <class name="TestSharedPtr" /> + <class pattern="TestMoving*" /> + +</lcgdict> diff --git a/pypy/module/_cppyy/test/test_cpp11features.py b/pypy/module/_cppyy/test/test_cpp11features.py new file mode 100644 --- /dev/null +++ b/pypy/module/_cppyy/test/test_cpp11features.py @@ -0,0 +1,94 @@ +import py, os, sys +from .support import setup_make + + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp11featuresDict.so")) + +def setup_module(mod): + setup_make("cpp11featuresDict.so") + +class AppTestCPP11FEATURES: + spaceconfig = dict(usemodules=['_cppyy', '_rawffi', 'itertools']) + + def setup_class(cls): + cls.w_test_dct = cls.space.newtext(test_dct) + cls.w_example01 = cls.space.appexec([], """(): + import ctypes + return ctypes.CDLL(%r, ctypes.RTLD_GLOBAL)""" % (test_dct, )) + + def test01_shared_ptr(self): + """Usage and access of std::shared_ptr<>""" + + import _cppyy + TestSharedPtr = _cppyy.gbl.TestSharedPtr + create_shared_ptr_instance = _cppyy.gbl.create_shared_ptr_instance + + # proper memory accounting + assert TestSharedPtr.s_counter == 0 + + ptr1 = create_shared_ptr_instance() + assert ptr1 + assert not not ptr1 + assert TestSharedPtr.s_counter == 1 + + ptr2 = create_shared_ptr_instance() + assert ptr2 + assert not not ptr2 + assert TestSharedPtr.s_counter == 2 + + del ptr2 + import gc; gc.collect() + assert TestSharedPtr.s_counter == 1 + + del ptr1 + gc.collect() + assert TestSharedPtr.s_counter == 0 + + def test02_nullptr(self): + """Allow the programmer to pass NULL in certain cases""" + + import _cppyy + + # test existence + nullptr = _cppyy.nullptr + assert not hasattr(_cppyy.gbl, 'nullptr') + + # usage is tested in datatypes.py:test15_nullptr_passing + + def test03_move(self): + """Move construction, assignment, and methods""" + + import _cppyy + + def moveit(T): + std = _cppyy.gbl.std + + # move constructor + i1 = T() + assert T.s_move_counter == 0 + + i2 = T(i1) # cctor + assert T.s_move_counter == 0 + + i3 = T(std.move(T())) # Note: in CPython can check for + # ref-count == 1, so no move() needed + assert T.s_move_counter == 1 + + i4 = T(std.move(i1)) + assert T.s_move_counter == 2 + + # move assignment + i4.__assign__(i2) + assert T.s_move_counter == 2 + + i4.__assign__(std.move(T())) # same note as above move ctor + assert T.s_move_counter == 3 + + i4.__assign__(std.move(i2)) + assert T.s_move_counter == 4 + + # order of moving and normal functions are reversed in 1, 2, for + # overload resolution testing + moveit(_cppyy.gbl.TestMoving1) + moveit(_cppyy.gbl.TestMoving2) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit