PageRenderTime 46ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 1ms

/pypy/objspace/std/mapdict.py

https://bitbucket.org/pypy/pypy/
Python | 1029 lines | 785 code | 162 blank | 82 comment | 110 complexity | 9c550d670fc41f00cf3a7c32f783b771 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import weakref, sys
  2. from rpython.rlib import jit, objectmodel, debug, rerased
  3. from rpython.rlib.rarithmetic import intmask, r_uint
  4. from pypy.interpreter.baseobjspace import W_Root
  5. from pypy.objspace.std.dictmultiobject import (
  6. W_DictMultiObject, DictStrategy, ObjectDictStrategy, BaseKeyIterator,
  7. BaseValueIterator, BaseItemIterator, _never_equal_to_string,
  8. W_DictObject,
  9. )
  10. from pypy.objspace.std.typeobject import MutableCell
  11. erase_item, unerase_item = rerased.new_erasing_pair("mapdict storage item")
  12. erase_map, unerase_map = rerased.new_erasing_pair("map")
  13. erase_list, unerase_list = rerased.new_erasing_pair("mapdict storage list")
  14. # ____________________________________________________________
  15. # attribute shapes
  16. NUM_DIGITS = 4
  17. NUM_DIGITS_POW2 = 1 << NUM_DIGITS
  18. # note: we use "x * NUM_DIGITS_POW2" instead of "x << NUM_DIGITS" because
  19. # we want to propagate knowledge that the result cannot be negative
  20. class AbstractAttribute(object):
  21. _immutable_fields_ = ['terminator']
  22. cache_attrs = None
  23. _size_estimate = 0
  24. def __init__(self, space, terminator):
  25. self.space = space
  26. assert isinstance(terminator, Terminator)
  27. self.terminator = terminator
  28. def read(self, obj, name, index):
  29. attr = self.find_map_attr(name, index)
  30. if attr is None:
  31. return self.terminator._read_terminator(obj, name, index)
  32. if (
  33. jit.isconstant(attr.storageindex) and
  34. jit.isconstant(obj) and
  35. not attr.ever_mutated
  36. ):
  37. return self._pure_mapdict_read_storage(obj, attr.storageindex)
  38. else:
  39. return obj._mapdict_read_storage(attr.storageindex)
  40. @jit.elidable
  41. def _pure_mapdict_read_storage(self, obj, storageindex):
  42. return obj._mapdict_read_storage(storageindex)
  43. def write(self, obj, name, index, w_value):
  44. attr = self.find_map_attr(name, index)
  45. if attr is None:
  46. return self.terminator._write_terminator(obj, name, index, w_value)
  47. if not attr.ever_mutated:
  48. attr.ever_mutated = True
  49. obj._mapdict_write_storage(attr.storageindex, w_value)
  50. return True
  51. def delete(self, obj, name, index):
  52. pass
  53. @jit.elidable
  54. def find_map_attr(self, name, index):
  55. # attr cache
  56. space = self.space
  57. cache = space.fromcache(MapAttrCache)
  58. SHIFT2 = r_uint.BITS - space.config.objspace.std.methodcachesizeexp
  59. SHIFT1 = SHIFT2 - 5
  60. attrs_as_int = objectmodel.current_object_addr_as_int(self)
  61. # ^^^Note: see comment in typeobject.py for
  62. # _pure_lookup_where_with_method_cache()
  63. # unrolled hash computation for 2-tuple
  64. c1 = 0x345678
  65. c2 = 1000003
  66. hash_name = objectmodel.compute_hash(name)
  67. hash_selector = intmask((c2 * ((c2 * c1) ^ hash_name)) ^ index)
  68. product = intmask(attrs_as_int * hash_selector)
  69. attr_hash = (r_uint(product) ^ (r_uint(product) << SHIFT1)) >> SHIFT2
  70. # ^^^Note2: same comment too
  71. cached_attr = cache.attrs[attr_hash]
  72. if cached_attr is self:
  73. cached_name = cache.names[attr_hash]
  74. cached_index = cache.indexes[attr_hash]
  75. if cached_name == name and cached_index == index:
  76. attr = cache.cached_attrs[attr_hash]
  77. if space.config.objspace.std.withmethodcachecounter:
  78. cache.hits[name] = cache.hits.get(name, 0) + 1
  79. return attr
  80. attr = self._find_map_attr(name, index)
  81. cache.attrs[attr_hash] = self
  82. cache.names[attr_hash] = name
  83. cache.indexes[attr_hash] = index
  84. cache.cached_attrs[attr_hash] = attr
  85. if space.config.objspace.std.withmethodcachecounter:
  86. cache.misses[name] = cache.misses.get(name, 0) + 1
  87. return attr
  88. def _find_map_attr(self, name, index):
  89. while isinstance(self, PlainAttribute):
  90. if index == self.index and name == self.name:
  91. return self
  92. self = self.back
  93. return None
  94. def copy(self, obj):
  95. raise NotImplementedError("abstract base class")
  96. def length(self):
  97. raise NotImplementedError("abstract base class")
  98. def get_terminator(self):
  99. return self.terminator
  100. def set_terminator(self, obj, terminator):
  101. raise NotImplementedError("abstract base class")
  102. @jit.elidable
  103. def size_estimate(self):
  104. return self._size_estimate >> NUM_DIGITS
  105. def search(self, attrtype):
  106. return None
  107. @jit.elidable
  108. def _get_new_attr(self, name, index):
  109. cache = self.cache_attrs
  110. if cache is None:
  111. cache = self.cache_attrs = {}
  112. attr = cache.get((name, index), None)
  113. if attr is None:
  114. attr = PlainAttribute(name, index, self)
  115. cache[name, index] = attr
  116. return attr
  117. def add_attr(self, obj, name, index, w_value):
  118. self._reorder_and_add(obj, name, index, w_value)
  119. if not jit.we_are_jitted():
  120. oldattr = self
  121. attr = obj._get_mapdict_map()
  122. size_est = (oldattr._size_estimate + attr.size_estimate()
  123. - oldattr.size_estimate())
  124. assert size_est >= (oldattr.length() * NUM_DIGITS_POW2)
  125. oldattr._size_estimate = size_est
  126. def _add_attr_without_reordering(self, obj, name, index, w_value):
  127. attr = self._get_new_attr(name, index)
  128. attr._switch_map_and_write_storage(obj, w_value)
  129. @jit.unroll_safe
  130. def _switch_map_and_write_storage(self, obj, w_value):
  131. if self.length() > obj._mapdict_storage_length():
  132. # note that self.size_estimate() is always at least self.length()
  133. new_storage = [None] * self.size_estimate()
  134. for i in range(obj._mapdict_storage_length()):
  135. new_storage[i] = obj._mapdict_read_storage(i)
  136. obj._set_mapdict_storage_and_map(new_storage, self)
  137. # the order is important here: first change the map, then the storage,
  138. # for the benefit of the special subclasses
  139. obj._set_mapdict_map(self)
  140. obj._mapdict_write_storage(self.storageindex, w_value)
  141. @jit.elidable
  142. def _find_branch_to_move_into(self, name, index):
  143. # walk up the map chain to find an ancestor with lower order that
  144. # already has the current name as a child inserted
  145. current_order = sys.maxint
  146. number_to_readd = 0
  147. current = self
  148. key = (name, index)
  149. while True:
  150. attr = None
  151. if current.cache_attrs is not None:
  152. attr = current.cache_attrs.get(key, None)
  153. if attr is None or attr.order > current_order:
  154. # we reached the top, so we didn't find it anywhere,
  155. # just add it to the top attribute
  156. if not isinstance(current, PlainAttribute):
  157. return 0, self._get_new_attr(name, index)
  158. else:
  159. return number_to_readd, attr
  160. # if not found try parent
  161. number_to_readd += 1
  162. current_order = current.order
  163. current = current.back
  164. @jit.look_inside_iff(lambda self, obj, name, index, w_value:
  165. jit.isconstant(self) and
  166. jit.isconstant(name) and
  167. jit.isconstant(index))
  168. def _reorder_and_add(self, obj, name, index, w_value):
  169. # the idea is as follows: the subtrees of any map are ordered by
  170. # insertion. the invariant is that subtrees that are inserted later
  171. # must not contain the name of the attribute of any earlier inserted
  172. # attribute anywhere
  173. # m______
  174. # inserted first / \ ... \ further attributes
  175. # attrname a 0/ 1\ n\
  176. # m a must not appear here anywhere
  177. #
  178. # when inserting a new attribute in an object we check whether any
  179. # parent of lower order has seen that attribute yet. if yes, we follow
  180. # that branch. if not, we normally append that attribute. When we
  181. # follow a prior branch, we necessarily remove some attributes to be
  182. # able to do that. They need to be re-added, which has to follow the
  183. # reordering procedure recusively.
  184. # we store the to-be-readded attribute in the stack, with the map and
  185. # the value paired up those are lazily initialized to a list large
  186. # enough to store all current attributes
  187. stack = None
  188. stack_index = 0
  189. while True:
  190. current = self
  191. number_to_readd, attr = self._find_branch_to_move_into(name, index)
  192. # we found the attributes further up, need to save the
  193. # previous values of the attributes we passed
  194. if number_to_readd:
  195. if stack is None:
  196. stack = [erase_map(None)] * (self.length() * 2)
  197. current = self
  198. for i in range(number_to_readd):
  199. assert isinstance(current, PlainAttribute)
  200. w_self_value = obj._mapdict_read_storage(
  201. current.storageindex)
  202. stack[stack_index] = erase_map(current)
  203. stack[stack_index + 1] = erase_item(w_self_value)
  204. stack_index += 2
  205. current = current.back
  206. attr._switch_map_and_write_storage(obj, w_value)
  207. if not stack_index:
  208. return
  209. # readd the current top of the stack
  210. stack_index -= 2
  211. next_map = unerase_map(stack[stack_index])
  212. w_value = unerase_item(stack[stack_index + 1])
  213. name = next_map.name
  214. index = next_map.index
  215. self = obj._get_mapdict_map()
  216. def materialize_r_dict(self, space, obj, dict_w):
  217. raise NotImplementedError("abstract base class")
  218. def remove_dict_entries(self, obj):
  219. raise NotImplementedError("abstract base class")
  220. def __repr__(self):
  221. return "<%s>" % (self.__class__.__name__,)
  222. class Terminator(AbstractAttribute):
  223. _immutable_fields_ = ['w_cls']
  224. def __init__(self, space, w_cls):
  225. AbstractAttribute.__init__(self, space, self)
  226. self.w_cls = w_cls
  227. def _read_terminator(self, obj, name, index):
  228. return None
  229. def _write_terminator(self, obj, name, index, w_value):
  230. obj._get_mapdict_map().add_attr(obj, name, index, w_value)
  231. return True
  232. def copy(self, obj):
  233. result = Object()
  234. result.space = self.space
  235. result._mapdict_init_empty(self)
  236. return result
  237. def length(self):
  238. return 0
  239. def set_terminator(self, obj, terminator):
  240. result = Object()
  241. result.space = self.space
  242. result._mapdict_init_empty(terminator)
  243. return result
  244. def remove_dict_entries(self, obj):
  245. return self.copy(obj)
  246. def __repr__(self):
  247. return "<%s w_cls=%s>" % (self.__class__.__name__, self.w_cls)
  248. class DictTerminator(Terminator):
  249. _immutable_fields_ = ['devolved_dict_terminator']
  250. def __init__(self, space, w_cls):
  251. Terminator.__init__(self, space, w_cls)
  252. self.devolved_dict_terminator = DevolvedDictTerminator(space, w_cls)
  253. def materialize_r_dict(self, space, obj, dict_w):
  254. result = Object()
  255. result.space = space
  256. result._mapdict_init_empty(self.devolved_dict_terminator)
  257. return result
  258. class NoDictTerminator(Terminator):
  259. def _write_terminator(self, obj, name, index, w_value):
  260. if index == DICT:
  261. return False
  262. return Terminator._write_terminator(self, obj, name, index, w_value)
  263. class DevolvedDictTerminator(Terminator):
  264. def _read_terminator(self, obj, name, index):
  265. if index == DICT:
  266. space = self.space
  267. w_dict = obj.getdict(space)
  268. return space.finditem_str(w_dict, name)
  269. return Terminator._read_terminator(self, obj, name, index)
  270. def _write_terminator(self, obj, name, index, w_value):
  271. if index == DICT:
  272. space = self.space
  273. w_dict = obj.getdict(space)
  274. space.setitem_str(w_dict, name, w_value)
  275. return True
  276. return Terminator._write_terminator(self, obj, name, index, w_value)
  277. def delete(self, obj, name, index):
  278. from pypy.interpreter.error import OperationError
  279. if index == DICT:
  280. space = self.space
  281. w_dict = obj.getdict(space)
  282. try:
  283. space.delitem(w_dict, space.wrap(name))
  284. except OperationError as ex:
  285. if not ex.match(space, space.w_KeyError):
  286. raise
  287. return Terminator.copy(self, obj)
  288. return Terminator.delete(self, obj, name, index)
  289. def remove_dict_entries(self, obj):
  290. assert 0, "should be unreachable"
  291. def set_terminator(self, obj, terminator):
  292. if not isinstance(terminator, DevolvedDictTerminator):
  293. assert isinstance(terminator, DictTerminator)
  294. terminator = terminator.devolved_dict_terminator
  295. return Terminator.set_terminator(self, obj, terminator)
  296. class PlainAttribute(AbstractAttribute):
  297. _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 'ever_mutated?', 'order']
  298. def __init__(self, name, index, back):
  299. AbstractAttribute.__init__(self, back.space, back.terminator)
  300. self.name = name
  301. self.index = index
  302. self.storageindex = back.length()
  303. self.back = back
  304. self._size_estimate = self.length() * NUM_DIGITS_POW2
  305. self.ever_mutated = False
  306. self.order = len(back.cache_attrs) if back.cache_attrs else 0
  307. def _copy_attr(self, obj, new_obj):
  308. w_value = self.read(obj, self.name, self.index)
  309. new_obj._get_mapdict_map().add_attr(new_obj, self.name, self.index, w_value)
  310. def delete(self, obj, name, index):
  311. if index == self.index and name == self.name:
  312. # ok, attribute is deleted
  313. if not self.ever_mutated:
  314. self.ever_mutated = True
  315. return self.back.copy(obj)
  316. new_obj = self.back.delete(obj, name, index)
  317. if new_obj is not None:
  318. self._copy_attr(obj, new_obj)
  319. return new_obj
  320. def copy(self, obj):
  321. new_obj = self.back.copy(obj)
  322. self._copy_attr(obj, new_obj)
  323. return new_obj
  324. def length(self):
  325. return self.storageindex + 1
  326. def set_terminator(self, obj, terminator):
  327. new_obj = self.back.set_terminator(obj, terminator)
  328. self._copy_attr(obj, new_obj)
  329. return new_obj
  330. def search(self, attrtype):
  331. if self.index == attrtype:
  332. return self
  333. return self.back.search(attrtype)
  334. def materialize_r_dict(self, space, obj, dict_w):
  335. new_obj = self.back.materialize_r_dict(space, obj, dict_w)
  336. if self.index == DICT:
  337. w_attr = space.wrap(self.name)
  338. dict_w[w_attr] = obj._mapdict_read_storage(self.storageindex)
  339. else:
  340. self._copy_attr(obj, new_obj)
  341. return new_obj
  342. def remove_dict_entries(self, obj):
  343. new_obj = self.back.remove_dict_entries(obj)
  344. if self.index != DICT:
  345. self._copy_attr(obj, new_obj)
  346. return new_obj
  347. def __repr__(self):
  348. return "<PlainAttribute %s %s %s %r>" % (self.name, self.index, self.storageindex, self.back)
  349. class MapAttrCache(object):
  350. def __init__(self, space):
  351. SIZE = 1 << space.config.objspace.std.methodcachesizeexp
  352. self.attrs = [None] * SIZE
  353. self.names = [None] * SIZE
  354. self.indexes = [INVALID] * SIZE
  355. self.cached_attrs = [None] * SIZE
  356. if space.config.objspace.std.withmethodcachecounter:
  357. self.hits = {}
  358. self.misses = {}
  359. def clear(self):
  360. for i in range(len(self.attrs)):
  361. self.attrs[i] = None
  362. for i in range(len(self.names)):
  363. self.names[i] = None
  364. self.indexes[i] = INVALID
  365. for i in range(len(self.cached_attrs)):
  366. self.cached_attrs[i] = None
  367. # ____________________________________________________________
  368. # object implementation
  369. DICT = 0
  370. SPECIAL = 1
  371. INVALID = 2
  372. SLOTS_STARTING_FROM = 3
  373. # a little bit of a mess of mixin classes that implement various pieces of
  374. # objspace user object functionality in terms of mapdict
  375. class BaseUserClassMapdict:
  376. # everything that's needed to use mapdict for a user subclass at all.
  377. # This immediately makes slots possible.
  378. # assumes presence of _get_mapdict_map, _set_mapdict_map
  379. # _mapdict_init_empty, _mapdict_read_storage,
  380. # _mapdict_write_storage, _mapdict_storage_length,
  381. # _set_mapdict_storage_and_map
  382. # _____________________________________________
  383. # objspace interface
  384. # class access
  385. def getclass(self, space):
  386. return self._get_mapdict_map().terminator.w_cls
  387. def setclass(self, space, w_cls):
  388. new_obj = self._get_mapdict_map().set_terminator(self, w_cls.terminator)
  389. self._set_mapdict_storage_and_map(new_obj.storage, new_obj.map)
  390. def user_setup(self, space, w_subtype):
  391. from pypy.module.__builtin__.interp_classobj import W_InstanceObject
  392. assert (not self.typedef.hasdict or
  393. isinstance(w_subtype.terminator, NoDictTerminator) or
  394. self.typedef is W_InstanceObject.typedef)
  395. self._mapdict_init_empty(w_subtype.terminator)
  396. # methods needed for slots
  397. def getslotvalue(self, slotindex):
  398. index = SLOTS_STARTING_FROM + slotindex
  399. return self._get_mapdict_map().read(self, "slot", index)
  400. def setslotvalue(self, slotindex, w_value):
  401. index = SLOTS_STARTING_FROM + slotindex
  402. self._get_mapdict_map().write(self, "slot", index, w_value)
  403. def delslotvalue(self, slotindex):
  404. index = SLOTS_STARTING_FROM + slotindex
  405. new_obj = self._get_mapdict_map().delete(self, "slot", index)
  406. if new_obj is None:
  407. return False
  408. self._set_mapdict_storage_and_map(new_obj.storage, new_obj.map)
  409. return True
  410. class MapdictWeakrefSupport(object):
  411. # stuff used by the _weakref implementation
  412. def getweakref(self):
  413. from pypy.module._weakref.interp__weakref import WeakrefLifeline
  414. lifeline = self._get_mapdict_map().read(self, "weakref", SPECIAL)
  415. if lifeline is None:
  416. return None
  417. assert isinstance(lifeline, WeakrefLifeline)
  418. return lifeline
  419. getweakref._cannot_really_call_random_things_ = True
  420. def setweakref(self, space, weakreflifeline):
  421. from pypy.module._weakref.interp__weakref import WeakrefLifeline
  422. assert isinstance(weakreflifeline, WeakrefLifeline)
  423. self._get_mapdict_map().write(self, "weakref", SPECIAL, weakreflifeline)
  424. setweakref._cannot_really_call_random_things_ = True
  425. def delweakref(self):
  426. self._get_mapdict_map().write(self, "weakref", SPECIAL, None)
  427. delweakref._cannot_really_call_random_things_ = True
  428. class MapdictDictSupport(object):
  429. # objspace interface for dictionary operations
  430. def getdictvalue(self, space, attrname):
  431. return self._get_mapdict_map().read(self, attrname, DICT)
  432. def setdictvalue(self, space, attrname, w_value):
  433. return self._get_mapdict_map().write(self, attrname, DICT, w_value)
  434. def deldictvalue(self, space, attrname):
  435. new_obj = self._get_mapdict_map().delete(self, attrname, DICT)
  436. if new_obj is None:
  437. return False
  438. self._set_mapdict_storage_and_map(new_obj.storage, new_obj.map)
  439. return True
  440. def getdict(self, space):
  441. return _obj_getdict(self, space)
  442. def setdict(self, space, w_dict):
  443. _obj_setdict(self, space, w_dict)
  444. # a couple of helpers for the classes above, factored out to reduce
  445. # the translated code size
  446. @objectmodel.dont_inline
  447. def _obj_getdict(self, space):
  448. terminator = self._get_mapdict_map().terminator
  449. assert isinstance(terminator, DictTerminator) or isinstance(terminator, DevolvedDictTerminator)
  450. w_dict = self._get_mapdict_map().read(self, "dict", SPECIAL)
  451. if w_dict is not None:
  452. assert isinstance(w_dict, W_DictMultiObject)
  453. return w_dict
  454. strategy = space.fromcache(MapDictStrategy)
  455. storage = strategy.erase(self)
  456. w_dict = W_DictObject(space, strategy, storage)
  457. flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict)
  458. assert flag
  459. return w_dict
  460. @objectmodel.dont_inline
  461. def _obj_setdict(self, space, w_dict):
  462. from pypy.interpreter.error import oefmt
  463. terminator = self._get_mapdict_map().terminator
  464. assert isinstance(terminator, DictTerminator) or isinstance(terminator, DevolvedDictTerminator)
  465. if not space.isinstance_w(w_dict, space.w_dict):
  466. raise oefmt(space.w_TypeError, "setting dictionary to a non-dict")
  467. assert isinstance(w_dict, W_DictMultiObject)
  468. w_olddict = self.getdict(space)
  469. assert isinstance(w_olddict, W_DictMultiObject)
  470. # The old dict has got 'self' as dstorage, but we are about to
  471. # change self's ("dict", SPECIAL) attribute to point to the
  472. # new dict. If the old dict was using the MapDictStrategy, we
  473. # have to force it now: otherwise it would remain an empty
  474. # shell that continues to delegate to 'self'.
  475. if type(w_olddict.get_strategy()) is MapDictStrategy:
  476. w_olddict.get_strategy().switch_to_object_strategy(w_olddict)
  477. flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict)
  478. assert flag
  479. class MapdictStorageMixin(object):
  480. def _get_mapdict_map(self):
  481. return jit.promote(self.map)
  482. def _set_mapdict_map(self, map):
  483. self.map = map
  484. def _mapdict_init_empty(self, map):
  485. from rpython.rlib.debug import make_sure_not_resized
  486. self.map = map
  487. self.storage = make_sure_not_resized([None] * map.size_estimate())
  488. def _mapdict_read_storage(self, storageindex):
  489. assert storageindex >= 0
  490. return self.storage[storageindex]
  491. def _mapdict_write_storage(self, storageindex, value):
  492. self.storage[storageindex] = value
  493. def _mapdict_storage_length(self):
  494. return len(self.storage)
  495. def _set_mapdict_storage_and_map(self, storage, map):
  496. self.storage = storage
  497. self.map = map
  498. class ObjectWithoutDict(W_Root):
  499. # mainly for tests
  500. objectmodel.import_from_mixin(MapdictStorageMixin)
  501. objectmodel.import_from_mixin(BaseUserClassMapdict)
  502. objectmodel.import_from_mixin(MapdictWeakrefSupport)
  503. class Object(W_Root):
  504. # mainly for tests
  505. objectmodel.import_from_mixin(MapdictStorageMixin)
  506. objectmodel.import_from_mixin(BaseUserClassMapdict)
  507. objectmodel.import_from_mixin(MapdictWeakrefSupport)
  508. objectmodel.import_from_mixin(MapdictDictSupport)
  509. SUBCLASSES_NUM_FIELDS = 5
  510. def _make_storage_mixin_size_n(n=SUBCLASSES_NUM_FIELDS):
  511. from rpython.rlib import unroll
  512. rangen = unroll.unrolling_iterable(range(n))
  513. nmin1 = n - 1
  514. rangenmin1 = unroll.unrolling_iterable(range(nmin1))
  515. valnmin1 = "_value%s" % nmin1
  516. class subcls(object):
  517. def _get_mapdict_map(self):
  518. return jit.promote(self.map)
  519. def _set_mapdict_map(self, map):
  520. self.map = map
  521. def _mapdict_init_empty(self, map):
  522. for i in rangenmin1:
  523. setattr(self, "_value%s" % i, None)
  524. setattr(self, valnmin1, erase_item(None))
  525. self.map = map
  526. def _has_storage_list(self):
  527. return self.map.length() > n
  528. def _mapdict_get_storage_list(self):
  529. erased = getattr(self, valnmin1)
  530. return unerase_list(erased)
  531. def _mapdict_read_storage(self, storageindex):
  532. assert storageindex >= 0
  533. if storageindex < nmin1:
  534. for i in rangenmin1:
  535. if storageindex == i:
  536. return getattr(self, "_value%s" % i)
  537. if self._has_storage_list():
  538. return self._mapdict_get_storage_list()[storageindex - nmin1]
  539. erased = getattr(self, "_value%s" % nmin1)
  540. return unerase_item(erased)
  541. def _mapdict_write_storage(self, storageindex, value):
  542. for i in rangenmin1:
  543. if storageindex == i:
  544. setattr(self, "_value%s" % i, value)
  545. return
  546. if self._has_storage_list():
  547. self._mapdict_get_storage_list()[storageindex - nmin1] = value
  548. return
  549. setattr(self, "_value%s" % nmin1, erase_item(value))
  550. def _mapdict_storage_length(self):
  551. if self._has_storage_list():
  552. return len(self._mapdict_get_storage_list()) + (n - 1)
  553. return n
  554. def _set_mapdict_storage_and_map(self, storage, map):
  555. self.map = map
  556. len_storage = len(storage)
  557. for i in rangenmin1:
  558. if i < len_storage:
  559. erased = storage[i]
  560. else:
  561. erased = None
  562. setattr(self, "_value%s" % i, erased)
  563. has_storage_list = self._has_storage_list()
  564. if len_storage < n:
  565. assert not has_storage_list
  566. erased = erase_item(None)
  567. elif len_storage == n:
  568. assert not has_storage_list
  569. erased = erase_item(storage[nmin1])
  570. elif not has_storage_list:
  571. # storage is longer than self.map.length() only due to
  572. # overallocation
  573. erased = erase_item(storage[nmin1])
  574. # in theory, we should be ultra-paranoid and check all entries,
  575. # but checking just one should catch most problems anyway:
  576. assert storage[n] is None
  577. else:
  578. storage_list = storage[nmin1:]
  579. erased = erase_list(storage_list)
  580. setattr(self, "_value%s" % nmin1, erased)
  581. subcls.__name__ = "Size%s" % n
  582. return subcls
  583. # ____________________________________________________________
  584. # dict implementation
  585. def get_terminator_for_dicts(space):
  586. return DictTerminator(space, None)
  587. class MapDictStrategy(DictStrategy):
  588. erase, unerase = rerased.new_erasing_pair("map")
  589. erase = staticmethod(erase)
  590. unerase = staticmethod(unerase)
  591. def __init__(self, space):
  592. self.space = space
  593. def get_empty_storage(self):
  594. w_result = Object()
  595. terminator = self.space.fromcache(get_terminator_for_dicts)
  596. w_result._mapdict_init_empty(terminator)
  597. return self.erase(w_result)
  598. def switch_to_object_strategy(self, w_dict):
  599. w_obj = self.unerase(w_dict.dstorage)
  600. strategy = self.space.fromcache(ObjectDictStrategy)
  601. dict_w = strategy.unerase(strategy.get_empty_storage())
  602. w_dict.set_strategy(strategy)
  603. w_dict.dstorage = strategy.erase(dict_w)
  604. assert w_obj.getdict(self.space) is w_dict or w_obj._get_mapdict_map().terminator.w_cls is None
  605. materialize_r_dict(self.space, w_obj, dict_w)
  606. def getitem(self, w_dict, w_key):
  607. space = self.space
  608. w_lookup_type = space.type(w_key)
  609. if space.is_w(w_lookup_type, space.w_str):
  610. return self.getitem_str(w_dict, space.str_w(w_key))
  611. elif _never_equal_to_string(space, w_lookup_type):
  612. return None
  613. else:
  614. self.switch_to_object_strategy(w_dict)
  615. return w_dict.getitem(w_key)
  616. def getitem_str(self, w_dict, key):
  617. w_obj = self.unerase(w_dict.dstorage)
  618. return w_obj.getdictvalue(self.space, key)
  619. def setitem_str(self, w_dict, key, w_value):
  620. w_obj = self.unerase(w_dict.dstorage)
  621. flag = w_obj.setdictvalue(self.space, key, w_value)
  622. assert flag
  623. def setitem(self, w_dict, w_key, w_value):
  624. space = self.space
  625. if space.is_w(space.type(w_key), space.w_str):
  626. self.setitem_str(w_dict, self.space.str_w(w_key), w_value)
  627. else:
  628. self.switch_to_object_strategy(w_dict)
  629. w_dict.setitem(w_key, w_value)
  630. def setdefault(self, w_dict, w_key, w_default):
  631. space = self.space
  632. if space.is_w(space.type(w_key), space.w_str):
  633. key = space.str_w(w_key)
  634. w_result = self.getitem_str(w_dict, key)
  635. if w_result is not None:
  636. return w_result
  637. self.setitem_str(w_dict, key, w_default)
  638. return w_default
  639. else:
  640. self.switch_to_object_strategy(w_dict)
  641. return w_dict.setdefault(w_key, w_default)
  642. def delitem(self, w_dict, w_key):
  643. space = self.space
  644. w_key_type = space.type(w_key)
  645. w_obj = self.unerase(w_dict.dstorage)
  646. if space.is_w(w_key_type, space.w_str):
  647. key = self.space.str_w(w_key)
  648. flag = w_obj.deldictvalue(space, key)
  649. if not flag:
  650. raise KeyError
  651. elif _never_equal_to_string(space, w_key_type):
  652. raise KeyError
  653. else:
  654. self.switch_to_object_strategy(w_dict)
  655. w_dict.delitem(w_key)
  656. def length(self, w_dict):
  657. res = 0
  658. curr = self.unerase(w_dict.dstorage)._get_mapdict_map().search(DICT)
  659. while curr is not None:
  660. curr = curr.back
  661. curr = curr.search(DICT)
  662. res += 1
  663. return res
  664. def clear(self, w_dict):
  665. w_obj = self.unerase(w_dict.dstorage)
  666. new_obj = w_obj._get_mapdict_map().remove_dict_entries(w_obj)
  667. w_obj._set_mapdict_storage_and_map(new_obj.storage, new_obj.map)
  668. def popitem(self, w_dict):
  669. curr = self.unerase(w_dict.dstorage)._get_mapdict_map().search(DICT)
  670. if curr is None:
  671. raise KeyError
  672. key = curr.name
  673. w_value = self.getitem_str(w_dict, key)
  674. w_key = self.space.wrap(key)
  675. self.delitem(w_dict, w_key)
  676. return (w_key, w_value)
  677. # XXX could implement a more efficient w_keys based on space.newlist_bytes
  678. def iterkeys(self, w_dict):
  679. return MapDictIteratorKeys(self.space, self, w_dict)
  680. def itervalues(self, w_dict):
  681. return MapDictIteratorValues(self.space, self, w_dict)
  682. def iteritems(self, w_dict):
  683. return MapDictIteratorItems(self.space, self, w_dict)
  684. def materialize_r_dict(space, obj, dict_w):
  685. map = obj._get_mapdict_map()
  686. new_obj = map.materialize_r_dict(space, obj, dict_w)
  687. obj._set_mapdict_storage_and_map(new_obj.storage, new_obj.map)
  688. class MapDictIteratorKeys(BaseKeyIterator):
  689. def __init__(self, space, strategy, w_dict):
  690. BaseKeyIterator.__init__(self, space, strategy, w_dict)
  691. w_obj = strategy.unerase(w_dict.dstorage)
  692. self.w_obj = w_obj
  693. self.orig_map = self.curr_map = w_obj._get_mapdict_map()
  694. def next_key_entry(self):
  695. assert isinstance(self.w_dict.get_strategy(), MapDictStrategy)
  696. if self.orig_map is not self.w_obj._get_mapdict_map():
  697. return None
  698. if self.curr_map:
  699. curr_map = self.curr_map.search(DICT)
  700. if curr_map:
  701. self.curr_map = curr_map.back
  702. attr = curr_map.name
  703. w_attr = self.space.wrap(attr)
  704. return w_attr
  705. return None
  706. class MapDictIteratorValues(BaseValueIterator):
  707. def __init__(self, space, strategy, w_dict):
  708. BaseValueIterator.__init__(self, space, strategy, w_dict)
  709. w_obj = strategy.unerase(w_dict.dstorage)
  710. self.w_obj = w_obj
  711. self.orig_map = self.curr_map = w_obj._get_mapdict_map()
  712. def next_value_entry(self):
  713. assert isinstance(self.w_dict.get_strategy(), MapDictStrategy)
  714. if self.orig_map is not self.w_obj._get_mapdict_map():
  715. return None
  716. if self.curr_map:
  717. curr_map = self.curr_map.search(DICT)
  718. if curr_map:
  719. self.curr_map = curr_map.back
  720. attr = curr_map.name
  721. return self.w_obj.getdictvalue(self.space, attr)
  722. return None
  723. class MapDictIteratorItems(BaseItemIterator):
  724. def __init__(self, space, strategy, w_dict):
  725. BaseItemIterator.__init__(self, space, strategy, w_dict)
  726. w_obj = strategy.unerase(w_dict.dstorage)
  727. self.w_obj = w_obj
  728. self.orig_map = self.curr_map = w_obj._get_mapdict_map()
  729. def next_item_entry(self):
  730. assert isinstance(self.w_dict.get_strategy(), MapDictStrategy)
  731. if self.orig_map is not self.w_obj._get_mapdict_map():
  732. return None, None
  733. if self.curr_map:
  734. curr_map = self.curr_map.search(DICT)
  735. if curr_map:
  736. self.curr_map = curr_map.back
  737. attr = curr_map.name
  738. w_attr = self.space.wrap(attr)
  739. return w_attr, self.w_obj.getdictvalue(self.space, attr)
  740. return None, None
  741. # ____________________________________________________________
  742. # Magic caching
  743. class CacheEntry(object):
  744. version_tag = None
  745. storageindex = 0
  746. w_method = None # for callmethod
  747. success_counter = 0
  748. failure_counter = 0
  749. def is_valid_for_obj(self, w_obj):
  750. map = w_obj._get_mapdict_map()
  751. return self.is_valid_for_map(map)
  752. @jit.dont_look_inside
  753. def is_valid_for_map(self, map):
  754. # note that 'map' can be None here
  755. mymap = self.map_wref()
  756. if mymap is not None and mymap is map:
  757. version_tag = map.terminator.w_cls.version_tag()
  758. if version_tag is self.version_tag:
  759. # everything matches, it's incredibly fast
  760. if map.space.config.objspace.std.withmethodcachecounter:
  761. self.success_counter += 1
  762. return True
  763. return False
  764. _invalid_cache_entry_map = objectmodel.instantiate(AbstractAttribute)
  765. _invalid_cache_entry_map.terminator = None
  766. INVALID_CACHE_ENTRY = CacheEntry()
  767. INVALID_CACHE_ENTRY.map_wref = weakref.ref(_invalid_cache_entry_map)
  768. # different from any real map ^^^
  769. def init_mapdict_cache(pycode):
  770. num_entries = len(pycode.co_names_w)
  771. pycode._mapdict_caches = [INVALID_CACHE_ENTRY] * num_entries
  772. @jit.dont_look_inside
  773. def _fill_cache(pycode, nameindex, map, version_tag, storageindex, w_method=None):
  774. entry = pycode._mapdict_caches[nameindex]
  775. if entry is INVALID_CACHE_ENTRY:
  776. entry = CacheEntry()
  777. pycode._mapdict_caches[nameindex] = entry
  778. entry.map_wref = weakref.ref(map)
  779. entry.version_tag = version_tag
  780. entry.storageindex = storageindex
  781. entry.w_method = w_method
  782. if pycode.space.config.objspace.std.withmethodcachecounter:
  783. entry.failure_counter += 1
  784. def LOAD_ATTR_caching(pycode, w_obj, nameindex):
  785. # this whole mess is to make the interpreter quite a bit faster; it's not
  786. # used if we_are_jitted().
  787. entry = pycode._mapdict_caches[nameindex]
  788. map = w_obj._get_mapdict_map()
  789. if entry.is_valid_for_map(map) and entry.w_method is None:
  790. # everything matches, it's incredibly fast
  791. return w_obj._mapdict_read_storage(entry.storageindex)
  792. return LOAD_ATTR_slowpath(pycode, w_obj, nameindex, map)
  793. LOAD_ATTR_caching._always_inline_ = True
  794. def LOAD_ATTR_slowpath(pycode, w_obj, nameindex, map):
  795. space = pycode.space
  796. w_name = pycode.co_names_w[nameindex]
  797. if map is not None:
  798. w_type = map.terminator.w_cls
  799. w_descr = w_type.getattribute_if_not_from_object()
  800. if w_descr is not None:
  801. return space._handle_getattribute(w_descr, w_obj, w_name)
  802. version_tag = w_type.version_tag()
  803. if version_tag is not None:
  804. name = space.str_w(w_name)
  805. # We need to care for obscure cases in which the w_descr is
  806. # a MutableCell, which may change without changing the version_tag
  807. _, w_descr = w_type._pure_lookup_where_with_method_cache(
  808. name, version_tag)
  809. #
  810. attrname, index = ("", INVALID)
  811. if w_descr is None:
  812. attrname, index = (name, DICT) # common case: no such attr in the class
  813. elif isinstance(w_descr, MutableCell):
  814. pass # we have a MutableCell in the class: give up
  815. elif space.is_data_descr(w_descr):
  816. # we have a data descriptor, which means the dictionary value
  817. # (if any) has no relevance.
  818. from pypy.interpreter.typedef import Member
  819. if isinstance(w_descr, Member): # it is a slot -- easy case
  820. attrname, index = ("slot", SLOTS_STARTING_FROM + w_descr.index)
  821. else:
  822. # There is a non-data descriptor in the class. If there is
  823. # also a dict attribute, use the latter, caching its storageindex.
  824. # If not, we loose. We could do better in this case too,
  825. # but we don't care too much; the common case of a method
  826. # invocation is handled by LOOKUP_METHOD_xxx below.
  827. attrname = name
  828. index = DICT
  829. #
  830. if index != INVALID:
  831. attr = map.find_map_attr(attrname, index)
  832. if attr is not None:
  833. # Note that if map.terminator is a DevolvedDictTerminator,
  834. # map.find_map_attr will always return None if index==DICT.
  835. _fill_cache(pycode, nameindex, map, version_tag, attr.storageindex)
  836. return w_obj._mapdict_read_storage(attr.storageindex)
  837. if space.config.objspace.std.withmethodcachecounter:
  838. INVALID_CACHE_ENTRY.failure_counter += 1
  839. return space.getattr(w_obj, w_name)
  840. LOAD_ATTR_slowpath._dont_inline_ = True
  841. def LOOKUP_METHOD_mapdict(f, nameindex, w_obj):
  842. pycode = f.getcode()
  843. entry = pycode._mapdict_caches[nameindex]
  844. if entry.is_valid_for_obj(w_obj):
  845. w_method = entry.w_method
  846. if w_method is not None:
  847. f.pushvalue(w_method)
  848. f.pushvalue(w_obj)
  849. return True
  850. return False
  851. def LOOKUP_METHOD_mapdict_fill_cache_method(space, pycode, name, nameindex,
  852. w_obj, w_type, w_method):
  853. if w_method is None or isinstance(w_method, MutableCell):
  854. # don't cache the MutableCell XXX could be fixed
  855. return
  856. version_tag = w_type.version_tag()
  857. assert version_tag is not None
  858. map = w_obj._get_mapdict_map()
  859. if map is None or isinstance(map.terminator, DevolvedDictTerminator):
  860. return
  861. _fill_cache(pycode, nameindex, map, version_tag, -1, w_method)
  862. # XXX fix me: if a function contains a loop with both LOAD_ATTR and
  863. # XXX LOOKUP_METHOD on the same attribute name, it keeps trashing and
  864. # XXX rebuilding the cache