Author: Matti Picus <matti.pi...@gmail.com> Branch: pycheck-macros Changeset: r92342:568bae2b4fa4 Date: 2017-09-06 21:26 +0300 http://bitbucket.org/pypy/pypy/changeset/568bae2b4fa4/
Log: merge default into branch diff too long, truncating to 2000 out of 2013 lines diff --git a/pypy/doc/build.rst b/pypy/doc/build.rst --- a/pypy/doc/build.rst +++ b/pypy/doc/build.rst @@ -119,8 +119,15 @@ To run untranslated tests, you need the Boehm garbage collector libgc. -On Debian and Ubuntu, this is the command to install all build-time -dependencies:: +On recent Debian and Ubuntu (like 17.04), this is the command to install +all build-time dependencies:: + + apt-get install gcc make libffi-dev pkg-config zlib1g-dev libbz2-dev \ + libsqlite3-dev libncurses5-dev libexpat1-dev libssl-dev libgdbm-dev \ + tk-dev libgc-dev python-cffi \ + liblzma-dev libncursesw5-dev # these two only needed on PyPy3 + +On older Debian and Ubuntu (12.04 to 16.04):: apt-get install gcc make libffi-dev pkg-config libz-dev libbz2-dev \ libsqlite3-dev libncurses-dev libexpat1-dev libssl-dev libgdbm-dev \ diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst --- a/pypy/doc/cpython_differences.rst +++ b/pypy/doc/cpython_differences.rst @@ -535,6 +535,11 @@ or ``float`` subtypes. Currently PyPy does not support the ``__class__`` attribute assignment for any non heaptype subtype. +* In PyPy, module and class dictionaries are optimized under the assumption + that deleting attributes from them are rare. Because of this, e.g. + ``del foo.bar`` where ``foo`` is a module (or class) that contains the + function ``bar``, is significantly slower than CPython. + .. _`is ignored in PyPy`: http://bugs.python.org/issue14621 .. _`little point`: http://events.ccc.de/congress/2012/Fahrplan/events/5152.en.html .. _`#2072`: https://bitbucket.org/pypy/pypy/issue/2072/ 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 @@ -73,3 +73,7 @@ Add support for leakfinder in cpyext tests (disabled for now, due to too many failures). + +.. branch: pypy_swappedbytes + +Added ``_swappedbytes_`` support for ``ctypes.Structure`` diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -1629,12 +1629,15 @@ # return text_w(w_obj) or None return None if self.is_none(w_obj) else self.text_w(w_obj) + @specialize.argtype(1) def bytes_w(self, w_obj): """ Takes an application level :py:class:`bytes` (on PyPy2 this equals `str`) and returns a rpython byte string. """ + assert w_obj is not None return w_obj.str_w(self) + @specialize.argtype(1) def text_w(self, w_obj): """ PyPy2 takes either a :py:class:`str` and returns a rpython byte string, or it takes an :py:class:`unicode` @@ -1644,6 +1647,7 @@ On PyPy3 it takes a :py:class:`str` and it will return an utf-8 encoded rpython string. """ + assert w_obj is not None return w_obj.str_w(self) @not_rpython # tests only; should be replaced with bytes_w or text_w @@ -1692,6 +1696,7 @@ raise oefmt(self.w_ValueError, "byte must be in range(0, 256)") return chr(value) + @specialize.argtype(1) def int_w(self, w_obj, allow_conversion=True): """ Unwrap an app-level int object into an interpret-level int. @@ -1704,26 +1709,35 @@ If allow_conversion=False, w_obj needs to be an app-level int or a subclass. """ + assert w_obj is not None return w_obj.int_w(self, allow_conversion) + @specialize.argtype(1) def int(self, w_obj): + assert w_obj is not None return w_obj.int(self) + @specialize.argtype(1) def uint_w(self, w_obj): + assert w_obj is not None return w_obj.uint_w(self) + @specialize.argtype(1) def bigint_w(self, w_obj, allow_conversion=True): """ Like int_w, but return a rlib.rbigint object and call __long__ if allow_conversion is True. """ + assert w_obj is not None return w_obj.bigint_w(self, allow_conversion) + @specialize.argtype(1) def float_w(self, w_obj, allow_conversion=True): """ Like int_w, but return an interp-level float and call __float__ if allow_conversion is True. """ + assert w_obj is not None return w_obj.float_w(self, allow_conversion) def realtext_w(self, w_obj): @@ -1733,7 +1747,9 @@ raise oefmt(self.w_TypeError, "argument must be a string") return self.bytes_w(w_obj) + @specialize.argtype(1) def unicode_w(self, w_obj): + assert w_obj is not None return w_obj.unicode_w(self) def unicode0_w(self, w_obj): @@ -1758,7 +1774,9 @@ # This is here mostly just for gateway.int_unwrapping_space_method(). return bool(self.int_w(w_obj)) + @specialize.argtype(1) def ord(self, w_obj): + assert w_obj is not None return w_obj.ord(self) # This is all interface for gateway.py. diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -40,6 +40,7 @@ from rpython.rlib import rawrefcount from rpython.rlib import rthread from rpython.rlib.debug import fatalerror_notb +from rpython.rlib import rstackovf from pypy.objspace.std.typeobject import W_TypeObject, find_best_base from pypy.module.cpyext.cparser import CTypeSpace @@ -984,6 +985,11 @@ message = str(e) state.set_exception(OperationError(space.w_SystemError, space.newtext(message))) + except rstackovf.StackOverflow as e: + rstackovf.check_stack_overflow() + failed = True + state.set_exception(OperationError(space.w_RuntimeError, + space.newtext("maximum recursion depth exceeded"))) else: failed = False 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 @@ -136,7 +136,7 @@ """Base class for all cpyext tests.""" spaceconfig = dict(usemodules=['cpyext', 'thread', 'struct', 'array', 'itertools', 'time', 'binascii', - 'micronumpy', 'mmap' + 'mmap' ]) @classmethod diff --git a/pypy/module/cpyext/test/test_memoryobject.py b/pypy/module/cpyext/test/test_memoryobject.py --- a/pypy/module/cpyext/test/test_memoryobject.py +++ b/pypy/module/cpyext/test/test_memoryobject.py @@ -125,52 +125,6 @@ ten = foo.test_buffer(arr) assert ten == 10 - @pytest.mark.skipif(only_pypy, reason='pypy only test') - def test_buffer_info(self): - try: - from _numpypy import multiarray as np - except ImportError: - skip('pypy built without _numpypy') - module = self.import_module(name='buffer_test') - get_buffer_info = module.get_buffer_info - raises(ValueError, get_buffer_info, np.arange(5)[::2], ('SIMPLE',)) - arr = np.zeros((1, 10), order='F') - shape, strides = get_buffer_info(arr, ['F_CONTIGUOUS']) - assert strides[0] == 8 - arr = np.zeros((10, 1), order='C') - shape, strides = get_buffer_info(arr, ['C_CONTIGUOUS']) - assert strides[-1] == 8 - dt1 = np.dtype( - [('a', 'b'), ('b', 'i'), - ('sub0', np.dtype('b,i')), - ('sub1', np.dtype('b,i')), - ('sub2', np.dtype('b,i')), - ('sub3', np.dtype('b,i')), - ('sub4', np.dtype('b,i')), - ('sub5', np.dtype('b,i')), - ('sub6', np.dtype('b,i')), - ('sub7', np.dtype('b,i')), - ('c', 'i')], - ) - x = np.arange(dt1.itemsize, dtype='int8').view(dt1) - # pytest can catch warnings from v2.8 and up, we ship 2.5 - import warnings - warnings.filterwarnings("error") - try: - try: - y = get_buffer_info(x, ['SIMPLE']) - except UserWarning as e: - pass - else: - assert False ,"PyPy-specific UserWarning not raised" \ - " on too long format string" - finally: - warnings.resetwarnings() - # calling get_buffer_info on x creates a memory leak, - # which is detected as an error at test teardown: - # Exception TypeError: "'NoneType' object is not callable" - # in <bound method ConcreteArray.__del__ ...> ignored - def test_releasebuffer(self): module = self.import_extension('foo', [ ("create_test", "METH_NOARGS", @@ -240,3 +194,55 @@ self.debug_collect() assert module.get_cnt() == 0 assert module.get_dealloc_cnt() == 1 + +class AppTestBufferInfo(AppTestCpythonExtensionBase): + spaceconfig = AppTestCpythonExtensionBase.spaceconfig.copy() + spaceconfig['usemodules'].append('micronumpy') + + @pytest.mark.skipif(only_pypy, reason='pypy only test') + def test_buffer_info(self): + try: + from _numpypy import multiarray as np + except ImportError: + skip('pypy built without _numpypy') + module = self.import_module(name='buffer_test') + get_buffer_info = module.get_buffer_info + raises(ValueError, get_buffer_info, np.arange(5)[::2], ('SIMPLE',)) + arr = np.zeros((1, 10), order='F') + shape, strides = get_buffer_info(arr, ['F_CONTIGUOUS']) + assert strides[0] == 8 + arr = np.zeros((10, 1), order='C') + shape, strides = get_buffer_info(arr, ['C_CONTIGUOUS']) + assert strides[-1] == 8 + dt1 = np.dtype( + [('a', 'b'), ('b', 'i'), + ('sub0', np.dtype('b,i')), + ('sub1', np.dtype('b,i')), + ('sub2', np.dtype('b,i')), + ('sub3', np.dtype('b,i')), + ('sub4', np.dtype('b,i')), + ('sub5', np.dtype('b,i')), + ('sub6', np.dtype('b,i')), + ('sub7', np.dtype('b,i')), + ('c', 'i')], + ) + x = np.arange(dt1.itemsize, dtype='int8').view(dt1) + # pytest can catch warnings from v2.8 and up, we ship 2.5 + import warnings + warnings.filterwarnings("error") + try: + try: + y = get_buffer_info(x, ['SIMPLE']) + except UserWarning as e: + pass + else: + assert False ,"PyPy-specific UserWarning not raised" \ + " on too long format string" + finally: + warnings.resetwarnings() + # calling get_buffer_info on x creates a memory leak, + # which is detected as an error at test teardown: + # Exception TypeError: "'NoneType' object is not callable" + # in <bound method ConcreteArray.__del__ ...> ignored + + diff --git a/pypy/module/cpyext/test/test_ndarrayobject.py b/pypy/module/cpyext/test/test_ndarrayobject.py --- a/pypy/module/cpyext/test/test_ndarrayobject.py +++ b/pypy/module/cpyext/test/test_ndarrayobject.py @@ -26,6 +26,8 @@ NULL = lltype.nullptr(rffi.VOIDP.TO) class TestNDArrayObject(BaseApiTest): + spaceconfig = AppTestCpythonExtensionBase.spaceconfig.copy() + spaceconfig['usemodules'].append('micronumpy') def test_Check(self, space, api): a = array(space, [10, 5, 3]) diff --git a/pypy/module/itertools/test/test_itertools.py b/pypy/module/itertools/test/test_itertools.py --- a/pypy/module/itertools/test/test_itertools.py +++ b/pypy/module/itertools/test/test_itertools.py @@ -1,7 +1,7 @@ import py -class AppTestItertools: +class AppTestItertools(object): spaceconfig = dict(usemodules=['itertools']) def test_count(self): @@ -330,11 +330,11 @@ def test_chain(self): import itertools - + it = itertools.chain() raises(StopIteration, it.next) raises(StopIteration, it.next) - + it = itertools.chain([1, 2, 3]) for x in [1, 2, 3]: assert it.next() == x @@ -378,7 +378,7 @@ def test_imap_wrongargs(self): import itertools - + # Duplicate python 2.4 behaviour for invalid arguments it = itertools.imap(0, []) raises(StopIteration, it.next) @@ -401,11 +401,11 @@ for x in obj_list: assert it.next() == (x, ) raises(StopIteration, it.next) - + it = itertools.izip([1, 2, 3], [4], [5, 6]) assert it.next() == (1, 4, 5) raises(StopIteration, it.next) - + it = itertools.izip([], [], [1], []) raises(StopIteration, it.next) @@ -423,7 +423,7 @@ def test_izip_wrongargs(self): import itertools, re - + # Duplicate python 2.4 behaviour for invalid arguments raises(TypeError, itertools.izip, None, 0) @@ -442,7 +442,7 @@ it = itertools.cycle([]) raises(StopIteration, it.next) - + it = itertools.cycle([1, 2, 3]) for x in [1, 2, 3, 1, 2, 3, 1, 2, 3]: assert it.next() == x @@ -498,7 +498,7 @@ def test_tee_wrongargs(self): import itertools - + raises(TypeError, itertools.tee, 0) raises(ValueError, itertools.tee, [], -1) raises(TypeError, itertools.tee, [], None) @@ -536,7 +536,7 @@ def test_groupby(self): import itertools - + it = itertools.groupby([]) raises(StopIteration, it.next) @@ -613,7 +613,7 @@ assert g.next() is x raises(StopIteration, g.next) raises(StopIteration, it.next) - + # Grouping is based on key equality class AlwaysEqual(object): def __eq__(self, other): @@ -647,7 +647,7 @@ def test_iterables(self): import itertools - + iterables = [ itertools.chain(), itertools.count(), @@ -665,7 +665,7 @@ itertools.tee([])[0], itertools.tee([])[1], ] - + for it in iterables: assert hasattr(it, '__iter__') assert iter(it) is it @@ -674,7 +674,7 @@ def test_docstrings(self): import itertools - + assert itertools.__doc__ methods = [ itertools.chain, @@ -756,15 +756,9 @@ assert itertools.tee(a, 0) == () -class AppTestItertools26: +class AppTestItertools26(object): spaceconfig = dict(usemodules=['itertools']) - def setup_class(cls): - if cls.space.is_true(cls.space.appexec([], """(): - import sys; return sys.version_info < (2, 6) - """)): - py.test.skip("Requires Python 2.6") - def test_count_overflow(self): import itertools, sys it = itertools.count(sys.maxint - 1) @@ -1010,16 +1004,8 @@ raises(ValueError, permutations, [1, 2], -1) -class AppTestItertools27: - spaceconfig = { - "usemodules": ['itertools', 'struct', 'binascii'], - } - - def setup_class(cls): - if cls.space.is_true(cls.space.appexec([], """(): - import sys; return sys.version_info < (2, 7) - """)): - py.test.skip("Requires Python 2.7") +class AppTestItertools27(object): + spaceconfig = {"usemodules": ['itertools', 'struct', 'binascii']} def test_compress(self): import itertools diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py --- a/pypy/objspace/std/listobject.py +++ b/pypy/objspace/std/listobject.py @@ -116,10 +116,10 @@ if check_int_or_float: for w_obj in list_w: if type(w_obj) is W_IntObject: - if longlong2float.can_encode_int32(space.int_w(w_obj)): + if longlong2float.can_encode_int32(w_obj.int_w(space)): continue # ok elif type(w_obj) is W_FloatObject: - if longlong2float.can_encode_float(space.float_w(w_obj)): + if longlong2float.can_encode_float(w_obj.float_w(space)): continue # ok break else: diff --git a/pypy/objspace/std/specialisedtupleobject.py b/pypy/objspace/std/specialisedtupleobject.py --- a/pypy/objspace/std/specialisedtupleobject.py +++ b/pypy/objspace/std/specialisedtupleobject.py @@ -31,23 +31,23 @@ class cls(W_AbstractTupleObject): _immutable_fields_ = ['value%s' % i for i in iter_n] - def __init__(self, space, *values_w): + def __init__(self, space, *values): self.space = space - assert len(values_w) == typelen + assert len(values) == typelen for i in iter_n: - w_obj = values_w[i] + obj = values[i] val_type = typetuple[i] if val_type == int: - unwrapped = w_obj.int_w(space) + assert isinstance(obj, int) elif val_type == float: - unwrapped = w_obj.float_w(space) + assert isinstance(obj, float) elif val_type == str: - unwrapped = w_obj.str_w(space) + assert isinstance(obj, str) elif val_type == object: - unwrapped = w_obj + pass else: raise AssertionError - setattr(self, 'value%s' % i, unwrapped) + setattr(self, 'value%s' % i, obj) def length(self): return typelen @@ -150,10 +150,10 @@ w_arg1, w_arg2 = list_w if type(w_arg1) is W_IntObject: if type(w_arg2) is W_IntObject: - return Cls_ii(space, w_arg1, w_arg2) + return Cls_ii(space, space.int_w(w_arg1), space.int_w(w_arg2)) elif type(w_arg1) is W_FloatObject: if type(w_arg2) is W_FloatObject: - return Cls_ff(space, w_arg1, w_arg2) + return Cls_ff(space, space.float_w(w_arg1), space.float_w(w_arg2)) return Cls_oo(space, w_arg1, w_arg2) else: raise NotSpecialised @@ -170,10 +170,9 @@ # faster to move the decision out of the loop. @specialize.arg(1) -def _build_zipped_spec(space, Cls, lst1, lst2, wrap1, wrap2): +def _build_zipped_spec(space, Cls, lst1, lst2): length = min(len(lst1), len(lst2)) - return [Cls(space, wrap1(lst1[i]), - wrap2(lst2[i])) for i in range(length)] + return [Cls(space, lst1[i], lst2[i]) for i in range(length)] def _build_zipped_spec_oo(space, w_list1, w_list2): strat1 = w_list1.strategy @@ -200,8 +199,7 @@ intlist2 = w_list2.getitems_int() if intlist2 is not None: lst_w = _build_zipped_spec( - space, Cls_ii, intlist1, intlist2, - space.newint, space.newint) + space, Cls_ii, intlist1, intlist2) return space.newlist(lst_w) else: floatlist1 = w_list1.getitems_float() @@ -209,8 +207,7 @@ floatlist2 = w_list2.getitems_float() if floatlist2 is not None: lst_w = _build_zipped_spec( - space, Cls_ff, floatlist1, floatlist2, space.newfloat, - space.newfloat) + space, Cls_ff, floatlist1, floatlist2) return space.newlist(lst_w) lst_w = _build_zipped_spec_oo(space, w_list1, w_list2) diff --git a/rpython/annotator/test/test_annrpython.py b/rpython/annotator/test/test_annrpython.py --- a/rpython/annotator/test/test_annrpython.py +++ b/rpython/annotator/test/test_annrpython.py @@ -4644,6 +4644,25 @@ s_exc = a.binding(graphof(a, f).exceptblock.inputargs[1]) assert not s_exc.can_be_none() + def test_specialize_argtype_with_subclasses(self): + # checks that specialize:argtype() makes two copies of a + # function f(), one for the base class and one for the subclass + class A: + def foo(self): + return 123 + class B(A): + def foo(self): + return 456 + def f(x): + return x.foo() + f._annspecialcase_ = "specialize:argtype(0)" + def h(y): + if y > 5: + f(A()) + return f(B()) + a = self.RPythonAnnotator() + assert a.build_types(h, [int]).const == 456 + def g(n): return [0, 1, 2, n] diff --git a/rpython/jit/codewriter/call.py b/rpython/jit/codewriter/call.py --- a/rpython/jit/codewriter/call.py +++ b/rpython/jit/codewriter/call.py @@ -185,8 +185,30 @@ FUNC.RESULT, EffectInfo.MOST_GENERAL) return (fnaddr, calldescr) + def _raise_effect_error(self, op, extraeffect, functype, calling_graph): + explanation = [] + if extraeffect == EffectInfo.EF_RANDOM_EFFECTS: + explanation = self.randomeffects_analyzer.explain_analyze_slowly(op) + elif extraeffect == EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE: + explanation = self.virtualizable_analyzer.explain_analyze_slowly(op) + msg = [] + if explanation: + msg = [ + "_______ ERROR AT BOTTOM ______", + "RPython callstack leading to problem:", + ] + msg.extend(explanation) + msg.append("_______ ERROR: ______") + msg.append("operation %r" % op) + msg.append("in graph %s" % (calling_graph or "<unknown>")) + msg.append("this calls a %s function," % (functype, )) + msg.append(" but this contradicts other sources (e.g. it can have random" + " effects): EF=%s" % (extraeffect, )) + raise Exception("\n".join(msg)) + def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, - extraeffect=None, extradescr=None): + extraeffect=None, extradescr=None, + calling_graph=None): """Return the calldescr that describes all calls done by 'op'. This returns a calldescr that we can put in the corresponding call operation in the calling jitcode. It gets an effectinfo @@ -202,13 +224,13 @@ ARGS = FUNC.ARGS if NON_VOID_ARGS != [T for T in ARGS if T is not lltype.Void]: raise Exception( - "in operation %r: calling a function with signature %r, " + "operation %r in %s: calling a function with signature %r, " "but passing actual arguments (ignoring voids) of types %r" - % (op, FUNC, NON_VOID_ARGS)) + % (op, calling_graph, FUNC, NON_VOID_ARGS)) if RESULT != FUNC.RESULT: raise Exception( - "in operation %r: calling a function with signature %r, " - "but the actual return type is %r" % (op, FUNC, RESULT)) + "%r in %s: calling a function with signature %r, " + "but the actual return type is %r" % (op, calling_graph, FUNC, RESULT)) # ok # get the 'elidable' and 'loopinvariant' flags from the function object elidable = False @@ -217,7 +239,7 @@ if op.opname == "direct_call": funcobj = op.args[0].value._obj assert getattr(funcobj, 'calling_conv', 'c') == 'c', ( - "%r: getcalldescr() with a non-default call ABI" % (op,)) + "%r in %s: getcalldescr() with a non-default call ABI" % (op, calling_graph)) func = getattr(funcobj, '_callable', None) elidable = getattr(func, "_elidable_function_", False) loopinvariant = getattr(func, "_jit_loop_invariant_", False) @@ -245,11 +267,11 @@ if not error: continue raise Exception( - "%r is an indirect call to a family of functions " + "%r in %s is an indirect call to a family of functions " "(or methods) that includes %r. However, the latter " "is marked %r. You need to use an indirection: replace " "it with a non-marked function/method which calls the " - "marked function." % (op, graph, error)) + "marked function." % (op, calling_graph, graph, error)) # build the extraeffect random_effects = self.randomeffects_analyzer.analyze(op) if random_effects: @@ -277,22 +299,17 @@ # check that the result is really as expected if loopinvariant: if extraeffect != EffectInfo.EF_LOOPINVARIANT: - raise Exception( - "in operation %r: this calls a _jit_loop_invariant_ function," - " but this contradicts other sources (e.g. it can have random" - " effects): EF=%s" % (op, extraeffect)) + self._raise_effect_error(op, extraeffect, "_jit_loop_invariant_", calling_graph) if elidable: if extraeffect not in (EffectInfo.EF_ELIDABLE_CANNOT_RAISE, EffectInfo.EF_ELIDABLE_OR_MEMORYERROR, EffectInfo.EF_ELIDABLE_CAN_RAISE): - raise Exception( - "in operation %r: this calls an elidable function," - " but this contradicts other sources (e.g. it can have random" - " effects): EF=%s" % (op, extraeffect)) + + self._raise_effect_error(op, extraeffect, "elidable", calling_graph) elif RESULT is lltype.Void: raise Exception( - "in operation %r: this calls an elidable function " - "but the function has no result" % (op, )) + "operation %r in %s: this calls an elidable function " + "but the function has no result" % (op, calling_graph)) # effectinfo = effectinfo_from_writeanalyze( self.readwrite_analyzer.analyze(op, self.seen_rw), self.cpu, diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py --- a/rpython/jit/codewriter/jtransform.py +++ b/rpython/jit/codewriter/jtransform.py @@ -64,6 +64,7 @@ self.cpu = cpu self.callcontrol = callcontrol self.portal_jd = portal_jd # non-None only for the portal graph(s) + self.graph = None def transform(self, graph): self.graph = graph @@ -424,7 +425,8 @@ of 'residual_call_xxx' are the function to call, and its calldescr.""" calldescr = self.callcontrol.getcalldescr(op, oopspecindex=oopspecindex, extraeffect=extraeffect, - extradescr=extradescr) + extradescr=extradescr, + calling_graph=self.graph) op1 = self.rewrite_call(op, 'residual_call', [op.args[0]] + extraargs, calldescr=calldescr) if may_call_jitcodes or self.callcontrol.calldescr_canraise(calldescr): @@ -1613,7 +1615,9 @@ if len(op.args) > 4 + 2 or have_floats: raise Exception("Conditional call does not support floats or more than 4 arguments") callop = SpaceOperation('direct_call', op.args[1:], op.result) - calldescr = self.callcontrol.getcalldescr(callop) + calldescr = self.callcontrol.getcalldescr( + callop, + calling_graph=self.graph) assert not calldescr.get_extra_info().check_forces_virtual_or_virtualizable() op1 = self.rewrite_call(op, rewritten_opname, op.args[:2], args=op.args[2:], @@ -1924,7 +1928,8 @@ extradescr=None): calldescr = self.callcontrol.getcalldescr(op, oopspecindex, extraeffect, - extradescr=extradescr) + extradescr=extradescr, + calling_graph=self.graph) if extraeffect is not None: assert (is_test_calldescr(calldescr) # for tests or calldescr.get_extra_info().extraeffect == extraeffect) @@ -1954,7 +1959,8 @@ [c_func] + [varoftype(T) for T in argtypes], varoftype(resulttype)) calldescr = self.callcontrol.getcalldescr(op, oopspecindex, - effectinfo) + effectinfo, + calling_graph=self.graph) if isinstance(c_func.value, str): # in tests only func = c_func.value else: diff --git a/rpython/jit/codewriter/test/test_call.py b/rpython/jit/codewriter/test/test_call.py --- a/rpython/jit/codewriter/test/test_call.py +++ b/rpython/jit/codewriter/test/test_call.py @@ -299,11 +299,23 @@ def f4(n, m): return compute_hash(str(n) + str(m)) + T = rffi.CArrayPtr(rffi.TIME_T) + external = rffi.llexternal("time", [T], rffi.TIME_T, releasegil=True) + + def effect(): + return external(lltype.nullptr(T.TO)) + + @jit.elidable + def f5(n, m): + effect() + return 1 + def f(n, m): a = f1(n, m) b = f2(n, m) c = f3(n, m) d = f4(n, m) + f5(n, m) enable_siphash24() return a + len(b) + c + d @@ -323,25 +335,33 @@ call_descr = cc.getcalldescr(call_op) assert call_descr.extrainfo.extraeffect == expected + call_op = f_graph.startblock.operations[4] + assert call_op.opname == 'direct_call' + excinfo = py.test.raises(Exception, cc.getcalldescr, call_op) + lines = excinfo.value.args[0].splitlines() + assert "f5" in lines[2] + assert "effect" in lines[3] + assert "random effects" in lines[-1] + def test_raise_elidable_no_result(): from rpython.jit.backend.llgraph.runner import LLGraphCPU l = [] @jit.elidable def f1(n, m): l.append(n) - def f(n, m): + def fancy_graph_name(n, m): f1(n, m) return n + m - rtyper = support.annotate(f, [7, 9]) + rtyper = support.annotate(fancy_graph_name, [7, 9]) jitdriver_sd = FakeJitDriverSD(rtyper.annotator.translator.graphs[0]) cc = CallControl(LLGraphCPU(rtyper), jitdrivers_sd=[jitdriver_sd]) res = cc.find_all_graphs(FakePolicy()) - [f_graph] = [x for x in res if x.func is f] + [f_graph] = [x for x in res if x.func is fancy_graph_name] call_op = f_graph.startblock.operations[0] assert call_op.opname == 'direct_call' - with py.test.raises(Exception): - call_descr = cc.getcalldescr(call_op) + x = py.test.raises(Exception, cc.getcalldescr, call_op, calling_graph=f_graph) + assert "fancy_graph_name" in str(x.value) def test_can_or_cannot_collect(): from rpython.jit.backend.llgraph.runner import LLGraphCPU diff --git a/rpython/jit/codewriter/test/test_flatten.py b/rpython/jit/codewriter/test/test_flatten.py --- a/rpython/jit/codewriter/test/test_flatten.py +++ b/rpython/jit/codewriter/test/test_flatten.py @@ -73,7 +73,7 @@ def guess_call_kind(self, op): return 'residual' def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE, - extraeffect=None, extradescr=None): + extraeffect=None, extradescr=None, calling_graph=None): try: name = op.args[0].value._obj._name if 'cannot_raise' in name or name.startswith('cast_'): diff --git a/rpython/jit/codewriter/test/test_jtransform.py b/rpython/jit/codewriter/test/test_jtransform.py --- a/rpython/jit/codewriter/test/test_jtransform.py +++ b/rpython/jit/codewriter/test/test_jtransform.py @@ -48,7 +48,7 @@ def guess_call_kind(self, op): return 'residual' def getcalldescr(self, op, oopspecindex=None, extraeffect=None, - extradescr=None): + extradescr=None, calling_graph=None): return 'calldescr' def calldescr_canraise(self, calldescr): return True @@ -106,7 +106,7 @@ def guess_call_kind(self, op): return 'builtin' def getcalldescr(self, op, oopspecindex=None, extraeffect=None, - extradescr=None): + extradescr=None, calling_graph=None): assert oopspecindex is not None # in this test EI = effectinfo.EffectInfo if oopspecindex != EI.OS_ARRAYCOPY: diff --git a/rpython/jit/codewriter/test/test_list.py b/rpython/jit/codewriter/test/test_list.py --- a/rpython/jit/codewriter/test/test_list.py +++ b/rpython/jit/codewriter/test/test_list.py @@ -39,7 +39,7 @@ class FakeCallControl: class getcalldescr(AbstractDescr): def __init__(self, op, oopspecindex=0, extraeffect=None, - extradescr=None): + extradescr=None, calling_graph=None): self.op = op self.oopspecindex = oopspecindex def __repr__(self): diff --git a/rpython/jit/metainterp/optimizeopt/info.py b/rpython/jit/metainterp/optimizeopt/info.py --- a/rpython/jit/metainterp/optimizeopt/info.py +++ b/rpython/jit/metainterp/optimizeopt/info.py @@ -329,11 +329,14 @@ def make_guards(self, op, short, optimizer): if self._known_class is not None: - short.append(ResOperation(rop.GUARD_NONNULL, [op])) if not optimizer.cpu.remove_gctypeptr: + short.append(ResOperation(rop.GUARD_NONNULL, [op])) short.append(ResOperation(rop.GUARD_IS_OBJECT, [op])) - short.append(ResOperation(rop.GUARD_CLASS, - [op, self._known_class])) + short.append(ResOperation(rop.GUARD_CLASS, + [op, self._known_class])) + else: + short.append(ResOperation(rop.GUARD_NONNULL_CLASS, + [op, self._known_class])) elif self.descr is not None: short.append(ResOperation(rop.GUARD_NONNULL, [op])) if not optimizer.cpu.remove_gctypeptr: diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py @@ -7541,6 +7541,33 @@ """ self.optimize_loop(ops, expected, expected_short=short) + def test_guards_before_getfields_in_short_preamble_removetypeptr(self, monkeypatch): + monkeypatch.setattr(self.cpu, "remove_gctypeptr", True) + ops = """ + [p0] + guard_nonnull_class(p0, ConstClass(node_vtable)) [] + p1 = getfield_gc_r(p0, descr=nextdescr) + guard_nonnull_class(p1, ConstClass(node_vtable)) [] + p2 = getfield_gc_r(p1, descr=nextdescr) + guard_nonnull_class(p2, ConstClass(node_vtable)) [] + jump(p0) + """ + expected = """ + [p0, p1] + jump(p0, p1) + """ + short = """ + [p0] + guard_nonnull_class(p0, ConstClass(node_vtable)) [] + p1 = getfield_gc_r(p0, descr=nextdescr) + guard_nonnull_class(p1, ConstClass(node_vtable)) [] + p2 = getfield_gc_r(p1, descr=nextdescr) + guard_nonnull_class(p2, ConstClass(node_vtable)) [] + jump(p1) + """ + self.optimize_loop(ops, expected, expected_short=short) + + def test_forced_virtual_pure_getfield(self): ops = """ [p0] diff --git a/rpython/rlib/_rsocket_rffi.py b/rpython/rlib/_rsocket_rffi.py --- a/rpython/rlib/_rsocket_rffi.py +++ b/rpython/rlib/_rsocket_rffi.py @@ -2,6 +2,7 @@ from rpython.rtyper.lltypesystem import lltype from rpython.rtyper.tool import rffi_platform as platform from rpython.rtyper.lltypesystem.rffi import CCHARP +from rpython.rlib import jit from rpython.translator.tool.cbuild import ExternalCompilationInfo from rpython.translator.platform import platform as target_platform @@ -190,6 +191,8 @@ IPX_TYPE +SCM_RIGHTS + POLLIN POLLPRI POLLOUT POLLERR POLLHUP POLLNVAL POLLRDNORM POLLRDBAND POLLWRNORM POLLWEBAND POLLMSG @@ -260,6 +263,7 @@ sockaddr_ptr = lltype.Ptr(lltype.ForwardReference()) addrinfo_ptr = lltype.Ptr(lltype.ForwardReference()) + # struct types CConfig.sockaddr = platform.Struct('struct sockaddr', [('sa_family', rffi.INT), @@ -343,6 +347,650 @@ [('ifr_ifindex', rffi.INT), ('ifr_name', rffi.CFixedArray(rffi.CHAR, 8))]) +# insert handler for sendmsg / recvmsg here +if _POSIX: + includes = ['stddef.h', + 'sys/socket.h', + 'unistd.h', + 'string.h', + 'stdlib.h', + 'errno.h', + 'limits.h', + 'stdio.h', + 'sys/types.h', + 'netinet/in.h', + 'arpa/inet.h'] + separate_module_sources = [''' + + // special defines for returning from recvmsg + #define BAD_MSG_SIZE_GIVEN -10000 + #define BAD_ANC_SIZE_GIVEN -10001 + #define MAL_ANC -10002 + + // special defines for returning from sendmsg + #define MUL_MSGS_NOT_SUP -1000 + #define ANC_DATA_TOO_LARGE -1001 + #define ANC_DATA_TOO_LARGEX -1002 + + /* + Even though you could, theoretically, receive more than one message, IF you set the socket option, + CPython has hardcoded the message number to 1, and implemented the option to receive more then 1 in a + different socket method: recvmsg_into + */ + #define MSG_IOVLEN 1 // CPython has hardcoded this as well. + #if INT_MAX > 0x7fffffff + #define SOCKLEN_T_LIMIT 0x7fffffff + #else + #define SOCKLEN_T_LIMIT INT_MAX + #endif + + // ################################################################################################ + // Recvmsg implementation and associated functions + + // Taken from CPython. Determines the minimum memory space required for the ancillary data. + #ifdef CMSG_SPACE + static int + cmsg_min_space(struct msghdr *msg, struct cmsghdr *cmsgh, size_t space) + { + size_t cmsg_offset; + static const size_t cmsg_len_end = (offsetof(struct cmsghdr, cmsg_len) + + sizeof(cmsgh->cmsg_len)); + + /* Note that POSIX allows msg_controllen to be of signed type. */ + if (cmsgh == NULL || msg->msg_control == NULL) + return 0; + /* Note that POSIX allows msg_controllen to be of a signed type. This is + annoying under OS X as it's unsigned there and so it triggers a + tautological comparison warning under Clang when compared against 0. + Since the check is valid on other platforms, silence the warning under + Clang. */ + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wtautological-compare" + #endif + #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wtype-limits" + #endif + if (msg->msg_controllen < 0) + return 0; + #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))) + #pragma GCC diagnostic pop + #endif + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + if (space < cmsg_len_end) + space = cmsg_len_end; + cmsg_offset = (char *)cmsgh - (char *)msg->msg_control; + return (cmsg_offset <= (size_t)-1 - space && + cmsg_offset + space <= msg->msg_controllen); + } + #endif + + // Taken from CPython. + #ifdef CMSG_LEN + /* If pointer CMSG_DATA(cmsgh) is in buffer msg->msg_control, set + *space to number of bytes following it in the buffer and return + true; otherwise, return false. Assumes cmsgh, msg->msg_control and + msg->msg_controllen are valid. */ + static int + get_cmsg_data_space(struct msghdr *msg, struct cmsghdr *cmsgh, size_t *space) + { + size_t data_offset; + char *data_ptr; + + if ((data_ptr = (char *)CMSG_DATA(cmsgh)) == NULL) + return 0; + data_offset = data_ptr - (char *)msg->msg_control; + if (data_offset > msg->msg_controllen) + return 0; + *space = msg->msg_controllen - data_offset; + return 1; + } + + // Taken from CPython. + /* If cmsgh is invalid or not contained in the buffer pointed to by + msg->msg_control, return -1. If cmsgh is valid and its associated + data is entirely contained in the buffer, set *data_len to the + length of the associated data and return 0. If only part of the + associated data is contained in the buffer but cmsgh is otherwise + valid, set *data_len to the length contained in the buffer and + return 1. */ + static int + get_cmsg_data_len(struct msghdr *msg, struct cmsghdr *cmsgh, size_t *data_len) + { + size_t space, cmsg_data_len; + + if (!cmsg_min_space(msg, cmsgh, CMSG_LEN(0)) || + cmsgh->cmsg_len < CMSG_LEN(0)) + return -1; + cmsg_data_len = cmsgh->cmsg_len - CMSG_LEN(0); + if (!get_cmsg_data_space(msg, cmsgh, &space)) + return -1; + if (space >= cmsg_data_len) { + *data_len = cmsg_data_len; + return 0; + } + *data_len = space; + return 1; + } + #endif /* CMSG_LEN */ + + /* + Structure meant to hold the information received after a recvmsg is performed. + Essentially it holds: the address, the message, the ancillary data and the return flags. + I use this structure for 2 main reasons: + - keep things ordered + - some of the ancillary parameters need to be int not long (rffi SignedP is actually long*), + therefore I cannot use the parameters directly + */ + struct recvmsg_info + { + struct sockaddr* address; // address fields + socklen_t addrlen; + int* length_of_messages; // message fields + char** messages; + int no_of_messages; + int size_of_ancillary; // ancillary fields + int* levels; + int* types; + char** file_descr; + int* descr_per_ancillary; + int retflag; // return flag field + }; + + /* + Wrapper function over recvmsg. Since it returns a lot of data, + in a structure that is hard to parse in rffi, it was implemented in C. + All the parameters, save the socket fd, message_size, ancillary_size + will be malloc'd and/or modified. + */ + RPY_EXTERN + int recvmsg_implementation( + int socket_fd, + int message_size, + int ancillary_size, + int flags, + struct sockaddr* address, + socklen_t* addrlen, + long** length_of_messages, + char** messages, + long* no_of_messages, + long* size_of_ancillary, + long** levels, + long** types, + char** file_descr, + long** descr_per_ancillary, + long* retflag) + + { + + struct sockaddr* recvd_address; + socklen_t recvd_addrlen; + struct msghdr msg = {0}; + void *controlbuf = NULL; + struct cmsghdr *cmsgh; + int cmsg_status; + struct iovec iov; + struct recvmsg_info* retinfo; + int error_flag; // variable to be set in case of special errors. + int cmsgdatalen = 0; + + // variables that are set to 1, if the message charp has been allocated + // and if the ancillary variables have been allocated. To be used in case of failure. + int iov_alloc = 0; + int anc_alloc = 0; + + retinfo = (struct recvmsg_info*) malloc(sizeof(struct recvmsg_info)); + + if (ancillary_size > SOCKLEN_T_LIMIT){ + error_flag = BAD_ANC_SIZE_GIVEN; + goto fail; + } + + // Setup the messages iov struct memory + iov.iov_base = (char*) malloc(message_size); + memset(iov.iov_base, 0, message_size); + iov.iov_len = message_size; + + // Setup the ancillary buffer memory + controlbuf = malloc(ancillary_size); + + // Setup the recv address memory + recvd_addrlen = sizeof(struct sockaddr_storage); + recvd_address = (struct sockaddr*) malloc(recvd_addrlen); + + memset(recvd_address, 0,recvd_addrlen); + + // Setup the msghdr struct + msg.msg_name = recvd_address; + msg.msg_namelen = recvd_addrlen; + msg.msg_iov = &iov; + msg.msg_iovlen = MSG_IOVLEN; + msg.msg_control = controlbuf; + msg.msg_controllen = ancillary_size; + + // Link my structure to the msghdr fields + retinfo->address = msg.msg_name; + retinfo->length_of_messages = (int*) malloc (MSG_IOVLEN * sizeof(int)); + retinfo->no_of_messages = MSG_IOVLEN; + retinfo->messages = (char**) malloc (MSG_IOVLEN * sizeof(char*)); + retinfo->messages[0] = msg.msg_iov->iov_base; + + iov_alloc = 1; + ssize_t bytes_recvd = 0; + + bytes_recvd = recvmsg(socket_fd, &msg, flags); + + if (bytes_recvd < 0){ + goto fail; + } + + retinfo->addrlen = (socklen_t) msg.msg_namelen; + retinfo->length_of_messages[0] = msg.msg_iov->iov_len; + + // Count the ancillary items & allocate the memory + int anc_counter = 0; + for (cmsgh = ((msg.msg_controllen > 0) ? CMSG_FIRSTHDR(&msg) : NULL); + cmsgh != NULL; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + + anc_counter++; + } + retinfo->size_of_ancillary = anc_counter; + retinfo->file_descr = (char**) malloc (anc_counter * sizeof(char*)); + retinfo->levels = (int*) malloc(anc_counter * sizeof(int)); + retinfo->types = (int*) malloc(anc_counter * sizeof(int)); + retinfo->descr_per_ancillary = (int*) malloc(anc_counter * sizeof(int)); + anc_alloc = 1; + + // Extract the ancillary items + int i=0; + for (cmsgh = ((msg.msg_controllen > 0) ? CMSG_FIRSTHDR(&msg) : NULL); + cmsgh != NULL; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + size_t local_size = 0; + cmsg_status = get_cmsg_data_len(&msg, cmsgh, &local_size); + if (cmsg_status !=0 ){ + error_flag = MAL_ANC; + goto err_closefds; + } + retinfo->file_descr[i] = (char*) malloc(local_size); + memcpy(retinfo->file_descr[i], CMSG_DATA(cmsgh), local_size); + retinfo->levels[i] = cmsgh->cmsg_level; + retinfo->types[i] = cmsgh->cmsg_type; + retinfo->descr_per_ancillary[i] =local_size; + i++; + + } + retinfo->retflag = msg.msg_flags; + + // Set the parameters of address + memcpy(address,retinfo->address,retinfo->addrlen); + *addrlen = retinfo->addrlen; + + // Set the parameters of message + no_of_messages[0] = retinfo->no_of_messages; + size_of_ancillary[0] = retinfo->size_of_ancillary; + *length_of_messages = (long*) malloc (sizeof(long) * retinfo->no_of_messages); + //memcpy(*length_of_messages, retinfo->length_of_messages, sizeof(int) * retinfo->no_of_messages); + int counter = 0; + for (i=0; i< retinfo->no_of_messages; i++){ + counter += retinfo->length_of_messages[i]; + length_of_messages[0][i] = retinfo->length_of_messages[i]; + } + memset(*messages, 0, sizeof(char) * counter); + counter = 0; + for(i=0; i< retinfo->no_of_messages; i++){ + memcpy(*messages+counter,retinfo->messages[i],retinfo->length_of_messages[i]); + counter += retinfo->length_of_messages[i]; + } + + // Set the parameters of ancillary + *levels = (long*) malloc (sizeof(long) * retinfo->size_of_ancillary); + *types = (long*) malloc (sizeof(long) * retinfo->size_of_ancillary); + *descr_per_ancillary = (long*) malloc (sizeof(long) * retinfo->size_of_ancillary); + counter = 0; + for (i=0; i < retinfo->size_of_ancillary; i++){ + counter += retinfo->descr_per_ancillary[i]; + // Convert the int* to long* + levels[0][i] = (long) retinfo->levels[i]; + types[0][i] = (long) retinfo->types[i]; + descr_per_ancillary[0][i] = (long) retinfo->descr_per_ancillary[i]; + } + *file_descr = (char*) malloc (sizeof(char) * counter); + memset(*file_descr, 0, sizeof(char) * counter); + counter = 0; + for (i=0; i<retinfo->size_of_ancillary; i++){ + memcpy(*file_descr+counter,retinfo->file_descr[i], retinfo->descr_per_ancillary[i]); + counter += retinfo->descr_per_ancillary[i]; + } + + // Set the retflag + retflag[0] = retinfo->retflag; + + // Free the memory + free(retinfo->address); + free(retinfo->length_of_messages); + free(retinfo->levels); + free(retinfo->types); + free(retinfo->descr_per_ancillary); + for(i = 0; i<retinfo->no_of_messages; i++) + free(retinfo->messages[i]); + for (i = 0; i < retinfo->size_of_ancillary; i++) + free(retinfo->file_descr[i]); + free(retinfo->file_descr); + free(retinfo->messages); + free(retinfo); + free(controlbuf); + + return bytes_recvd; + + fail: + if (anc_alloc){ + free(retinfo->file_descr); + free(retinfo->levels); + free(retinfo->types); + free(retinfo->descr_per_ancillary); + free(retinfo->length_of_messages); + free(retinfo->messages[0]); + free(retinfo->messages); + free(retinfo->address); + free(retinfo); + free(controlbuf); + + }else{ + if (iov_alloc){ + free(retinfo->length_of_messages); + free(retinfo->messages[0]); + free(retinfo->messages); + free(retinfo->address); + free(controlbuf); + free(retinfo); + } + } + return error_flag; + + err_closefds: + // Special case for UNIX sockets. In case file descriptors are received, they need to be closed. + // Taken from CPython + #ifdef SCM_RIGHTS + /* Close all descriptors coming from SCM_RIGHTS, so they don't leak. */ + for (cmsgh = ((msg.msg_controllen > 0) ? CMSG_FIRSTHDR(&msg) : NULL); + cmsgh != NULL; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + size_t dataleng; + cmsg_status = get_cmsg_data_len(&msg, cmsgh, &dataleng); + cmsgdatalen = (int) dataleng; + if (cmsg_status < 0) + break; + if (cmsgh->cmsg_level == SOL_SOCKET && + cmsgh->cmsg_type == SCM_RIGHTS) { + size_t numfds; + int *fdp; + + numfds = cmsgdatalen / sizeof(int); + fdp = (int *)CMSG_DATA(cmsgh); + while (numfds-- > 0) + close(*fdp++); + } + if (cmsg_status != 0) + break; + } + #endif /* SCM_RIGHTS */ + goto fail; + } + + + // ################################################################################################ + // Sendmsg implementation and associated functions + + #ifdef CMSG_LEN + static int + get_CMSG_LEN(size_t length, size_t *result) + { + size_t tmp; + + if (length > (SOCKLEN_T_LIMIT - CMSG_LEN(0))) + return 0; + tmp = CMSG_LEN(length); + if ((tmp > SOCKLEN_T_LIMIT) || (tmp < length)) + return 0; + *result = tmp; + return 1; + } + #endif + + #ifdef CMSG_SPACE + /* If length is in range, set *result to CMSG_SPACE(length) and return + true; otherwise, return false. */ + static int + get_CMSG_SPACE(size_t length, size_t *result) + { + size_t tmp; + + /* Use CMSG_SPACE(1) here in order to take account of the padding + necessary before *and* after the data. */ + if (length > (SOCKLEN_T_LIMIT - CMSG_SPACE(1))) + return 0; + tmp = CMSG_SPACE(length); + if ((tmp > SOCKLEN_T_LIMIT) || (tmp < length)) + return 0; + *result = tmp; + return 1; + } + #endif + + /* + sendmsg_implementation is a wrapper over sendmsg of the API. + It was inspired from the way CPython did their implementation of this. + The main reason that it was written in C, is the struct msghdr, + which contains the ancillary data in a linked list of cmsghdr structures. + It was simpler to use it in C, and then push the simpler types of data via rffi. + */ + RPY_EXTERN + int sendmsg_implementation + (int socket, + struct sockaddr* address, + socklen_t addrlen, + long* length_of_messages, + char** messages, + int no_of_messages, + long* levels, + long* types, + char** file_descriptors, + long* no_of_fds, + int control_length, + int flag + ) + { + + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + void* controlbuf = NULL; + int retval; + size_t i; + + // Prepare the msghdr structure for the send: + + // Add the address + if (address != NULL) { + msg.msg_name = address; + msg.msg_namelen = addrlen; + } + + // Add the message + struct iovec *iovs = NULL; + if (no_of_messages > 0){ + + iovs = (struct iovec*) malloc(no_of_messages * sizeof(struct iovec)); + memset(iovs, 0, no_of_messages * sizeof(struct iovec)); + msg.msg_iov = iovs; + msg.msg_iovlen = no_of_messages; + + for (i=0; i< no_of_messages; i++){ + iovs[i].iov_base = messages[i]; + iovs[i].iov_len = length_of_messages[i]; + } + } + + // Add the ancillary + #ifndef CMSG_SPACE + if (control_length > 1){ + free(iovs); + return MUL_MSGS_NOT_SUP; + } + #endif + if (control_length > 0){ + + //compute the total size of the ancillary + //getting the exact amount of space can be tricky and os dependent. + size_t total_size_of_ancillary = 0; + size_t space; + size_t controllen = 0, controllen_last = 0; + for (i = 0; i< control_length; i++){ + total_size_of_ancillary = no_of_fds[i]; + #ifdef CMSG_SPACE + if (!get_CMSG_SPACE(total_size_of_ancillary, &space)) { + #else + if (!get_CMSG_LEN(total_size_of_ancillary, &space)) { + #endif + if (iovs != NULL) + free(iovs); + return ANC_DATA_TOO_LARGE; + } + controllen +=space; + if ((controllen > SOCKLEN_T_LIMIT) || (controllen < controllen_last)) { + if (iovs != NULL) + free(iovs); + return ANC_DATA_TOO_LARGEX; + } + controllen_last = controllen; + } + + controlbuf = malloc(controllen); + msg.msg_control= controlbuf; + msg.msg_controllen = controllen; + + // memset controlbuf to 0 to avoid trash in the ancillary + memset(controlbuf, 0, controllen); + cmsg = NULL; + for (i = 0; i< control_length; i++){ + cmsg = (i == 0) ? CMSG_FIRSTHDR(&msg) : CMSG_NXTHDR(&msg, cmsg); + + cmsg->cmsg_level = (int) levels[i]; + cmsg->cmsg_type = (int) types[i]; + cmsg->cmsg_len = CMSG_LEN(sizeof(char) * no_of_fds[i]); + memcpy(CMSG_DATA(cmsg), file_descriptors[i], sizeof(char) * no_of_fds[i]); + } + + + } + // Add the flags + msg.msg_flags = flag; + + // Send the data + retval = sendmsg(socket, &msg, flag); + + // free everything that was allocated here, and we would not need in rsocket + if (iovs != NULL) + free(iovs); + if (controlbuf !=NULL) + free(controlbuf); + + return retval; + } + + // ################################################################################################ + // Wrappers for CMSG_SPACE and CMSG_LEN + + /* + These 2 functions are wrappers over sys/socket.h's CMSG_SPACE and CMSG_LEN. + They are identical to CPython's. + */ + #ifdef CMSG_SPACE + RPY_EXTERN + size_t CMSG_SPACE_wrapper(size_t desired_space){ + size_t result; + if (!get_CMSG_SPACE(desired_space, &result)){ + return 0; + } + return result; + } + #endif + + #ifdef CMSG_LEN + RPY_EXTERN + size_t CMSG_LEN_wrapper(size_t desired_len){ + size_t result; + if (!get_CMSG_LEN(desired_len, &result)){ + return 0; + } + return result; + } + #endif + + // ################################################################################################ + // Extra functions that I needed + + /* + This function is used to memcpy from a char* at an offset. + Could not get rffi.c_memcpy to do it at an offset, so I made my own. + */ + RPY_EXTERN + int memcpy_from_CCHARP_at_offset_and_size(char* stringfrom, char** stringto, int offset, int size){ + *stringto = memcpy(*stringto, stringfrom + offset, size); + return 0; + } + + /* + These functions free memory that was allocated in C (sendmsg or recvmsg) was used in rsocket and now needs cleanup + */ + RPY_EXTERN + int free_pointer_to_signedp(int** ptrtofree){ + free(*ptrtofree); + return 0; + } + + RPY_EXTERN + int free_ptr_to_charp(char** ptrtofree){ + free(*ptrtofree); + return 0; + } + + ''',] + + post_include_bits =[ "RPY_EXTERN " + "int sendmsg_implementation(int socket, struct sockaddr* address, socklen_t addrlen, long* length_of_messages, char** messages, int no_of_messages, long* levels, long* types, char** file_descriptors, long* no_of_fds, int control_length, int flag );\n" + "RPY_EXTERN " + "int recvmsg_implementation(int socket_fd, int message_size, int ancillary_size, int flags, struct sockaddr* address, socklen_t* addrlen, long** length_of_messages, char** messages, long* no_of_messages, long* size_of_ancillary, long** levels, long** types, char** file_descr, long** descr_per_ancillary, long* flag);\n" + "static " + "int cmsg_min_space(struct msghdr *msg, struct cmsghdr *cmsgh, size_t space);\n" + "static " + "int get_cmsg_data_space(struct msghdr *msg, struct cmsghdr *cmsgh, size_t *space);\n" + "static " + "int get_cmsg_data_len(struct msghdr *msg, struct cmsghdr *cmsgh, size_t *data_len);\n" + "static " + "int get_CMSG_LEN(size_t length, size_t *result);\n" + "static " + "int get_CMSG_SPACE(size_t length, size_t *result);\n" + "RPY_EXTERN " + "size_t CMSG_LEN_wrapper(size_t desired_len);\n" + "RPY_EXTERN " + "size_t CMSG_SPACE_wrapper(size_t desired_space);\n" + "RPY_EXTERN " + "int memcpy_from_CCHARP_at_offset_and_size(char* stringfrom, char** stringto, int offset, int size);\n" + "RPY_EXTERN " + "int free_pointer_to_signedp(int** ptrtofree);\n" + "RPY_EXTERN " + "int free_ptr_to_charp(char** ptrtofree);\n" + ] + + + compilation_info = ExternalCompilationInfo( + includes=includes, + separate_module_sources=separate_module_sources, + post_include_bits=post_include_bits, + ) + if _WIN32: CConfig.WSAEVENT = platform.SimpleType('WSAEVENT', rffi.VOIDP) CConfig.WSANETWORKEVENTS = platform.Struct( @@ -387,6 +1035,7 @@ sockaddr_ptr.TO.become(cConfig.sockaddr) addrinfo_ptr.TO.become(cConfig.addrinfo) + # fill in missing constants with reasonable defaults cConfig.NI_MAXHOST = cConfig.NI_MAXHOST or 1025 cConfig.NI_MAXSERV = cConfig.NI_MAXSERV or 32 @@ -571,11 +1220,32 @@ recvfrom = external('recvfrom', [socketfd_type, rffi.VOIDP, size_t, rffi.INT, sockaddr_ptr, socklen_t_ptr], rffi.INT, save_err=SAVE_ERR) +recvmsg = jit.dont_look_inside(rffi.llexternal("recvmsg_implementation", + [rffi.INT, rffi.INT, rffi.INT, rffi.INT,sockaddr_ptr, socklen_t_ptr, rffi.SIGNEDPP, rffi.CCHARPP, + rffi.SIGNEDP,rffi.SIGNEDP, rffi.SIGNEDPP, rffi.SIGNEDPP, rffi.CCHARPP, rffi.SIGNEDPP, rffi.SIGNEDP], + rffi.INT, save_err=SAVE_ERR, + compilation_info=compilation_info)) + +memcpy_from_CCHARP_at_offset = jit.dont_look_inside(rffi.llexternal("memcpy_from_CCHARP_at_offset_and_size", + [rffi.CCHARP, rffi.CCHARPP,rffi.INT,rffi.INT],rffi.INT,save_err=SAVE_ERR,compilation_info=compilation_info)) +freeccharp = jit.dont_look_inside(rffi.llexternal("free_ptr_to_charp", + [rffi.CCHARPP],rffi.INT,save_err=SAVE_ERR,compilation_info=compilation_info)) +freesignedp = jit.dont_look_inside(rffi.llexternal("free_pointer_to_signedp", + [rffi.SIGNEDPP],rffi.INT,save_err=SAVE_ERR,compilation_info=compilation_info)) + send = external('send', [socketfd_type, rffi.CCHARP, size_t, rffi.INT], ssize_t, save_err=SAVE_ERR) sendto = external('sendto', [socketfd_type, rffi.VOIDP, size_t, rffi.INT, sockaddr_ptr, socklen_t], ssize_t, save_err=SAVE_ERR) +sendmsg = jit.dont_look_inside(rffi.llexternal("sendmsg_implementation", + [rffi.INT, sockaddr_ptr, socklen_t, rffi.SIGNEDP, rffi.CCHARPP, rffi.INT, + rffi.SIGNEDP, rffi.SIGNEDP, rffi.CCHARPP, rffi.SIGNEDP, rffi.INT, rffi.INT], + rffi.INT, save_err=SAVE_ERR, + compilation_info=compilation_info)) +CMSG_SPACE = jit.dont_look_inside(rffi.llexternal("CMSG_SPACE_wrapper",[size_t], size_t, save_err=SAVE_ERR,compilation_info=compilation_info)) +CMSG_LEN = jit.dont_look_inside(rffi.llexternal("CMSG_LEN_wrapper",[size_t], size_t, save_err=SAVE_ERR,compilation_info=compilation_info)) + socketshutdown = external('shutdown', [socketfd_type, rffi.INT], rffi.INT, save_err=SAVE_ERR) gethostname = external('gethostname', [rffi.CCHARP, rffi.INT], rffi.INT, diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py --- a/rpython/rlib/rposix.py +++ b/rpython/rlib/rposix.py @@ -1312,9 +1312,17 @@ @replace_os_function('link') @specialize.argtype(0, 1) def link(oldpath, newpath): - oldpath = _as_bytes0(oldpath) - newpath = _as_bytes0(newpath) - handle_posix_error('link', c_link(oldpath, newpath)) + if not _WIN32: + oldpath = _as_bytes0(oldpath) + newpath = _as_bytes0(newpath) + handle_posix_error('link', c_link(oldpath, newpath)) + else: + traits = _preferred_traits(oldpath) + win32traits = make_win32_traits(traits) + oldpath = traits.as_str0(oldpath) + newpath = traits.as_str0(newpath) + if not win32traits.CreateHardLink(newpath, oldpath, None): + raise rwin32.lastSavedWindowsError() @replace_os_function('symlink') @specialize.argtype(0, 1) diff --git a/rpython/rlib/rsocket.py b/rpython/rlib/rsocket.py --- a/rpython/rlib/rsocket.py +++ b/rpython/rlib/rsocket.py @@ -963,6 +963,126 @@ return (read_bytes, address) raise self.error_handler() + @jit.dont_look_inside + def recvmsg(self, message_size, ancbufsize = 0, flags = 0): + """ + Receive up to message_size bytes from a message. Also receives ancillary data. + Returns the message, ancillary, flag and address of the sender. + :param message_size: Maximum size of the message to be received + :param ancbufsize: Maximum size of the ancillary data to be received + :param flags: Receive flag. For more details, please check the Unix manual + :return: a tuple consisting of the message, the ancillary data, return flag and the address. + """ + if message_size < 0: + raise RSocketError("Invalid message size") + if ancbufsize < 0: + raise RSocketError("invalid ancillary data buffer length") + + self.wait_for_data(False) + address, addr_p, addrlen_p = self._addrbuf() + len_of_msgs = lltype.malloc(rffi.SIGNEDPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False) + messages = lltype.malloc(rffi.CCHARPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False ) + messages[0] = lltype.malloc(rffi.CCHARP.TO, message_size,flavor='raw',track_allocation=True,nonmovable=False) + rffi.c_memset(messages[0], 0, message_size) + no_of_messages = lltype.malloc(rffi.SIGNEDP.TO,1,flavor='raw',track_allocation=True,nonmovable=False ) + no_of_messages[0] = rffi.cast(rffi.SIGNED, 0) + size_of_anc = lltype.malloc(rffi.SIGNEDP.TO,1,flavor='raw',track_allocation=True,nonmovable=False ) + size_of_anc[0] = rffi.cast(rffi.SIGNED,0) + levels = lltype.malloc(rffi.SIGNEDPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False) + types = lltype.malloc(rffi.SIGNEDPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False) + file_descr = lltype.malloc(rffi.CCHARPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False ) + descr_per_anc = lltype.malloc(rffi.SIGNEDPP.TO,1,flavor='raw',track_allocation=True,nonmovable=False) + retflag = lltype.malloc(rffi.SIGNEDP.TO,1,flavor='raw',track_allocation=True,nonmovable=False ) + retflag[0] = rffi.cast(rffi.SIGNED,0) + + # a mask for the SIGNEDP's that need to be cast to int. (long default) + reply = _c.recvmsg(self.fd, rffi.cast(lltype.Signed,message_size), + rffi.cast(lltype.Signed,ancbufsize),rffi.cast(lltype.Signed,flags), + addr_p, addrlen_p, len_of_msgs, messages, no_of_messages,size_of_anc, + levels, types,file_descr,descr_per_anc,retflag) + if reply >= 0: + anc_size = rffi.cast(rffi.SIGNED,size_of_anc[0]) + returnflag = rffi.cast(rffi.SIGNED,retflag[0]) + addrlen = rffi.cast(rffi.SIGNED,addrlen_p[0]) + + retmsg = rffi.charpsize2str(messages[0],reply) + + offset = 0 + list_of_tuples = [] + + pre_anc = lltype.malloc(rffi.CCHARPP.TO, 1, flavor='raw', track_allocation=True, nonmovable=False) + for i in range(anc_size): + level = rffi.cast(rffi.SIGNED, levels[0][i]) + type = rffi.cast(rffi.SIGNED, types[0][i]) + bytes_in_anc = rffi.cast(rffi.SIGNED, descr_per_anc[0][i]) + pre_anc[0] = lltype.malloc(rffi.CCHARP.TO, bytes_in_anc,flavor='raw',track_allocation=True,nonmovable=False) + _c.memcpy_from_CCHARP_at_offset(file_descr[0], pre_anc,rffi.cast(rffi.SIGNED,offset), bytes_in_anc) + anc = rffi.charpsize2str(pre_anc[0],bytes_in_anc) + tup = (level,type, anc) + list_of_tuples.append(tup) + offset += bytes_in_anc + lltype.free(pre_anc[0], flavor='raw') + + if addrlen: + address.addrlen = addrlen + else: + address.unlock() + address = None + + rettup = (retmsg,list_of_tuples,returnflag,address) + + if address is not None: + address.unlock() + # free underlying complexity first + _c.freeccharp(file_descr) + _c.freesignedp(len_of_msgs) + _c.freesignedp(levels) + _c.freesignedp(types) + _c.freesignedp(descr_per_anc) + + lltype.free(messages[0], flavor='raw') + lltype.free(pre_anc,flavor='raw') + lltype.free(messages,flavor='raw') + lltype.free(file_descr,flavor='raw') + lltype.free(len_of_msgs,flavor='raw') + lltype.free(no_of_messages, flavor='raw') + lltype.free(size_of_anc, flavor='raw') + lltype.free(levels, flavor='raw') + lltype.free(types, flavor='raw') + lltype.free(descr_per_anc, flavor='raw') + lltype.free(retflag, flavor='raw') + lltype.free(addrlen_p,flavor='raw') + + return rettup + else: + + #in case of failure the underlying complexity has already been freed + lltype.free(messages[0], flavor='raw') + lltype.free(messages, flavor='raw') + lltype.free(file_descr, flavor='raw') + lltype.free(len_of_msgs, flavor='raw') + lltype.free(no_of_messages, flavor='raw') + lltype.free(size_of_anc, flavor='raw') + lltype.free(levels, flavor='raw') + lltype.free(types, flavor='raw') + lltype.free(descr_per_anc, flavor='raw') + lltype.free(retflag, flavor='raw') + lltype.free(addrlen_p, flavor='raw') + + if address is not None: + address.unlock() + if _c.geterrno() == _c.EINTR: + raise last_error() + if (reply == -10000): + raise RSocketError("Invalid message size") + if (reply == -10001): + raise RSocketError("Invalid ancillary data buffer length") + if (reply == -10002): + raise RSocketError("received malformed or improperly truncated ancillary data") + raise last_error() + + + def send_raw(self, dataptr, length, flags=0): """Send data from a CCHARP buffer.""" self.wait_for_data(True) @@ -1009,6 +1129,86 @@ raise self.error_handler() return res + @jit.dont_look_inside + def sendmsg(self, messages, ancillary=None, flags=0, address=None): + """ + Send data and ancillary on a socket. For use of ancillary data, please check the Unix manual. + Work on connectionless sockets via the address parameter. + :param messages: a message that is a list of strings + :param ancillary: data to be sent separate from the message body. Needs to be a list of tuples. + E.g. [(level,type, bytes),...]. Default None. + :param flags: the flag to be set for sendmsg. Please check the Unix manual regarding values. Default 0 + :param address: address of the recepient. Useful for when sending on connectionless sockets. Default None + :return: Bytes sent from the message + """ + need_to_free_address = True + if address is None: + need_to_free_address = False + addr = lltype.nullptr(_c.sockaddr) + addrlen = 0 + else: + addr = address.lock() + addrlen = address.addrlen + + no_of_messages = len(messages) + messages_ptr = lltype.malloc(rffi.CCHARPP.TO,no_of_messages+1,flavor='raw',track_allocation=True,nonmovable=False) + messages_length_ptr = lltype.malloc(rffi.SIGNEDP.TO,no_of_messages,flavor='raw',zero=True, track_allocation=True,nonmovable=False) + counter = 0 + for message in messages: + messages_ptr[counter] = rffi.str2charp(message) + messages_length_ptr[counter] = rffi.cast(rffi.SIGNED, len(message)) + counter += 1 + messages_ptr[counter] = lltype.nullptr(rffi.CCHARP.TO) + if ancillary is not None: + size_of_ancillary = len(ancillary) + else: + size_of_ancillary = 0 + levels = lltype.malloc(rffi.SIGNEDP.TO, size_of_ancillary,flavor='raw',zero=True, track_allocation=True,nonmovable=False) + types = lltype.malloc(rffi.SIGNEDP.TO, size_of_ancillary,flavor='raw',zero=True, track_allocation=True,nonmovable=False) + desc_per_ancillary = lltype.malloc(rffi.SIGNEDP.TO, size_of_ancillary,flavor='raw',zero=True, track_allocation=True,nonmovable=False) + file_descr = lltype.malloc(rffi.CCHARPP.TO, size_of_ancillary,flavor='raw', track_allocation=True,nonmovable=False) + if ancillary is not None: + counter = 0 + for level, type, content in ancillary: + assert isinstance(type,int) + assert isinstance(level, int) + levels[counter] = rffi.cast(rffi.SIGNED,level) + types[counter] = rffi.cast(rffi.SIGNED,type) + desc_per_ancillary[counter] = rffi.cast(rffi.SIGNED, (len(content))) + file_descr[counter] = rffi.str2charp(content, track_allocation=True) + counter +=1 + else: + size_of_ancillary = 0 + snd_no_msgs = rffi.cast(rffi.SIGNED, no_of_messages) + snd_anc_size =rffi.cast(rffi.SIGNED, size_of_ancillary) + + + bytes_sent = _c.sendmsg(self.fd, addr, addrlen, messages_length_ptr, messages_ptr, snd_no_msgs,levels,types,file_descr,desc_per_ancillary,snd_anc_size,flags) + + + if need_to_free_address: + address.unlock() + for i in range(len(messages)): + lltype.free(messages_ptr[i], flavor='raw', track_allocation=True) + lltype.free(messages_ptr, flavor='raw', track_allocation=True) + lltype.free(messages_length_ptr, flavor='raw', track_allocation=True) + + if size_of_ancillary > 0: + for i in range(len(ancillary)): + lltype.free(file_descr[i], flavor='raw', track_allocation=True) + lltype.free(desc_per_ancillary, flavor='raw', track_allocation=True) + lltype.free(types, flavor='raw', track_allocation=True) + lltype.free(levels, flavor='raw', track_allocation=True) + lltype.free(file_descr, flavor='raw', track_allocation=True) + + self.wait_for_data(True) + if (bytes_sent < 0) and (bytes_sent!=-1000) and (bytes_sent!=-1001) and (bytes_sent!=-1002): + raise last_error() + + return bytes_sent + + + def setblocking(self, block): if block: timeout = -1.0 @@ -1190,6 +1390,31 @@ return (make_socket(fd0, family, type, proto, SocketClass), make_socket(fd1, family, type, proto, SocketClass)) +if _c._POSIX: + def CMSG_LEN( demanded_len): + """ + Socket method to determine the optimal byte size of the ancillary. + Recommended to be used when computing the ancillary size for recvmsg. + :param demanded_len: an integer with the minimum size required. + :return: an integer with the minimum memory needed for the required size. The value is not memory alligned + """ + if demanded_len < 0: + return 0 + result = _c.CMSG_LEN(demanded_len) + return result + + def CMSG_SPACE( demanded_size): + """ + Socket method to determine the optimal byte size of the ancillary. + Recommended to be used when computing the ancillary size for recvmsg. + :param demanded_size: an integer with the minimum size required. + :return: an integer with the minimum memory needed for the required size. The value is memory alligned + """ + if demanded_size < 0: + return 0 + result = _c.CMSG_SPACE(demanded_size) + return result + if _c.WIN32: def dup(fd, inheritable=True): with lltype.scoped_alloc(_c.WSAPROTOCOL_INFO, zero=True) as info: diff --git a/rpython/rlib/rwin32file.py b/rpython/rlib/rwin32file.py --- a/rpython/rlib/rwin32file.py +++ b/rpython/rlib/rwin32file.py @@ -234,6 +234,12 @@ rwin32.BOOL, save_err=rffi.RFFI_SAVE_LASTERROR) + CreateHardLink = external( + 'CreateHardLink' + suffix, + [traits.CCHARP, traits.CCHARP, rwin32.LPSECURITY_ATTRIBUTES], + rwin32.BOOL, + save_err=rffi.RFFI_SAVE_LASTERROR) + return Win32Traits def make_longlong(high, low): diff --git a/rpython/rtyper/lltypesystem/rffi.py b/rpython/rtyper/lltypesystem/rffi.py --- a/rpython/rtyper/lltypesystem/rffi.py +++ b/rpython/rtyper/lltypesystem/rffi.py @@ -752,7 +752,8 @@ # Signed, Signed * SIGNED = lltype.Signed -SIGNEDP = lltype.Ptr(lltype.Array(SIGNED, hints={'nolength': True})) +SIGNEDP = lltype.Ptr(lltype.Array(lltype.Signed, hints={'nolength': True})) +SIGNEDPP = lltype.Ptr(lltype.Array(SIGNEDP, hints={'nolength': True})) # various type mapping diff --git a/rpython/translator/backendopt/graphanalyze.py b/rpython/translator/backendopt/graphanalyze.py --- a/rpython/translator/backendopt/graphanalyze.py +++ b/rpython/translator/backendopt/graphanalyze.py @@ -4,6 +4,7 @@ class GraphAnalyzer(object): verbose = False + explanation = None def __init__(self, translator): self.translator = translator @@ -73,6 +74,20 @@ def compute_graph_info(self, graph): return None + def explain_analyze_slowly(self, op): + # this is a hack! usually done before a crash + self.__init__(self.translator) + self.explanation = explanation = [] + oldverbose = self.verbose + self.verbose = True + try: + self.analyze(op) + finally: + del self.explanation + self.verbose = oldverbose + explanation.reverse() + return explanation + def analyze(self, op, seen=None, graphinfo=None): if op.opname == "direct_call": try: @@ -113,7 +128,11 @@ return x def dump_info(self, info): - print '[%s] %s' % (self.__class__.__name__, info) + st = '[%s] %s' % (self.__class__.__name__, info) + if self.explanation is not None: + self.explanation.append(st) + else: + print st def analyze_direct_call(self, graph, seen=None): if seen is None: diff --git a/rpython/translator/backendopt/test/test_writeanalyze.py b/rpython/translator/backendopt/test/test_writeanalyze.py --- a/rpython/translator/backendopt/test/test_writeanalyze.py +++ b/rpython/translator/backendopt/test/test_writeanalyze.py @@ -531,3 +531,37 @@ typed_effects = self._analyze_graph(t, wa, typed_write) typed_effects = self._filter_reads(typed_effects) assert typed_effects == direct_effects + + def test_explanation(self): + class A(object): + def methodname(self): + self.x = 1 + return 1 + def m(self): + raise ValueError + class B(A): + def methodname(self): + return 2 + def m(self): + return 3 + def fancyname(a): + return a.methodname() + def m(a): + return a.m() + def h(flag): + if flag: + obj = A() + else: _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit