Author: Matti Picus <[email protected]>
Branch: cpyext-for-merge
Changeset: r83967:05bf6936b917
Date: 2016-04-27 11:08 +0300
http://bitbucket.org/pypy/pypy/changeset/05bf6936b917/

Log:    merge default into branch

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
@@ -16,3 +16,9 @@
 
 Remove a number of options from the build process that were never tested and
 never set. Fix a performance bug in the method cache.
+
+.. branch: bitstring
+
+JIT: use bitstrings to compress the lists of read or written descrs
+that we attach to EffectInfo.  Fixes a problem we had in
+remove-objspace-options.
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -162,7 +162,10 @@
         # When translating PyPy, freeze the file name
         #     <builtin>/lastdirname/basename.py
         # instead of freezing the complete translation-time path.
-        filename = self.co_filename.lstrip('<').rstrip('>')
+        filename = self.co_filename
+        if filename.startswith('<builtin>'):
+            return
+        filename = filename.lstrip('<').rstrip('>')
         if filename.lower().endswith('.pyc'):
             filename = filename[:-1]
         basename = os.path.basename(filename)
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -742,7 +742,7 @@
     def RAISE_VARARGS(self, nbargs, next_instr):
         space = self.space
         if nbargs == 0:
-            last_operr = self._exc_info_unroll(space)
+            last_operr = self._exc_info_unroll(space, for_hidden=True)
             if last_operr is None:
                 raise oefmt(space.w_TypeError,
                             "No active exception to reraise")
diff --git a/pypy/module/__pypy__/test/test_special.py 
b/pypy/module/__pypy__/test/test_special.py
--- a/pypy/module/__pypy__/test/test_special.py
+++ b/pypy/module/__pypy__/test/test_special.py
@@ -60,6 +60,7 @@
         import __pypy__
         import sys
 
+        result = [False]
         @__pypy__.hidden_applevel
         def test_hidden_with_tb():
             def not_hidden(): 1/0
@@ -68,9 +69,11 @@
                 assert sys.exc_info() == (None, None, None)
                 tb = __pypy__.get_hidden_tb()
                 assert tb.tb_frame.f_code.co_name == 'not_hidden'
-                return True
+                result[0] = True
+                raise
             else: return False
-        assert test_hidden_with_tb()
+        raises(ZeroDivisionError, test_hidden_with_tb)
+        assert result[0]
 
     def test_lookup_special(self):
         from __pypy__ import lookup_special
diff --git a/pypy/module/cpyext/include/listobject.h 
b/pypy/module/cpyext/include/listobject.h
--- a/pypy/module/cpyext/include/listobject.h
+++ b/pypy/module/cpyext/include/listobject.h
@@ -1,2 +1,1 @@
 #define PyList_GET_ITEM PyList_GetItem
-#define PyList_SET_ITEM PyList_SetItem
diff --git a/pypy/module/cpyext/listobject.py b/pypy/module/cpyext/listobject.py
--- a/pypy/module/cpyext/listobject.py
+++ b/pypy/module/cpyext/listobject.py
@@ -3,7 +3,7 @@
 from pypy.module.cpyext.api import (cpython_api, CANNOT_FAIL, Py_ssize_t,
                                     build_type_checkers)
 from pypy.module.cpyext.pyerrors import PyErr_BadInternalCall
-from pypy.module.cpyext.pyobject import Py_DecRef, PyObject
+from pypy.module.cpyext.pyobject import Py_DecRef, PyObject, make_ref
 from pypy.objspace.std.listobject import W_ListObject
 from pypy.interpreter.error import OperationError
 
@@ -21,6 +21,25 @@
     """
     return space.newlist([None] * len)
 
+@cpython_api([PyObject, Py_ssize_t, PyObject], PyObject, error=CANNOT_FAIL,
+             result_borrowed=True)
+def PyList_SET_ITEM(space, w_list, index, w_item):
+    """Macro form of PyList_SetItem() without error checking. This is normally
+    only used to fill in new lists where there is no previous content.
+
+    This function "steals" a reference to item, and, unlike PyList_SetItem(),
+    does not discard a reference to any item that it being replaced; any
+    reference in list at position i will be leaked.
+    """
+    assert isinstance(w_list, W_ListObject)
+    assert 0 <= index < w_list.length()
+    # Deliberately leak, so that it can be safely decref'd.
+    make_ref(space, w_list.getitem(index))
+    Py_DecRef(space, w_item)
+    w_list.setitem(index, w_item)
+    return w_item
+
+
 @cpython_api([PyObject, Py_ssize_t, PyObject], rffi.INT_real, error=-1)
 def PyList_SetItem(space, w_list, index, w_item):
     """Set the item at index index in list to item.  Return 0 on success
diff --git a/pypy/module/cpyext/test/test_listobject.py 
b/pypy/module/cpyext/test/test_listobject.py
--- a/pypy/module/cpyext/test/test_listobject.py
+++ b/pypy/module/cpyext/test/test_listobject.py
@@ -136,3 +136,45 @@
         l = [1, 2, 3]
         module.setlistitem(l,0)
         assert l == [None, 2, 3]
+
+    def test_get_item_macro(self):
+        module = self.import_extension('foo', [
+             ("test_get_item", "METH_NOARGS",
+             """
+                PyObject* o = PyList_New(1);
+
+                PyObject* o2 = PyInt_FromLong(0);
+                PyList_SET_ITEM(o, 0, o2);
+                o2 = NULL;
+
+                PyObject* o3 = PyList_GET_ITEM(o, 0);
+                Py_INCREF(o3);
+                Py_CLEAR(o);
+                return o3;
+             """)])
+        assert module.test_get_item() == 0
+
+    def test_set_item_macro(self):
+        """PyList_SET_ITEM leaks a reference to the target."""
+        module = self.import_extension('foo', [
+             ("test_refcount_diff_after_setitem", "METH_NOARGS",
+             """
+                PyObject* o = PyList_New(0);
+                PyObject* o2 = PyList_New(0);
+
+                PyList_Append(o, o2);  // does not steal o2
+
+                Py_ssize_t refcount = Py_REFCNT(o2);
+
+                // Steal a reference to o2, but leak the old reference to o2.
+                // The net result should be no change in refcount.
+                PyList_SET_ITEM(o, 0, o2);
+
+                Py_ssize_t new_refcount = Py_REFCNT(o2);
+
+                Py_CLEAR(o);
+                Py_DECREF(o2); // append incref'd.
+                // Py_CLEAR(o2);  // naive implementation would fail here.
+                return PyLong_FromSsize_t(new_refcount - refcount);
+             """)])
+        assert module.test_refcount_diff_after_setitem() == 0
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
@@ -4577,6 +4577,13 @@
         with py.test.raises(AnnotatorError):
             a.build_types(f, [float])
 
+    def test_Ellipsis_not_rpython(self):
+        def f():
+            return Ellipsis
+        a = self.RPythonAnnotator()
+        e = py.test.raises(Exception, a.build_types, f, [])
+        assert str(e.value) == "Don't know how to represent Ellipsis"
+
 
 def g(n):
     return [0, 1, 2, n]
diff --git a/rpython/jit/backend/llgraph/runner.py 
b/rpython/jit/backend/llgraph/runner.py
--- a/rpython/jit/backend/llgraph/runner.py
+++ b/rpython/jit/backend/llgraph/runner.py
@@ -479,6 +479,9 @@
             all_descrs.append(v)
         return all_descrs
 
+    def fetch_all_descrs(self):
+        return self.descrs.values()
+
     def calldescrof(self, FUNC, ARGS, RESULT, effect_info):
         key = ('call', getkind(RESULT),
                tuple([getkind(A) for A in ARGS]),
diff --git a/rpython/jit/codewriter/effectinfo.py 
b/rpython/jit/codewriter/effectinfo.py
--- a/rpython/jit/codewriter/effectinfo.py
+++ b/rpython/jit/codewriter/effectinfo.py
@@ -1,7 +1,9 @@
+import sys
 from rpython.jit.metainterp.typesystem import deref, fieldType, arrayItem
 from rpython.rtyper.rclass import OBJECT
 from rpython.rtyper.lltypesystem import lltype, llmemory
 from rpython.translator.backendopt.graphanalyze import BoolGraphAnalyzer
+from rpython.tool.algo import bitstring
 
 
 class EffectInfo(object):
@@ -110,12 +112,20 @@
                 can_invalidate=False,
                 call_release_gil_target=_NO_CALL_RELEASE_GIL_TARGET,
                 extradescrs=None):
-        key = (frozenset_or_none(readonly_descrs_fields),
-               frozenset_or_none(readonly_descrs_arrays),
-               frozenset_or_none(readonly_descrs_interiorfields),
-               frozenset_or_none(write_descrs_fields),
-               frozenset_or_none(write_descrs_arrays),
-               frozenset_or_none(write_descrs_interiorfields),
+        readonly_descrs_fields = frozenset_or_none(readonly_descrs_fields)
+        readonly_descrs_arrays = frozenset_or_none(readonly_descrs_arrays)
+        readonly_descrs_interiorfields = frozenset_or_none(
+                                              readonly_descrs_interiorfields)
+        write_descrs_fields = frozenset_or_none(write_descrs_fields)
+        write_descrs_arrays = frozenset_or_none(write_descrs_arrays)
+        write_descrs_interiorfields = frozenset_or_none(
+                                              write_descrs_interiorfields)
+        key = (readonly_descrs_fields,
+               readonly_descrs_arrays,
+               readonly_descrs_interiorfields,
+               write_descrs_fields,
+               write_descrs_arrays,
+               write_descrs_interiorfields,
                extraeffect,
                oopspecindex,
                can_invalidate)
@@ -139,22 +149,34 @@
             assert write_descrs_arrays is not None
             assert write_descrs_interiorfields is not None
         result = object.__new__(cls)
-        result.readonly_descrs_fields = readonly_descrs_fields
-        result.readonly_descrs_arrays = readonly_descrs_arrays
-        result.readonly_descrs_interiorfields = readonly_descrs_interiorfields
+        # the frozensets "._readonly_xxx" and "._write_xxx" should not be
+        # translated.
+        result._readonly_descrs_fields = readonly_descrs_fields
+        result._readonly_descrs_arrays = readonly_descrs_arrays
+        result._readonly_descrs_interiorfields = readonly_descrs_interiorfields
         if extraeffect == EffectInfo.EF_LOOPINVARIANT or \
            extraeffect == EffectInfo.EF_ELIDABLE_CANNOT_RAISE or \
            extraeffect == EffectInfo.EF_ELIDABLE_OR_MEMORYERROR or \
            extraeffect == EffectInfo.EF_ELIDABLE_CAN_RAISE:
             # Ignore the writes.  Note that this ignores also writes with
             # no corresponding reads (rarely the case, but possible).
-            result.write_descrs_fields = []
-            result.write_descrs_arrays = []
-            result.write_descrs_interiorfields = []
+            result._write_descrs_fields = frozenset()
+            result._write_descrs_arrays = frozenset()
+            result._write_descrs_interiorfields = frozenset()
         else:
-            result.write_descrs_fields = write_descrs_fields
-            result.write_descrs_arrays = write_descrs_arrays
-            result.write_descrs_interiorfields = write_descrs_interiorfields
+            result._write_descrs_fields = write_descrs_fields
+            result._write_descrs_arrays = write_descrs_arrays
+            result._write_descrs_interiorfields = write_descrs_interiorfields
+        # initialized later, in compute_bitstrings()
+        # (the goal of this is to make sure we don't build new EffectInfo
+        # instances after compute_bitstrings() is called)
+        result.bitstring_readonly_descrs_fields = Ellipsis
+        result.bitstring_readonly_descrs_arrays = Ellipsis
+        result.bitstring_readonly_descrs_interiorfields = Ellipsis
+        result.bitstring_write_descrs_fields = Ellipsis
+        result.bitstring_write_descrs_arrays = Ellipsis
+        result.bitstring_write_descrs_interiorfields = Ellipsis
+        #
         result.extraeffect = extraeffect
         result.can_invalidate = can_invalidate
         result.oopspecindex = oopspecindex
@@ -162,9 +184,38 @@
         result.call_release_gil_target = call_release_gil_target
         if result.check_can_raise(ignore_memoryerror=True):
             assert oopspecindex in cls._OS_CANRAISE
+
+        if (result._write_descrs_arrays is not None and
+            len(result._write_descrs_arrays) == 1):
+            # this is used only for ARRAYCOPY operations
+            [result.single_write_descr_array] = result._write_descrs_arrays
+        else:
+            result.single_write_descr_array = None
+
         cls._cache[key] = result
         return result
 
+    def check_readonly_descr_field(self, fielddescr):
+        return bitstring.bitcheck(self.bitstring_readonly_descrs_fields,
+                                  fielddescr.ei_index)
+    def check_write_descr_field(self, fielddescr):
+        return bitstring.bitcheck(self.bitstring_write_descrs_fields,
+                                  fielddescr.ei_index)
+    def check_readonly_descr_array(self, arraydescr):
+        return bitstring.bitcheck(self.bitstring_readonly_descrs_arrays,
+                                  arraydescr.ei_index)
+    def check_write_descr_array(self, arraydescr):
+        return bitstring.bitcheck(self.bitstring_write_descrs_arrays,
+                                  arraydescr.ei_index)
+    def check_readonly_descr_interiorfield(self, interiorfielddescr):
+        # NOTE: this is not used so far
+        return 
bitstring.bitcheck(self.bitstring_readonly_descrs_interiorfields,
+                                  interiorfielddescr.ei_index)
+    def check_write_descr_interiorfield(self, interiorfielddescr):
+        # NOTE: this is not used so far
+        return bitstring.bitcheck(self.bitstring_write_descrs_interiorfields,
+                                  interiorfielddescr.ei_index)
+
     def check_can_raise(self, ignore_memoryerror=False):
         if ignore_memoryerror:
             return self.extraeffect > self.EF_ELIDABLE_OR_MEMORYERROR
@@ -382,3 +433,88 @@
         assert funcptr
         return funcptr
     funcptr_for_oopspec._annspecialcase_ = 'specialize:arg(1)'
+
+# ____________________________________________________________
+
+def compute_bitstrings(all_descrs):
+    # Compute the bitstrings in the EffectInfo,
+    # bitstring_{readonly,write}_descrs_{fieldd,arrays,interiordescrs},
+    # and for each FieldDescrs and ArrayDescrs compute 'ei_index'.
+    # Each bit in the bitstrings says whether this Descr is present in
+    # this EffectInfo or not.  We try to share the value of 'ei_index'
+    # across multiple Descrs if they always give the same answer (in
+    # PyPy, it reduces the length of the bitstrings from 4000+ to
+    # 373).
+    from rpython.jit.codewriter.policy import log
+
+    log("compute_bitstrings:")
+    effectinfos = []
+    descrs = {'fields': set(), 'arrays': set(), 'interiorfields': set()}
+    for descr in all_descrs:
+        if hasattr(descr, 'get_extra_info'):
+            ei = descr.get_extra_info()
+            if ei is None:
+                continue
+            if ei._readonly_descrs_fields is None:
+                for key in descrs:
+                    assert getattr(ei, '_readonly_descrs_' + key) is None
+                    assert getattr(ei, '_write_descrs_' + key) is None
+                    setattr(ei, 'bitstring_readonly_descrs_' + key, None)
+                    setattr(ei, 'bitstring_write_descrs_' + key, None)
+            else:
+                effectinfos.append(ei)
+                for key in descrs:
+                    descrs[key].update(getattr(ei, '_readonly_descrs_' + key))
+                    descrs[key].update(getattr(ei, '_write_descrs_' + key))
+        else:
+            descr.ei_index = sys.maxint
+    log("  %d effectinfos:" % (len(effectinfos),))
+    for key in sorted(descrs):
+        log("    %d descrs for %s" % (len(descrs[key]), key))
+
+    seen = set()
+    for key in descrs:
+        all_sets = []
+        for descr in descrs[key]:
+            eisetr = [ei for ei in effectinfos
+                         if descr in getattr(ei, '_readonly_descrs_' + key)]
+            eisetw = [ei for ei in effectinfos
+                         if descr in getattr(ei, '_write_descrs_' + key)]
+            # these are the set of all ei such that this descr is in
+            # ei._readonly_descrs or ei._write_descrs
+            eisetr = frozenset(eisetr)
+            eisetw = frozenset(eisetw)
+            all_sets.append((descr, eisetr, eisetw))
+
+        # heuristic to reduce the total size of the bitstrings: start with
+        # numbering the descrs that are seen in many EffectInfos.  If instead,
+        # by lack of chance, such a descr had a high number, then all these
+        # EffectInfos' bitstrings would need to store the same high number.
+        def size_of_both_sets((d, r, w)):
+            return len(r) + len(w)
+        all_sets.sort(key=size_of_both_sets, reverse=True)
+
+        mapping = {}
+        for (descr, eisetr, eisetw) in all_sets:
+            assert descr.ei_index == sys.maxint    # not modified yet
+            descr.ei_index = mapping.setdefault((eisetr, eisetw), len(mapping))
+
+        for ei in effectinfos:
+            bitstrr = [descr.ei_index
+                           for descr in getattr(ei, '_readonly_descrs_' + key)]
+            bitstrw = [descr.ei_index
+                           for descr in getattr(ei, '_write_descrs_' + key)]
+            assert sys.maxint not in bitstrr
+            assert sys.maxint not in bitstrw
+            bitstrr = bitstring.make_bitstring(bitstrr)
+            bitstrw = bitstring.make_bitstring(bitstrw)
+            setattr(ei, 'bitstring_readonly_descrs_' + key, bitstrr)
+            setattr(ei, 'bitstring_write_descrs_' + key, bitstrw)
+            seen.add(bitstrr)
+            seen.add(bitstrw)
+
+    if seen:
+        mean_length = float(sum(len(x) for x in seen)) / len(seen)
+        max_length = max(len(x) for x in seen)
+        log("-> %d bitstrings, mean length %.1f, max length %d" % (
+            len(seen), mean_length, max_length))
diff --git a/rpython/jit/codewriter/test/test_effectinfo.py 
b/rpython/jit/codewriter/test/test_effectinfo.py
--- a/rpython/jit/codewriter/test/test_effectinfo.py
+++ b/rpython/jit/codewriter/test/test_effectinfo.py
@@ -1,11 +1,12 @@
-import pytest
+import pytest, sys
 
 from rpython.jit.codewriter.effectinfo import (effectinfo_from_writeanalyze,
-    EffectInfo, VirtualizableAnalyzer)
+    EffectInfo, VirtualizableAnalyzer, compute_bitstrings)
 from rpython.rlib import jit
 from rpython.rtyper.lltypesystem import lltype
 from rpython.rtyper.rclass import OBJECT
 from rpython.translator.translator import TranslationContext, graphof
