[pypy-svn] r26822 - in pypy/dist/pypy: annotation rpython rpython/lltypesystem rpython/ootypesystem rpython/test

arigo at codespeak.net arigo at codespeak.net
Fri May 5 19:29:44 CEST 2006


Author: arigo
Date: Fri May  5 19:29:42 2006
New Revision: 26822

Modified:
   pypy/dist/pypy/annotation/bookkeeper.py
   pypy/dist/pypy/annotation/description.py
   pypy/dist/pypy/rpython/lltypesystem/rclass.py
   pypy/dist/pypy/rpython/lltypesystem/rpbc.py
   pypy/dist/pypy/rpython/normalizecalls.py
   pypy/dist/pypy/rpython/ootypesystem/rclass.py
   pypy/dist/pypy/rpython/rpbc.py
   pypy/dist/pypy/rpython/test/test_rpbc.py
Log:
Half refactoring of the AttrFamily: it is unchanged for FrozenDescs,
but for ClassDescs it now records attribute access for each attribute
name independently.  See new test in test_rpbc.

This is prompted by PyPy translation crashes: introducing a memo
function on the pbc of all W_Root subclasses forced them to be all in
the same AttrFamily, which had terribly bad effects, as follows: a few
subclasses of ast.Node are instantiated generically (by calling a
variable that can be any of them).  The same is true for the subclasses
of PyFrame.  However, the __init__ in these two parts of the hierarchy
are completely different.  So any common pbc attribute on all W_Root
subclasses caused the __init__ to come together too => obsure crash.



Modified: pypy/dist/pypy/annotation/bookkeeper.py
==============================================================================
--- pypy/dist/pypy/annotation/bookkeeper.py	(original)
+++ pypy/dist/pypy/annotation/bookkeeper.py	Fri May  5 19:29:42 2006
@@ -165,7 +165,8 @@
         self.dictdefs = {}       # map position_keys to DictDefs
         self.immutable_cache = {}
 
-        self.pbc_maximal_access_sets = UnionFind(description.AttrFamily)
+        self.classpbc_attr_families = {} # {'attr': UnionFind(ClassAttrFamily)}
+        self.frozenpbc_attr_families = UnionFind(description.FrozenAttrFamily)
         self.pbc_maximal_call_families = UnionFind(description.CallFamily)
 
         self.emulated_pbc_calls = {}
@@ -533,6 +534,17 @@
                 o.knowntype = t
             return o
 
+    def get_classpbc_attr_families(self, attrname):
+        """Return the UnionFind for the ClassAttrFamilies corresponding to
+        attributes of the given name.
+        """
+        map = self.classpbc_attr_families
+        try:
+            access_sets = map[attrname]
+        except KeyError:
+            access_sets = map[attrname] = UnionFind(description.ClassAttrFamily)
+        return access_sets
+
     def pbc_getattr(self, pbc, s_attr):
         assert s_attr.is_constant()
         attr = s_attr.const
@@ -541,8 +553,8 @@
         if not descs:
             return SomeImpossibleValue()
         first = descs[0]
-        change = first.mergeattrfamilies(*descs[1:])
-        attrfamily = first.getattrfamily()
+        change = first.mergeattrfamilies(descs[1:], attr)
+        attrfamily = first.getattrfamily(attr)
 
         position = self.position_key
         attrfamily.read_locations[position] = True
@@ -552,8 +564,8 @@
             actuals.append(desc.s_read_attribute(attr))
         s_result = unionof(*actuals)
 
-        attrfamily.attrs[attr] = unionof(s_result,
-            attrfamily.attrs.get(attr, s_ImpossibleValue))
+        s_oldvalue = attrfamily.get_s_value(attr)
+        attrfamily.set_s_value(attr, unionof(s_result, s_oldvalue))
 
         if change:
             for position in attrfamily.read_locations:

Modified: pypy/dist/pypy/annotation/description.py
==============================================================================
--- pypy/dist/pypy/annotation/description.py	(original)
+++ pypy/dist/pypy/annotation/description.py	Fri May  5 19:29:42 2006
@@ -47,15 +47,15 @@
             self.total_calltable_size += 1
 
 
