[pypy-svn] r76857 - in pypy/trunk/pypy: jit/codewriter objspace/std rpython rpython/lltypesystem rpython/lltypesystem/test rpython/memory/test rpython/ootypesystem rpython/test translator/backendopt/test

arigo at codespeak.net arigo at codespeak.net
Fri Sep 3 17:39:50 CEST 2010


Author: arigo
Date: Fri Sep  3 17:39:47 2010
New Revision: 76857

Modified:
   pypy/trunk/pypy/jit/codewriter/jtransform.py
   pypy/trunk/pypy/objspace/std/tupleobject.py
   pypy/trunk/pypy/rpython/lltypesystem/lloperation.py
   pypy/trunk/pypy/rpython/lltypesystem/lltype.py
   pypy/trunk/pypy/rpython/lltypesystem/opimpl.py
   pypy/trunk/pypy/rpython/lltypesystem/test/test_lloperation.py
   pypy/trunk/pypy/rpython/lltypesystem/test/test_lltype.py
   pypy/trunk/pypy/rpython/memory/test/test_gctypelayout.py
   pypy/trunk/pypy/rpython/ootypesystem/ootype.py
   pypy/trunk/pypy/rpython/ootypesystem/rclass.py
   pypy/trunk/pypy/rpython/rclass.py
   pypy/trunk/pypy/rpython/test/test_rclass.py
   pypy/trunk/pypy/translator/backendopt/test/test_constfold.py
Log:
Merge branch/no-_immutable_.

Unlike hinted by the name of the branch, this does not kill the
_immutable_ hint; it just makes it saner (and _immutable_fields_ too).
Now, the definition in pure Python terms is precisely that an instance x
is fully immutable if and only if hasattr(x, '_immutable_').

During translation, it is now an error (reported by rpython/rclass.py)
if we find the _immutable_ hint on a class but, on some parent class, we
have non-trivial non-read-only attributes.

Similarly, it is an error to list a field name in _immutable_fields_ if
that field ends up on some parent class.

However, it is ok to give a field name that is not actually present in
this class.  If/when it shows up on some subclass, it will be flagged as
read-only there.  The idea is again that if we say
_immutable_fields_=['x'] on some class A, then any attribute 'x' on any
instance of (a subclass of) A will be read-only.


Modified: pypy/trunk/pypy/jit/codewriter/jtransform.py
==============================================================================
--- pypy/trunk/pypy/jit/codewriter/jtransform.py	(original)
+++ pypy/trunk/pypy/jit/codewriter/jtransform.py	Fri Sep  3 17:39:47 2010
@@ -511,14 +511,11 @@
                                                 arraydescr)
             return []
         # check for deepfrozen structures that force constant-folding
-        hints = v_inst.concretetype.TO._hints
-        accessor = hints.get("immutable_fields")
-        if accessor and c_fieldname.value in accessor.fields:
+        immut = v_inst.concretetype.TO._immutable_field(c_fieldname.value)
+        if immut:
             pure = '_pure'
-            if accessor.fields[c_fieldname.value] == "[*]":
+            if immut == "[*]":
                 self.immutable_arrays[op.result] = True
-        elif hints.get('immutable'):
-            pure = '_pure'
         else:
             pure = ''
         argname = getattr(v_inst.concretetype.TO, '_gckind', 'gc')

Modified: pypy/trunk/pypy/objspace/std/tupleobject.py
==============================================================================
--- pypy/trunk/pypy/objspace/std/tupleobject.py	(original)
+++ pypy/trunk/pypy/objspace/std/tupleobject.py	Fri Sep  3 17:39:47 2010
@@ -10,7 +10,7 @@
 
 class W_TupleObject(W_Object):
     from pypy.objspace.std.tupletype import tuple_typedef as typedef
-    _immutable_ = True
+    _immutable_fields_ = ['wrappeditems[*]']
 
     def __init__(w_self, wrappeditems):
         make_sure_not_resized(wrappeditems)

Modified: pypy/trunk/pypy/rpython/lltypesystem/lloperation.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/lloperation.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/lloperation.py	Fri Sep  3 17:39:47 2010
@@ -85,16 +85,20 @@
     fold = roproperty(get_fold_impl)
 
     def is_pure(self, args_v):