+from rpython.tool.algo.bitstring import bitcheck
 
 
 class FakeCPU(object):
@@ -29,37 +30,39 @@
     S = lltype.GcStruct("S", ("a", lltype.Signed))
     effects = frozenset([("readstruct", lltype.Ptr(S), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert list(effectinfo.readonly_descrs_fields) == [('fielddescr', S, "a")]
-    assert not effectinfo.write_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert list(effectinfo._readonly_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo._write_descrs_fields
+    assert not effectinfo._write_descrs_arrays
+    assert effectinfo.single_write_descr_array is None
 
 
 def test_include_write_field():
     S = lltype.GcStruct("S", ("a", lltype.Signed))
     effects = frozenset([("struct", lltype.Ptr(S), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert list(effectinfo.write_descrs_fields) == [('fielddescr', S, "a")]
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert list(effectinfo._write_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._write_descrs_arrays
 
 
 def test_include_read_array():
     A = lltype.GcArray(lltype.Signed)
     effects = frozenset([("readarray", lltype.Ptr(A))])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert not effectinfo.readonly_descrs_fields
-    assert list(effectinfo.readonly_descrs_arrays) == [('arraydescr', A)]
-    assert not effectinfo.write_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert not effectinfo._readonly_descrs_fields
+    assert list(effectinfo._readonly_descrs_arrays) == [('arraydescr', A)]
+    assert not effectinfo._write_descrs_fields
+    assert not effectinfo._write_descrs_arrays
 
 
 def test_include_write_array():
     A = lltype.GcArray(lltype.Signed)
     effects = frozenset([("array", lltype.Ptr(A))])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.write_descrs_fields
-    assert list(effectinfo.write_descrs_arrays) == [('arraydescr', A)]
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._write_descrs_fields
+    assert list(effectinfo._write_descrs_arrays) == [('arraydescr', A)]
+    assert effectinfo.single_write_descr_array == ('arraydescr', A)
 
 
 def test_dont_include_read_and_write_field():
@@ -67,9 +70,9 @@
     effects = frozenset([("readstruct", lltype.Ptr(S), "a"),
                          ("struct", lltype.Ptr(S), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert not effectinfo.readonly_descrs_fields
-    assert list(effectinfo.write_descrs_fields) == [('fielddescr', S, "a")]
-    assert not effectinfo.write_descrs_arrays
+    assert not effectinfo._readonly_descrs_fields
+    assert list(effectinfo._write_descrs_fields) == [('fielddescr', S, "a")]
+    assert not effectinfo._write_descrs_arrays
 
 
 def test_dont_include_read_and_write_array():
@@ -77,34 +80,34 @@
     effects = frozenset([("readarray", lltype.Ptr(A)),
                          ("array", lltype.Ptr(A))])
     effectinfo = effectinfo_from_writeanalyze(effects, FakeCPU())
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.readonly_descrs_arrays
-    assert not effectinfo.write_descrs_fields
-    assert list(effectinfo.write_descrs_arrays) == [('arraydescr', A)]
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._readonly_descrs_arrays
+    assert not effectinfo._write_descrs_fields
+    assert list(effectinfo._write_descrs_arrays) == [('arraydescr', A)]
 
 
 def test_filter_out_typeptr():
     effects = frozenset([("struct", lltype.Ptr(OBJECT), "typeptr")])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.write_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._write_descrs_fields
+    assert not effectinfo._write_descrs_arrays
 
 
 def test_filter_out_array_of_void():
     effects = frozenset([("array", lltype.Ptr(lltype.GcArray(lltype.Void)))])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.write_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._write_descrs_fields
+    assert not effectinfo._write_descrs_arrays
 
 
 def test_filter_out_struct_with_void():
     effects = frozenset([("struct", lltype.Ptr(lltype.GcStruct("x", ("a", 
lltype.Void))), "a")])
     effectinfo = effectinfo_from_writeanalyze(effects, None)
-    assert not effectinfo.readonly_descrs_fields
-    assert not effectinfo.write_descrs_fields
-    assert not effectinfo.write_descrs_arrays
+    assert not effectinfo._readonly_descrs_fields
+    assert not effectinfo._write_descrs_fields
+    assert not effectinfo._write_descrs_arrays
 
 
 class TestVirtualizableAnalyzer(object):
@@ -138,3 +141,64 @@
 
         res = self.analyze(entry, [int])
         assert not res
+
+
+def test_compute_bitstrings():
+    class FDescr:
+        pass
+    class ADescr:
+        pass
+    class CDescr:
+        def __init__(self, ei):
+            self._ei = ei
+        def get_extra_info(self):
+            return self._ei
+
+    f1descr = FDescr()
+    f2descr = FDescr()
+    f3descr = FDescr()
+    a1descr = ADescr()
+    a2descr = ADescr()
+
+    ei1 = EffectInfo(None, None, None, None, None, None,
+                         EffectInfo.EF_RANDOM_EFFECTS)
+    ei2 = EffectInfo([f1descr], [], [], [], [], [])
+    ei3 = EffectInfo([f1descr], [a1descr, a2descr], [], [f2descr], [], [])
+
+    compute_bitstrings([CDescr(ei1), CDescr(ei2), CDescr(ei3),
+                        f1descr, f2descr, f3descr, a1descr, a2descr])
+
+    assert f1descr.ei_index in (0, 1)
+    assert f2descr.ei_index == 1 - f1descr.ei_index
+    assert f3descr.ei_index == sys.maxint
+    assert a1descr.ei_index == 0
+    assert a2descr.ei_index == 0
+
+    assert ei1.bitstring_readonly_descrs_fields is None
+    assert ei1.bitstring_readonly_descrs_arrays is None
+    assert ei1.bitstring_write_descrs_fields is None
+
+    def expand(bitstr):
+        return [n for n in range(10) if bitcheck(bitstr, n)]
+
+    assert expand(ei2.bitstring_readonly_descrs_fields) == [f1descr.ei_index]
+    assert expand(ei2.bitstring_write_descrs_fields) == []
+    assert expand(ei2.bitstring_readonly_descrs_arrays) == []
+    assert expand(ei2.bitstring_write_descrs_arrays) == []
+
+    assert expand(ei3.bitstring_readonly_descrs_fields) == [f1descr.ei_index]
+    assert expand(ei3.bitstring_write_descrs_fields) == [f2descr.ei_index]
+    assert expand(ei3.bitstring_readonly_descrs_arrays) == [0] #a1descr,a2descr
+    assert expand(ei3.bitstring_write_descrs_arrays) == []
+
+    for ei in [ei2, ei3]:
+        for fdescr in [f1descr, f2descr]:
+            assert ei.check_readonly_descr_field(fdescr) == (
+                fdescr in ei._readonly_descrs_fields)
+            assert ei.check_write_descr_field(fdescr) == (
+                fdescr in ei._write_descrs_fields)
+        for adescr in [a1descr, a2descr]:
+            assert ei.check_readonly_descr_array(adescr) == (
+                adescr in ei._readonly_descrs_arrays)
+            assert ei.check_write_descr_array(adescr) == (
+                adescr in ei._write_descrs_arrays)
diff --git a/rpython/jit/metainterp/heapcache.py 
b/rpython/jit/metainterp/heapcache.py
--- a/rpython/jit/metainterp/heapcache.py
+++ b/rpython/jit/metainterp/heapcache.py
@@ -209,7 +209,7 @@
               isinstance(argboxes[3], ConstInt) and
               isinstance(argboxes[4], ConstInt) and
               isinstance(argboxes[5], ConstInt) and
-              len(descr.get_extra_info().write_descrs_arrays) == 1):
+              descr.get_extra_info().single_write_descr_array is not None):
             # ARRAYCOPY with constant starts and constant length doesn't escape
             # its argument
             # XXX really?
@@ -299,9 +299,9 @@
             isinstance(argboxes[3], ConstInt) and
             isinstance(argboxes[4], ConstInt) and
             isinstance(argboxes[5], ConstInt) and
-            len(effectinfo.write_descrs_arrays) == 1
+            effectinfo.single_write_descr_array is not None
         ):
-            descr = effectinfo.write_descrs_arrays[0]
+            descr = effectinfo.single_write_descr_array
             cache = self.heap_array_cache.get(descr, None)
             srcstart = argboxes[3].getint()
             dststart = argboxes[4].getint()
@@ -328,10 +328,10 @@
                         
idx_cache._clear_cache_on_write(seen_allocation_of_target)
             return
         elif (
-            len(effectinfo.write_descrs_arrays) == 1
+            effectinfo.single_write_descr_array is not None
         ):
             # Fish the descr out of the effectinfo
-            cache = 
self.heap_array_cache.get(effectinfo.write_descrs_arrays[0], None)
+            cache = 
self.heap_array_cache.get(effectinfo.single_write_descr_array, None)
             if cache is not None:
                 for idx, cache in cache.iteritems():
                     cache._clear_cache_on_write(seen_allocation_of_target)
diff --git a/rpython/jit/metainterp/history.py 
b/rpython/jit/metainterp/history.py
--- a/rpython/jit/metainterp/history.py
+++ b/rpython/jit/metainterp/history.py
@@ -1,3 +1,4 @@
+import sys
 from rpython.rtyper.extregistry import ExtRegistryEntry
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
 from rpython.rlib.objectmodel import we_are_translated, Symbolic
@@ -87,9 +88,10 @@
 
 
 class AbstractDescr(AbstractValue):
-    __slots__ = ('descr_index',)
+    __slots__ = ('descr_index', 'ei_index')
     llopaque = True
     descr_index = -1
+    ei_index = sys.maxint
 
     def repr_of_descr(self):
         return '%r' % (self,)
diff --git a/rpython/jit/metainterp/optimizeopt/heap.py 
b/rpython/jit/metainterp/optimizeopt/heap.py
--- a/rpython/jit/metainterp/optimizeopt/heap.py
+++ b/rpython/jit/metainterp/optimizeopt/heap.py
@@ -432,28 +432,35 @@
     optimize_GUARD_EXCEPTION = optimize_GUARD_NO_EXCEPTION
 
     def force_from_effectinfo(self, effectinfo):
-        # XXX we can get the wrong complexity here, if the lists
-        # XXX stored on effectinfo are large
-        for fielddescr in effectinfo.readonly_descrs_fields:
-            self.force_lazy_set(fielddescr)
-        for arraydescr in effectinfo.readonly_descrs_arrays:
-            self.force_lazy_setarrayitem(arraydescr)
-        for fielddescr in effectinfo.write_descrs_fields:
-            if fielddescr.is_always_pure():
-                continue
-            try:
-                del self.cached_dict_reads[fielddescr]
-            except KeyError:
-                pass
-            self.force_lazy_set(fielddescr, can_cache=False)
-        for arraydescr in effectinfo.write_descrs_arrays:
-            self.force_lazy_setarrayitem(arraydescr, can_cache=False)
-            if arraydescr in self.corresponding_array_descrs:
-                dictdescr = self.corresponding_array_descrs.pop(arraydescr)
+        # Note: this version of the code handles effectively
+        # effectinfos that store arbitrarily many descrs, by looping
+        # on self.cached_{fields, arrayitems} and looking them up in
+        # the bitstrings stored in the effectinfo.
+        for fielddescr, cf in self.cached_fields.items():
+            if effectinfo.check_readonly_descr_field(fielddescr):
+                cf.force_lazy_set(self, fielddescr)
+            if effectinfo.check_write_descr_field(fielddescr):
+                if fielddescr.is_always_pure():
+                    continue
+                try:
+                    del self.cached_dict_reads[fielddescr]
+                except KeyError:
+                    pass
+                cf.force_lazy_set(self, fielddescr, can_cache=False)
+        #
+        for arraydescr, submap in self.cached_arrayitems.items():
+            if effectinfo.check_readonly_descr_array(arraydescr):
+                self.force_lazy_setarrayitem_submap(submap)
+            if effectinfo.check_write_descr_array(arraydescr):
+                self.force_lazy_setarrayitem_submap(submap, can_cache=False)
+        #
+        for arraydescr, dictdescr in self.corresponding_array_descrs.items():
+            if effectinfo.check_write_descr_array(arraydescr):
                 try:
                     del self.cached_dict_reads[dictdescr]
                 except KeyError:
                     pass # someone did it already
+        #
         if effectinfo.check_forces_virtual_or_virtualizable():
             vrefinfo = self.optimizer.metainterp_sd.virtualref_info
             self.force_lazy_set(vrefinfo.descr_forced)
@@ -476,6 +483,10 @@
             if indexb is None or indexb.contains(idx):
                 cf.force_lazy_set(self, None, can_cache)
 
+    def force_lazy_setarrayitem_submap(self, submap, can_cache=True):
+        for cf in submap.itervalues():
+            cf.force_lazy_set(self, None, can_cache)
+
     def force_all_lazy_sets(self):
         items = self.cached_fields.items()
         if not we_are_translated():
diff --git a/rpython/jit/metainterp/optimizeopt/rewrite.py 
b/rpython/jit/metainterp/optimizeopt/rewrite.py
--- a/rpython/jit/metainterp/optimizeopt/rewrite.py
+++ b/rpython/jit/metainterp/optimizeopt/rewrite.py
@@ -620,10 +620,10 @@
             and length and ((dest_info and dest_info.is_virtual()) or
                             length.getint() <= 8) and
             ((source_info and source_info.is_virtual()) or length.getint() <= 
8)
-            and len(extrainfo.write_descrs_arrays) == 1):   # <-sanity check
+            and extrainfo.single_write_descr_array is not None): #<-sanity 
check
             source_start = source_start_box.getint()
             dest_start = dest_start_box.getint()
-            arraydescr = extrainfo.write_descrs_arrays[0]
+            arraydescr = extrainfo.single_write_descr_array
             if arraydescr.is_array_of_structs():
                 return False       # not supported right now
 
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_util.py 
b/rpython/jit/metainterp/optimizeopt/test/test_util.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_util.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_util.py
@@ -10,7 +10,7 @@
 from rpython.jit.metainterp.history import (TreeLoop, AbstractDescr,
                                             JitCellToken, TargetToken)
 from rpython.jit.metainterp.optimizeopt.util import sort_descrs, equaloplists
-from rpython.jit.codewriter.effectinfo import EffectInfo
+from rpython.jit.codewriter.effectinfo import EffectInfo, compute_bitstrings
 from rpython.jit.metainterp.logger import LogOperations
 from rpython.jit.tool.oparser import OpParser, pure_parse, 
convert_loop_to_trace
 from rpython.jit.metainterp.quasiimmut import QuasiImmutDescr
@@ -530,6 +530,7 @@
             metainterp_sd.virtualref_info = self.vrefinfo
         if hasattr(self, 'callinfocollection'):
             metainterp_sd.callinfocollection = self.callinfocollection
+        compute_bitstrings(self.cpu.fetch_all_descrs())
         #
         compile_data.enable_opts = self.enable_opts
         state = optimize_trace(metainterp_sd, None, compile_data)
diff --git a/rpython/jit/metainterp/pyjitpl.py 
b/rpython/jit/metainterp/pyjitpl.py
--- a/rpython/jit/metainterp/pyjitpl.py
+++ b/rpython/jit/metainterp/pyjitpl.py
@@ -1838,7 +1838,11 @@
         self.cpu.propagate_exception_descr = exc_descr
         #
         self.globaldata = MetaInterpGlobalData(self)
+
+    def finish_setup_descrs(self):
+        from rpython.jit.codewriter import effectinfo
         self.all_descrs = self.cpu.setup_descrs()
+        effectinfo.compute_bitstrings(self.all_descrs)
 
     def _setup_once(self):
         """Runtime setup needed by the various components of the JIT."""
diff --git a/rpython/jit/metainterp/test/support.py 
b/rpython/jit/metainterp/test/support.py
--- a/rpython/jit/metainterp/test/support.py
+++ b/rpython/jit/metainterp/test/support.py
@@ -132,6 +132,7 @@
     metainterp_sd = pyjitpl.MetaInterpStaticData(cw.cpu, opt)
     stats.metainterp_sd = metainterp_sd
     metainterp_sd.finish_setup(cw)
+    metainterp_sd.finish_setup_descrs()
 
     [jitdriver_sd] = metainterp_sd.jitdrivers_sd
     metainterp = pyjitpl.MetaInterp(metainterp_sd, jitdriver_sd)
diff --git a/rpython/jit/metainterp/test/test_heapcache.py 
b/rpython/jit/metainterp/test/test_heapcache.py
--- a/rpython/jit/metainterp/test/test_heapcache.py
+++ b/rpython/jit/metainterp/test/test_heapcache.py
@@ -27,8 +27,12 @@
     def __init__(self, extraeffect, oopspecindex, write_descrs_fields, 
write_descrs_arrays):
         self.extraeffect = extraeffect
         self.oopspecindex = oopspecindex
-        self.write_descrs_fields = write_descrs_fields
-        self.write_descrs_arrays = write_descrs_arrays
+        self._write_descrs_fields = write_descrs_fields
+        self._write_descrs_arrays = write_descrs_arrays
+        if len(write_descrs_arrays) == 1:
+            [self.single_write_descr_array] = write_descrs_arrays
+        else:
+            self.single_write_descr_array = None
 
     def has_random_effects(self):
         return self.extraeffect == self.EF_RANDOM_EFFECTS
@@ -37,14 +41,14 @@
     def __init__(self, extraeffect, oopspecindex=None, write_descrs_fields=[], 
write_descrs_arrays=[]):
         self.extraeffect = extraeffect
         self.oopspecindex = oopspecindex
-        self.write_descrs_fields = write_descrs_fields
-        self.write_descrs_arrays = write_descrs_arrays
+        self.__write_descrs_fields = write_descrs_fields
+        self.__write_descrs_arrays = write_descrs_arrays
 
     def get_extra_info(self):
         return FakeEffectinfo(
             self.extraeffect, self.oopspecindex,
-            write_descrs_fields=self.write_descrs_fields,
-            write_descrs_arrays=self.write_descrs_arrays,
+            write_descrs_fields=self.__write_descrs_fields,
+            write_descrs_arrays=self.__write_descrs_arrays,
         )
 
 arraycopydescr1 = FakeCallDescr(FakeEffectinfo.EF_CANNOT_RAISE, 
FakeEffectinfo.OS_ARRAYCOPY, write_descrs_arrays=[descr1])
diff --git a/rpython/jit/metainterp/test/test_warmspot.py 
b/rpython/jit/metainterp/test/test_warmspot.py
--- a/rpython/jit/metainterp/test/test_warmspot.py
+++ b/rpython/jit/metainterp/test/test_warmspot.py
@@ -624,7 +624,7 @@
                 pass
 
             def setup_descrs(self):
-                pass
+                return []
 
             def get_latest_descr(self, deadframe):
                 assert isinstance(deadframe, FakeDeadFrame)
diff --git a/rpython/jit/metainterp/warmspot.py 
b/rpython/jit/metainterp/warmspot.py
--- a/rpython/jit/metainterp/warmspot.py
+++ b/rpython/jit/metainterp/warmspot.py
@@ -277,6 +277,7 @@
         for vinfo in vinfos:
             if vinfo is not None:
                 vinfo.finish()
+        self.metainterp_sd.finish_setup_descrs()
         if self.cpu.translate_support_code:
             self.annhelper.finish()
 
diff --git a/rpython/tool/algo/bitstring.py b/rpython/tool/algo/bitstring.py
new file mode 100644
--- /dev/null
+++ b/rpython/tool/algo/bitstring.py
@@ -0,0 +1,23 @@
+
+
+def make_bitstring(lst):
+    "NOT_RPYTHON"
+    if not lst:
+        return ''
+    num_bits = max(lst) + 1
+    num_bytes = (num_bits + 7) // 8
+    entries = [0] * num_bytes
+    for x in lst:
+        assert x >= 0
+        entries[x >> 3] |= 1 << (x & 7)
+    return ''.join(map(chr, entries))
+
+def bitcheck(bitstring, n):
+    assert n >= 0
+    byte_number = n >> 3
+    if byte_number >= len(bitstring):
+        return False
+    return (ord(bitstring[byte_number]) & (1 << (n & 7))) != 0
+
+def num_bits(bitstring):
+    return len(bitstring) << 3
diff --git a/rpython/tool/algo/test/test_bitstring.py 
b/rpython/tool/algo/test/test_bitstring.py
new file mode 100644
--- /dev/null
+++ b/rpython/tool/algo/test/test_bitstring.py
@@ -0,0 +1,25 @@
+from rpython.tool.algo.bitstring import *
+from hypothesis import given, strategies
+
+def test_make():
+    assert make_bitstring([]) == ''
+    assert make_bitstring([0]) == '\x01'
+    assert make_bitstring([7]) == '\x80'
+    assert make_bitstring([8]) == '\x00\x01'
+    assert make_bitstring([2, 4, 20]) == '\x14\x00\x10'
+
+def test_bitcheck():
+    assert bitcheck('\x01', 0) is True
+    assert bitcheck('\x01', 1) is False
+    assert bitcheck('\x01', 10) is False
+    assert [n for n in range(32) if bitcheck('\x14\x00\x10', n)] == [2, 4, 20]
+
+@given(strategies.lists(strategies.integers(min_value=0, max_value=299)))
+def test_random(lst):
+    bitstring = make_bitstring(lst)
+    assert set([n for n in range(300) if bitcheck(bitstring, n)]) == set(lst)
+
+def test_num_bits():
+    assert num_bits('') == 0
+    assert num_bits('a') == 8
+    assert num_bits('bcd') == 24
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
@@ -1,3 +1,4 @@
+import py
 from rpython.rtyper.lltypesystem import lltype
 from rpython.translator.translator import TranslationContext, graphof
 from rpython.translator.backendopt.writeanalyze import WriteAnalyzer, top_set
@@ -314,6 +315,7 @@
         assert T1 == T2
 
     def test_cutoff(self):
+        py.test.skip("cutoff: disabled")
         from rpython.rlib.unroll import unrolling_iterable
         cutoff = 20
         attrs = unrolling_iterable(["s%s" % i for i in range(cutoff + 5)])
diff --git a/rpython/translator/backendopt/writeanalyze.py 
b/rpython/translator/backendopt/writeanalyze.py
--- a/rpython/translator/backendopt/writeanalyze.py
+++ b/rpython/translator/backendopt/writeanalyze.py
@@ -4,10 +4,14 @@
 top_set = object()
 empty_set = frozenset()
 
-CUTOFF = 3000
+# CUTOFF is disabled, as it gave a strangely not-working-any-more effect
+# if the size of the result grows past that bound.  The main user was
+# optimizeopt/heap.py (force_from_effectinfo), which has been rewritten
+# to be happy with any size now.
+#CUTOFF = 3000
 
 class WriteAnalyzer(graphanalyze.GraphAnalyzer):
-    cutoff = CUTOFF
+    #cutoff = CUTOFF
 
     def bottom_result(self):
         return empty_set
@@ -25,8 +29,8 @@
         if other is top_set:
             return top_set
         result.update(other)
-        if len(result) > self.cutoff:
-            return top_set
+        #if len(result) > self.cutoff:
+        #    return top_set
         return result
 
     def finalize_builder(self, result):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to