diff options
45 files changed, 583 insertions, 117 deletions
diff --git a/extra_tests/ctypes_tests/test_structures.py b/extra_tests/ctypes_tests/test_structures.py index b6bd075942..fa6ed900ae 100644 --- a/extra_tests/ctypes_tests/test_structures.py +++ b/extra_tests/ctypes_tests/test_structures.py @@ -1,6 +1,7 @@ from ctypes import * import pytest +import sys def test_subclass_initializer(): @@ -207,6 +208,9 @@ def test_memoryview(): ) mv = memoryview(c_array) - assert mv.format == 'T{<h:a:<h:b:}' + if sys.byteorder == 'little': + assert mv.format == 'T{<h:a:<h:b:}' + else: + assert mv.format == 'T{>h:a:>h:b:}' assert mv.shape == (2, 3) assert mv.itemsize == 4 diff --git a/lib_pypy/tools/build_cffi_imports.py b/lib_pypy/tools/build_cffi_imports.py index 489dfb5ea8..52563b2ed5 100644 --- a/lib_pypy/tools/build_cffi_imports.py +++ b/lib_pypy/tools/build_cffi_imports.py @@ -196,6 +196,8 @@ def create_cffi_import_libraries(pypy_c, options, basedir, only=None, except: import traceback;traceback.print_exc() failures.append((key, module)) + if os.path.exists(deps_destdir): + shutil.rmtree(deps_destdir, ignore_errors=True) return failures if __name__ == '__main__': diff --git a/pypy/doc/index.rst b/pypy/doc/index.rst index 88f036d3ab..ebb2a26e00 100644 --- a/pypy/doc/index.rst +++ b/pypy/doc/index.rst @@ -106,7 +106,7 @@ Meeting PyPy developers .. _here: https://quodlibet.duckdns.org/irc/pypy/latest.log.html#irc-end .. _Development mailing list: http://mail.python.org/mailman/listinfo/pypy-dev .. _Commit mailing list: http://mail.python.org/mailman/listinfo/pypy-commit -.. _Development bug/feature tracker: https://bitbucket.org/pypy/pypy/issues +.. _Development bug/feature tracker: https://foss.heptapod.net/pypy/pypy/issues Indices and tables diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst index 31c3155cbb..473147a271 100644 --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -21,6 +21,16 @@ Fixes incorrect error text for ``int('1', base=1000)`` adapt contributing documentation to heptapod +.. branch: pypy-jitdriver-greenkeys + +Improve code generation for generators (and generator expressions in +particular) when passing them to builtin functions that consume iterators, such +as ``sum``, ``map``, ``max``, etc. + +.. branch: warmup-improvements-various + +Improves warmup time by up to 20%. + .. branch: StringIO-perf Improve performance of io.StringIO(). It should now be faster than CPython in diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py index 98b6144ae0..6a7f3a5339 100644 --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -22,12 +22,12 @@ from pypy.interpreter.miscutils import ThreadLocals, make_weak_value_dictionary __all__ = ['ObjSpace', 'OperationError', 'W_Root'] -def get_printable_location(tp): - return "unpackiterable: %s" % (tp, ) +def get_printable_location(greenkey): + return "unpackiterable [%s]" % (greenkey.iterator_greenkey_printable(), ) unpackiterable_driver = jit.JitDriver(name='unpackiterable', - greens=['tp'], - reds=['items', 'w_iterator'], + greens=['greenkey'], + reds='auto', get_printable_location=get_printable_location) @@ -373,6 +373,15 @@ class W_Root(object): return lst[:] return None + def iterator_greenkey(self, space): + """ Return something that can be used as a green key in jit drivers + that iterate over self. by default, it's just the type of self, but + custom iterators should override it. """ + return space.type(self) + + def iterator_greenkey_printable(self): + return "?" + class InternalSpaceCache(Cache): """A generic cache for an object space. Arbitrary information can @@ -966,11 +975,9 @@ class ObjSpace(object): except MemoryError: items = [] # it might have lied - tp = self.type(w_iterator) + greenkey = self.iterator_greenkey(w_iterator) while True: - unpackiterable_driver.jit_merge_point(tp=tp, - w_iterator=w_iterator, - items=items) + unpackiterable_driver.jit_merge_point(greenkey=greenkey) try: w_item = self.next(w_iterator) except OperationError as e: @@ -2025,6 +2032,12 @@ class ObjSpace(object): finally: self.sys.track_resources = flag + def iterator_greenkey(self, w_iterable): + """ Return something that can be used as a green key in jit drivers + that iterate over self. by default, it's just the type of self, but + custom iterators should override it. """ + return w_iterable.iterator_greenkey(self) + class AppExecCache(SpaceCache): @not_rpython diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py index c1c9e8aab8..ddb47ad51a 100644 --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -23,8 +23,6 @@ class ExecutionContext(object): # XXX [fijal] but they're not. is_being_profiled is guarded a bit all # over the place as well as w_tracefunc - _immutable_fields_ = ['profilefunc?', 'w_tracefunc?'] - def __init__(self, space): self.space = space self.topframeref = jit.vref_None diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py index c7f73fe9a6..62fc71cd8e 100644 --- a/pypy/interpreter/generator.py +++ b/pypy/interpreter/generator.py @@ -231,6 +231,9 @@ return next yielded value or raise StopIteration.""" self.frame = None rgc.may_ignore_finalizer(self) + def iterator_greenkey(self, space): + return self.pycode + def get_printable_location_genentry(bytecode): return '%s <generator>' % (bytecode.get_repr(),) diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py index 3d6438fa5f..6cbc67b595 100644 --- a/pypy/interpreter/pycode.py +++ b/pypy/interpreter/pycode.py @@ -425,6 +425,9 @@ class PyCode(eval.Code): return "<code object %s, file '%s', line %d>" % ( self.co_name, self.co_filename, self.co_firstlineno) + def iterator_greenkey_printable(self): + return self.get_repr() + def __repr__(self): return self.get_repr() diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py index 2944f230ab..365a4a3b86 100644 --- a/pypy/interpreter/pyframe.py +++ b/pypy/interpreter/pyframe.py @@ -310,9 +310,12 @@ class PyFrame(W_Root): assert self.locals_cells_stack_w[depth] is None self.valuestackdepth = depth + 1 + def assert_stack_index(self, index): + if we_are_translated(): + return + assert self._check_stack_index(index) + def _check_stack_index(self, index): - # will be completely removed by the optimizer if only used in an assert - # and if asserts are disabled code = self.pycode ncellvars = len(code.co_cellvars) nfreevars = len(code.co_freevars) @@ -324,7 +327,7 @@ class PyFrame(W_Root): def popvalue_maybe_none(self): depth = self.valuestackdepth - 1 - assert self._check_stack_index(depth) + self.assert_stack_index(depth) assert depth >= 0 w_object = self.locals_cells_stack_w[depth] self.locals_cells_stack_w[depth] = None @@ -353,7 +356,7 @@ class PyFrame(W_Root): def peekvalues(self, n): values_w = [None] * n base = self.valuestackdepth - n - assert self._check_stack_index(base) + self.assert_stack_index(base) assert base >= 0 while True: n -= 1 @@ -366,7 +369,7 @@ class PyFrame(W_Root): def dropvalues(self, n): n = hint(n, promote=True) finaldepth = self.valuestackdepth - n - assert self._check_stack_index(finaldepth) + self.assert_stack_index(finaldepth) assert finaldepth >= 0 while True: n -= 1 @@ -402,14 +405,14 @@ class PyFrame(W_Root): def peekvalue_maybe_none(self, index_from_top=0): index_from_top = hint(index_from_top, promote=True) index = self.valuestackdepth + ~index_from_top - assert self._check_stack_index(index) + self.assert_stack_index(index) assert index >= 0 return self.locals_cells_stack_w[index] def settopvalue(self, w_object, index_from_top=0): index_from_top = hint(index_from_top, promote=True) index = self.valuestackdepth + ~index_from_top - assert self._check_stack_index(index) + self.assert_stack_index(index) assert index >= 0 self.locals_cells_stack_w[index] = ll_assert_not_none(w_object) diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py index 1e1f172afc..dfbd2a5dd2 100644 --- a/pypy/interpreter/pyopcode.py +++ b/pypy/interpreter/pyopcode.py @@ -147,10 +147,12 @@ class __extend__(pyframe.PyFrame): while True: self.last_instr = intmask(next_instr) if jit.we_are_jitted(): - ec.bytecode_only_trace(self) + if self.debugdata: + ec.bytecode_only_trace(self) + next_instr = r_uint(self.last_instr) else: ec.bytecode_trace(self) - next_instr = r_uint(self.last_instr) + next_instr = r_uint(self.last_instr) opcode = ord(co_code[next_instr]) next_instr += 1 @@ -176,10 +178,13 @@ class __extend__(pyframe.PyFrame): oparg = (oparg * 65536) | (hi * 256) | lo if opcode == opcodedesc.RETURN_VALUE.index: + if not self.blockstack_non_empty(): + self.frame_finished_execution = True # for generators + raise Return w_returnvalue = self.popvalue() block = self.unrollstack(SReturnValue.kind) if block is None: - self.pushvalue(w_returnvalue) # XXX ping pong + self.pushvalue(w_returnvalue) raise Return else: unroller = SReturnValue(w_returnvalue) diff --git a/pypy/module/__builtin__/functional.py b/pypy/module/__builtin__/functional.py index 9106ee410c..cf6fc81c06 100644 --- a/pypy/module/__builtin__/functional.py +++ b/pypy/module/__builtin__/functional.py @@ -131,10 +131,21 @@ def range_with_longs(space, w_start, w_stop, w_step): v = v.add(step) return space.newlist(res_w) +def get_printable_location(has_key, has_item, greenkey): + return "min [has_key=%s, has_item=%s, %s]" % ( + has_key, has_item, greenkey.iterator_greenkey_printable()) + min_jitdriver = jit.JitDriver(name='min', - greens=['has_key', 'has_item', 'w_type'], reds='auto') + greens=['has_key', 'has_item', 'greenkey'], reds='auto', + get_printable_location=get_printable_location) + +def get_printable_location(has_key, has_item, greenkey): + return "min [has_key=%s, has_item=%s, %s]" % ( + has_key, has_item, greenkey.iterator_greenkey_printable()) + max_jitdriver = jit.JitDriver(name='max', - greens=['has_key', 'has_item', 'w_type'], reds='auto') + greens=['has_key', 'has_item', 'greenkey'], reds='auto', + get_printable_location=get_printable_location) @specialize.arg(3) def min_max_sequence(space, w_sequence, w_key, implementation_of): @@ -145,14 +156,14 @@ def min_max_sequence(space, w_sequence, w_key, implementation_of): compare = space.lt jitdriver = min_jitdriver w_iter = space.iter(w_sequence) - w_type = space.type(w_iter) + greenkey = space.iterator_greenkey(w_iter) has_key = w_key is not None has_item = False w_max_item = None w_max_val = None while True: jitdriver.jit_merge_point(has_key=has_key, has_item=has_item, - w_type=w_type) + greenkey=greenkey) try: w_item = space.next(w_iter) except OperationError as e: diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py index 19ad6ab823..58927af9b8 100644 --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -72,7 +72,8 @@ def compare_arrays(space, arr1, arr2, comp_op): if comp_op == NE and arr1.len != arr2.len: return space.w_True lgt = min(arr1.len, arr2.len) - for i in range(lgt): + i = 0 + while i < lgt: arr_eq_driver.jit_merge_point(comp_func=comp_op) w_elem1 = arr1.w_getitem(space, i, integer_instead_of_char=True) w_elem2 = arr2.w_getitem(space, i, integer_instead_of_char=True) @@ -102,6 +103,7 @@ def compare_arrays(space, arr1, arr2, comp_op): return space.w_False elif not space.is_true(space.eq(w_elem1, w_elem2)): return space.w_True + i += 1 # we have some leftovers if comp_op == EQ: return space.w_True @@ -128,7 +130,8 @@ def index_count_array(arr, w_val, count=False): tp_item = space.type(w_val) arrclass = arr.__class__ cnt = 0 - for i in range(arr.len): + i = 0 + while i < arr.len: index_count_jd.jit_merge_point( tp_item=tp_item, count=count, arrclass=arrclass) @@ -138,6 +141,7 @@ def index_count_array(arr, w_val, count=False): cnt += 1 else: return i + i += 1 if count: return cnt return -1 diff --git a/pypy/module/cpyext/test/test_abstract.py b/pypy/module/cpyext/test/test_abstract.py index 8649f880bd..ee7b3e927d 100644 --- a/pypy/module/cpyext/test/test_abstract.py +++ b/pypy/module/cpyext/test/test_abstract.py @@ -127,4 +127,29 @@ class AppTestBufferProtocol(AppTestCpythonExtensionBase): assert raises(TypeError, buffer_support.writebuffer_as_string, buf) assert s == buffer_support.charbuffer_as_string(buf) - + def test_user_bufferable(self): + try: + import __pypy__ + except ImportError: + skip('PyPy only test') + + class MyBuf(__pypy__.bufferable.bufferable): + def __init__(self, mview): + if not isinstance(mview, memoryview): + raise ValueError('mview must be a memoryview') + self.mview = mview + + def __buffer__(self, flags): + if flags & 1 and self.buffer.readonly: + raise TypeError('cannot return a writable buffer') + return __pypy__.newmemoryview(self.mview, self.mview.itemsize, + self.mview.format, self.mview.shape) + + s = b'abc' + m = memoryview(s) + buf = MyBuf(m) + buffer_support = self.get_buffer_support() + assert buffer_support.check_readbuffer(buf) + assert s == buffer_support.readbuffer_as_string(buf) + assert raises(TypeError, buffer_support.writebuffer_as_string, buf) + assert s == buffer_support.charbuffer_as_string(buf) diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py index b65582a79b..5300214b05 100644 --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -156,6 +156,32 @@ class AppTestUnicodeObject(AppTestCpythonExtensionBase): s = module.asutf32(u) assert s == u.encode('utf-32') + def test_lower_cython(self): + # mimic exactly what cython does, without the extra checks + import time + module = self.import_extension('foo', [ + ("lower", "METH_O", + """ + PyObject *p, *res, *tup; + p = PyObject_GetAttrString(args, "lower"); + if (p == NULL) { + return NULL; + } + tup = PyTuple_New(0); + Py_INCREF(tup); + res = PyObject_Call(p, tup, NULL); + Py_DECREF(tup); + return res; + """)]) + assert module.lower('ABC') == 'abc' + try: + time.tzset() + except AttributeError: + # only on posix + pass + tz1 = time.tzname[1] + assert module.lower(tz1) == tz1.lower() + class TestUnicode(BaseApiTest): def test_unicodeobject(self, space): diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py index 388c689096..d0b5a3b6c4 100644 --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -595,8 +595,9 @@ def bf_getreadbuffer(space, w_buf, segment, ref): raise oefmt(space.w_SystemError, "accessing non-existent segment") buf = space.readbuf_w(w_buf) - if isinstance(buf, StringBuffer): - return str_getreadbuffer(space, w_buf, segment, ref) + # if isinstance(buf, StringBuffer): + # # Link the data pointer of buf to ref[0] + # return _str_getreadbuffer(space, w_buf, segment, ref) address = buf.get_raw_address() ref[0] = address return len(buf) @@ -616,6 +617,9 @@ def bf_getwritebuffer(space, w_buf, segment, ref): @slot_function([PyObject, Py_ssize_t, rffi.VOIDPP], lltype.Signed, error=-1) def str_getreadbuffer(space, w_str, segment, ref): + return _str_getreadbuffer(space, w_str, segment, ref) + +def _str_getreadbuffer(space, w_str, segment, ref): from pypy.module.cpyext.bytesobject import PyString_AsString if segment != 0: raise oefmt(space.w_SystemError, @@ -641,7 +645,7 @@ def unicode_getreadbuffer(space, w_str, segment, ref): @slot_function([PyObject, Py_ssize_t, rffi.CCHARPP], lltype.Signed, error=-1) def str_getcharbuffer(space, w_buf, segment, ref): - return str_getreadbuffer(space, w_buf, segment, rffi.cast(rffi.VOIDPP, ref)) + return _str_getreadbuffer(space, w_buf, segment, rffi.cast(rffi.VOIDPP, ref)) @slot_function([PyObject, Py_ssize_t, rffi.VOIDPP], lltype.Signed, error=-1) def buf_getreadbuffer(space, pyref, segment, ref): diff --git a/pypy/module/itertools/interp_itertools.py b/pypy/module/itertools/interp_itertools.py index ac667c983b..116fa5371c 100644 --- a/pypy/module/itertools/interp_itertools.py +++ b/pypy/module/itertools/interp_itertools.py @@ -322,10 +322,12 @@ W_IFilterFalse.typedef = TypeDef( """) +def get_printable_location(greenkey): + return "islice_ignore_items [%s]" % (greenkey.iterator_greenkey_printable(), ) islice_ignore_items_driver = jit.JitDriver(name='islice_ignore_items', - greens=['tp'], - reds=['num', 'w_islice', - 'w_iterator']) + greens=['greenkey'], + reds='auto', + get_printable_location=get_printable_location) class W_ISlice(W_Root): def __init__(self, space, w_iterable, w_startstop, args_w): @@ -418,12 +420,9 @@ class W_ISlice(W_Root): if w_iterator is None: raise OperationError(self.space.w_StopIteration, self.space.w_None) - tp = self.space.type(w_iterator) + greenkey = self.space.iterator_greenkey(w_iterator) while True: - islice_ignore_items_driver.jit_merge_point(tp=tp, - num=num, - w_islice=self, - w_iterator=w_iterator) + islice_ignore_items_driver.jit_merge_point(greenkey=greenkey) try: self.space.next(w_iterator) except OperationError as e: diff --git a/pypy/module/pypyjit/test_pypy_c/test_generators.py b/pypy/module/pypyjit/test_pypy_c/test_generators.py index d1f11eec52..313c438461 100644 --- a/pypy/module/pypyjit/test_pypy_c/test_generators.py +++ b/pypy/module/pypyjit/test_pypy_c/test_generators.py @@ -61,3 +61,13 @@ class TestGenerators(BaseTestPyPyC): i2 = int_sub_ovf(i1, 42) guard_no_overflow(descr=...) """) + + def test_nonstd_jitdriver_distinguishes_generators(self): + def main(): + # test the "contains" jitdriver, but the others are the same + res = (9999 in (i for i in range(20000))) + res += (9999 in (i for i in range(20000))) + return res + log = self.run(main, []) + assert len(log.loops) == 2 # as opposed to one loop, one bridge + diff --git a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py index 12820c75da..e31287f77f 100644 --- a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py +++ b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py @@ -153,6 +153,7 @@ class TestMicroNumPy(BaseTestPyPyC): guard_class(p1, #, descr=...) p4 = getfield_gc_r(p1, descr=<FieldP pypy.module.micronumpy.iterators.ArrayIter.inst_array \d+ pure>) i5 = getfield_gc_i(p0, descr=<FieldS pypy.module.micronumpy.iterators.IterState.inst_offset \d+>) + guard_not_invalidated(descr=...) p6 = getfield_gc_r(p4, descr=<FieldP pypy.module.micronumpy.concrete.BaseConcreteArray.inst_dtype \d+ pure>) p7 = getfield_gc_r(p6, descr=<FieldP pypy.module.micronumpy.descriptor.W_Dtype.inst_itemtype \d+ pure>) guard_class(p7, ConstClass(Float64), descr=...) @@ -164,7 +165,6 @@ class TestMicroNumPy(BaseTestPyPyC): %(align_check)s f16 = raw_load_f(i9, i5, descr=<ArrayF \d+>) guard_true(i15, descr=...) - guard_not_invalidated(descr=...) i18 = float_ne(f16, 0.000000) guard_true(i18, descr=...) guard_nonnull_class(p2, ConstClass(W_BoolBox), descr=...) @@ -207,8 +207,8 @@ class TestMicroNumPy(BaseTestPyPyC): loop = log._filter(log.loops[0]) loop.match(""" %(align_check)s - f31 = raw_load_f(i9, i29, descr=<ArrayF 8>) guard_not_invalidated(descr=...) + f31 = raw_load_f(i9, i29, descr=<ArrayF 8>) i32 = float_ne(f31, 0.000000) guard_true(i32, descr=...) i36 = int_add(i24, 1) @@ -255,6 +255,7 @@ class TestMicroNumPy(BaseTestPyPyC): assert loop.match(""" i76 = int_lt(i71, 300) guard_true(i76, descr=...) + guard_not_invalidated(descr=...) i77 = int_ge(i71, i59) guard_false(i77, descr=...) i78 = int_mul(i71, i61) @@ -294,13 +295,13 @@ class TestMicroNumPy(BaseTestPyPyC): assert loop.match(""" i81 = int_lt(i76, 300) guard_true(i81, descr=...) + guard_not_invalidated(descr=...) i82 = int_ge(i76, i62) guard_false(i82, descr=...) i83 = int_mul(i76, i64) i84 = int_add(i58, i83) """ + alignment_check + """ f85 = raw_load_f(i70, i84, descr=<ArrayF 8>) - guard_not_invalidated(descr=...) f86 = float_add(f74, f85) i87 = int_add(i76, 1) --TICK-- @@ -323,9 +324,9 @@ class TestMicroNumPy(BaseTestPyPyC): assert log.result == 42.0 loop, = log.loops_by_filename(self.filepath) assert loop.match(""" + guard_not_invalidated(descr=...) i86 = int_lt(i79, i45) guard_true(i86, descr=...) - guard_not_invalidated(descr=...) i88 = int_ge(i87, i59) guard_false(i88, descr=...) %(align_check)s @@ -348,27 +349,19 @@ class TestMicroNumPy(BaseTestPyPyC): ai = arr.flat i = 0 while i < arr.size: - a = ai[i] + a = ai[i] # ID: getitem i += 1 return a log = self.run(main, []) assert log.result == 42.0 loop, = log.loops_by_filename(self.filepath) - assert loop.match(""" - i125 = int_lt(i117, i44) - guard_true(i125, descr=...) + assert loop.match_by_id("getitem", """ i126 = int_lt(i117, i50) guard_true(i126, descr=...) i128 = int_mul(i117, i59) i129 = int_add(i55, i128) %(align_check)s f149 = raw_load_f(i100, i129, descr=<ArrayF 8>) - i151 = int_add(i117, 1) - setfield_gc(p156, i55, descr=<FieldS pypy.module.micronumpy.iterators.IterState.inst_offset .+>) - setarrayitem_gc(p150, 1, 0, descr=<ArrayS .+>) - setarrayitem_gc(p150, 0, 0, descr=<ArrayS .+>) - --TICK-- - jump(..., descr=...) """ % {'align_check': align_check('i129')}) def test_array_flatiter_setitem_single(self): @@ -385,13 +378,13 @@ class TestMicroNumPy(BaseTestPyPyC): assert log.result == 42.0 loop, = log.loops_by_filename(self.filepath) assert loop.match(""" + guard_not_invalidated(descr=...) i128 = int_lt(i120, i42) guard_true(i128, descr=...) i129 = int_lt(i120, i48) guard_true(i129, descr=...) i131 = int_mul(i120, i57) i132 = int_add(i53, i131) - guard_not_invalidated(descr=...) %(align_check)s raw_store(i103, i132, 42.000000, descr=<ArrayF 8>) i153 = int_add(i120, 1) @@ -422,13 +415,13 @@ class TestMicroNumPy(BaseTestPyPyC): guard_false(i92, descr=...) i93 = int_add(i91, 1) setfield_gc(p23, i93, descr=<FieldS pypy.objspace.std.iterobject.W_AbstractSeqIterObject.inst_index 8>) + guard_not_invalidated(descr=...) i94 = int_ge(i91, i56) guard_false(i94, descr=...) i96 = int_mul(i91, i58) i97 = int_add(i51, i96) %(align_check)s f98 = raw_load_f(i63, i97, descr=<ArrayF 8>) - guard_not_invalidated(descr=...) f100 = float_mul(f98, 0.500000) i101 = int_add(i79, 1) i102 = arraylen_gc(p85, descr=<ArrayP .>) diff --git a/pypy/module/time/interp_time.py b/pypy/module/time/interp_time.py index f4c6b4f023..e6e9934ec6 100644 --- a/pypy/module/time/interp_time.py +++ b/pypy/module/time/interp_time.py @@ -236,11 +236,11 @@ def _init_timezone(space): timezone = c_get_timezone() altzone = timezone - 3600 daylight = c_get_daylight() - with rffi.scoped_alloc_buffer(100) as buf: - s = c_get_tzname(100, 0, buf.raw) - tzname[0] = buf.str(s) - s = c_get_tzname(100, 1, buf.raw) - tzname[1] = buf.str(s) + for i in [0, 1]: + blen = c_get_tzname(0, i, None) + with rffi.scoped_alloc_buffer(blen) as buf: + s = c_get_tzname(blen, i, buf.raw) + tzname[i] = buf.str(s - 1) if _POSIX: if _CYGWIN: diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py index 1cfe23136b..32ec45addd 100644 --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -124,8 +124,14 @@ class Object(object): def descr__init__(space, w_obj, __args__): pass +def get_printable_location(itergreenkey, w_itemtype): + return "DescrOperation.contains [%s, %s]" % ( + itergreenkey.iterator_greenkey_printable(), + w_itemtype.getname(w_itemtype.space)) + contains_jitdriver = jit.JitDriver(name='contains', - greens=['w_type'], reds='auto') + greens=['itergreenkey', 'w_itemtype'], reds='auto', + get_printable_location=get_printable_location) class DescrOperation(object): # This is meant to be a *mixin*. @@ -406,9 +412,10 @@ class DescrOperation(object): def _contains(space, w_container, w_item): w_iter = space.iter(w_container) - w_type = space.type(w_iter) + itergreenkey = space.iterator_greenkey(w_iter) + w_itemtype = space.type(w_item) while 1: - contains_jitdriver.jit_merge_point(w_type=w_type) + contains_jitdriver.jit_merge_point(itergreenkey=itergreenkey, w_itemtype=w_itemtype) try: w_next = space.next(w_iter) except OperationError as e: diff --git a/pypy/objspace/std/bytearrayobject.py b/pypy/objspace/std/bytearrayobject.py index fca124e120..9cecb9a22f 100644 --- a/pypy/objspace/std/bytearrayobject.py +++ b/pypy/objspace/std/bytearrayobject.py @@ -561,14 +561,14 @@ def makebytearraydata_w(space, w_source): return list(buf.as_str()) return _from_byte_sequence(space, w_source) -def _get_printable_location(w_type): - return ('bytearray_from_byte_sequence [w_type=%s]' % - w_type.getname(w_type.space)) +def _get_printable_location(greenkey): + return ('bytearray_from_byte_sequence [%s]' % + greenkey.iterator_greenkey_printable()) _byteseq_jitdriver = jit.JitDriver( name='bytearray_from_byte_sequence', - greens=['w_type'], - reds=['w_iter', 'data'], + greens=['greenkey'], + reds='auto', get_printable_location=_get_printable_location) def _from_byte_sequence(space, w_source): @@ -586,11 +586,9 @@ def _from_byte_sequence(space, w_source): return data def _from_byte_sequence_loop(space, w_iter, data): - w_type = space.type(w_iter) + greenkey = space.iterator_greenkey(w_iter) while True: - _byteseq_jitdriver.jit_merge_point(w_type=w_type, - w_iter=w_iter, - data=data) + _byteseq_jitdriver.jit_merge_point(greenkey=greenkey) try: w_item = space.next(w_iter) except OperationError as e: diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py index 1afe562307..2dc9ae8d97 100644 --- a/pypy/objspace/std/listobject.py +++ b/pypy/objspace/std/listobject.py @@ -131,26 +131,26 @@ def get_strategy_from_list_objects(space, list_w, sizehint): return space.fromcache(ObjectListStrategy) -def _get_printable_location(w_type): - return ('list__do_extend_from_iterable [w_type=%s]' % - w_type.getname(w_type.space)) +def _get_printable_location(strategy_type, greenkey): + return 'list__do_extend_from_iterable [%s, %s]' % ( + strategy_type, + greenkey.iterator_greenkey_printable()) _do_extend_jitdriver = jit.JitDriver( name='list__do_extend_from_iterable', - greens=['w_type'], - reds=['i', 'w_iterator', 'w_list'], + greens=['strategy_type', 'greenkey'], + reds='auto', get_printable_location=_get_printable_location) def _do_extend_from_iterable(space, w_list, w_iterable): w_iterator = space.iter(w_iterable) - w_type = space.type(w_iterator) + greenkey = space.iterator_greenkey(w_iterator) i = 0 while True: - _do_extend_jitdriver.jit_merge_point(w_type=w_type, - i=i, - w_iterator=w_iterator, - w_list=w_list) + _do_extend_jitdriver.jit_merge_point( + greenkey=greenkey, + strategy_type=type(w_list.strategy)) try: w_list.append(space.next(w_iterator)) except OperationError as e: @@ -749,7 +749,9 @@ class W_ListObject(W_Root): if mucked: raise oefmt(space.w_ValueError, "list modified during sort") -find_jmp = jit.JitDriver(greens = ['tp'], reds = 'auto', name = 'list.find') +def get_printable_location(strategy_type, tp): + return "list.find [%s, %s]" % (strategy_type, tp.getname(tp.space), ) +find_jmp = jit.JitDriver(greens=['strategy_type', 'tp'], reds='auto', name='list.find', get_printable_location=get_printable_location) class ListStrategy(object): @@ -777,7 +779,7 @@ class ListStrategy(object): # needs to be safe against eq_w mutating stuff tp = space.type(w_item) while i < stop and i < w_list.length(): - find_jmp.jit_merge_point(tp=tp) + find_jmp.jit_merge_point(tp=tp, strategy_type=type(self)) if space.eq_w(w_item, w_list.getitem(i)): return i i += 1 diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py index 235cab2a82..605627dc95 100644 --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -1681,7 +1681,7 @@ def _pick_correct_strategy_unroll(space, w_set, w_iterable): def get_printable_location(tp, strategy): - return "create_set: %s %s" % (tp, strategy) + return "create_set: %s %s" % (tp.iterator_greenkey_printable(), strategy) create_set_driver = jit.JitDriver(name='create_set', greens=['tp', 'strategy'], @@ -1692,7 +1692,7 @@ def _create_from_iterable(space, w_set, w_iterable): w_set.strategy = strategy = space.fromcache(EmptySetStrategy) w_set.sstorage = strategy.get_empty_storage() - tp = space.type(w_iterable) + tp = space.iterator_greenkey(w_iterable) w_iter = space.iter(w_iterable) while True: diff --git a/pypy/objspace/std/tupleobject.py b/pypy/objspace/std/tupleobject.py index cbdae7efe6..d45521ebec 100644 --- a/pypy/objspace/std/tupleobject.py +++ b/pypy/objspace/std/tupleobject.py @@ -27,13 +27,22 @@ def _unroll_condition_cmp(self, space, other): jit.loop_unrolling_heuristic(other, other.length(), UNROLL_CUTOFF)) -contains_jmp = jit.JitDriver(greens = ['tp'], reds = 'auto', - name = 'tuple.contains') +def get_printable_location(tp): + return "tuple.contains [%s]" % (tp.getname(tp.space), ) + +contains_driver = jit.JitDriver(greens = ['tp'], reds = 'auto', + name = 'tuple.contains', + get_printable_location=get_printable_location) + +def get_printable_location(w_type): + return "tuple.hash [%s]" % (w_type.getname(w_type.space), ) hash_driver = jit.JitDriver( name='tuple.hash', greens=['w_type'], - reds='auto') + reds='auto', + get_printable_location=get_printable_location + ) class W_AbstractTupleObject(W_Root): __slots__ = () @@ -159,10 +168,14 @@ class W_AbstractTupleObject(W_Root): def _descr_contains_jmp(self, space, w_obj): tp = space.type(w_obj) - for w_item in self.tolist(): - contains_jmp.jit_merge_point(tp=tp) + list_w = self.tolist() + i = 0 + while i < len(list_w): + w_item = list_w[i] + contains_driver.jit_merge_point(tp=tp) if space.eq_w(w_obj, w_item): return space.w_True + i += 1 return space.w_False def descr_add(self, space, w_other): @@ -307,12 +320,16 @@ class W_TupleObject(W_AbstractTupleObject): x = 0x345678 z = len(self.wrappeditems) w_type = space.type(self.wrappeditems[0]) - for w_item in self.wrappeditems: + wrappeditems = self.wrappeditems + i = 0 + while i < len(wrappeditems): hash_driver.jit_merge_point(w_type=w_type) + w_item = wrappeditems[i] y = space.hash_w(w_item) x = (x ^ y) * mult z -= 1 mult += 82520 + z + z + i += 1 x += 97531 return space.newint(intmask(x)) diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py index 039b3d5881..3d042356a4 100644 --- a/pypy/objspace/std/typeobject.py +++ b/pypy/objspace/std/typeobject.py @@ -711,6 +711,9 @@ class W_TypeObject(W_Root): else: return space.newtext("<%s '%s'>" % (kind, self.name)) + def iterator_greenkey_printable(self): + return self.name + def descr_getattribute(self, space, w_name): name = space.text_w(w_name) w_descr = space.lookup(self, name) diff --git a/rpython/jit/backend/zarch/test/test_regalloc.py b/rpython/jit/backend/zarch/test/test_regalloc.py index 9f8655cf8b..e8ff7fc686 100644 --- a/rpython/jit/backend/zarch/test/test_regalloc.py +++ b/rpython/jit/backend/zarch/test/test_regalloc.py @@ -13,6 +13,9 @@ CPU = getcpuclass() class FakeAssembler(object): def __init__(self): self.move_count = 0 + self.num_spills = 0 + self.num_spills_to_existing = 0 + def regalloc_mov(self, f, t): self.move_count += 1 diff --git a/rpython/jit/metainterp/heapcache.py b/rpython/jit/metainterp/heapcache.py index d643907182..0d46b4e120 100644 --- a/rpython/jit/metainterp/heapcache.py +++ b/rpython/jit/metainterp/heapcache.py @@ -1,5 +1,6 @@ from rpython.jit.metainterp.history import Const, ConstInt from rpython.jit.metainterp.history import FrontendOp, RefFrontendOp +from rpython.jit.metainterp.history import new_ref_dict from rpython.jit.metainterp.resoperation import rop, OpHelpers from rpython.jit.metainterp.executor import constant_from_op from rpython.rlib.rarithmetic import r_uint32, r_uint @@ -61,12 +62,17 @@ class CacheEntry(object): # on writes to the field. self.quasiimmut_seen = None + # set of refs for *constants* that we've seen a quasi-immut field on. + self.quasiimmut_seen_refs = None + def _clear_cache_on_write(self, seen_allocation_of_target): if not seen_allocation_of_target: self.cache_seen_allocation.clear() self.cache_anything.clear() if self.quasiimmut_seen is not None: self.quasiimmut_seen.clear() + if self.quasiimmut_seen_refs is not None: + self.quasiimmut_seen_refs.clear() def _seen_alloc(self, ref_box): if not isinstance(ref_box, RefFrontendOp): @@ -100,6 +106,8 @@ class CacheEntry(object): self._invalidate_unescaped(self.cache_seen_allocation) if self.quasiimmut_seen is not None: self.quasiimmut_seen.clear() + if self.quasiimmut_seen_refs is not None: + self.quasiimmut_seen_refs.clear() def _invalidate_unescaped(self, d): for ref_box in d.keys(): @@ -162,6 +170,12 @@ class HeapCache(object): # heap array cache # maps descrs to {index: CacheEntry} dicts self.heap_array_cache = {} + self.need_guard_not_invalidated = True + + # result of one loop invariant call + self.loop_invariant_result = None + self.loop_invariant_descr = None + self.loop_invariant_arg0int = -1 def reset_keep_likely_virtuals(self): # Update only 'head_version', but 'likely_virtual_version' remains @@ -272,6 +286,7 @@ class HeapCache(object): rop._NOSIDEEFFECT_FIRST <= opnum <= rop._NOSIDEEFFECT_LAST or rop._GUARD_FIRST <= opnum <= rop._GUARD_LAST): return + self.need_guard_not_invalidated = True # can do better, but good start if (OpHelpers.is_plain_call(opnum) or OpHelpers.is_call_loopinvariant(opnum) or OpHelpers.is_cond_call_value(opnum) or @@ -387,7 +402,7 @@ class HeapCache(object): self._set_flag(box, HF_KNOWN_NULLITY) def is_nonstandard_virtualizable(self, box): - return self._check_flag(box, HF_NONSTD_VABLE) + return self._check_flag(box, HF_NONSTD_VABLE) or self._check_flag(box, HF_SEEN_ALLOCATION) def nonstandard_virtualizables_now_known(self, box): self._set_flag(box, HF_NONSTD_VABLE) @@ -500,15 +515,37 @@ class HeapCache(object): def is_quasi_immut_known(self, fielddescr, box): cache = self.heap_cache.get(fielddescr, None) - if cache is not None and cache.quasiimmut_seen is not None: - return box in cache.quasiimmut_seen + if cache is not None: + if isinstance(box, Const): + if cache.quasiimmut_seen_refs is not None: + return box.getref_base() in cache.quasiimmut_seen_refs + else: + if cache.quasiimmut_seen is not None: + return box in cache.quasiimmut_seen return False def quasi_immut_now_known(self, fielddescr, box): cache = self.heap_cache.get(fielddescr, None) if cache is None: cache = self.heap_cache[fielddescr] = CacheEntry(self) - if cache.quasiimmut_seen is not None: - cache.quasiimmut_seen[box] = None + if isinstance(box, Const): + if cache.quasiimmut_seen_refs is None: + cache.quasiimmut_seen_refs = new_ref_dict() + cache.quasiimmut_seen_refs[box.getref_base()] = None else: - cache.quasiimmut_seen = {box: None} + if cache.quasiimmut_seen is not None: + cache.quasiimmut_seen[box] = None + else: + cache.quasiimmut_seen = {box: None} + + def call_loopinvariant_known_result(self, allboxes, descr): + if self.loop_invariant_descr is not descr: + return None + if self.loop_invariant_arg0int != allboxes[0].getint(): + return None + return self.loop_invariant_result + + def call_loopinvariant_now_known(self, allboxes, descr, res): + self.loop_invariant_descr = descr + self.loop_invariant_arg0int = allboxes[0].getint() + self.loop_invariant_result = res diff --git a/rpython/jit/metainterp/jitprof.py b/rpython/jit/metainterp/jitprof.py index c3d3d70454..4ba150a97f 100644 --- a/rpython/jit/metainterp/jitprof.py +++ b/rpython/jit/metainterp/jitprof.py @@ -138,6 +138,7 @@ class Profiler(BaseProfiler): line = "TOTAL: \t\t%f" % (self.tk - self.starttime, ) debug_print(line) self._print_intline("ops", cnt[Counters.OPS]) + self._print_intline("heapcached ops", cnt[Counters.HEAPCACHED_OPS]) self._print_intline("recorded ops", cnt[Counters.RECORDED_OPS]) self._print_intline(" calls", calls) self._print_intline("guards", cnt[Counters.GUARDS]) diff --git a/rpython/jit/metainterp/optimizeopt/heap.py b/rpython/jit/metainterp/optimizeopt/heap.py index bfaa58e1bc..53c8b3a883 100644 --- a/rpython/jit/metainterp/optimizeopt/heap.py +++ b/rpython/jit/metainterp/optimizeopt/heap.py @@ -229,7 +229,6 @@ class OptHeap(Optimization): # cache of corresponding {array descrs: dict 'entries' field descr} self.corresponding_array_descrs = {} # - self._remove_guard_not_invalidated = False self._seen_guard_not_invalidated = False def setup(self): @@ -655,7 +654,6 @@ class OptHeap(Optimization): # registered. structvalue = self.ensure_ptr_info_arg0(op) if not structvalue.is_constant(): - self._remove_guard_not_invalidated = True return # not a constant at all; ignore QUASIIMMUT_FIELD # from rpython.jit.metainterp.quasiimmut import QuasiImmutDescr @@ -670,11 +668,19 @@ class OptHeap(Optimization): if self.optimizer.quasi_immutable_deps is None: self.optimizer.quasi_immutable_deps = {} self.optimizer.quasi_immutable_deps[qmutdescr.qmut] = None - self._remove_guard_not_invalidated = False def optimize_GUARD_NOT_INVALIDATED(self, op): - if self._remove_guard_not_invalidated: - return + # logic: we need one guard_not_invalidated after every call that can + # invalidate something. This is independent to whether the + # quasiimmut_field op is removed or not! The tracer will only trace one + # guard_not_invalidated after each call, so even if the first + # quasiimmut_field is removed, the second one might not be and could + # rely on the presence of an earlier guard_not_invalidated. This might + # under rare circumstances leave a extra guard_not_invalidated in the + # trace! But guard_not_invalidated is cheap, it emits no instructions + # and its only cost is the size of the resume data. Therfore that is + # still a better tradeoff than capturing resume data for every + # quasiimmut_field in the front end *all the time* if self._seen_guard_not_invalidated: return self._seen_guard_not_invalidated = True diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py index c2816d7421..4de28a02ba 100644 --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py @@ -7217,6 +7217,7 @@ class TestOptimizeOpt(BaseTestWithUnroll): """ expected = """ [p0, p1, i0] + guard_not_invalidated() [] i1 = getfield_gc_i(p0, descr=quasifielddescr) escape_n(i1) jump(p1, p0, i1) @@ -7240,6 +7241,29 @@ class TestOptimizeOpt(BaseTestWithUnroll): """ self.optimize_loop(ops, expected, expected) + def test_always_leave_one_guard_not_invalidated(self): + ops = """ + [p0, p1, i0] + quasiimmut_field(p0, descr=quasiimmutdescr) + guard_not_invalidated() [] # needs to stay + i1 = getfield_gc_i(p0, descr=quasifielddescr) + quasiimmut_field(ConstPtr(quasiptr), descr=quasiimmutdescr) + guard_not_invalidated() [] + i2 = getfield_gc_i(ConstPtr(quasiptr), descr=quasifielddescr) + escape_n(i1) + escape_n(i2) + jump(p1, p0, i1) + """ + expected = """ + [p0, p1, i0] + guard_not_invalidated() [] + i1 = getfield_gc_i(p0, descr=quasifielddescr) + escape_n(i1) + escape_n(-4247) + jump(p1, p0, i1) + """ + self.optimize_loop(ops, expected) + def test_remove_extra_guards_not_invalidated(self): ops = """ [i0] @@ -7318,7 +7342,9 @@ class TestOptimizeOpt(BaseTestWithUnroll): """ expected = """ [i0a, i0b] + guard_not_invalidated() [] call_may_force_n(i0b, descr=mayforcevirtdescr) + guard_not_invalidated() [] i3 = escape_i(421) i4 = escape_i(421) jump(i3, i4) diff --git a/rpython/jit/metainterp/pyjitpl.py b/rpython/jit/metainterp/pyjitpl.py index 4c9ee96686..3b092633ad 100644 --- a/rpython/jit/metainterp/pyjitpl.py +++ b/rpython/jit/metainterp/pyjitpl.py @@ -281,6 +281,7 @@ class MIFrame(object): @arguments("box") def opimpl_assert_not_none(self, box): if self.metainterp.heapcache.is_nullity_known(box): + self.metainterp.staticdata.profiler.count_ops(rop.ASSERT_NOT_NONE, Counters.HEAPCACHED_OPS) return self.execute(rop.ASSERT_NOT_NONE, box) self.metainterp.heapcache.nullity_now_known(box) @@ -289,6 +290,7 @@ class MIFrame(object): def opimpl_record_exact_class(self, box, clsbox): from rpython.rtyper.lltypesystem import llmemory if self.metainterp.heapcache.is_class_known(box): + self.metainterp.staticdata.profiler.count_ops(rop.RECORD_EXACT_CLASS, Counters.HEAPCACHED_OPS) return if isinstance(clsbox, Const): self.execute(rop.RECORD_EXACT_CLASS, box, clsbox) @@ -395,6 +397,7 @@ class MIFrame(object): heapcache = self.metainterp.heapcache value = box.nonnull() if heapcache.is_nullity_known(box): + self.metainterp.staticdata.profiler.count_ops(rop.GUARD_NONNULL, Counters.HEAPCACHED_OPS) return value if value: if not self.metainterp.heapcache.is_class_known(box): @@ -478,6 +481,7 @@ class MIFrame(object): if tobox: # sanity check: see whether the current array value # corresponds to what the cache thinks the value is + self.metainterp.staticdata.profiler.count_ops(rop.GETARRAYITEM_GC_I, Counters.HEAPCACHED_OPS) resvalue = executor.execute(self.metainterp.cpu, self.metainterp, op, arraydescr, arraybox, indexbox) if op == 'i': @@ -579,6 +583,8 @@ class MIFrame(object): lengthbox = self.execute_with_descr( rop.ARRAYLEN_GC, arraydescr, arraybox) self.metainterp.heapcache.arraylen_now_known(arraybox, lengthbox) + else: + self.metainterp.staticdata.profiler.count_ops(rop.ARRAYLEN_GC, Counters.HEAPCACHED_OPS) return lengthbox @arguments("box", "box", "descr", "orgpc") @@ -734,6 +740,7 @@ class MIFrame(object): # see ConstFloat.same_constant assert ConstFloat(resvalue).same_constant( upd.currfieldbox.constbox()) + self.metainterp.staticdata.profiler.count_ops(rop.GETFIELD_GC_I, Counters.HEAPCACHED_OPS) return upd.currfieldbox resbox = self.execute_with_descr(opnum, fielddescr, box) upd.getfield_now_known(resbox) @@ -764,6 +771,7 @@ class MIFrame(object): def _opimpl_setfield_gc_any(self, box, valuebox, fielddescr): upd = self.metainterp.heapcache.get_field_updater(box, fielddescr) if upd.currfieldbox is valuebox: + self.metainterp.staticdata.profiler.count_ops(rop.SETFIELD_GC, Counters.HEAPCACHED_OPS) return self.metainterp.execute_and_record(rop.SETFIELD_GC, fielddescr, box, valuebox) upd.setfield(valuebox) @@ -865,14 +873,19 @@ class MIFrame(object): from rpython.jit.metainterp.quasiimmut import QuasiImmutDescr cpu = self.metainterp.cpu if self.metainterp.heapcache.is_quasi_immut_known(fielddescr, box): + self.metainterp.staticdata.profiler.count_ops(rop.QUASIIMMUT_FIELD, Counters.HEAPCACHED_OPS) return descr = QuasiImmutDescr(cpu, box.getref_base(), fielddescr, mutatefielddescr) self.metainterp.heapcache.quasi_immut_now_known(fielddescr, box) self.metainterp.history.record(rop.QUASIIMMUT_FIELD, [box], None, descr=descr) - self.metainterp.generate_guard(rop.GUARD_NOT_INVALIDATED, - resumepc=orgpc) + if self.metainterp.heapcache.need_guard_not_invalidated: + self.metainterp.generate_guard(rop.GUARD_NOT_INVALIDATED, + resumepc=orgpc) + self.metainterp.heapcache.need_guard_not_invalidated = False + + @arguments("box", "descr", "orgpc") def opimpl_jit_force_quasi_immutable(self, box, mutatefielddescr, orgpc): @@ -905,6 +918,7 @@ class MIFrame(object): # returns True if 'box' is actually not the "standard" virtualizable # that is stored in metainterp.virtualizable_boxes[-1] if self.metainterp.heapcache.is_nonstandard_virtualizable(box): + self.metainterp.staticdata.profiler.count_ops(rop.PTR_EQ, Counters.HEAPCACHED_OPS) return True if box is self.metainterp.forced_virtualizable: self.metainterp.forced_virtualizable = None @@ -1743,24 +1757,29 @@ class MIFrame(object): effect = effectinfo.extraeffect tp = descr.get_normalized_result_type() if effect == effectinfo.EF_LOOPINVARIANT: + res = self.metainterp.heapcache.call_loopinvariant_known_result(allboxes, descr) + if res is not None: + return res if tp == 'i': - return self.execute_varargs(rop.CALL_LOOPINVARIANT_I, + res = self.execute_varargs(rop.CALL_LOOPINVARIANT_I, allboxes, descr, False, False) elif tp == 'r': - return self.execute_varargs(rop.CALL_LOOPINVARIANT_R, + res = self.execute_varargs(rop.CALL_LOOPINVARIANT_R, allboxes, descr, False, False) elif tp == 'f': - return self.execute_varargs(rop.CALL_LOOPINVARIANT_F, + res = self.execute_varargs(rop.CALL_LOOPINVARIANT_F, allboxes, descr, False, False) elif tp == 'v': - return self.execute_varargs(rop.CALL_LOOPINVARIANT_N, + res = self.execute_varargs(rop.CALL_LOOPINVARIANT_N, allboxes, descr, False, False) else: assert False + self.metainterp.heapcache.call_loopinvariant_now_known(allboxes, descr, res) + return res exc = effectinfo.check_can_raise() pure = effectinfo.check_is_elidable() if tp == 'i': @@ -1815,6 +1834,7 @@ class MIFrame(object): if standard_box is vref_box: return vref_box if self.metainterp.heapcache.is_nonstandard_virtualizable(vref_box): + self.metainterp.staticdata.profiler.count_ops(rop.PTR_EQ, Counters.HEAPCACHED_OPS) return None eqbox = self.metainterp.execute_and_record(rop.PTR_EQ, None, vref_box, standard_box) eqbox = self.implement_guard_value(eqbox, pc) diff --git a/rpython/jit/metainterp/test/test_heapcache.py b/rpython/jit/metainterp/test/test_heapcache.py index 7d7a8eee78..72649a81bd 100644 --- a/rpython/jit/metainterp/test/test_heapcache.py +++ b/rpython/jit/metainterp/test/test_heapcache.py @@ -3,6 +3,7 @@ from rpython.jit.metainterp.heapcache import HeapCache from rpython.jit.metainterp.resoperation import rop, InputArgInt from rpython.jit.metainterp.history import ConstInt, ConstPtr, BasicFailDescr from rpython.jit.metainterp.history import IntFrontendOp, RefFrontendOp +from rpython.rtyper.lltypesystem import llmemory, rffi descr1 = object() descr2 = object() @@ -111,6 +112,16 @@ class TestHeapCache(object): assert not h.is_nonstandard_virtualizable(box1) assert not h.is_nonstandard_virtualizable(box2) + def test_nonstandard_virtualizable_allocation(self): + h = HeapCache() + box1 = RefFrontendOp(1) + h.new(box1) + # we've seen the allocation, so it's not the virtualizable + assert h.is_nonstandard_virtualizable(box1) + + h.reset() + assert not h.is_nonstandard_virtualizable(box1) + def test_heapcache_fields(self): h = HeapCache() box1 = RefFrontendOp(1) @@ -853,3 +864,39 @@ class TestHeapCache(object): rop.CALL_N, FakeCallDescr(FakeEffectinfo.EF_CAN_RAISE), []) assert not h.is_quasi_immut_known(descr2, box3) assert not h.is_quasi_immut_known(descr2, box4) + + + def test_quasiimmut_seen_consts(self): + h = HeapCache() + box1 = ConstPtr(rffi.cast(llmemory.GCREF, 1)) + box2 = ConstPtr(rffi.cast(llmemory.GCREF, 1)) + box3 = ConstPtr(rffi.cast(llmemory.GCREF, 1)) + box4 = ConstPtr(rffi.cast(llmemory.GCREF, 1)) + assert not h.is_quasi_immut_known(descr1, box1) + assert not h.is_quasi_immut_known(descr1, box2) + assert not h.is_quasi_immut_known(descr2, box3) + assert not h.is_quasi_immut_known(descr2, box4) + h.quasi_immut_now_known(descr1, box1) + assert h.is_quasi_immut_known(descr1, box1) + assert h.is_quasi_immut_known(descr1, box2) + assert not h.is_quasi_immut_known(descr2, box3) + assert not h.is_quasi_immut_known(descr2, box4) + h.quasi_immut_now_known(descr2, box3) + assert h.is_quasi_immut_known(descr1, box1) + assert h.is_quasi_immut_known(descr1, box2) + assert h.is_quasi_immut_known(descr2, box3) + assert h.is_quasi_immut_known(descr2, box4) + + # invalidate the descr1 cache + + vbox1 = RefFrontendOp(1) + vbox2 = RefFrontendOp(2) + h.setfield(vbox1, vbox2, descr1) + assert not h.is_quasi_immut_known(descr1, box1) + assert not h.is_quasi_immut_known(descr1, box2) + + # a call invalidates everything + h.invalidate_caches( + rop.CALL_N, FakeCallDescr(FakeEffectinfo.EF_CAN_RAISE), []) + assert not h.is_quasi_immut_known(descr2, box3) + assert not h.is_quasi_immut_known(descr2, box4) diff --git a/rpython/jit/metainterp/test/test_jitprof.py b/rpython/jit/metainterp/test/test_jitprof.py index a083124a78..41173a52a2 100644 --- a/rpython/jit/metainterp/test/test_jitprof.py +++ b/rpython/jit/metainterp/test/test_jitprof.py @@ -97,3 +97,32 @@ class TestProfile(ProfilerMixin): assert res == f(6, 7, 2) profiler = pyjitpl._warmrunnerdesc.metainterp_sd.profiler assert profiler.calls == 1 + + def test_heapcache_stats(self): + class A: + pass + class B(A): + pass + @dont_look_inside + def extern(n): + if n == -7: + return None + elif n: + return A() + else: + return B() + myjitdriver = JitDriver(greens = [], reds='auto') + def f(x, y): + res = 0 + while y > 0: + myjitdriver.jit_merge_point() + obj = extern(y) + res += x + isinstance(obj, B) + isinstance(obj, B) + isinstance(obj, B) + isinstance(obj, B) + res += x + y -= 1 + return res * 2 + res = self.meta_interp(f, [6, 7]) + assert res == f(6, 7) + profiler = pyjitpl._warmrunnerdesc.metainterp_sd.profiler + assert profiler.counters[Counters.HEAPCACHED_OPS] == 3 + diff --git a/rpython/jit/metainterp/test/test_quasiimmut.py b/rpython/jit/metainterp/test/test_quasiimmut.py index 0e34c25057..380dc80de1 100644 --- a/rpython/jit/metainterp/test/test_quasiimmut.py +++ b/rpython/jit/metainterp/test/test_quasiimmut.py @@ -121,7 +121,7 @@ class QuasiImmutTests(object): assert f(100, 7) == 721 res = self.meta_interp(f, [100, 7]) assert res == 721 - self.check_resops(guard_not_invalidated=0, getfield_gc_r=1, getfield_gc_i=2) + self.check_resops(guard_not_invalidated=2, getfield_gc_r=1, getfield_gc_i=2) # from rpython.jit.metainterp.warmspot import get_stats loops = get_stats().loops @@ -575,5 +575,29 @@ class QuasiImmutTests(object): res = self.meta_interp(main, [10]) assert res == main(10) + def test_dont_emit_too_many_guard_not_invalidated(self): + myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total']) + class Foo: + _immutable_fields_ = ['a?', 'b?', 'c?'] + def __init__(self, a): + self.a = a + self.b = a - 1 + self.c = a - 3 + def f(a, x): + foo = Foo(a) + total = 0 + while x > 0: + myjitdriver.jit_merge_point(foo=foo, x=x, total=total) + # read a few quasi-immutable fields out of a Constant + total += foo.a + foo.b + foo.c + x -= 1 + return total + # + res = self.meta_interp(f, [100, 7], enable_opts="") + assert res == f(100, 7) + # there should be no getfields, even though optimizations are turned off + self.check_resops(guard_not_invalidated=1) + + class TestLLtypeGreenFieldsTests(QuasiImmutTests, LLJitMixin): pass diff --git a/rpython/jit/metainterp/test/test_threadlocal.py b/rpython/jit/metainterp/test/test_threadlocal.py index 322e41a405..3207f33910 100644 --- a/rpython/jit/metainterp/test/test_threadlocal.py +++ b/rpython/jit/metainterp/test/test_threadlocal.py @@ -27,6 +27,16 @@ class ThreadLocalTest(object): res = self.interp_operations(f, []) assert res == 0x92 + def test_threadlocalref_get_loopinvariant(self): + tlfield = rthread.ThreadLocalField(lltype.Signed, 'foobar_test_', True) + + def f(): + tlfield.setraw(0x544c) + return tlfield.getraw() + tlfield.getraw() + + res = self.interp_operations(f, []) + assert res == 0x544c * 2 + self.check_operations_history(call_loopinvariant_i=1) class TestLLtype(ThreadLocalTest, LLJitMixin): pass diff --git a/rpython/jit/metainterp/test/test_tracingopts.py b/rpython/jit/metainterp/test/test_tracingopts.py index 6d37e0fbe8..0f3a22f895 100644 --- a/rpython/jit/metainterp/test/test_tracingopts.py +++ b/rpython/jit/metainterp/test/test_tracingopts.py @@ -431,6 +431,43 @@ class TestLLtype(LLJitMixin): self.check_history(getarrayitem_gc_i=0, getfield_gc_i=0, getfield_gc_r=0) + + def test_nonstandard_virtualizable(self): + myjitdriver = jit.JitDriver(greens = [], reds = ['n', 'x', 'i', 'frame'], + virtualizables = ['frame']) + + class Frame(object): + _virtualizable_ = ['s'] + + def __init__(self, s): + self.s = s + self.next = None + + def f(n, a, i): + frame = Frame(5) + x = 0 + while n > 0: + myjitdriver.can_enter_jit(frame=frame, n=n, x=x, i=i) + myjitdriver.jit_merge_point(frame=frame, n=n, x=x, i=i) + n -= 1 + s = frame.s + assert s >= 0 + frame.s += 1 + # make a new frame + f = Frame(7) + frame.next = f + x += f.s + frame.s -= 1 + frame.next = None + return x + + res = self.meta_interp(f, [10, 1, 1], listops=True) + assert res == f(10, 1, 1) + # we now that f is not the standard virtualizable, since we've seen its + # allocation + self.check_history(ptr_eq=0) + + def test_heap_caching_array_pure(self): class A(object): pass @@ -476,7 +513,6 @@ class TestLLtype(LLJitMixin): res = self.interp_operations(fn, [-7]) assert res == -7 + 7 self.check_operations_history(getfield_gc_i=0) - return def test_heap_caching_multiple_objects(self): class Gbl(object): @@ -537,6 +573,31 @@ class TestLLtype(LLJitMixin): assert res == 10 self.check_operations_history(quasiimmut_field=1) + def test_heap_caching_quasi_immutable_2(self): + class A: + _immutable_fields_ = ['x?'] + a1 = A() + a1.x = 5 + a2 = A() + a2.x = 7 + + @jit.elidable + def get(n): + if n > 0: + return a1 + return a2 + + def g(a): + return a.x + + def fn(n): + jit.promote(n) + return get(n).x + get(n).x + res = self.interp_operations(fn, [7]) + assert res == 10 + self.check_operations_history(quasiimmut_field=1) + + def test_heap_caching_multiple_tuples(self): class Gbl(object): @@ -770,3 +831,31 @@ class TestLLtype(LLJitMixin): assert res == 2 * 14 self.check_operations_history(getfield_gc_i=1) + def test_loop_invariant1(self): + class A(object): + pass + a = A() + a.current_a = A() + a.current_a.x = 1 + @jit.loop_invariant + def f(): + return a.current_a + + @jit.loop_invariant + def f1(): + return a.current_a + + def g(x): + res = 0 + res += f().x + res += f().x + res += f().x + res += f1().x # not reused! + res += f1().x + if x > 1000: + a.current_a = A() + a.current_a.x = 2 + return res + res = self.interp_operations(g, [21]) + assert res == g(21) + self.check_operations_history(call_loopinvariant_r=2) diff --git a/rpython/jit/metainterp/warmspot.py b/rpython/jit/metainterp/warmspot.py index 6953be1437..00f7f915ef 100644 --- a/rpython/jit/metainterp/warmspot.py +++ b/rpython/jit/metainterp/warmspot.py @@ -425,9 +425,9 @@ class WarmRunnerDesc(object): graph = copygraph(graph) [jmpp] = find_jit_merge_points([graph]) graph.startblock = support.split_before_jit_merge_point(*jmpp) - # XXX this is incredibly obscure, but this is sometiems necessary + # XXX this is incredibly obscure, but this is sometimes necessary # so we don't explode in checkgraph. for reasons unknown this - # is not contanied within simplify_graph + # is not contained within simplify_graph removenoops.remove_same_as(graph) # a crash in the following checkgraph() means that you forgot # to list some variable in greens=[] or reds=[] in JitDriver, diff --git a/rpython/jit/tool/jitoutput.py b/rpython/jit/tool/jitoutput.py index 32b7ff5dc9..df57438545 100644 --- a/rpython/jit/tool/jitoutput.py +++ b/rpython/jit/tool/jitoutput.py @@ -12,6 +12,7 @@ REGEXES = [ (('backend_no', 'backend_time'), '^Backend:\s+([\d.]+)\s+([\d.]+)$'), (None, '^TOTAL.*$'), (('ops.total',), '^ops:\s+(\d+)$'), + (('heapcached_ops', ), '^heapcached ops:\s+(\d+)$'), (('recorded_ops.total',), '^recorded ops:\s+(\d+)$'), (('recorded_ops.calls',), '^\s+calls:\s+(\d+)$'), (('guards',), '^guards:\s+(\d+)$'), diff --git a/rpython/jit/tool/test/test_jitoutput.py b/rpython/jit/tool/test/test_jitoutput.py index 1fe9b8303b..abfef9241c 100644 --- a/rpython/jit/tool/test/test_jitoutput.py +++ b/rpython/jit/tool/test/test_jitoutput.py @@ -47,6 +47,7 @@ DATA = '''Tracing: 1 0.006992 Backend: 1 0.000525 TOTAL: 0.025532 ops: 2 +heapcached ops: 111 recorded ops: 6 calls: 3 guards: 1 @@ -77,6 +78,7 @@ def test_parse(): assert info.backend_no == 1 assert info.backend_time == 0.000525 assert info.ops.total == 2 + assert info.heapcached_ops == 111 assert info.recorded_ops.total == 6 assert info.recorded_ops.calls == 3 assert info.guards == 1 diff --git a/rpython/rlib/_rsocket_rffi.py b/rpython/rlib/_rsocket_rffi.py index 9956b51888..81ba133d6d 100644 --- a/rpython/rlib/_rsocket_rffi.py +++ b/rpython/rlib/_rsocket_rffi.py @@ -217,7 +217,7 @@ for name in constant_names: if _WIN32: # some SDKs define these values with an enum, #ifdef won't work for name in ('RCVALL_ON', 'RCVALL_OFF', 'RCVALL_SOCKETLEVELONLY', 'TCP_FASTOPEN'): - setattr(CConfig, name, platform.ConstantInteger(name)) + setattr(CConfig, name, platform.DefinedConstantInteger(name)) constant_names.append(name) constants["BDADDR_ANY"] = "00:00:00:00:00:00" diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py index d316b4dc3e..e61e372f87 100644 --- a/rpython/rlib/jit.py +++ b/rpython/rlib/jit.py @@ -1315,6 +1315,7 @@ class Counters(object): TRACING BACKEND OPS + HEAPCACHED_OPS RECORDED_OPS GUARDS OPT_OPS diff --git a/rpython/rlib/rvmprof/src/shared/vmprof_common.h b/rpython/rlib/rvmprof/src/shared/vmprof_common.h index b52ee5f7cd..1b2278b1d1 100644 --- a/rpython/rlib/rvmprof/src/shared/vmprof_common.h +++ b/rpython/rlib/rvmprof/src/shared/vmprof_common.h @@ -89,7 +89,7 @@ int opened_profile(const char *interp_name, int memory, int proflines, int nativ result is NULL. */ #if PY_MAJOR_VERSION >= 3 && !defined(_Py_atomic_load_relaxed) /* this was abruptly un-defined in 3.5.1 */ -void *volatile _PyThreadState_Current; +extern void *volatile _PyThreadState_Current; /* XXX simple volatile access is assumed atomic */ # define _Py_atomic_load_relaxed(pp) (*(pp)) #endif diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py index 245362edf2..0d09123617 100644 --- a/rpython/rtyper/rclass.py +++ b/rpython/rtyper/rclass.py @@ -416,6 +416,9 @@ class ClassRepr(Repr): v_cls1, v_cls2 = hop.inputargs(class_repr, class_repr) return hop.gendirectcall(ll_issubclass, v_cls1, v_cls2) + def ll_str(self, cls): + return cls.name + class RootClassRepr(ClassRepr): """ClassRepr for the root of the class hierarchy""" diff --git a/rpython/rtyper/test/test_rclass.py b/rpython/rtyper/test/test_rclass.py index 6ba42cc1c6..cb97129392 100644 --- a/rpython/rtyper/test/test_rclass.py +++ b/rpython/rtyper/test/test_rclass.py @@ -13,6 +13,8 @@ from rpython.rtyper.rclass import (IR_IMMUTABLE, IR_IMMUTABLE_ARRAY, from rpython.rtyper.test.tool import BaseRtypingTest from rpython.translator.translator import TranslationContext, graphof +from rpython.rtyper.annlowlevel import llstr, hlstr + class EmptyBase(object): pass @@ -1339,3 +1341,28 @@ class TestRclass(BaseRtypingTest): def f(): return a.next.next.next.next is not None assert self.interpret(f, []) == True + + def test_str_of_type(self): + class A(object): + pass + + class B(A): + pass + + def f(i): + if i: + a = A() + else: + a = B() + return str(type(a)) + assert "A" in hlstr(self.interpret(f, [1])) + assert "B" in hlstr(self.interpret(f, [0])) + + def g(i): + if i: + a = A() + else: + a = B() + return str(a.__class__) + assert "A" in hlstr(self.interpret(g, [1])) + assert "B" in hlstr(self.interpret(g, [0])) diff --git a/rpython/translator/c/src/support.c b/rpython/translator/c/src/support.c index 7bac3aaf52..3b05c5aa4f 100644 --- a/rpython/translator/c/src/support.c +++ b/rpython/translator/c/src/support.c @@ -14,7 +14,7 @@ #define NAN_WORD1 0 #define PY_UINT32_T unsigned int -#ifndef __BIG_ENDIAN__ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define IEEE_8087 #endif |