Author: Carl Friedrich Bolz-Tereick <cfb...@gmx.de> Branch: guard-compatible Changeset: r94051:7825277c4910 Date: 2018-03-20 10:51 +0100 http://bitbucket.org/pypy/pypy/changeset/7825277c4910/
Log: re-enable support for immutability tracking of attributes of user- defined instances. This works by piggybacking the elidability on the map's version. When an attribute goes from immutable to mutable (ie a lot during startup) this approach changes versions a bit too much: for all children of the terminator of the involved attribute, a new version is generated. Could be improved a little bit. diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -76,23 +76,22 @@ # XXX can improve the devolved case terminator = self._get_terminator() return terminator._read_terminator(obj, name, index) - #if ( # XXX in the guard_compatible world the following isconstant may never be true? - # jit.isconstant(attr.storageindex) and - # jit.isconstant(obj) and - # not attr.ever_mutated - #): - # return self._pure_mapdict_read_storage(obj, attr.storageindex) - #else: - return obj._mapdict_read_storage(storageindex) - - @jit.elidable - def _pure_mapdict_read_storage(self, obj, storageindex): + if ( + jit.isconstant(name) and + jit.isconstant(index) and + jit.isconstant(obj) + ): + can_constfold = self.can_constfold_attr_read(name, index) + if can_constfold: + return _pure_mapdict_read_storage(obj, storageindex) return obj._mapdict_read_storage(storageindex) def write(self, obj, name, index, w_value): storageindex = self.find_map_storageindex(name, index) if storageindex < 0: return self._get_terminator()._write_terminator(obj, name, index, w_value) + if self.can_constfold_attr_read(name, index): + self.invalidate_ever_mutated(name, index) obj._mapdict_write_storage(storageindex, w_value) return True @@ -112,6 +111,24 @@ return NOATTR return attr.storageindex + @jit.elidable_compatible(quasi_immut_field_name_for_second_arg="version") + def can_constfold_attr_read(self, version, name, index): + if version is None: + return False + attr = self.find_map_attr(name, index) + if attr is None: + return False + return not attr.ever_mutated + + def invalidate_ever_mutated(self, name, index): + attr = self.find_map_attr(name, index) + assert not attr.ever_mutated + attr.ever_mutated = True + # XXX is the next line too expensive? + # it's necessary for the version to change, to make + # can_constfold_attr_read actually elidable + self.terminator.mutated(self.version is not None) + @jit.elidable_compatible() def find_map_attr(self, name, index): # attr cache @@ -384,14 +401,14 @@ self.w_cls = w_cls self.all_children = None - def mutated_w_cls_version(self, version): - if version is None: + def mutated(self, have_version): + if not have_version: self.version = None else: self.version = Version() if self.all_children is not None: for map in self.all_children: - if version is None: + if not have_version: map.version = None else: map.version = Version() @@ -437,9 +454,9 @@ Terminator.__init__(self, space, w_cls) self.devolved_dict_terminator = DevolvedDictTerminator(space, w_cls) - def mutated_w_cls_version(self, version): - self.devolved_dict_terminator.mutated_w_cls_version(version) - Terminator.mutated_w_cls_version(self, version) + def mutated(self, have_version): + self.devolved_dict_terminator.mutated(have_version) + Terminator.mutated(self, have_version) def materialize_r_dict(self, space, obj, dict_w): return self._make_devolved(space) @@ -500,7 +517,7 @@ return Terminator.set_terminator(self, obj, terminator) class PlainAttribute(AbstractAttribute): - _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 'ever_mutated?', 'order'] + _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 'ever_mutated', 'order'] def __init__(self, name, index, back): AbstractAttribute.__init__(self, back.space, back.terminator) @@ -509,7 +526,7 @@ self.storageindex = back.length() self.back = back self._size_estimate = self.length() * NUM_DIGITS_POW2 - #self.ever_mutated = False # XXX XXX XXX immutability is disabled for now + self.ever_mutated = False self.order = len(back.cache_attrs) if back.cache_attrs else 0 def _copy_attr(self, obj, new_obj): @@ -518,9 +535,9 @@ def delete(self, obj, name, index): if index == self.index and name == self.name: + if not self.ever_mutated: + self.invalidate_ever_mutated(name, index) # ok, attribute is deleted - #if not self.ever_mutated: - # self.ever_mutated = True return self.back.copy(obj) new_obj = self.back.delete(obj, name, index) if new_obj is not None: @@ -602,6 +619,13 @@ INVALID = 2 SLOTS_STARTING_FROM = 3 + + +@jit.elidable +def _pure_mapdict_read_storage(obj, storageindex): + return obj._mapdict_read_storage(storageindex) + + # a little bit of a mess of mixin classes that implement various pieces of # objspace user object functionality in terms of mapdict diff --git a/pypy/objspace/std/test/test_mapdict.py b/pypy/objspace/std/test/test_mapdict.py --- a/pypy/objspace/std/test/test_mapdict.py +++ b/pypy/objspace/std/test/test_mapdict.py @@ -17,7 +17,7 @@ class Class(object): def __init__(self, hasdict=True): self.hasdict = hasdict - self._version_tag = None + self._version_tag = object() if hasdict: self.terminator = DictTerminator(space, self) else: @@ -33,6 +33,9 @@ result.user_setup(sp, self) return result + def mutated(self, _): + self._version_tag = object() + class ObjectWithoutDict(ObjectWithoutDict): class typedef: hasdict = False @@ -291,7 +294,7 @@ def test_attr_immutability(monkeypatch): - pytest.skip("disabled for now") + from pypy.objspace.std import mapdict cls = Class() obj = cls.instantiate() obj.setdictvalue(space, "a", 10) @@ -308,7 +311,7 @@ indices.append(storageindex) return obj._mapdict_read_storage(storageindex) - obj.map._pure_mapdict_read_storage = _pure_mapdict_read_storage + monkeypatch.setattr(mapdict, "_pure_mapdict_read_storage", _pure_mapdict_read_storage) monkeypatch.setattr(jit, "isconstant", lambda c: True) assert obj.getdictvalue(space, "a") == 10 @@ -329,7 +332,6 @@ assert obj2.map is obj.map def test_attr_immutability_delete(): - pytest.skip("disabled for now") cls = Class() obj = cls.instantiate() obj.setdictvalue(space, "a", 10) @@ -1031,14 +1033,14 @@ a.x = 42 def f(): return a.x - # + res = self.check(f, 'x') assert res == (1, 0, 0) res = self.check(f, 'x') assert res == (0, 1, 0) res = self.check(f, 'x') assert res == (0, 1, 0) - # + a.y = "foo" # changes the map res = self.check(f, 'x') assert res == (1, 0, 0) @@ -1046,8 +1048,18 @@ assert res == (0, 1, 0) res = self.check(f, 'x') assert res == (0, 1, 0) - # - a.y = "bar" # does not change the map any more + + # the following does not change the map, but changes the version since + # y goes from immutable to mutable + a.y = "bar" + res = self.check(f, 'x') + assert res == (1, 0, 0) + res = self.check(f, 'x') + assert res == (0, 1, 0) + res = self.check(f, 'x') + assert res == (0, 1, 0) + + a.y = "baz" # now everything works res = self.check(f, 'x') assert res == (0, 1, 0) res = self.check(f, 'x') diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py --- a/pypy/objspace/std/typeobject.py +++ b/pypy/objspace/std/typeobject.py @@ -255,7 +255,7 @@ def _set_version_tag(self, version_tag): self._version_tag = version_tag - self.terminator.mutated_w_cls_version(version_tag) + self.terminator.mutated(version_tag is not None) def getattribute_if_not_from_object(self): """ this method returns the applevel __getattribute__ if that is not _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit