Commits

Armin Rigo committed c965adb

Implement quasi-immutable fields which contain immutable lists,
defined with _immutable_fields_ = ['foo?[*]'].

It actually means that 'foo' is a regular quasi-immutable field,
and that all list accesses we do directly by 'obj.foo[index]' are
supposed to read the **immutable** content of the list. The
content of the list is not itself quasi-immutable. Hence the
precise notation: 'lst?[*]', and not 'lst[*]?'.

Comments (0)

Files changed (14)

pypy/annotation/description.py

         return None
 
     def maybe_return_immutable_list(self, attr, s_result):
-        # hack: 'x.lst' where lst is listed in _immutable_fields_ as 'lst[*]'
+        # hack: 'x.lst' where lst is listed in _immutable_fields_ as
+        # either 'lst[*]' or 'lst?[*]'
         # should really return an immutable list as a result.  Implemented
         # by changing the result's annotation (but not, of course, doing an
         # actual copy in the rtyper).  Tested in pypy.rpython.test.test_rlist,
         # test_immutable_list_out_of_instance.
-        search = '%s[*]' % (attr,)
+        search1 = '%s[*]' % (attr,)
+        search2 = '%s?[*]' % (attr,)
         cdesc = self
         while cdesc is not None:
             if '_immutable_fields_' in cdesc.classdict:
-                if search in cdesc.classdict['_immutable_fields_'].value:
+                if (search1 in cdesc.classdict['_immutable_fields_'].value or
+                    search2 in cdesc.classdict['_immutable_fields_'].value):
                     s_result.listdef.never_resize()
                     s_copy = s_result.listdef.offspring()
                     s_copy.listdef.mark_as_immutable()

pypy/annotation/test/test_annrpython.py

         s = a.build_types(f, [int])
         assert s.listdef.listitem.immutable
 
+    def test_return_immutable_list_quasiimmut_field(self):
+        class A:
+            _immutable_fields_ = 'lst?[*]'
+        def f(n):
+            a = A()
+            l1 = [n, 0]
+            l1[1] = n+1
+            a.lst = l1
+            return a.lst
+
+        a = self.RPythonAnnotator()
+        s = a.build_types(f, [int])
+        assert s.listdef.listitem.immutable
+
     def test_immutable_list_is_actually_resized(self):
         class A:
             _immutable_fields_ = 'lst[*]'

pypy/jit/codewriter/jtransform.py

 from pypy.jit.codewriter.policy import log
 from pypy.jit.metainterp.typesystem import deref, arrayItem
 from pypy.jit.metainterp import quasiimmut
+from pypy.rpython.rclass import IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY
 from pypy.rlib import objectmodel
 from pypy.rlib.jit import _we_are_jitted
 from pypy.translator.simplify import get_funcobj
                     "known non-negative, or catching IndexError, or\n"
                     "not inlining at all (for tests: use listops=True).\n"
                     "Occurred in: %r" % self.graph)
-            # extra expanation: with the way things are organized in
+            # extra explanation: with the way things are organized in
             # rpython/rlist.py, the ll_getitem becomes a function call
             # that is typically meant to be inlined by the JIT, but
             # this does not work with vable arrays because
         op1 = SpaceOperation('getfield_%s_%s%s' % (argname, kind, pure),
                              [v_inst, descr], op.result)
         #
-        if immut is quasiimmut.IR_QUASI_IMMUTABLE:
+        if immut in (IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY):
             descr1 = self.cpu.fielddescrof(
                 v_inst.concretetype.TO,
                 quasiimmut.get_mutate_field_name(c_fieldname.value))

pypy/jit/codewriter/test/test_jtransform.py

     assert op1.args[3] == ListOfKind('ref', [v1, v2])
 
 def test_quasi_immutable():
-    from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+    from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
     accessor = FieldListAccessor()