-        return (self.canfold or                # canfold => pure operation
-                self is llop.debug_assert or   # debug_assert is pure enough
-                                               # reading from immutable
-                (self in (llop.getfield, llop.getarrayitem) and
-                 args_v[0].concretetype.TO._hints.get('immutable')) or
-                (self is llop.getfield and     # reading from immutable_field
-                 'immutable_fields' in args_v[0].concretetype.TO._hints and
-                 args_v[1].value in args_v[0].concretetype.TO
-                                           ._hints['immutable_fields'].fields))
-        # XXX: what about ootype immutable arrays?
+        if self.canfold:                # canfold => pure operation
+            return True
+        if self is llop.debug_assert:   # debug_assert is pure enough
+            return True
+        # reading from immutable (lltype)
+        if self is llop.getfield or self is llop.getarrayitem:
+            field = getattr(args_v[1], 'value', None)
+            return args_v[0].concretetype.TO._immutable_field(field)
+        # reading from immutable (ootype) (xxx what about arrays?)
+        if self is llop.oogetfield:
+            field = getattr(args_v[1], 'value', None)
+            return args_v[0].concretetype._immutable_field(field)
+        # default
+        return False
 
     def __repr__(self):
         return '<LLOp %s>' % (getattr(self, 'opname', '?'),)

Modified: pypy/trunk/pypy/rpython/lltypesystem/lltype.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/lltype.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/lltype.py	Fri Sep  3 17:39:47 2010
@@ -297,6 +297,15 @@
             n = 1
         return _struct(self, n, initialization='example')
 
+    def _immutable_field(self, field):
+        if 'immutable_fields' in self._hints:
+            try:
+                s = self._hints['immutable_fields'].fields[field]
+                return s or True
+            except KeyError:
+                pass
+        return self._hints.get('immutable', False)
+
 class RttiStruct(Struct):
     _runtime_type_info = None
 
@@ -391,6 +400,9 @@
     def _container_example(self):
         return _array(self, 1, initialization='example')
 
+    def _immutable_field(self, index=None):
+        return self._hints.get('immutable', False)
+
 class GcArray(Array):
     _gckind = 'gc'
     def _inline_is_varsize(self, last):

Modified: pypy/trunk/pypy/rpython/lltypesystem/opimpl.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/opimpl.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/opimpl.py	Fri Sep  3 17:39:47 2010
@@ -150,12 +150,7 @@
     # we can constant-fold this if the innermost structure from which we
     # read the final field is immutable.
     T = lltype.typeOf(innermostcontainer).TO
-    if T._hints.get('immutable'):
-        pass
-    elif ('immutable_fields' in T._hints and
-          offsets[-1] in T._hints['immutable_fields'].fields):
-        pass
-    else:
+    if not T._immutable_field(offsets[-1]):
         raise TypeError("cannot fold getinteriorfield on mutable struct")
     assert not isinstance(ob, lltype._interior_ptr)
     return ob
@@ -437,19 +432,15 @@
 def op_getfield(p, name):
     checkptr(p)
     TYPE = lltype.typeOf(p).TO
-    if TYPE._hints.get('immutable'):
-        pass
-    elif ('immutable_fields' in TYPE._hints and
-          name in TYPE._hints['immutable_fields'].fields):
-        pass
-    else:
+    if not TYPE._immutable_field(name):
         raise TypeError("cannot fold getfield on mutable struct")
     return getattr(p, name)
 
 def op_getarrayitem(p, index):
     checkptr(p)
-    if not lltype.typeOf(p).TO._hints.get('immutable'):
-        raise TypeError("cannot fold getfield on mutable array")
+    ARRAY = lltype.typeOf(p).TO
+    if not ARRAY._immutable_field(index):
+        raise TypeError("cannot fold getarrayitem on mutable array")
     return p[index]
 
 def _normalize(x):

Modified: pypy/trunk/pypy/rpython/lltypesystem/test/test_lloperation.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/test/test_lloperation.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/test/test_lloperation.py	Fri Sep  3 17:39:47 2010
@@ -88,7 +88,7 @@
     accessor = rclass.FieldListAccessor()
     S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
                          hints={'immutable_fields': accessor})
-    accessor.initialize(S3, ['x'])
+    accessor.initialize(S3, {'x': ''})
     v_s3 = Variable()
     v_s3.concretetype = lltype.Ptr(S3)
     assert not llop.setfield.is_pure([v_s3, Constant('x'), Variable()])
