/rpython/memory/support.py
Python | 401 lines | 358 code | 30 blank | 13 comment | 15 complexity | 8eaddc93049b681327b9b2c4ac2fb8e5 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
- from rpython.rtyper.lltypesystem import lltype, llmemory
- from rpython.rlib.objectmodel import free_non_gc_object, we_are_translated
- from rpython.rlib.debug import ll_assert
- from rpython.tool.identity_dict import identity_dict
- from rpython.rtyper.rclass import NONGCOBJECTPTR
- from rpython.rtyper.annlowlevel import cast_nongc_instance_to_base_ptr
- from rpython.rtyper.annlowlevel import cast_base_ptr_to_nongc_instance
- def mangle_hash(i):
- # To hash pointers in dictionaries. Assumes that i shows some
- # alignment (to 4, 8, maybe 16 bytes), so we use the following
- # formula to avoid the trailing bits being always 0.
- return i ^ (i >> 4)
- # ____________________________________________________________
- DEFAULT_CHUNK_SIZE = 1019
- def get_chunk_manager(chunk_size=DEFAULT_CHUNK_SIZE, cache={}):
- try:
- return cache[chunk_size]
- except KeyError:
- pass
- CHUNK = lltype.ForwardReference()
- CHUNK.become(lltype.Struct('AddressChunk',
- ('next', lltype.Ptr(CHUNK)),
- ('items', lltype.FixedSizeArray(
- llmemory.Address, chunk_size))))
- null_chunk = lltype.nullptr(CHUNK)
- class FreeList(object):
- _alloc_flavor_ = "raw"
- def __init__(self):
- self.free_list = null_chunk
- def get(self):
- if not self.free_list:
- # we zero-initialize the chunks to make the translation
- # backends happy, but we don't need to do it at run-time.
- zero = not we_are_translated()
- return lltype.malloc(CHUNK, flavor="raw", zero=zero,
- track_allocation=False)
-
- result = self.free_list
- self.free_list = result.next
- return result
- def put(self, chunk):
- if we_are_translated():
- chunk.next = self.free_list
- self.free_list = chunk
- else:
- # Don't cache the old chunks but free them immediately.
- # Helps debugging, and avoids that old chunks full of
- # addresses left behind by a test end up in genc...
- lltype.free(chunk, flavor="raw", track_allocation=False)
- unused_chunks = FreeList()
- cache[chunk_size] = unused_chunks, null_chunk
- def partition(array, left, right):
- last_item = array[right]
- pivot = last_item
- storeindex = left
- for i in range(left, right):
- if array[i] >= pivot:
- array[i], array[storeindex] = array[storeindex], array[i]
- storeindex += 1
- # Move pivot to its final place
- array[storeindex], array[right] = last_item, array[storeindex]
- return storeindex
- def quicksort(array, left, right):
- # sort array[left:right+1] (i.e. bounds included)
- if right > left:
- pivotnewindex = partition(array, left, right)
- quicksort(array, left, pivotnewindex - 1)
- quicksort(array, pivotnewindex + 1, right)
- def sort_chunk(chunk, size):
- quicksort(chunk.items, 0, size - 1)
-
- return unused_chunks, null_chunk, sort_chunk
- def get_address_stack(chunk_size=DEFAULT_CHUNK_SIZE, cache={}):
- try:
- return cache[chunk_size]
- except KeyError:
- pass
- unused_chunks, null_chunk, sort_chunk = get_chunk_manager(chunk_size)
- class AddressStack(object):
- _alloc_flavor_ = "raw"
-
- def __init__(self):
- self.chunk = unused_chunks.get()
- self.chunk.next = null_chunk
- self.used_in_last_chunk = 0
- # invariant: self.used_in_last_chunk == 0 if and only if
- # the AddressStack is empty
- def enlarge(self):
- new = unused_chunks.get()
- new.next = self.chunk
- self.chunk = new
- self.used_in_last_chunk = 0
- enlarge._dont_inline_ = True
- def shrink(self):
- old = self.chunk
- self.chunk = old.next
- unused_chunks.put(old)
- self.used_in_last_chunk = chunk_size
- shrink._dont_inline_ = True
- def append(self, addr):
- used = self.used_in_last_chunk
- if used == chunk_size:
- self.enlarge()
- used = 0
- self.chunk.items[used] = addr
- self.used_in_last_chunk = used + 1 # always > 0 here
- def non_empty(self):
- return self.used_in_last_chunk != 0
- def pop(self):
- used = self.used_in_last_chunk - 1
- ll_assert(used >= 0, "pop on empty AddressStack")
- result = self.chunk.items[used]
- self.used_in_last_chunk = used
- if used == 0 and self.chunk.next:
- self.shrink()
- return result
- def delete(self):
- cur = self.chunk
- while cur:
- next = cur.next
- unused_chunks.put(cur)
- cur = next
- free_non_gc_object(self)
- def length(self):
- chunk = self.chunk
- result = 0
- count = self.used_in_last_chunk
- while chunk:
- result += count
- chunk = chunk.next
- count = chunk_size
- return result
- def foreach(self, callback, arg):
- """Invoke 'callback(address, arg)' for all addresses in the stack.
- Typically, 'callback' is a bound method and 'arg' can be None.
- """
- chunk = self.chunk
- count = self.used_in_last_chunk
- while chunk:
- while count > 0:
- count -= 1
- callback(chunk.items[count], arg)
- chunk = chunk.next
- count = chunk_size
- foreach._annspecialcase_ = 'specialize:arg(1)'
- def stack2dict(self):
- result = AddressDict(self.length())
- self.foreach(_add_in_dict, result)
- return result
- def tolist(self):
- """NOT_RPYTHON. Returns the content as a list."""
- lst = []
- def _add(obj, lst):
- lst.append(obj)
- self.foreach(_add, lst)
- return lst
- def remove(self, addr):
- """Remove 'addr' from the stack. The addr *must* be in the list,
- and preferrably near the top.
- """
- got = self.pop()
- chunk = self.chunk
- count = self.used_in_last_chunk
- while got != addr:
- count -= 1
- if count < 0:
- chunk = chunk.next
- count = chunk_size - 1
- next = chunk.items[count]
- chunk.items[count] = got
- got = next
- def sort(self):
- """Sorts the items in the AddressStack. They must not be more
- than one chunk of them. This results in a **reverse** order,
- so that the first pop()ped items are the smallest ones."""
- ll_assert(self.chunk.next == null_chunk, "too big for sorting")
- sort_chunk(self.chunk, self.used_in_last_chunk)
- cache[chunk_size] = AddressStack
- return AddressStack
- def _add_in_dict(item, d):
- d.add(item)
- def get_address_deque(chunk_size=DEFAULT_CHUNK_SIZE, cache={}):
- try:
- return cache[chunk_size]
- except KeyError:
- pass
- unused_chunks, null_chunk = get_chunk_manager(chunk_size)
- class AddressDeque(object):
- _alloc_flavor_ = "raw"
-
- def __init__(self):
- chunk = unused_chunks.get()
- chunk.next = null_chunk
- self.oldest_chunk = self.newest_chunk = chunk
- self.index_in_oldest = 0
- self.index_in_newest = 0
- def enlarge(self):
- new = unused_chunks.get()
- new.next = null_chunk
- self.newest_chunk.next = new
- self.newest_chunk = new
- self.index_in_newest = 0
- enlarge._dont_inline_ = True
- def shrink(self):
- old = self.oldest_chunk
- self.oldest_chunk = old.next
- unused_chunks.put(old)
- self.index_in_oldest = 0
- shrink._dont_inline_ = True
- def append(self, addr):
- index = self.index_in_newest
- if index == chunk_size:
- self.enlarge()
- index = 0
- self.newest_chunk.items[index] = addr
- self.index_in_newest = index + 1
- def non_empty(self):
- return (self.oldest_chunk != self.newest_chunk
- or self.index_in_oldest < self.index_in_newest)
- def popleft(self):
- ll_assert(self.non_empty(), "pop on empty AddressDeque")
- index = self.index_in_oldest
- if index == chunk_size:
- self.shrink()
- index = 0
- result = self.oldest_chunk.items[index]
- self.index_in_oldest = index + 1
- return result
- def foreach(self, callback, arg):
- """Invoke 'callback(address, arg)' for all addresses in the deque.
- Typically, 'callback' is a bound method and 'arg' can be None.
- """
- chunk = self.oldest_chunk
- index = self.index_in_oldest
- while chunk is not self.newest_chunk:
- while index < chunk_size:
- callback(chunk.items[index], arg)
- index += 1
- chunk = chunk.next
- index = 0
- limit = self.index_in_newest
- while index < limit:
- callback(chunk.items[index], arg)
- index += 1
- foreach._annspecialcase_ = 'specialize:arg(1)'
- def delete(self):
- cur = self.oldest_chunk
- while cur:
- next = cur.next
- unused_chunks.put(cur)
- cur = next
- free_non_gc_object(self)
- def _was_freed(self):
- return False # otherwise, the __class__ changes
- cache[chunk_size] = AddressDeque
- return AddressDeque
- # ____________________________________________________________
- def AddressDict(length_estimate=0):
- if we_are_translated():
- from rpython.memory import lldict
- return lldict.newdict(length_estimate)
- else:
- return BasicAddressDict()
- def null_address_dict():
- from rpython.memory import lldict
- return lltype.nullptr(lldict.DICT)
- class BasicAddressDict(object):
- def __init__(self):
- self.data = identity_dict() # {_key(addr): value}
- def _key(self, addr):
- "NOT_RPYTHON: prebuilt AddressDicts are not supported"
- return addr._fixup().ptr._obj
- def _wrapkey(self, obj):
- return llmemory.cast_ptr_to_adr(obj._as_ptr())
- def delete(self):
- pass
- def length(self):
- return len(self.data)
- def contains(self, keyaddr):
- return self._key(keyaddr) in self.data
- def get(self, keyaddr, default=llmemory.NULL):
- return self.data.get(self._key(keyaddr), default)
- def setitem(self, keyaddr, valueaddr):
- assert keyaddr
- self.data[self._key(keyaddr)] = valueaddr
- def insertclean(self, keyaddr, valueaddr):
- assert keyaddr
- key = self._key(keyaddr)
- assert key not in self.data
- self.data[key] = valueaddr
- def add(self, keyaddr):
- self.setitem(keyaddr, llmemory.NULL)
- def clear(self):
- self.data.clear()
- def foreach(self, callback, arg):
- """Invoke 'callback(key, value, arg)' for all items in the dict.
- Typically, 'callback' is a bound method and 'arg' can be None."""
- for key, value in self.data.iteritems():
- callback(self._wrapkey(key), value, arg)
- def copy_and_update(dict, surviving, updated_address):
- """Make a copy of 'dict' in which the keys are updated as follows:
- * if surviving(key) returns False, the item is removed
- * otherwise, updated_address(key) is inserted in the copy.
- """
- newdict = AddressDict
- if not we_are_translated():
- # when not translated, return a dict of the same kind as 'dict'
- if not isinstance(dict, BasicAddressDict):
- from rpython.memory.lldict import newdict
- result = newdict(dict.length())
- dict.foreach(_get_updater(surviving, updated_address), result)
- return result
- copy_and_update._annspecialcase_ = 'specialize:arg(1,2)'
- def _get_updater(surviving, updated_address):
- def callback(key, value, arg):
- if surviving(key):
- newkey = updated_address(key)
- arg.setitem(newkey, value)
- return callback
- _get_updater._annspecialcase_ = 'specialize:memo'
- def copy_without_null_values(dict):
- """Make a copy of 'dict' without the key/value pairs where value==NULL."""
- newdict = AddressDict
- if not we_are_translated():
- # when not translated, return a dict of the same kind as 'dict'
- if not isinstance(dict, BasicAddressDict):
- from rpython.memory.lldict import newdict
- result = newdict()
- dict.foreach(_null_value_checker, result)
- return result
- def _null_value_checker(key, value, arg):
- if value:
- arg.setitem(key, value)