-    accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+    accessor.initialize(None, {'inst_x': IR_QUASIIMMUTABLE})
     v2 = varoftype(lltype.Signed)
     STRUCT = lltype.GcStruct('struct', ('inst_x', lltype.Signed),
                              ('mutate_x', rclass.OBJECTPTR),
         assert op2.result is op.result
 
 def test_quasi_immutable_setfield():
-    from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+    from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
     accessor = FieldListAccessor()
-    accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+    accessor.initialize(None, {'inst_x': IR_QUASIIMMUTABLE})
     v1 = varoftype(lltype.Signed)
     STRUCT = lltype.GcStruct('struct', ('inst_x', lltype.Signed),
                              ('mutate_x', rclass.OBJECTPTR),

pypy/jit/metainterp/pyjitpl.py

         # During tracing, a 'jit_force_quasi_immutable' usually turns into
         # the operations that check that the content of 'mutate_xxx' is null.
         # If it is actually not null already now, then we abort tracing.
+        # The idea is that if we use 'jit_force_quasi_immutable' on a freshly
+        # allocated object, then the GETFIELD_GC will know that the answer is
+        # null, and the guard will be removed.  So the fact that the field is
+        # quasi-immutable will have no effect, and instead it will work as a
+        # regular, probably virtual, structure.
         mutatebox = self.execute_with_descr(rop.GETFIELD_GC,
                                             mutatefielddescr, box)
         if mutatebox.nonnull():

pypy/jit/metainterp/quasiimmut.py

 import weakref
-from pypy.rpython.rclass import IR_QUASI_IMMUTABLE
 from pypy.rpython.lltypesystem import lltype, rclass
 from pypy.rpython.annlowlevel import cast_base_ptr_to_instance
 from pypy.jit.metainterp.history import AbstractDescr
 
 
-def is_quasi_immutable(STRUCT, fieldname):
-    imm_fields = STRUCT._hints.get('immutable_fields')
-    return (imm_fields is not None and
-            imm_fields.fields.get(fieldname) is IR_QUASI_IMMUTABLE)
-
 def get_mutate_field_name(fieldname):
     if fieldname.startswith('inst_'):    # lltype
         return 'mutate_' + fieldname[5:]

pypy/jit/metainterp/test/test_optimizeutil.py

 from pypy.rpython.lltypesystem import lltype, llmemory, rclass, rstr
 from pypy.rpython.ootypesystem import ootype
 from pypy.rpython.lltypesystem.rclass import OBJECT, OBJECT_VTABLE
-from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
 
 from pypy.jit.backend.llgraph import runner
 from pypy.jit.metainterp.history import (BoxInt, BoxPtr, ConstInt, ConstPtr,
     otherdescr = cpu.fielddescrof(NODE2, 'other')
 
     accessor = FieldListAccessor()
-    accessor.initialize(None, {'inst_field': IR_QUASI_IMMUTABLE})
+    accessor.initialize(None, {'inst_field': IR_QUASIIMMUTABLE})
     QUASI = lltype.GcStruct('QUASIIMMUT', ('inst_field', lltype.Signed),
                             ('mutate_field', rclass.OBJECTPTR),
                             hints={'immutable_fields': accessor})

pypy/jit/metainterp/test/test_quasiimmut.py

 import py
 
 from pypy.rpython.lltypesystem import lltype, llmemory, rclass
-from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
 from pypy.jit.metainterp import typesystem
 from pypy.jit.metainterp.quasiimmut import QuasiImmut
 from pypy.jit.metainterp.quasiimmut import get_current_qmut_instance
 
 def test_get_current_qmut_instance():
     accessor = FieldListAccessor()
-    accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+    accessor.initialize(None, {'inst_x': IR_QUASIIMMUTABLE})
     STRUCT = lltype.GcStruct('Foo', ('inst_x', lltype.Signed),
                              ('mutate_x', rclass.OBJECTPTR),
                              hints={'immutable_fields': accessor})
         assert f(100, 15) == 3009
         res = self.meta_interp(f, [100, 15])
         assert res == 3009
-        self.check_loops(guard_not_invalidated=2,
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
                          call_may_force=0, guard_not_forced=0)
 
+    def test_list_simple_1(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        def f(a, x):
+            lst1 = [0, 0]
+            lst1[1] = a
+            foo = Foo(lst1)
+            total = 0
+            while x > 0:
+                myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+                # read a quasi-immutable field out of a Constant
+                total += foo.lst[1]
+                x -= 1
+            return total
+        #
+        res = self.meta_interp(f, [100, 7])
+        assert res == 700
+        self.check_loops(getfield_gc=0, getarrayitem_gc=0,
+                         getarrayitem_gc_pure=0, everywhere=True)
+        #
+        from pypy.jit.metainterp.warmspot import get_stats
+        loops = get_stats().loops
+        for loop in loops:
+            assert len(loop.quasi_immutable_deps) == 1
+            assert isinstance(loop.quasi_immutable_deps.keys()[0], QuasiImmut)
+
+    def test_list_change_during_running(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        @dont_look_inside
+        def residual_call(foo, x):
+            if x == 5:
+                lst2 = [0, 0]
+                lst2[1] = foo.lst[1] + 1
+                foo.lst = lst2
+        def f(a, x):
+            lst1 = [0, 0]
+            lst1[1] = a
+            foo = Foo(lst1)
+            total = 0
+            while x > 0:
+                myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+                # read a quasi-immutable field out of a Constant
+                total += foo.lst[1]
+                residual_call(foo, x)
+                total += foo.lst[1]
+                x -= 1
+            return total
+        #
+        assert f(100, 15) == 3009
+        res = self.meta_interp(f, [100, 15])
+        assert res == 3009
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         getarrayitem_gc=0, getarrayitem_gc_pure=0,
+                         call_may_force=0, guard_not_forced=0)
+
+
 class TestLLtypeGreenFieldsTests(QuasiImmutTests, LLJitMixin):
     pass

pypy/jit/metainterp/test/test_virtualizable.py

 from pypy.rpython.extregistry import ExtRegistryEntry
 from pypy.rpython.lltypesystem import lltype, lloperation, rclass, llmemory
 from pypy.rpython.annlowlevel import llhelper
-from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_IMMUTABLE_ARRAY
 from pypy.jit.codewriter.policy import StopAtXPolicy
 from pypy.jit.codewriter import heaptracker
 from pypy.rlib.jit import JitDriver, hint, dont_look_inside
         hints = {'virtualizable2_accessor': FieldListAccessor()})
     XY2._hints['virtualizable2_accessor'].initialize(
         XY2, {'inst_x' : IR_IMMUTABLE,
-              'inst_l1' : IR_ARRAY_IMMUTABLE, 'inst_l2' : IR_ARRAY_IMMUTABLE})
+              'inst_l1' : IR_IMMUTABLE_ARRAY, 'inst_l2' : IR_IMMUTABLE_ARRAY})
 
     xy2_vtable = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True)
     heaptracker.set_testing_vtable_for_gcstruct(XY2, xy2_vtable, 'XY2')

pypy/jit/metainterp/virtualizable.py

 from pypy.rpython.lltypesystem import lltype, llmemory
 from pypy.rpython.ootypesystem import ootype
 from pypy.rpython.annlowlevel import cast_base_ptr_to_instance
-from pypy.rpython.rclass import IR_ARRAY_IMMUTABLE, IR_IMMUTABLE
+from pypy.rpython.rclass import IR_IMMUTABLE_ARRAY, IR_IMMUTABLE
 from pypy.rpython import rvirtualizable2
 from pypy.rlib.objectmodel import we_are_translated
 from pypy.rlib.unroll import unrolling_iterable
         static_fields = []
         array_fields = []
         for name, tp in all_fields.iteritems():
-            if tp == IR_ARRAY_IMMUTABLE:
+            if tp == IR_IMMUTABLE_ARRAY:
                 array_fields.append(name)
             elif tp == IR_IMMUTABLE:
                 static_fields.append(name)

pypy/rpython/lltypesystem/test/test_lloperation.py

     assert llop.getarraysize.is_pure([v_a2])
     #
     for kind in [rclass.IR_MUTABLE, rclass.IR_IMMUTABLE,
-                 rclass.IR_ARRAY_IMMUTABLE, rclass.IR_QUASI_IMMUTABLE]:
+                 rclass.IR_IMMUTABLE_ARRAY, rclass.IR_QUASIIMMUTABLE,
+                 rclass.IR_QUASIIMMUTABLE_ARRAY]:
         accessor = rclass.FieldListAccessor()
         S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
                              hints={'immutable_fields': accessor})
     assert llop.getinteriorfield(lltype.Signed, s2, 'x') == 45
     #
     for kind in [rclass.IR_MUTABLE, rclass.IR_IMMUTABLE,
-                 rclass.IR_ARRAY_IMMUTABLE, rclass.IR_QUASI_IMMUTABLE]:
+                 rclass.IR_IMMUTABLE_ARRAY, rclass.IR_QUASIIMMUTABLE,
+                 rclass.IR_QUASIIMMUTABLE_ARRAY]:
         #
         S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
                              hints={'immutable_fields': accessor})
         accessor.initialize(S3, {'x': kind})
         s3 = lltype.malloc(S3); s3.x = 46; s3.y = 47
-        if kind in [rclass.IR_IMMUTABLE, rclass.IR_ARRAY_IMMUTABLE]:
+        if kind in [rclass.IR_IMMUTABLE, rclass.IR_IMMUTABLE_ARRAY]:
             assert llop.getfield(lltype.Signed, s3, 'x') == 46
             assert llop.getinteriorfield(lltype.Signed, s3, 'x') == 46
         else:

pypy/rpython/rclass.py

     def __repr__(self):
         return '<%s>' % self.name
 
-IR_MUTABLE         = ImmutableRanking('mutable', False)
-IR_IMMUTABLE       = ImmutableRanking('immutable', True)
-IR_ARRAY_IMMUTABLE = ImmutableRanking('array_immutable', True)
-IR_QUASI_IMMUTABLE = ImmutableRanking('quasi_immutable', False)
+IR_MUTABLE              = ImmutableRanking('mutable', False)
+IR_IMMUTABLE            = ImmutableRanking('immutable', True)
+IR_IMMUTABLE_ARRAY      = ImmutableRanking('immutable_array', True)
+IR_QUASIIMMUTABLE       = ImmutableRanking('quasiimmutable', False)
+IR_QUASIIMMUTABLE_ARRAY = ImmutableRanking('quasiimmutable_array', False)
 
 class ImmutableConflictError(Exception):
     """Raised when the _immutable_ or _immutable_fields_ hints are
     def _parse_field_list(self, fields, accessor):
         ranking = {}
         for name in fields:
-            if name.endswith('[*]'):    # for virtualizables' lists
+            if name.endswith('?[*]'):   # a quasi-immutable field pointing to
+                name = name[:-4]        # an immutable array
+                rank = IR_QUASIIMMUTABLE_ARRAY
+            elif name.endswith('[*]'):    # for virtualizables' lists
                 name = name[:-3]
-                rank = IR_ARRAY_IMMUTABLE
+                rank = IR_IMMUTABLE_ARRAY
             elif name.endswith('?'):    # a quasi-immutable field
                 name = name[:-1]
-                rank = IR_QUASI_IMMUTABLE
+                rank = IR_QUASIIMMUTABLE
             else:                       # a regular immutable/green field
                 rank = IR_IMMUTABLE
             try:
             llops.genop('jit_force_quasi_immutable', [vinst, c_fieldname])
 
     def is_quasi_immutable(self, fieldname):
-        search = fieldname + '?'
+        search1 = fieldname + '?'
+        search2 = fieldname + '?[*]'
         rbase = self
         while rbase.classdef is not None:
-            if search in rbase.immutable_field_set:
+            if (search1 in rbase.immutable_field_set or
+                search2 in rbase.immutable_field_set):
                 return True
             rbase = rbase.rbase
         return False

pypy/rpython/test/test_rclass.py

 from pypy.rpython.ootypesystem import ootype
 from pypy.rlib.rarithmetic import intmask, r_longlong
 from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
-from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
-from pypy.rpython.rclass import IR_QUASI_IMMUTABLE
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_IMMUTABLE_ARRAY
+from pypy.rpython.rclass import IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY
 from pypy.objspace.flow.model import summary
 
 class EmptyBase(object):
         A_TYPE = deref(graph.getreturnvar().concretetype)
         accessor = A_TYPE._hints["immutable_fields"]
         assert accessor.fields == {"inst_x": IR_IMMUTABLE,
-                                   "inst_y": IR_ARRAY_IMMUTABLE} or \
+                                   "inst_y": IR_IMMUTABLE_ARRAY} or \
                accessor.fields == {"ox": IR_IMMUTABLE,
-                                   "oy": IR_ARRAY_IMMUTABLE} # for ootype
+                                   "oy": IR_IMMUTABLE_ARRAY} # for ootype
 
     def test_immutable_fields_subclass_1(self):
         from pypy.jit.metainterp.typesystem import deref
         B_TYPE = deref(graph.getreturnvar().concretetype)
         accessor = B_TYPE._hints["immutable_fields"]
         assert accessor.fields == {"inst_y": IR_IMMUTABLE,
-                                   "inst_b": IR_QUASI_IMMUTABLE} or \
+                                   "inst_b": IR_QUASIIMMUTABLE} or \
                accessor.fields == {"ox": IR_IMMUTABLE,
                                    "oy": IR_IMMUTABLE,
-                                   "oa": IR_QUASI_IMMUTABLE,
-                                   "ob": IR_QUASI_IMMUTABLE} # for ootype
+                                   "oa": IR_QUASIIMMUTABLE,
+                                   "ob": IR_QUASIIMMUTABLE} # for ootype
         found = []
         for op in graph.startblock.operations:
             if op.opname == 'jit_force_quasi_immutable':
                 found.append(op.args[1].value)
         assert found == ['mutate_a', 'mutate_a', 'mutate_b']
 
+    def test_quasi_immutable_array(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            _immutable_fields_ = ['c?[*]']
+        class B(A):
+            pass
+        def f():
+            a = A()
+            a.c = [3, 4, 5]
+            return A()
+        t, typer, graph = self.gengraph(f, [])
+        A_TYPE = deref(graph.getreturnvar().concretetype)
+        accessor = A_TYPE._hints["immutable_fields"]
+        assert accessor.fields == {"inst_c": IR_QUASIIMMUTABLE_ARRAY} or \
+               accessor.fields == {"oc": IR_QUASIIMMUTABLE_ARRAY} # for ootype
+        found = []
+        for op in graph.startblock.operations:
+            if op.opname == 'jit_force_quasi_immutable':
+                found.append(op.args[1].value)
+        assert found == ['mutate_c']
+
 
 class TestLLtype(BaseTestRclass, LLRtypeMixin):
 

pypy/rpython/test/test_rvirtualizable2.py

 from pypy.rlib.jit import hint
 from pypy.objspace.flow.model import summary
 from pypy.rpython.llinterp import LLInterpreter
-from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_IMMUTABLE_ARRAY
 from pypy import conftest
 
 
         accessor = TYPE._hints['virtualizable2_accessor']
         assert accessor.TYPE == TYPE
         assert accessor.fields == {self.prefix + 'v1': IR_IMMUTABLE,
-                                   self.prefix + 'v2': IR_ARRAY_IMMUTABLE}
+                                   self.prefix + 'v2': IR_IMMUTABLE_ARRAY}
         #
         def fn2(n):
             Base().base1 = 42