@@ -103,7 +103,7 @@
     accessor = rclass.FieldListAccessor()
     S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
                          hints={'immutable_fields': accessor})
-    accessor.initialize(S3, ['x'])
+    accessor.initialize(S3, {'x': ''})
     #
     s1 = lltype.malloc(S1); s1.x = 45
     py.test.raises(TypeError, llop.getfield, lltype.Signed, s1, 'x')

Modified: pypy/trunk/pypy/rpython/lltypesystem/test/test_lltype.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/test/test_lltype.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/test/test_lltype.py	Fri Sep  3 17:39:47 2010
@@ -781,6 +781,28 @@
     p = cast_opaque_ptr(llmemory.GCREF, a)
     assert hash1 == identityhash(p)
 
+def test_immutable_hint():
+    S = GcStruct('S', ('x', lltype.Signed))
+    assert S._immutable_field('x') == False
+    #
+    S = GcStruct('S', ('x', lltype.Signed), hints={'immutable': True})
+    assert S._immutable_field('x') == True
+    #
+    class FieldListAccessor(object):
+        def __init__(self, fields):
+            self.fields = fields
+    S = GcStruct('S', ('x', lltype.Signed),
+                 hints={'immutable_fields': FieldListAccessor({'x':''})})
+    assert S._immutable_field('x') == True
+    #
+    class FieldListAccessor(object):
+        def __init__(self, fields):
+            self.fields = fields
+    S = GcStruct('S', ('x', lltype.Signed),
+                 hints={'immutable_fields': FieldListAccessor({'x':'[*]'})})
+    assert S._immutable_field('x') == '[*]'
+
+
 class TestTrackAllocation:
     def setup_method(self, func):
         start_tracking_allocations()

Modified: pypy/trunk/pypy/rpython/memory/test/test_gctypelayout.py
==============================================================================
--- pypy/trunk/pypy/rpython/memory/test/test_gctypelayout.py	(original)
+++ pypy/trunk/pypy/rpython/memory/test/test_gctypelayout.py	Fri Sep  3 17:39:47 2010
@@ -101,7 +101,7 @@
     accessor = rclass.FieldListAccessor()
     S3 = lltype.GcStruct('S', ('x', PT), ('y', PT),
                          hints={'immutable_fields': accessor})
-    accessor.initialize(S3, ['x'])
+    accessor.initialize(S3, {'x': ''})
     #
     s1 = lltype.malloc(S1)
     adr = llmemory.cast_ptr_to_adr(s1)

Modified: pypy/trunk/pypy/rpython/ootypesystem/ootype.py
==============================================================================
--- pypy/trunk/pypy/rpython/ootypesystem/ootype.py	(original)
+++ pypy/trunk/pypy/rpython/ootypesystem/ootype.py	Fri Sep  3 17:39:47 2010
@@ -267,6 +267,14 @@
             return self._fields_with_default[:]
         return self._superclass._get_fields_with_default() + self._fields_with_default
 
+    def _immutable_field(self, field):
+        if 'immutable_fields' in self._hints:
+            try:
+                s = self._hints['immutable_fields'].fields[field]
+                return s or True
+            except KeyError:
+                pass
+        return self._hints.get('immutable', False)
 
 
 class SpecializableType(OOType):

Modified: pypy/trunk/pypy/rpython/ootypesystem/rclass.py
==============================================================================
--- pypy/trunk/pypy/rpython/ootypesystem/rclass.py	(original)
+++ pypy/trunk/pypy/rpython/ootypesystem/rclass.py	Fri Sep  3 17:39:47 2010
@@ -194,6 +194,7 @@
             self.lowleveltype._hints.update(hints)
 
         if self.classdef is None:
+            self.fields = {}
             self.allfields = {}
             self.allmethods = {}
             self.allclassattributes = {}
@@ -210,6 +211,7 @@
             allclassattributes = {}
 
         fields = {}
+        nonmangledfields = []
         fielddefaults = {}
 
         if llfields:
@@ -224,6 +226,7 @@
                 allfields[mangled] = repr
                 oot = repr.lowleveltype
                 fields[mangled] = oot
+                nonmangledfields.append(name)
                 try:
                     value = self.classdef.classdesc.read_attribute(name)
                     fielddefaults[mangled] = repr.convert_desc_or_const(value)
@@ -294,6 +297,7 @@
                     if not attrdef.s_value.is_constant():
                         classattributes[mangled] = attrdef.s_value, value
 
+        self.fields = nonmangledfields
         self.allfields = allfields
         self.allmethods = allmethods
         self.allclassattributes = allclassattributes

Modified: pypy/trunk/pypy/rpython/rclass.py
==============================================================================
--- pypy/trunk/pypy/rpython/rclass.py	(original)
+++ pypy/trunk/pypy/rpython/rclass.py	Fri Sep  3 17:39:47 2010
@@ -9,6 +9,7 @@
 class FieldListAccessor(object):
 
     def initialize(self, TYPE, fields):
+        assert type(fields) is dict
         self.TYPE = TYPE
         self.fields = fields
 
@@ -18,6 +19,10 @@
     def _freeze_(self):
         return True
 
+class ImmutableConflictError(Exception):
+    """Raised when the _immutable_ or _immutable_fields_ hints are
+    not consistent across a class hierarchy."""
+
 
 def getclassrepr(rtyper, classdef):
     try:
@@ -153,7 +158,7 @@
         pass
 
     def _check_for_immutable_hints(self, hints):
-        if '_immutable_' in self.classdef.classdesc.classdict:
+        if self.classdef.classdesc.lookup('_immutable_') is not None:
             hints = hints.copy()
             hints['immutable'] = True
         self.immutable_field_list = []  # unless overwritten below
@@ -182,16 +187,20 @@
         return 'InstanceR %s' % (clsname,)
 
     def _setup_repr_final(self):
+        self._setup_immutable_field_list()
+        self._check_for_immutable_conflicts()
+
+    def _setup_immutable_field_list(self):
         hints = self.object_type._hints
         if "immutable_fields" in hints:
             accessor = hints["immutable_fields"]
-            immutable_fields = {}
-            rbase = self
-            while rbase.classdef is not None:
-                immutable_fields.update(
-                    dict.fromkeys(rbase.immutable_field_list))
-                rbase = rbase.rbase
-            self._parse_field_list(immutable_fields, accessor)
+            if not hasattr(accessor, 'fields'):
+                immutable_fields = []
+                rbase = self
+                while rbase.classdef is not None:
+                    immutable_fields += rbase.immutable_field_list
+                    rbase = rbase.rbase
+                self._parse_field_list(immutable_fields, accessor)
 
     def _parse_field_list(self, fields, accessor):
         with_suffix = {}
@@ -209,6 +218,36 @@
         accessor.initialize(self.object_type, with_suffix)
         return with_suffix
 
+    def _check_for_immutable_conflicts(self):
+        # check for conflicts, i.e. a field that is defined normally as
+        # mutable in some parent class but that is now declared immutable
+        from pypy.rpython.lltypesystem.lltype import Void
+        is_self_immutable = "immutable" in self.object_type._hints
+        base = self
+        while base.classdef is not None:
+            base = base.rbase
+            for fieldname in base.fields:
+                try:
+                    mangled, r = base._get_field(fieldname)
+                except KeyError:
+                    continue
+                if r.lowleveltype == Void:
+                    continue
+                base._setup_immutable_field_list()
+                if base.object_type._immutable_field(mangled):
+                    continue
+                # 'fieldname' is a mutable, non-Void field in the parent
+                if is_self_immutable:
+                    raise ImmutableConflictError(
+                        "class %r has _immutable_=True, but parent class %r "
+                        "defines (at least) the mutable field %r" % (
+                        self, base, fieldname))
+                if fieldname in self.immutable_field_list:
+                    raise ImmutableConflictError(
+                        "field %r is defined mutable in class %r, but "
+                        "listed in _immutable_fields_ in subclass %r" % (
+                        fieldname, base, self))
+
     def new_instance(self, llops, classcallhop=None):
         raise NotImplementedError
 

Modified: pypy/trunk/pypy/rpython/test/test_rclass.py
==============================================================================
--- pypy/trunk/pypy/rpython/test/test_rclass.py	(original)
+++ pypy/trunk/pypy/rpython/test/test_rclass.py	Fri Sep  3 17:39:47 2010
@@ -796,27 +796,92 @@
         assert accessor.fields == {"inst_y" : ""} or \
                accessor.fields == {"oy" : ""} # for ootype
 
-    def test_immutable_inheritance(self):
-        class I(object):
-            def __init__(self, v):
-                self.v = v
-        
-        class J(I):
+    def test_immutable_forbidden_inheritance_1(self):
+        from pypy.rpython.rclass import ImmutableConflictError
+        class A(object):
+            pass
+        class B(A):
+            _immutable_fields_ = ['v']
+        def f():
+            A().v = 123
+            B()             # crash: class B says 'v' is immutable,
+                            # but it is defined on parent class A
+        py.test.raises(ImmutableConflictError, self.gengraph, f, [])
+
+    def test_immutable_forbidden_inheritance_2(self):
+        from pypy.rpython.rclass import ImmutableConflictError
+        class A(object):
+            pass
+        class B(A):
+            _immutable_ = True
+        def f():
+            A().v = 123
+            B()             # crash: class B has _immutable_ = True
+                            # but class A defines 'v' to be mutable
+        py.test.raises(ImmutableConflictError, self.gengraph, f, [])
+
+    def test_immutable_ok_inheritance_2(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            _immutable_fields_ = ['v']
+        class B(A):
+            _immutable_ = True
+        def f():
+            A().v = 123
+            B().w = 456
+            return B()
+        t, typer, graph = self.gengraph(f, [])
+        B_TYPE = deref(graph.getreturnvar().concretetype)
+        assert B_TYPE._hints["immutable"]
+        try:
+            A_TYPE = B_TYPE.super
+        except AttributeError:
+            A_TYPE = B_TYPE._superclass  # for ootype
+        accessor = A_TYPE._hints["immutable_fields"]
+        assert accessor.fields == {"inst_v" : ""} or \
+               accessor.fields == {"ov" : ""} # for ootype
+
+    def test_immutable_subclass_1(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            _immutable_ = True
+        class B(A):
+            pass
+        def f():
+            B().v = 123
+            return B()
+        t, typer, graph = self.gengraph(f, [])
+        B_TYPE = deref(graph.getreturnvar().concretetype)
+        assert B_TYPE._hints["immutable"]    # inherited from A
+
+    def test_immutable_subclass_2(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            pass
+        class B(A):
             _immutable_ = True
-            def __init__(self, v, w):
-                self.w = w
-                I.__init__(self, v)
-
-        j = J(3, 4)
-        def f():
-            j.v = j.v * 1 # make the annotator think it is mutated
-            j.w = j.w * 1 # make the annotator think it is mutated
-            return j.v + j.w
-
-        t, typer, graph = self.gengraph(f, [], backendopt=True)
-        f_summary = summary(graph)
-        assert f_summary == {"setfield": 2} or \
-               f_summary == {"oosetfield": 2} # for ootype
+        def f():
+            B().v = 123
+            return B()
+        t, typer, graph = self.gengraph(f, [])
+        B_TYPE = deref(graph.getreturnvar().concretetype)
+        assert B_TYPE._hints["immutable"]
+
+    def test_immutable_subclass_void(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            pass
+        class B(A):
+            _immutable_ = True
+        def myfunc():
+            pass
+        def f():
+            A().f = myfunc    # it's ok to add Void attributes to A
+            B().v = 123       # even though only B is declared _immutable_
+            return B()
+        t, typer, graph = self.gengraph(f, [])
+        B_TYPE = deref(graph.getreturnvar().concretetype)
+        assert B_TYPE._hints["immutable"]
 
 
 class TestLLtype(BaseTestRclass, LLRtypeMixin):

Modified: pypy/trunk/pypy/translator/backendopt/test/test_constfold.py
==============================================================================
--- pypy/trunk/pypy/translator/backendopt/test/test_constfold.py	(original)
+++ pypy/trunk/pypy/translator/backendopt/test/test_constfold.py	Fri Sep  3 17:39:47 2010
@@ -49,7 +49,7 @@
     accessor = rclass.FieldListAccessor()
     S2 = lltype.GcStruct('S2', ('x', lltype.Signed),
                          hints={'immutable_fields': accessor})
-    accessor.initialize(S2, ['x'])
+    accessor.initialize(S2, {'x': ''})
     test_simple(S2)
 
 



More information about the Pypy-commit mailing list