-class AttrFamily:
-    """A family of Desc objects that have common 'getattr' sites.
-    The attr families are conceptually a partition of FrozenDesc and ClassDesc
-    objects, where the equivalence relation is the transitive closure of
-    'd1~d2 if d1 and d2 might have an attribute read on them by the same
-    getattr operation.'
+class FrozenAttrFamily:
+    """A family of FrozenDesc objects that have any common 'getattr' sites.
+    The attr families are conceptually a partition of FrozenDesc objects,
+    where the equivalence relation is the transitive closure of:
+    d1~d2 if d1 and d2 might have some attribute read on them by the same
+    getattr operation.
     """
     def __init__(self, desc):
-        self.descs = { desc: True }
+        self.descs = {desc: True}
         self.read_locations = {}     # set of position_keys
         self.attrs = {}              # { attr: s_value }
 
@@ -64,6 +64,49 @@
         self.read_locations.update(other.read_locations)
         self.attrs.update(other.attrs)
 
+    def get_s_value(self, attrname):
+        try:
+            return self.attrs[attrname]
+        except KeyError:
+            from pypy.annotation.model import s_ImpossibleValue
+            return s_ImpossibleValue
+
+    def set_s_value(self, attrname, s_value):
+        self.attrs[attrname] = s_value
+
+
+class ClassAttrFamily:
+    """A family of ClassDesc objects that have common 'getattr' sites for a
+    given attribute name.  The attr families are conceptually a partition
+    of ClassDesc objects, where the equivalence relation is the transitive
+    closure of:  d1~d2 if d1 and d2 might have a common attribute 'attrname'
+    read on them by the same getattr operation.
+
+    The 'attrname' is not explicitly stored here, but is the key used
+    in the dictionary bookkeeper.pbc_maximal_access_sets_map.
+    """
+    # The difference between ClassAttrFamily and FrozenAttrFamily is that
+    # FrozenAttrFamily is the union for all attribute names, but
+    # ClassAttrFamily is more precise: it is only about one attribut name.
+
+    def __init__(self, desc):
+        from pypy.annotation.model import s_ImpossibleValue
+        self.descs = { desc: True }
+        self.read_locations = {}     # set of position_keys
+        self.s_value = s_ImpossibleValue    # union of possible values
+
+    def update(self, other):
+        from pypy.annotation.model import unionof
+        self.descs.update(other.descs)
+        self.read_locations.update(other.read_locations)
+        self.s_value = unionof(self.s_value, other.s_value)
+
+    def get_s_value(self, attrname):
+        return self.s_value
+
+    def set_s_value(self, attrname, s_value):
+        self.s_value = s_value
+
 # ____________________________________________________________
 
 class Desc(object):
@@ -102,30 +145,6 @@
             changed = changed or changed1
         return changed
 
-    def getattrfamily(self):
-        """Get the AttrFamily object. Possibly creates one."""
-        access_sets = self.bookkeeper.pbc_maximal_access_sets
-        _, _, attrfamily = access_sets.find(self)
-        return attrfamily
-
-    def queryattrfamily(self):
-        """Retrieve the AttrFamily object if there is one, otherwise
-           return None."""
-        access_sets = self.bookkeeper.pbc_maximal_access_sets
-        try:
-            return access_sets[self]
-        except KeyError:
-            return None
-
-    def mergeattrfamilies(self, *others):
-        """Merge the attr families of the given Descs into one."""
-        access_sets = self.bookkeeper.pbc_maximal_access_sets
-        changed, rep, attrfamily = access_sets.find(self)
-        for desc in others:
-            changed1, rep, attrfamily = access_sets.union(rep, desc)
-            changed = changed or changed1
-        return changed
-
     def bind_under(self, classdef, name):
         return self
 
@@ -548,6 +567,30 @@
     def rowkey(self):
         return self
 
+    def getattrfamily(self, attrname):
+        "Get the ClassAttrFamily object for attrname. Possibly creates one."
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        _, _, attrfamily = access_sets.find(self)
+        return attrfamily
+
+    def queryattrfamily(self, attrname):
+        """Retrieve the ClassAttrFamily object for attrname if there is one,
+           otherwise return None."""
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        try:
+            return access_sets[self]
+        except KeyError:
+            return None
+
+    def mergeattrfamilies(self, others, attrname):
+        """Merge the attr families of the given Descs into one."""
+        access_sets = self.bookkeeper.get_classpbc_attr_families(attrname)
+        changed, rep, attrfamily = access_sets.find(self)
+        for desc in others:
+            changed1, rep, attrfamily = access_sets.union(rep, desc)
+            changed = changed or changed1
+        return changed
+
 
 class MethodDesc(Desc):
     knowntype = types.MethodType
@@ -664,6 +707,30 @@
             raise AssertionError("name clash: %r" % (name,))
         self.attrcache[name] = value
 
+    def getattrfamily(self, attrname=None):
+        "Get the FrozenAttrFamily object for attrname. Possibly creates one."
+        access_sets = self.bookkeeper.frozenpbc_attr_families
+        _, _, attrfamily = access_sets.find(self)
+        return attrfamily
+
+    def queryattrfamily(self, attrname=None):
+        """Retrieve the FrozenAttrFamily object for attrname if there is one,
+           otherwise return None."""
+        access_sets = self.bookkeeper.frozenpbc_attr_families
+        try:
+            return access_sets[self]
+        except KeyError:
+            return None
+
+    def mergeattrfamilies(self, others, attrname=None):
+        """Merge the attr families of the given Descs into one."""
+        access_sets = self.bookkeeper.frozenpbc_attr_families
+        changed, rep, attrfamily = access_sets.find(self)
+        for desc in others:
+            changed1, rep, attrfamily = access_sets.union(rep, desc)
+            changed = changed or changed1
+        return changed
+
 
 class MethodOfFrozenDesc(Desc):
     knowntype = types.MethodType

Modified: pypy/dist/pypy/rpython/lltypesystem/rclass.py
==============================================================================
--- pypy/dist/pypy/rpython/lltypesystem/rclass.py	(original)
+++ pypy/dist/pypy/rpython/lltypesystem/rclass.py	Fri May  5 19:29:42 2006
@@ -114,12 +114,11 @@
             # attributes showing up in getattrs done on the class as a PBC
             extra_access_sets = self.rtyper.class_pbc_attributes.get(
                 self.classdef, {})
-            for access_set, counter in extra_access_sets.items():
-                for attr, s_value in access_set.attrs.items():
-                    r = self.rtyper.getrepr(s_value)
-                    mangled_name = mangle('pbc%d' % counter, attr)
-                    pbcfields[access_set, attr] = mangled_name, r
-                    llfields.append((mangled_name, r.lowleveltype))
+            for access_set, (attr, counter) in extra_access_sets.items():
+                r = self.rtyper.getrepr(access_set.s_value)
+                mangled_name = mangle('pbc%d' % counter, attr)
+                pbcfields[access_set, attr] = mangled_name, r
+                llfields.append((mangled_name, r.lowleveltype))
             #
             self.rbase = getclassrepr(self.rtyper, self.classdef.basedef)
             self.rbase.setup()

Modified: pypy/dist/pypy/rpython/lltypesystem/rpbc.py
==============================================================================
--- pypy/dist/pypy/rpython/lltypesystem/rpbc.py	(original)
+++ pypy/dist/pypy/rpython/lltypesystem/rpbc.py	Fri May  5 19:29:42 2006
@@ -177,14 +177,14 @@
             # instantiating a class from multiple possible classes
             from pypy.rpython.lltypesystem.rbuiltin import ll_instantiate
             vtypeptr = hop.inputarg(self, arg=0)
-            access_set = self.get_access_set()
-            r_class = self.get_class_repr()
-            if '__init__' in access_set.attrs:
-                s_init = access_set.attrs['__init__']
+            try:
+                access_set, r_class = self.get_access_set('__init__')
+            except MissingRTypeAttribute:
+                s_init = annmodel.s_ImpossibleValue
+            else:
+                s_init = access_set.s_value
                 v_init = r_class.getpbcfield(vtypeptr, access_set, '__init__',
                                              hop.llops)
-            else:
-                s_init = annmodel.s_ImpossibleValue
             v_inst1 = hop.gendirectcall(ll_instantiate, vtypeptr)
             v_instance = hop.genop('cast_pointer', [v_inst1],
                                    resulttype = r_instance)

Modified: pypy/dist/pypy/rpython/normalizecalls.py
==============================================================================
--- pypy/dist/pypy/rpython/normalizecalls.py	(original)
+++ pypy/dist/pypy/rpython/normalizecalls.py	Fri May  5 19:29:42 2006
@@ -186,29 +186,30 @@
     # PBC access set of the family of classes of 'some_class'.  If the classes
     # have corresponding ClassDefs, they are not updated by the annotator.
     # We have to do it now.
-    access_sets = rtyper.annotator.bookkeeper.pbc_maximal_access_sets
-    for access_set in access_sets.infos():
-        descs = access_set.descs
-        if len(descs) <= 1:
-            continue
-        if not isinstance(descs.iterkeys().next(), description.ClassDesc):
-            continue
-        classdefs = [desc.getuniqueclassdef() for desc in descs]
-        commonbase = classdefs[0]
-        for cdef in classdefs[1:]:
-            commonbase = commonbase.commonbase(cdef)
-            if commonbase is None:
-                raise TyperError("reading attributes %r: no common base class "
-                                 "for %r" % (
-                    access_set.attrs.keys(), descs.keys()))
-        extra_access_sets = rtyper.class_pbc_attributes.setdefault(commonbase,
-                                                                   {})
-        if commonbase in rtyper.class_reprs:
-            assert access_set in extra_access_sets # minimal sanity check
-            return
-        access_set.commonbase = commonbase
-        if access_set not in extra_access_sets:
-            extra_access_sets[access_set] = len(extra_access_sets)
+    all_families = rtyper.annotator.bookkeeper.classpbc_attr_families
+    for attrname, access_sets in all_families.items():
+        for access_set in access_sets.infos():
+            descs = access_set.descs
+            if len(descs) <= 1:
+                continue
+            if not isinstance(descs.iterkeys().next(), description.ClassDesc):
+                continue
+            classdefs = [desc.getuniqueclassdef() for desc in descs]
+            commonbase = classdefs[0]
+            for cdef in classdefs[1:]:
+                commonbase = commonbase.commonbase(cdef)
+                if commonbase is None:
+                    raise TyperError("reading attribute %r: no common base "
+                                     "class for %r" % (attrname, descs.keys()))
+            extra_access_sets = rtyper.class_pbc_attributes.setdefault(
+                commonbase, {})
+            if commonbase in rtyper.class_reprs:
+                assert access_set in extra_access_sets # minimal sanity check
+                continue
+            access_set.commonbase = commonbase
+            if access_set not in extra_access_sets:
+                counter = len(extra_access_sets)
+                extra_access_sets[access_set] = attrname, counter
 
 # ____________________________________________________________
 
@@ -222,19 +223,17 @@
         descs = family.descs.keys()
         if not isinstance(descs[0], description.ClassDesc):
             continue
-        # Note that a callfamily of classes must really be in the same
-        # attrfamily as well; This property is relied upon on various
-        # places in the rtyper
-        change = descs[0].mergeattrfamilies(*descs[1:])
+        # Note that if classes are in the same callfamily, their __init__
+        # attribute must be in the same attrfamily as well.
+        change = descs[0].mergeattrfamilies(descs[1:], '__init__')
         if hasattr(descs[0].getuniqueclassdef(), 'my_instantiate_graph'):
             assert not change, "after the fact change to a family of classes" # minimal sanity check
             return
-        attrfamily = descs[0].getattrfamily()
         # Put __init__ into the attr family, for ClassesPBCRepr.call()
-        s_value = attrfamily.attrs.get('__init__', annmodel.s_ImpossibleValue)
+        attrfamily = descs[0].getattrfamily('__init__')
         inits_s = [desc.s_read_attribute('__init__') for desc in descs]
-        s_value = annmodel.unionof(s_value, *inits_s)
-        attrfamily.attrs['__init__'] = s_value
+        s_value = annmodel.unionof(attrfamily.s_value, *inits_s)
+        attrfamily.s_value = s_value
         # ClassesPBCRepr.call() will also need instantiate() support
         for desc in descs:
             bk.needs_generic_instantiate[desc.getuniqueclassdef()] = True

Modified: pypy/dist/pypy/rpython/ootypesystem/rclass.py
==============================================================================
--- pypy/dist/pypy/rpython/ootypesystem/rclass.py	(original)
+++ pypy/dist/pypy/rpython/ootypesystem/rclass.py	Fri May  5 19:29:42 2006
@@ -53,12 +53,11 @@
             # attributes showing up in getattrs done on the class as a PBC
             extra_access_sets = self.rtyper.class_pbc_attributes.get(
                 self.classdef, {})
-            for access_set, counter in extra_access_sets.items():
-                for attr, s_value in access_set.attrs.items():
-                    r = self.rtyper.getrepr(s_value)
-                    mangled_name = pbcmangle('pbc%d' % counter, attr)
-                    pbcfields[access_set, attr] = mangled_name, r
-                    llfields.append((mangled_name, r.lowleveltype))
+            for access_set, (attr, counter) in extra_access_sets.items():
+                r = self.rtyper.getrepr(access_set.s_value)
+                mangled_name = pbcmangle('pbc%d' % counter, attr)
+                pbcfields[access_set, attr] = mangled_name, r
+                llfields.append((mangled_name, r.lowleveltype))
             
             self.rbase.setup()
             ootype.addFields(self.lowleveltype, dict(llfields))

Modified: pypy/dist/pypy/rpython/rpbc.py
==============================================================================
--- pypy/dist/pypy/rpython/rpbc.py	(original)
+++ pypy/dist/pypy/rpython/rpbc.py	Fri May  5 19:29:42 2006
@@ -599,24 +599,22 @@
             self.lowleveltype = Void
         else:
             self.lowleveltype = rtyper.type_system.rclass.CLASSTYPE
-        self._access_set = None
-        self._class_repr = None
 
-    def get_access_set(self):
-        if self._access_set is None:
-            classdescs = self.s_pbc.descriptions.keys()
-            access = classdescs[0].getattrfamily()
-            for classdesc in classdescs[1:]:
-                access1 = classdesc.getattrfamily() 
-                assert access1 is access       # XXX not implemented
-            commonbase = access.commonbase
-            self._class_repr = rclass.getclassrepr(self.rtyper, commonbase)
-            self._access_set = access
-        return self._access_set
-
-    def get_class_repr(self):
-        self.get_access_set()
-        return self._class_repr
+    def get_access_set(self, attrname):
+        """Return the ClassAttrFamily corresponding to accesses to 'attrname'
+        and the ClassRepr of the class which stores this attribute in
+        its vtable.
+        """
+        classdescs = self.s_pbc.descriptions.keys()
+        access = classdescs[0].queryattrfamily(attrname)
+        for classdesc in classdescs[1:]:
+            access1 = classdesc.queryattrfamily(attrname)
+            assert access1 is access       # XXX not implemented
+        if access is None:
+            raise rclass.MissingRTypeAttribute(attrname)
+        commonbase = access.commonbase
+        class_repr = rclass.getclassrepr(self.rtyper, commonbase)
+        return access, class_repr
 
     def convert_desc(self, desc):
         if desc not in self.s_pbc.descriptions:
@@ -635,11 +633,10 @@
             return hop.inputconst(hop.r_result, hop.s_result.const)
         else:
             attr = hop.args_s[1].const
-            access_set = self.get_access_set()
-            class_repr = self.get_class_repr()
+            access_set, class_repr = self.get_access_set(attr)
             vcls, vattr = hop.inputargs(class_repr, Void)
             v_res = class_repr.getpbcfield(vcls, access_set, attr, hop.llops)
-            s_res = access_set.attrs[attr]
+            s_res = access_set.s_value
             r_res = self.rtyper.getrepr(s_res)
             return hop.llops.convertvar(v_res, r_res, hop.r_result)
 
@@ -663,11 +660,8 @@
         if r_clspbc.lowleveltype is Void:
             return inputconst(r_cls, r_clspbc.s_pbc.const)
         # convert from ptr-to-object-vtable to ptr-to-more-precise-vtable
-        # but first check if it is safe
         assert (r_clspbc.lowleveltype ==
             r_clspbc.rtyper.type_system.rclass.CLASSTYPE)
-        if not r_clspbc.get_class_repr().classdef.issubclass(r_cls.classdef):
-            return NotImplemented
         return r_cls.fromclasstype(v, llops)
 
 class __extend__(pairtype(AbstractClassesPBCRepr, AbstractClassesPBCRepr)):

Modified: pypy/dist/pypy/rpython/test/test_rpbc.py
==============================================================================
--- pypy/dist/pypy/rpython/test/test_rpbc.py	(original)
+++ pypy/dist/pypy/rpython/test/test_rpbc.py	Fri May  5 19:29:42 2006
@@ -1220,6 +1220,33 @@
         res = interpret(f, [1], type_system=self.ts)
         assert res == 65
 
+    def test_multiple_attribute_access_patterns(self):
+        class Base(object):
+            pass
+        class A(Base):
+            value = 1000
+            def meth(self): return self.n + 1
+        class B(A):
+            def meth(self): return self.n + 2
+        class C(Base):
+            value = 2000
+            def meth(self): ShouldNotBeSeen
+        def AorB(n):
+            if n == 5: return A
+            else:      return B
+        def BorC(n):
+            if n == 3: return B
+            else:      return C
+        def f(n):
+            value = BorC(n).value
+            x = B()
+            x.n = 100
+            return value + AorB(n).meth(x)
+
+        for i in [1, 3, 5]:
+            res = interpret(f, [i])
+            assert res == f(i)
+
 
 def test_call_from_list():
     # Don't test with ootype, since it doesn't support lists in a



More information about the Pypy-commit mailing list