Author: Carl Friedrich Bolz <cfb...@gmx.de> Branch: remove-objspace-options Changeset: r83834:311cb478ad96 Date: 2016-04-23 11:41 +0300 http://bitbucket.org/pypy/pypy/changeset/311cb478ad96/
Log: use mapdict for all the subclassing replace a huge mess by a different kind of (smaller) mess diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -98,175 +98,51 @@ # reason is that it is missing a place to store the __dict__, the slots, # the weakref lifeline, and it typically has no interp-level __del__. # So we create a few interp-level subclasses of W_XxxObject, which add -# some combination of features. -# -# We don't build 2**4 == 16 subclasses for all combinations of requested -# features, but limit ourselves to 6, chosen a bit arbitrarily based on -# typical usage (case 1 is the most common kind of app-level subclasses; -# case 2 is the memory-saving kind defined with __slots__). -# -# +----------------------------------------------------------------+ -# | NOTE: if withmapdict is enabled, the following doesn't apply! | -# | Map dicts can flexibly allow any slots/__dict__/__weakref__ to | -# | show up only when needed. In particular there is no way with | -# | mapdict to prevent some objects from being weakrefable. | -# +----------------------------------------------------------------+ -# -# dict slots del weakrefable -# -# 1. Y N N Y UserDictWeakref -# 2. N Y N N UserSlots -# 3. Y Y N Y UserDictWeakrefSlots -# 4. N Y N Y UserSlotsWeakref -# 5. Y Y Y Y UserDictWeakrefSlotsDel -# 6. N Y Y Y UserSlotsWeakrefDel -# -# Note that if the app-level explicitly requests no dict, we should not -# provide one, otherwise storing random attributes on the app-level -# instance would unexpectedly work. We don't care too much, though, if -# an object is weakrefable when it shouldn't really be. It's important -# that it has a __del__ only if absolutely needed, as this kills the -# performance of the GCs. -# -# Interp-level inheritance is like this: -# -# W_XxxObject base -# / \ -# 1 2 -# / \ -# 3 4 -# / \ -# 5 6 +# some combination of features. This is done using mapdict. -def get_unique_interplevel_subclass(config, cls, hasdict, wants_slots, - needsdel=False, weakrefable=False): +# we need two subclasses of the app-level type, one to add mapdict, and then one +# to add del to not slow down the GC. + +def get_unique_interplevel_subclass(config, cls, needsdel=False): "NOT_RPYTHON: initialization-time only" if hasattr(cls, '__del__') and getattr(cls, "handle_del_manually", False): needsdel = False assert cls.typedef.acceptable_as_base_class - key = config, cls, hasdict, wants_slots, needsdel, weakrefable + key = config, cls, needsdel try: return _subclass_cache[key] except KeyError: - subcls = _getusercls(config, cls, hasdict, wants_slots, needsdel, - weakrefable) + # XXX can save a class if cls already has a __del__ + if needsdel: + cls = get_unique_interplevel_subclass(config, cls, False) + subcls = _getusercls(config, cls, needsdel) assert key not in _subclass_cache _subclass_cache[key] = subcls return subcls get_unique_interplevel_subclass._annspecialcase_ = "specialize:memo" _subclass_cache = {} -def _getusercls(config, cls, wants_dict, wants_slots, wants_del, weakrefable): +def _getusercls(config, cls, wants_del, reallywantdict=False): + from rpython.rlib import objectmodel + from pypy.objspace.std.mapdict import (BaseUserClassMapdict, + MapdictDictSupport, MapdictWeakrefSupport, + _make_storage_mixin_size_n) typedef = cls.typedef - if wants_dict and typedef.hasdict: - wants_dict = False - if not typedef.hasdict: - # mapdict only works if the type does not already have a dict - if wants_del: - parentcls = get_unique_interplevel_subclass(config, cls, True, True, - False, True) - return _usersubclswithfeature(config, parentcls, "del") - return _usersubclswithfeature(config, cls, "user", "dict", "weakref", "slots") - # Forest of if's - see the comment above. + name = cls.__name__ + "User" + + mixins_needed = [BaseUserClassMapdict, _make_storage_mixin_size_n()] + if reallywantdict or not typedef.hasdict: + # the type has no dict, mapdict to provide the dict + mixins_needed.append(MapdictDictSupport) + name += "Dict" + if not typedef.weakrefable: + # the type does not support weakrefs yet, mapdict to provide weakref + # support + mixins_needed.append(MapdictWeakrefSupport) + name += "Weakrefable" if wants_del: - if wants_dict: - # case 5. Parent class is 3. - parentcls = get_unique_interplevel_subclass(config, cls, True, True, - False, True) - else: - # case 6. Parent class is 4. - parentcls = get_unique_interplevel_subclass(config, cls, False, True, - False, True) - return _usersubclswithfeature(config, parentcls, "del") - elif wants_dict: - if wants_slots: - # case 3. Parent class is 1. - parentcls = get_unique_interplevel_subclass(config, cls, True, False, - False, True) - return _usersubclswithfeature(config, parentcls, "slots") - else: - # case 1 (we need to add weakrefable unless it's already in 'cls') - if not typedef.weakrefable: - return _usersubclswithfeature(config, cls, "user", "dict", "weakref") - else: - return _usersubclswithfeature(config, cls, "user", "dict") - else: - if weakrefable and not typedef.weakrefable: - # case 4. Parent class is 2. - parentcls = get_unique_interplevel_subclass(config, cls, False, True, - False, False) - return _usersubclswithfeature(config, parentcls, "weakref") - else: - # case 2 (if the base is already weakrefable, case 2 == case 4) - return _usersubclswithfeature(config, cls, "user", "slots") - -def _usersubclswithfeature(config, parentcls, *features): - key = config, parentcls, features - try: - return _usersubclswithfeature_cache[key] - except KeyError: - subcls = _builduserclswithfeature(config, parentcls, *features) - _usersubclswithfeature_cache[key] = subcls - return subcls -_usersubclswithfeature_cache = {} -_allusersubcls_cache = {} - -def _builduserclswithfeature(config, supercls, *features): - "NOT_RPYTHON: initialization-time only" - name = supercls.__name__ - name += ''.join([name.capitalize() for name in features]) - body = {} - #print '..........', name, '(', supercls.__name__, ')' - - def add(Proto): - for key, value in Proto.__dict__.items(): - if (not key.startswith('__') and not key.startswith('_mixin_') - or key == '__del__'): - if hasattr(value, "func_name"): - value = func_with_new_name(value, value.func_name) - body[key] = value - - if "dict" in features: - from pypy.objspace.std.mapdict import BaseMapdictObject, ObjectMixin - add(BaseMapdictObject) - add(ObjectMixin) - body["user_overridden_class"] = True - features = () - - if "user" in features: # generic feature needed by all subcls - - class Proto(object): - user_overridden_class = True - - def getclass(self, space): - return promote(self.w__class__) - - def setclass(self, space, w_subtype): - # only used by descr_set___class__ - self.w__class__ = w_subtype - - def user_setup(self, space, w_subtype): - self.space = space - self.w__class__ = w_subtype - self.user_setup_slots(w_subtype.layout.nslots) - - def user_setup_slots(self, nslots): - assert nslots == 0 - add(Proto) - - if "weakref" in features: - class Proto(object): - _lifeline_ = None - def getweakref(self): - return self._lifeline_ - def setweakref(self, space, weakreflifeline): - self._lifeline_ = weakreflifeline - def delweakref(self): - self._lifeline_ = None - add(Proto) - - if "del" in features: - parent_destructor = getattr(supercls, '__del__', None) + name += "Del" + parent_destructor = getattr(cls, '__del__', None) def call_parent_del(self): assert isinstance(self, subcls) parent_destructor(self) @@ -281,39 +157,15 @@ if parent_destructor is not None: self.enqueue_for_destruction(self.space, call_parent_del, 'internal destructor of ') - add(Proto) + mixins_needed.append(Proto) - if "slots" in features: - class Proto(object): - slots_w = [] - def user_setup_slots(self, nslots): - if nslots > 0: - self.slots_w = [None] * nslots - def setslotvalue(self, index, w_value): - self.slots_w[index] = w_value - def delslotvalue(self, index): - if self.slots_w[index] is None: - return False - self.slots_w[index] = None - return True - def getslotvalue(self, index): - return self.slots_w[index] - add(Proto) - - subcls = type(name, (supercls,), body) - _allusersubcls_cache[subcls] = True + class subcls(cls): + user_overridden_class = True + for base in mixins_needed: + objectmodel.import_from_mixin(base) + subcls.__name__ = name return subcls -# a couple of helpers for the Proto classes above, factored out to reduce -# the translated code size -def check_new_dictionary(space, w_dict): - if not space.isinstance_w(w_dict, space.w_dict): - raise OperationError(space.w_TypeError, - space.wrap("setting dictionary to a non-dict")) - from pypy.objspace.std import dictmultiobject - assert isinstance(w_dict, dictmultiobject.W_DictMultiObject) - return w_dict -check_new_dictionary._dont_inline_ = True # ____________________________________________________________ diff --git a/pypy/module/__builtin__/interp_classobj.py b/pypy/module/__builtin__/interp_classobj.py --- a/pypy/module/__builtin__/interp_classobj.py +++ b/pypy/module/__builtin__/interp_classobj.py @@ -185,12 +185,11 @@ class Cache: def __init__(self, space): - from pypy.interpreter.typedef import _usersubclswithfeature - # evil - self.cls_without_del = _usersubclswithfeature( - space.config, W_InstanceObject, "dict", "weakref") - self.cls_with_del = _usersubclswithfeature( - space.config, self.cls_without_del, "del") + from pypy.interpreter.typedef import _getusercls + self.cls_without_del = _getusercls( + space.config, W_InstanceObject, False, reallywantdict=True) + self.cls_with_del = _getusercls( + space.config, W_InstanceObject, True, reallywantdict=True) def class_descr_call(space, w_self, __args__): 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 @@ -450,12 +450,19 @@ INVALID = 2 SLOTS_STARTING_FROM = 3 +# a little bit of a mess of mixin classes that implement various pieces of +# objspace user object functionality in terms of mapdict -class BaseMapdictObject: - _mixin_ = True +class BaseUserClassMapdict: + # everything that's needed to use mapdict for a user subclass at all. + # This immediately makes slots possible. - def _init_empty(self, map): - raise NotImplementedError("abstract base class") + # assumes presence of _init_empty, _mapdict_read_storage, + # _mapdict_write_storage, _mapdict_storage_length, + # _set_mapdict_storage_and_map + + # _____________________________________________ + # methods needed for mapdict def _become(self, new_obj): self._set_mapdict_storage_and_map(new_obj.storage, new_obj.map) @@ -464,49 +471,11 @@ return jit.promote(self.map) def _set_mapdict_map(self, map): self.map = map + # _____________________________________________ # objspace interface - def getdictvalue(self, space, attrname): - return self._get_mapdict_map().read(self, attrname, DICT) - - def setdictvalue(self, space, attrname, w_value): - return self._get_mapdict_map().write(self, attrname, DICT, w_value) - - def deldictvalue(self, space, attrname): - new_obj = self._get_mapdict_map().delete(self, attrname, DICT) - if new_obj is None: - return False - self._become(new_obj) - return True - - def getdict(self, space): - w_dict = self._get_mapdict_map().read(self, "dict", SPECIAL) - if w_dict is not None: - assert isinstance(w_dict, W_DictMultiObject) - return w_dict - - strategy = space.fromcache(MapDictStrategy) - storage = strategy.erase(self) - w_dict = W_DictObject(space, strategy, storage) - flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict) - assert flag - return w_dict - - def setdict(self, space, w_dict): - from pypy.interpreter.typedef import check_new_dictionary - w_dict = check_new_dictionary(space, w_dict) - w_olddict = self.getdict(space) - assert isinstance(w_dict, W_DictMultiObject) - # The old dict has got 'self' as dstorage, but we are about to - # change self's ("dict", SPECIAL) attribute to point to the - # new dict. If the old dict was using the MapDictStrategy, we - # have to force it now: otherwise it would remain an empty - # shell that continues to delegate to 'self'. - if type(w_olddict.get_strategy()) is MapDictStrategy: - w_olddict.get_strategy().switch_to_object_strategy(w_olddict) - flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict) - assert flag + # class access def getclass(self, space): return self._get_mapdict_map().terminator.w_cls @@ -519,9 +488,13 @@ from pypy.module.__builtin__.interp_classobj import W_InstanceObject self.space = space assert (not self.typedef.hasdict or + isinstance(w_subtype.terminator, NoDictTerminator) or self.typedef is W_InstanceObject.typedef) self._init_empty(w_subtype.terminator) + + # methods needed for slots + def getslotvalue(self, slotindex): index = SLOTS_STARTING_FROM + slotindex return self._get_mapdict_map().read(self, "slot", index) @@ -538,7 +511,9 @@ self._become(new_obj) return True - # used by _weakref implemenation + +class MapdictWeakrefSupport(object): + # stuff used by the _weakref implementation def getweakref(self): from pypy.module._weakref.interp__weakref import WeakrefLifeline @@ -559,8 +534,69 @@ self._get_mapdict_map().write(self, "weakref", SPECIAL, None) delweakref._cannot_really_call_random_things_ = True -class ObjectMixin(object): - _mixin_ = True + +class MapdictDictSupport(object): + + # objspace interface for dictionary operations + + def getdictvalue(self, space, attrname): + return self._get_mapdict_map().read(self, attrname, DICT) + + def setdictvalue(self, space, attrname, w_value): + return self._get_mapdict_map().write(self, attrname, DICT, w_value) + + def deldictvalue(self, space, attrname): + new_obj = self._get_mapdict_map().delete(self, attrname, DICT) + if new_obj is None: + return False + self._become(new_obj) + return True + + def getdict(self, space): + return _obj_getdict(self, space) + + def setdict(self, space, w_dict): + _obj_setdict(self, space, w_dict) + +# a couple of helpers for the classes above, factored out to reduce +# the translated code size + +@objectmodel.dont_inline +def _obj_getdict(self, space): + assert isinstance(self._get_mapdict_map().terminator, DictTerminator) + w_dict = self._get_mapdict_map().read(self, "dict", SPECIAL) + if w_dict is not None: + assert isinstance(w_dict, W_DictMultiObject) + return w_dict + + strategy = space.fromcache(MapDictStrategy) + storage = strategy.erase(self) + w_dict = W_DictObject(space, strategy, storage) + flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict) + assert flag + return w_dict + +@objectmodel.dont_inline +def _obj_setdict(self, space, w_dict): + from pypy.objspace.std import dictmultiobject + assert isinstance(self._get_mapdict_map().terminator, DictTerminator) + if not space.isinstance_w(w_dict, space.w_dict): + raise OperationError(space.w_TypeError, + space.wrap("setting dictionary to a non-dict")) + assert isinstance(w_dict, dictmultiobject.W_DictMultiObject) + w_olddict = self.getdict(space) + assert isinstance(w_dict, W_DictMultiObject) + # The old dict has got 'self' as dstorage, but we are about to + # change self's ("dict", SPECIAL) attribute to point to the + # new dict. If the old dict was using the MapDictStrategy, we + # have to force it now: otherwise it would remain an empty + # shell that continues to delegate to 'self'. + if type(w_olddict.get_strategy()) is MapDictStrategy: + w_olddict.get_strategy().switch_to_object_strategy(w_olddict) + flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict) + assert flag + +class MapdictStorageMixin(object): def _init_empty(self, map): from rpython.rlib.debug import make_sure_not_resized self.map = map @@ -579,50 +615,21 @@ self.storage = storage self.map = map -class Object(ObjectMixin, BaseMapdictObject, W_Root): +class ObjectWithoutDict(MapdictStorageMixin, BaseUserClassMapdict, MapdictWeakrefSupport, W_Root): pass # mainly for tests -def get_subclass_of_correct_size(space, cls, w_type): - map = w_type.terminator - classes = memo_get_subclass_of_correct_size(space, cls) - if SUBCLASSES_MIN_FIELDS == SUBCLASSES_MAX_FIELDS: - return classes[0] - size = map.size_estimate() - debug.check_nonneg(size) - if size < len(classes): - return classes[size] - else: - return classes[len(classes)-1] -get_subclass_of_correct_size._annspecialcase_ = "specialize:arg(1)" +class Object(MapdictStorageMixin, BaseUserClassMapdict, MapdictDictSupport, MapdictWeakrefSupport, W_Root): + pass # mainly for tests -SUBCLASSES_MIN_FIELDS = 5 # XXX tweak these numbers -SUBCLASSES_MAX_FIELDS = 5 +SUBCLASSES_NUM_FIELDS = 5 -def memo_get_subclass_of_correct_size(space, supercls): - key = space, supercls - try: - return _subclass_cache[key] - except KeyError: - assert not hasattr(supercls, "__del__") - result = [] - for i in range(SUBCLASSES_MIN_FIELDS, SUBCLASSES_MAX_FIELDS+1): - result.append(_make_subclass_size_n(supercls, i)) - for i in range(SUBCLASSES_MIN_FIELDS): - result.insert(0, result[0]) - if SUBCLASSES_MIN_FIELDS == SUBCLASSES_MAX_FIELDS: - assert len(set(result)) == 1 - _subclass_cache[key] = result - return result -memo_get_subclass_of_correct_size._annspecialcase_ = "specialize:memo" -_subclass_cache = {} - -def _make_subclass_size_n(supercls, n): +def _make_storage_mixin_size_n(n=SUBCLASSES_NUM_FIELDS): from rpython.rlib import unroll rangen = unroll.unrolling_iterable(range(n)) nmin1 = n - 1 rangenmin1 = unroll.unrolling_iterable(range(nmin1)) valnmin1 = "_value%s" % nmin1 - class subcls(BaseMapdictObject, supercls): + class subcls(object): def _init_empty(self, map): for i in rangenmin1: setattr(self, "_value%s" % i, None) @@ -690,7 +697,7 @@ erased = erase_list(storage_list) setattr(self, "_value%s" % nmin1, erased) - subcls.__name__ = supercls.__name__ + "Size%s" % n + subcls.__name__ = "Size%s" % n return subcls # ____________________________________________________________ diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -356,14 +356,8 @@ if cls.typedef.applevel_subclasses_base is not None: cls = cls.typedef.applevel_subclasses_base # - if cls is W_ObjectObject and not w_subtype.needsdel: - from pypy.objspace.std.mapdict import get_subclass_of_correct_size - subcls = get_subclass_of_correct_size(self, cls, w_subtype) - else: - subcls = get_unique_interplevel_subclass( - self.config, cls, w_subtype.hasdict, - w_subtype.layout.nslots != 0, - w_subtype.needsdel, w_subtype.weakrefable) + subcls = get_unique_interplevel_subclass( + self.config, cls, w_subtype.needsdel) instance = instantiate(subcls) assert isinstance(instance, cls) instance.user_setup(self, w_subtype) 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 @@ -13,7 +13,7 @@ class Class(object): def __init__(self, hasdict=True): - self.hasdict = True + self.hasdict = hasdict if hasdict: self.terminator = DictTerminator(space, self) else: @@ -22,10 +22,17 @@ def instantiate(self, sp=None): if sp is None: sp = space - result = Object() + if self.hasdict: + result = Object() + else: + result = ObjectWithoutDict() result.user_setup(sp, self) return result +class ObjectWithoutDict(ObjectWithoutDict): + class typedef: + hasdict = False + class Object(Object): class typedef: hasdict = False @@ -429,6 +436,9 @@ assert obj.getslotvalue(b) == 60 assert obj.storage == [50, 60] assert not obj.setdictvalue(space, "a", 70) + assert obj.getdict(space) is None + assert obj.getdictvalue(space, "a") is None + def test_getdict(): cls = Class() 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 @@ -177,7 +177,13 @@ # itself changes w_self._version_tag = VersionTag() from pypy.objspace.std.mapdict import DictTerminator, NoDictTerminator - if w_self.hasdict: + # if the typedef has a dict, then the rpython-class does all the dict + # management, which means from the point of view of mapdict there is no + # dict. However, W_InstanceObjects are an exception to this + from pypy.module.__builtin__.interp_classobj import W_InstanceObject + typedef = w_self.layout.typedef + if (w_self.hasdict and not typedef.hasdict or + typedef is W_InstanceObject.typedef): w_self.terminator = DictTerminator(space, w_self) else: w_self.terminator = NoDictTerminator(space, w_self) diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py --- a/rpython/rlib/objectmodel.py +++ b/rpython/rlib/objectmodel.py @@ -211,6 +211,12 @@ func._always_inline_ = True return func +def dont_inline(func): + """ mark the function as never-to-be-inlined by the RPython optimizations + (not the JIT!), no matter its size.""" + func._dont_inline_ = True + return func + # ____________________________________________________________ _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit