def __init__(self, gc, fd): self.gc = gc self.gcflag = gc.gcflag_extra self.fd = rffi.cast(rffi.INT, fd) self.writebuffer = lltype.malloc(rffi.LONGP.TO, self.BUFSIZE, flavor='raw') self.buf_count = 0 if self.gcflag == 0: self.seen = AddressDict() self.pending = AddressStack()
def thread_setup(): """Called once when the program starts.""" aid = get_aid() gcdata.main_thread = aid gcdata.active_thread = aid gcdata.thread_stacks = AddressDict() # {aid: root_stack_top} gcdata._fresh_rootstack = llmemory.NULL gcdata.dead_threads_count = 0
def debug_check_consistency(self): """To use after a collection. If self.DEBUG is set, this enumerates all roots and traces all objects to check if we didn't accidentally free a reachable object or forgot to update a pointer to an object that moved. """ if self.DEBUG: from pypy.rlib.objectmodel import we_are_translated from pypy.rpython.memory.support import AddressDict self._debug_seen = AddressDict() self._debug_pending = self.AddressStack() if not we_are_translated(): self.root_walker._walk_prebuilt_gc(self._debug_record) self.enumerate_all_roots(GCBase._debug_callback, self) pending = self._debug_pending while pending.non_empty(): obj = pending.pop() self.trace(obj, self._debug_callback2, None) self._debug_seen.delete() self._debug_pending.delete()
def debug_check_consistency(self): """To use after a collection. If self.DEBUG is set, this enumerates all roots and traces all objects to check if we didn't accidentally free a reachable object or forgot to update a pointer to an object that moved. """ if self.DEBUG: from pypy.rlib.objectmodel import we_are_translated from pypy.rpython.memory.support import AddressDict self._debug_seen = AddressDict() self._debug_pending = self.AddressStack() if not we_are_translated(): self.root_walker._walk_prebuilt_gc(self._debug_record) self.enumerate_all_roots(GCBase._debug_callback, self) pending = self._debug_pending while pending.non_empty(): obj = pending.pop() self.trace(obj, self._debug_callback2, None) self._debug_seen.delete() self._debug_pending.delete()
class GCBase(object): _alloc_flavor_ = "raw" moving_gc = False needs_write_barrier = False malloc_zero_filled = False prebuilt_gc_objects_are_static_roots = True can_realloc = False object_minimal_size = 0 def __init__(self, config, chunk_size=DEFAULT_CHUNK_SIZE): self.gcheaderbuilder = GCHeaderBuilder(self.HDR) self.AddressStack = get_address_stack(chunk_size) self.AddressDeque = get_address_deque(chunk_size) self.AddressDict = AddressDict self.config = config def setup(self): # all runtime mutable values' setup should happen here # and in its overriden versions! for the benefit of test_transformed_gc self.finalizer_lock_count = 0 self.run_finalizers = self.AddressDeque() def _teardown(self): pass def can_malloc_nonmovable(self): return not self.moving_gc # The following flag enables costly consistency checks after each # collection. It is automatically set to True by test_gc.py. The # checking logic is translatable, so the flag can be set to True # here before translation. DEBUG = False def set_query_functions(self, is_varsize, has_gcptr_in_varsize, is_gcarrayofgcptr, getfinalizer, offsets_to_gc_pointers, fixed_size, varsize_item_sizes, varsize_offset_to_variable_part, varsize_offset_to_length, varsize_offsets_to_gcpointers_in_var_part, weakpointer_offset): self.getfinalizer = getfinalizer self.is_varsize = is_varsize self.has_gcptr_in_varsize = has_gcptr_in_varsize self.is_gcarrayofgcptr = is_gcarrayofgcptr self.offsets_to_gc_pointers = offsets_to_gc_pointers self.fixed_size = fixed_size self.varsize_item_sizes = varsize_item_sizes self.varsize_offset_to_variable_part = varsize_offset_to_variable_part self.varsize_offset_to_length = varsize_offset_to_length self.varsize_offsets_to_gcpointers_in_var_part = varsize_offsets_to_gcpointers_in_var_part self.weakpointer_offset = weakpointer_offset def set_root_walker(self, root_walker): self.root_walker = root_walker def write_barrier(self, newvalue, addr_struct): pass def statistics(self, index): return -1 def size_gc_header(self, typeid=0): return self.gcheaderbuilder.size_gc_header def header(self, addr): addr -= self.gcheaderbuilder.size_gc_header return llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR)) def get_size(self, obj): typeid = self.get_type_id(obj) size = self.fixed_size(typeid) if self.is_varsize(typeid): lenaddr = obj + self.varsize_offset_to_length(typeid) length = lenaddr.signed[0] size += length * self.varsize_item_sizes(typeid) size = llarena.round_up_for_allocation(size) # XXX maybe we should parametrize round_up_for_allocation() # per GC; if we do, we also need to fix the call in # gctypelayout.encode_type_shape() return size def malloc(self, typeid, length=0, zero=False): """For testing. The interface used by the gctransformer is the four malloc_[fixed,var]size[_clear]() functions. """ # Rules about fallbacks in case of missing malloc methods: # * malloc_fixedsize_clear() and malloc_varsize_clear() are mandatory # * malloc_fixedsize() and malloc_varsize() fallback to the above # XXX: as of r49360, gctransformer.framework never inserts calls # to malloc_varsize(), but always uses malloc_varsize_clear() size = self.fixed_size(typeid) needs_finalizer = bool(self.getfinalizer(typeid)) contains_weakptr = self.weakpointer_offset(typeid) >= 0 assert not (needs_finalizer and contains_weakptr) if self.is_varsize(typeid): assert not contains_weakptr assert not needs_finalizer itemsize = self.varsize_item_sizes(typeid) offset_to_length = self.varsize_offset_to_length(typeid) if zero or not hasattr(self, 'malloc_varsize'): malloc_varsize = self.malloc_varsize_clear else: malloc_varsize = self.malloc_varsize ref = malloc_varsize(typeid, length, size, itemsize, offset_to_length, True) else: if zero or not hasattr(self, 'malloc_fixedsize'): malloc_fixedsize = self.malloc_fixedsize_clear else: malloc_fixedsize = self.malloc_fixedsize ref = malloc_fixedsize(typeid, size, True, needs_finalizer, contains_weakptr) # lots of cast and reverse-cast around... return llmemory.cast_ptr_to_adr(ref) def malloc_nonmovable(self, typeid, length=0, zero=False): return self.malloc(typeid, length, zero) def id(self, ptr): return lltype.cast_ptr_to_int(ptr) def can_move(self, addr): return False def set_max_heap_size(self, size): pass def x_swap_pool(self, newpool): return newpool def x_clone(self, clonedata): raise RuntimeError("no support for x_clone in the GC") def trace(self, obj, callback, arg): """Enumerate the locations inside the given obj that can contain GC pointers. For each such location, callback(pointer, arg) is called, where 'pointer' is an address inside the object. Typically, 'callback' is a bound method and 'arg' can be None. """ typeid = self.get_type_id(obj) if self.is_gcarrayofgcptr(typeid): # a performance shortcut for GcArray(gcptr) length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0] item = obj + llmemory.gcarrayofptr_itemsoffset while length > 0: if self.points_to_valid_gc_object(item): callback(item, arg) item += llmemory.gcarrayofptr_singleitemoffset length -= 1 return offsets = self.offsets_to_gc_pointers(typeid) i = 0 while i < len(offsets): item = obj + offsets[i] if self.points_to_valid_gc_object(item): callback(item, arg) i += 1 if self.has_gcptr_in_varsize(typeid): item = obj + self.varsize_offset_to_variable_part(typeid) length = (obj + self.varsize_offset_to_length(typeid)).signed[0] offsets = self.varsize_offsets_to_gcpointers_in_var_part(typeid) itemlength = self.varsize_item_sizes(typeid) while length > 0: j = 0 while j < len(offsets): itemobj = item + offsets[j] if self.points_to_valid_gc_object(itemobj): callback(itemobj, arg) j += 1 item += itemlength length -= 1 trace._annspecialcase_ = 'specialize:arg(2)' def points_to_valid_gc_object(self, addr): return self.is_valid_gc_object(addr.address[0]) def is_valid_gc_object(self, addr): return (addr != NULL and (not self.config.taggedpointers or llmemory.cast_adr_to_int(addr) & 1 == 0)) def debug_check_consistency(self): """To use after a collection. If self.DEBUG is set, this enumerates all roots and traces all objects to check if we didn't accidentally free a reachable object or forgot to update a pointer to an object that moved. """ if self.DEBUG: from pypy.rlib.objectmodel import we_are_translated from pypy.rpython.memory.support import AddressDict self._debug_seen = AddressDict() self._debug_pending = self.AddressStack() if not we_are_translated(): self.root_walker._walk_prebuilt_gc(self._debug_record) callback = GCBase._debug_callback self.root_walker.walk_roots(callback, callback, callback) pending = self._debug_pending while pending.non_empty(): obj = pending.pop() self.debug_check_object(obj) self.trace(obj, self._debug_callback2, None) self._debug_seen.delete() self._debug_pending.delete() def _debug_record(self, obj): seen = self._debug_seen if not seen.contains(obj): seen.add(obj) self._debug_pending.append(obj) def _debug_callback(self, root): obj = root.address[0] ll_assert(bool(obj), "NULL address from walk_roots()") self._debug_record(obj) def _debug_callback2(self, pointer, ignored): obj = pointer.address[0] ll_assert(bool(obj), "NULL address from self.trace()") self._debug_record(obj) def debug_check_object(self, obj): pass def execute_finalizers(self): self.finalizer_lock_count += 1 try: while self.run_finalizers.non_empty(): if self.finalizer_lock_count > 1: # the outer invocation of execute_finalizers() will do it break obj = self.run_finalizers.popleft() finalizer = self.getfinalizer(self.get_type_id(obj)) finalizer(obj) finally: self.finalizer_lock_count -= 1
class HeapDumper(object): _alloc_flavor_ = "raw" BUFSIZE = 8192 # words def __init__(self, gc, fd): self.gc = gc self.gcflag = gc.gcflag_extra self.fd = rffi.cast(rffi.INT, fd) self.writebuffer = lltype.malloc(rffi.LONGP.TO, self.BUFSIZE, flavor='raw') self.buf_count = 0 if self.gcflag == 0: self.seen = AddressDict() self.pending = AddressStack() def delete(self): if self.gcflag == 0: self.seen.delete() self.pending.delete() lltype.free(self.writebuffer, flavor='raw') free_non_gc_object(self) def flush(self): if self.buf_count > 0: bytes = self.buf_count * rffi.sizeof(rffi.LONG) count = raw_os_write(self.fd, rffi.cast(llmemory.Address, self.writebuffer), rffi.cast(rffi.SIZE_T, bytes)) if rffi.cast(lltype.Signed, count) != bytes: raise OSError(rposix.get_errno(), "raw_os_write failed") self.buf_count = 0 flush._dont_inline_ = True def write(self, value): x = self.buf_count self.writebuffer[x] = value x += 1 self.buf_count = x if x == self.BUFSIZE: self.flush() write._always_inline_ = True # ---------- def write_marker(self): self.write(0) self.write(0) self.write(0) self.write(-1) def writeobj(self, obj): gc = self.gc typeid = gc.get_type_id(obj) self.write(llmemory.cast_adr_to_int(obj)) self.write(gc.get_member_index(typeid)) self.write(gc.get_size_incl_hash(obj)) gc.trace(obj, self._writeref, None) self.write(-1) def _writeref(self, pointer, _): obj = pointer.address[0] self.write(llmemory.cast_adr_to_int(obj)) self.add(obj) def add(self, obj): if self.gcflag == 0: if not self.seen.contains(obj): self.seen.setitem(obj, obj) self.pending.append(obj) else: hdr = self.gc.header(obj) if (hdr.tid & self.gcflag) == 0: hdr.tid |= self.gcflag self.pending.append(obj) def add_roots(self): self.gc.enumerate_all_roots(_hd_add_root, self) pendingroots = self.pending self.pending = AddressStack() self.walk(pendingroots) pendingroots.delete() self.write_marker() def walk(self, pending): while pending.non_empty(): self.writeobj(pending.pop()) # ---------- # A simplified copy of the above, to make sure we walk again all the # objects to clear the 'gcflag'. def unwriteobj(self, obj): gc = self.gc gc.trace(obj, self._unwriteref, None) def _unwriteref(self, pointer, _): obj = pointer.address[0] self.unadd(obj) def unadd(self, obj): assert self.gcflag != 0 hdr = self.gc.header(obj) if (hdr.tid & self.gcflag) != 0: hdr.tid &= ~self.gcflag self.pending.append(obj) def clear_gcflag_again(self): self.gc.enumerate_all_roots(_hd_unadd_root, self) pendingroots = self.pending self.pending = AddressStack() self.unwalk(pendingroots) pendingroots.delete() def unwalk(self, pending): while pending.non_empty(): self.unwriteobj(pending.pop())
def thread_setup(): gcdata.aid2stack = AddressDict() gcdata.dead_threads_count = 0 # to also register the main thread's stack thread_start()
class GCBase(object): _alloc_flavor_ = "raw" moving_gc = False needs_write_barrier = False malloc_zero_filled = False prebuilt_gc_objects_are_static_roots = True object_minimal_size = 0 gcflag_extra = 0 # or a real GC flag that is always 0 when not collecting def __init__(self, config, chunk_size=DEFAULT_CHUNK_SIZE, translated_to_c=True): self.gcheaderbuilder = GCHeaderBuilder(self.HDR) self.AddressStack = get_address_stack(chunk_size) self.AddressDeque = get_address_deque(chunk_size) self.AddressDict = AddressDict self.null_address_dict = null_address_dict self.config = config assert isinstance(translated_to_c, bool) self.translated_to_c = translated_to_c def setup(self): # all runtime mutable values' setup should happen here # and in its overriden versions! for the benefit of test_transformed_gc self.finalizer_lock_count = 0 self.run_finalizers = self.AddressDeque() def post_setup(self): # More stuff that needs to be initialized when the GC is already # fully working. (Only called by gctransform/framework for now.) from pypy.rpython.memory.gc import env self.DEBUG = env.read_from_env('PYPY_GC_DEBUG') def _teardown(self): pass def can_malloc_nonmovable(self): return not self.moving_gc def can_optimize_clean_setarrayitems(self): return True # False in case of card marking # The following flag enables costly consistency checks after each # collection. It is automatically set to True by test_gc.py. The # checking logic is translatable, so the flag can be set to True # here before translation. At run-time, if PYPY_GC_DEBUG is set, # then it is also set to True. DEBUG = False def set_query_functions(self, is_varsize, has_gcptr_in_varsize, is_gcarrayofgcptr, getfinalizer, getlightfinalizer, offsets_to_gc_pointers, fixed_size, varsize_item_sizes, varsize_offset_to_variable_part, varsize_offset_to_length, varsize_offsets_to_gcpointers_in_var_part, weakpointer_offset, member_index, is_rpython_class, has_custom_trace, get_custom_trace, fast_path_tracing): self.getfinalizer = getfinalizer self.getlightfinalizer = getlightfinalizer self.is_varsize = is_varsize self.has_gcptr_in_varsize = has_gcptr_in_varsize self.is_gcarrayofgcptr = is_gcarrayofgcptr self.offsets_to_gc_pointers = offsets_to_gc_pointers self.fixed_size = fixed_size self.varsize_item_sizes = varsize_item_sizes self.varsize_offset_to_variable_part = varsize_offset_to_variable_part self.varsize_offset_to_length = varsize_offset_to_length self.varsize_offsets_to_gcpointers_in_var_part = varsize_offsets_to_gcpointers_in_var_part self.weakpointer_offset = weakpointer_offset self.member_index = member_index self.is_rpython_class = is_rpython_class self.has_custom_trace = has_custom_trace self.get_custom_trace = get_custom_trace self.fast_path_tracing = fast_path_tracing def get_member_index(self, type_id): return self.member_index(type_id) def set_root_walker(self, root_walker): self.root_walker = root_walker def write_barrier(self, newvalue, addr_struct): pass def statistics(self, index): return -1 def size_gc_header(self, typeid=0): return self.gcheaderbuilder.size_gc_header def header(self, addr): addr -= self.gcheaderbuilder.size_gc_header return llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR)) def _get_size_for_typeid(self, obj, typeid): size = self.fixed_size(typeid) if self.is_varsize(typeid): lenaddr = obj + self.varsize_offset_to_length(typeid) length = lenaddr.signed[0] size += length * self.varsize_item_sizes(typeid) size = llarena.round_up_for_allocation(size) # XXX maybe we should parametrize round_up_for_allocation() # per GC; if we do, we also need to fix the call in # gctypelayout.encode_type_shape() return size def get_size(self, obj): return self._get_size_for_typeid(obj, self.get_type_id(obj)) def get_size_incl_hash(self, obj): return self.get_size(obj) def malloc(self, typeid, length=0, zero=False): """For testing. The interface used by the gctransformer is the four malloc_[fixed,var]size[_clear]() functions. """ # Rules about fallbacks in case of missing malloc methods: # * malloc_fixedsize_clear() and malloc_varsize_clear() are mandatory # * malloc_fixedsize() and malloc_varsize() fallback to the above # XXX: as of r49360, gctransformer.framework never inserts calls # to malloc_varsize(), but always uses malloc_varsize_clear() size = self.fixed_size(typeid) needs_finalizer = bool(self.getfinalizer(typeid)) finalizer_is_light = bool(self.getlightfinalizer(typeid)) contains_weakptr = self.weakpointer_offset(typeid) >= 0 assert not (needs_finalizer and contains_weakptr) if self.is_varsize(typeid): assert not contains_weakptr assert not needs_finalizer itemsize = self.varsize_item_sizes(typeid) offset_to_length = self.varsize_offset_to_length(typeid) if zero or not hasattr(self, 'malloc_varsize'): malloc_varsize = self.malloc_varsize_clear else: malloc_varsize = self.malloc_varsize ref = malloc_varsize(typeid, length, size, itemsize, offset_to_length) else: if zero or not hasattr(self, 'malloc_fixedsize'): malloc_fixedsize = self.malloc_fixedsize_clear else: malloc_fixedsize = self.malloc_fixedsize ref = malloc_fixedsize(typeid, size, needs_finalizer, finalizer_is_light, contains_weakptr) # lots of cast and reverse-cast around... return llmemory.cast_ptr_to_adr(ref) def malloc_nonmovable(self, typeid, length=0, zero=False): return self.malloc(typeid, length, zero) def id(self, ptr): return lltype.cast_ptr_to_int(ptr) def can_move(self, addr): return False def set_max_heap_size(self, size): raise NotImplementedError def x_swap_pool(self, newpool): return newpool def x_clone(self, clonedata): raise RuntimeError("no support for x_clone in the GC") def trace(self, obj, callback, arg): """Enumerate the locations inside the given obj that can contain GC pointers. For each such location, callback(pointer, arg) is called, where 'pointer' is an address inside the object. Typically, 'callback' is a bound method and 'arg' can be None. """ typeid = self.get_type_id(obj) # # First, look if we need more than the simple fixed-size tracing if not self.fast_path_tracing(typeid): # # Yes. Two cases: either we are just a GcArray(gcptr), for # which we have a special case for performance, or we call # the slow path version. if self.is_gcarrayofgcptr(typeid): length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0] item = obj + llmemory.gcarrayofptr_itemsoffset while length > 0: if self.points_to_valid_gc_object(item): callback(item, arg) item += llmemory.gcarrayofptr_singleitemoffset length -= 1 return self._trace_slow_path(obj, callback, arg) # # Do the tracing on the fixed-size part of the object. offsets = self.offsets_to_gc_pointers(typeid) i = 0 while i < len(offsets): item = obj + offsets[i] if self.points_to_valid_gc_object(item): callback(item, arg) i += 1 trace._annspecialcase_ = 'specialize:arg(2)' def _trace_slow_path(self, obj, callback, arg): typeid = self.get_type_id(obj) if self.has_gcptr_in_varsize(typeid): item = obj + self.varsize_offset_to_variable_part(typeid) length = (obj + self.varsize_offset_to_length(typeid)).signed[0] offsets = self.varsize_offsets_to_gcpointers_in_var_part(typeid) itemlength = self.varsize_item_sizes(typeid) while length > 0: j = 0 while j < len(offsets): itemobj = item + offsets[j] if self.points_to_valid_gc_object(itemobj): callback(itemobj, arg) j += 1 item += itemlength length -= 1 if self.has_custom_trace(typeid): generator = self.get_custom_trace(typeid) item = llmemory.NULL while True: item = generator(obj, item) if not item: break if self.points_to_valid_gc_object(item): callback(item, arg) _trace_slow_path._annspecialcase_ = 'specialize:arg(2)' def trace_partial(self, obj, start, stop, callback, arg): """Like trace(), but only walk the array part, for indices in range(start, stop). Must only be called if has_gcptr_in_varsize(). """ length = stop - start typeid = self.get_type_id(obj) if self.is_gcarrayofgcptr(typeid): # a performance shortcut for GcArray(gcptr) item = obj + llmemory.gcarrayofptr_itemsoffset item += llmemory.gcarrayofptr_singleitemoffset * start while length > 0: if self.points_to_valid_gc_object(item): callback(item, arg) item += llmemory.gcarrayofptr_singleitemoffset length -= 1 return ll_assert(self.has_gcptr_in_varsize(typeid), "trace_partial() on object without has_gcptr_in_varsize()") item = obj + self.varsize_offset_to_variable_part(typeid) offsets = self.varsize_offsets_to_gcpointers_in_var_part(typeid) itemlength = self.varsize_item_sizes(typeid) item += itemlength * start while length > 0: j = 0 while j < len(offsets): itemobj = item + offsets[j] if self.points_to_valid_gc_object(itemobj): callback(itemobj, arg) j += 1 item += itemlength length -= 1 trace_partial._annspecialcase_ = 'specialize:arg(4)' def points_to_valid_gc_object(self, addr): return self.is_valid_gc_object(addr.address[0]) def is_valid_gc_object(self, addr): return (addr != NULL and (not self.config.taggedpointers or llmemory.cast_adr_to_int(addr) & 1 == 0)) def enumerate_all_roots(self, callback, arg): """For each root object, invoke callback(obj, arg). 'callback' should not be a bound method. Note that this method is not suitable for actually doing the collection in a moving GC, because you cannot write back a modified address. It is there only for inspection. """ # overridden in some subclasses, for GCs which have an additional # list of last generation roots callback2, attrname = _convert_callback_formats(callback) # :-/ setattr(self, attrname, arg) self.root_walker.walk_roots(callback2, callback2, callback2) self.run_finalizers.foreach(callback, arg) enumerate_all_roots._annspecialcase_ = 'specialize:arg(1)' def debug_check_consistency(self): """To use after a collection. If self.DEBUG is set, this enumerates all roots and traces all objects to check if we didn't accidentally free a reachable object or forgot to update a pointer to an object that moved. """ if self.DEBUG: from pypy.rlib.objectmodel import we_are_translated from pypy.rpython.memory.support import AddressDict self._debug_seen = AddressDict() self._debug_pending = self.AddressStack() if not we_are_translated(): self.root_walker._walk_prebuilt_gc(self._debug_record) self.enumerate_all_roots(GCBase._debug_callback, self) pending = self._debug_pending while pending.non_empty(): obj = pending.pop() self.trace(obj, self._debug_callback2, None) self._debug_seen.delete() self._debug_pending.delete() def _debug_record(self, obj): seen = self._debug_seen if not seen.contains(obj): seen.add(obj) self.debug_check_object(obj) self._debug_pending.append(obj) @staticmethod def _debug_callback(obj, self): self._debug_record(obj) def _debug_callback2(self, pointer, ignored): obj = pointer.address[0] ll_assert(bool(obj), "NULL address from self.trace()") self._debug_record(obj) def debug_check_object(self, obj): pass def execute_finalizers(self): self.finalizer_lock_count += 1 try: while self.run_finalizers.non_empty(): if self.finalizer_lock_count > 1: # the outer invocation of execute_finalizers() will do it break obj = self.run_finalizers.popleft() finalizer = self.getfinalizer(self.get_type_id(obj)) finalizer(obj, llmemory.NULL) finally: self.finalizer_lock_count -= 1
class GCBase(object): _alloc_flavor_ = "raw" moving_gc = False needs_write_barrier = False malloc_zero_filled = False prebuilt_gc_objects_are_static_roots = True object_minimal_size = 0 gcflag_extra = 0 # or a real GC flag that is always 0 when not collecting def __init__(self, config, chunk_size=DEFAULT_CHUNK_SIZE, translated_to_c=True): self.gcheaderbuilder = GCHeaderBuilder(self.HDR) self.AddressStack = get_address_stack(chunk_size) self.AddressDeque = get_address_deque(chunk_size) self.AddressDict = AddressDict self.null_address_dict = null_address_dict self.config = config assert isinstance(translated_to_c, bool) self.translated_to_c = translated_to_c def setup(self): # all runtime mutable values' setup should happen here # and in its overriden versions! for the benefit of test_transformed_gc self.finalizer_lock_count = 0 self.run_finalizers = self.AddressDeque() def post_setup(self): # More stuff that needs to be initialized when the GC is already # fully working. (Only called by gctransform/framework for now.) from pypy.rpython.memory.gc import env self.DEBUG = env.read_from_env('PYPY_GC_DEBUG') def _teardown(self): pass def can_malloc_nonmovable(self): return not self.moving_gc def can_optimize_clean_setarrayitems(self): return True # False in case of card marking # The following flag enables costly consistency checks after each # collection. It is automatically set to True by test_gc.py. The # checking logic is translatable, so the flag can be set to True # here before translation. At run-time, if PYPY_GC_DEBUG is set, # then it is also set to True. DEBUG = False def set_query_functions( self, is_varsize, has_gcptr_in_varsize, is_gcarrayofgcptr, getfinalizer, getlightfinalizer, offsets_to_gc_pointers, fixed_size, varsize_item_sizes, varsize_offset_to_variable_part, varsize_offset_to_length, varsize_offsets_to_gcpointers_in_var_part, weakpointer_offset, member_index, is_rpython_class, has_custom_trace, get_custom_trace, fast_path_tracing): self.getfinalizer = getfinalizer self.getlightfinalizer = getlightfinalizer self.is_varsize = is_varsize self.has_gcptr_in_varsize = has_gcptr_in_varsize self.is_gcarrayofgcptr = is_gcarrayofgcptr self.offsets_to_gc_pointers = offsets_to_gc_pointers self.fixed_size = fixed_size self.varsize_item_sizes = varsize_item_sizes self.varsize_offset_to_variable_part = varsize_offset_to_variable_part self.varsize_offset_to_length = varsize_offset_to_length self.varsize_offsets_to_gcpointers_in_var_part = varsize_offsets_to_gcpointers_in_var_part self.weakpointer_offset = weakpointer_offset self.member_index = member_index self.is_rpython_class = is_rpython_class self.has_custom_trace = has_custom_trace self.get_custom_trace = get_custom_trace self.fast_path_tracing = fast_path_tracing def get_member_index(self, type_id): return self.member_index(type_id) def set_root_walker(self, root_walker): self.root_walker = root_walker def write_barrier(self, newvalue, addr_struct): pass def statistics(self, index): return -1 def size_gc_header(self, typeid=0): return self.gcheaderbuilder.size_gc_header def header(self, addr): addr -= self.gcheaderbuilder.size_gc_header return llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR)) def _get_size_for_typeid(self, obj, typeid): size = self.fixed_size(typeid) if self.is_varsize(typeid): lenaddr = obj + self.varsize_offset_to_length(typeid) length = lenaddr.signed[0] size += length * self.varsize_item_sizes(typeid) size = llarena.round_up_for_allocation(size) # XXX maybe we should parametrize round_up_for_allocation() # per GC; if we do, we also need to fix the call in # gctypelayout.encode_type_shape() return size def get_size(self, obj): return self._get_size_for_typeid(obj, self.get_type_id(obj)) def get_size_incl_hash(self, obj): return self.get_size(obj) def malloc(self, typeid, length=0, zero=False): """For testing. The interface used by the gctransformer is the four malloc_[fixed,var]size[_clear]() functions. """ # Rules about fallbacks in case of missing malloc methods: # * malloc_fixedsize_clear() and malloc_varsize_clear() are mandatory # * malloc_fixedsize() and malloc_varsize() fallback to the above # XXX: as of r49360, gctransformer.framework never inserts calls # to malloc_varsize(), but always uses malloc_varsize_clear() size = self.fixed_size(typeid) needs_finalizer = bool(self.getfinalizer(typeid)) finalizer_is_light = bool(self.getlightfinalizer(typeid)) contains_weakptr = self.weakpointer_offset(typeid) >= 0 assert not (needs_finalizer and contains_weakptr) if self.is_varsize(typeid): assert not contains_weakptr assert not needs_finalizer itemsize = self.varsize_item_sizes(typeid) offset_to_length = self.varsize_offset_to_length(typeid) if zero or not hasattr(self, 'malloc_varsize'): malloc_varsize = self.malloc_varsize_clear else: malloc_varsize = self.malloc_varsize ref = malloc_varsize(typeid, length, size, itemsize, offset_to_length) else: if zero or not hasattr(self, 'malloc_fixedsize'): malloc_fixedsize = self.malloc_fixedsize_clear else: malloc_fixedsize = self.malloc_fixedsize ref = malloc_fixedsize(typeid, size, needs_finalizer, finalizer_is_light, contains_weakptr) # lots of cast and reverse-cast around... return llmemory.cast_ptr_to_adr(ref) def malloc_nonmovable(self, typeid, length=0, zero=False): return self.malloc(typeid, length, zero) def id(self, ptr): return lltype.cast_ptr_to_int(ptr) def can_move(self, addr): return False def set_max_heap_size(self, size): raise NotImplementedError def x_swap_pool(self, newpool): return newpool def x_clone(self, clonedata): raise RuntimeError("no support for x_clone in the GC") def trace(self, obj, callback, arg): """Enumerate the locations inside the given obj that can contain GC pointers. For each such location, callback(pointer, arg) is called, where 'pointer' is an address inside the object. Typically, 'callback' is a bound method and 'arg' can be None. """ typeid = self.get_type_id(obj) # # First, look if we need more than the simple fixed-size tracing if not self.fast_path_tracing(typeid): # # Yes. Two cases: either we are just a GcArray(gcptr), for # which we have a special case for performance, or we call # the slow path version. if self.is_gcarrayofgcptr(typeid): length = (obj + llmemory.gcarrayofptr_lengthoffset).signed[0] item = obj + llmemory.gcarrayofptr_itemsoffset while length > 0: if self.points_to_valid_gc_object(item): callback(item, arg) item += llmemory.gcarrayofptr_singleitemoffset length -= 1 return self._trace_slow_path(obj, callback, arg) # # Do the tracing on the fixed-size part of the object. offsets = self.offsets_to_gc_pointers(typeid) i = 0 while i < len(offsets): item = obj + offsets[i] if self.points_to_valid_gc_object(item): callback(item, arg) i += 1 trace._annspecialcase_ = 'specialize:arg(2)' def _trace_slow_path(self, obj, callback, arg): typeid = self.get_type_id(obj) if self.has_gcptr_in_varsize(typeid): item = obj + self.varsize_offset_to_variable_part(typeid) length = (obj + self.varsize_offset_to_length(typeid)).signed[0] offsets = self.varsize_offsets_to_gcpointers_in_var_part(typeid) itemlength = self.varsize_item_sizes(typeid) while length > 0: j = 0 while j < len(offsets): itemobj = item + offsets[j] if self.points_to_valid_gc_object(itemobj): callback(itemobj, arg) j += 1 item += itemlength length -= 1 if self.has_custom_trace(typeid): generator = self.get_custom_trace(typeid) item = llmemory.NULL while True: item = generator(obj, item) if not item: break if self.points_to_valid_gc_object(item): callback(item, arg) _trace_slow_path._annspecialcase_ = 'specialize:arg(2)' def trace_partial(self, obj, start, stop, callback, arg): """Like trace(), but only walk the array part, for indices in range(start, stop). Must only be called if has_gcptr_in_varsize(). """ length = stop - start typeid = self.get_type_id(obj) if self.is_gcarrayofgcptr(typeid): # a performance shortcut for GcArray(gcptr) item = obj + llmemory.gcarrayofptr_itemsoffset item += llmemory.gcarrayofptr_singleitemoffset * start while length > 0: if self.points_to_valid_gc_object(item): callback(item, arg) item += llmemory.gcarrayofptr_singleitemoffset length -= 1 return ll_assert(self.has_gcptr_in_varsize(typeid), "trace_partial() on object without has_gcptr_in_varsize()") item = obj + self.varsize_offset_to_variable_part(typeid) offsets = self.varsize_offsets_to_gcpointers_in_var_part(typeid) itemlength = self.varsize_item_sizes(typeid) item += itemlength * start while length > 0: j = 0 while j < len(offsets): itemobj = item + offsets[j] if self.points_to_valid_gc_object(itemobj): callback(itemobj, arg) j += 1 item += itemlength length -= 1 trace_partial._annspecialcase_ = 'specialize:arg(4)' def points_to_valid_gc_object(self, addr): return self.is_valid_gc_object(addr.address[0]) def is_valid_gc_object(self, addr): return (addr != NULL and (not self.config.taggedpointers or llmemory.cast_adr_to_int(addr) & 1 == 0)) def enumerate_all_roots(self, callback, arg): """For each root object, invoke callback(obj, arg). 'callback' should not be a bound method. Note that this method is not suitable for actually doing the collection in a moving GC, because you cannot write back a modified address. It is there only for inspection. """ # overridden in some subclasses, for GCs which have an additional # list of last generation roots callback2, attrname = _convert_callback_formats(callback) # :-/ setattr(self, attrname, arg) self.root_walker.walk_roots(callback2, callback2, callback2) self.run_finalizers.foreach(callback, arg) enumerate_all_roots._annspecialcase_ = 'specialize:arg(1)' def debug_check_consistency(self): """To use after a collection. If self.DEBUG is set, this enumerates all roots and traces all objects to check if we didn't accidentally free a reachable object or forgot to update a pointer to an object that moved. """ if self.DEBUG: from pypy.rlib.objectmodel import we_are_translated from pypy.rpython.memory.support import AddressDict self._debug_seen = AddressDict() self._debug_pending = self.AddressStack() if not we_are_translated(): self.root_walker._walk_prebuilt_gc(self._debug_record) self.enumerate_all_roots(GCBase._debug_callback, self) pending = self._debug_pending while pending.non_empty(): obj = pending.pop() self.trace(obj, self._debug_callback2, None) self._debug_seen.delete() self._debug_pending.delete() def _debug_record(self, obj): seen = self._debug_seen if not seen.contains(obj): seen.add(obj) self.debug_check_object(obj) self._debug_pending.append(obj) @staticmethod def _debug_callback(obj, self): self._debug_record(obj) def _debug_callback2(self, pointer, ignored): obj = pointer.address[0] ll_assert(bool(obj), "NULL address from self.trace()") self._debug_record(obj) def debug_check_object(self, obj): pass def execute_finalizers(self): self.finalizer_lock_count += 1 try: while self.run_finalizers.non_empty(): if self.finalizer_lock_count > 1: # the outer invocation of execute_finalizers() will do it break obj = self.run_finalizers.popleft() finalizer = self.getfinalizer(self.get_type_id(obj)) finalizer(obj, llmemory.NULL) finally: self.finalizer_lock_count -= 1