1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
|
from rpython.rtyper.lltypesystem.llmemory import raw_malloc, raw_free
from rpython.rtyper.lltypesystem.llmemory import raw_memcopy, raw_memclear
from rpython.rtyper.lltypesystem.llmemory import NULL, raw_malloc_usage
from rpython.memory.support import get_address_stack, get_address_deque
from rpython.memory.support import AddressDict
from rpython.rtyper.lltypesystem import lltype, llmemory, llarena, rffi, llgroup
from rpython.rlib.objectmodel import free_non_gc_object
from rpython.rlib.debug import ll_assert, have_debug_prints
from rpython.rlib.debug import debug_print, debug_start, debug_stop
from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.rlib.rarithmetic import ovfcheck, LONG_BIT
from rpython.memory.gc.base import MovingGCBase, ARRAY_TYPEID_MAP,\
TYPEID_MAP
import sys, os
first_gcflag = 1 << (LONG_BIT//2)
GCFLAG_FORWARDED = first_gcflag
# GCFLAG_EXTERNAL is set on objects not living in the semispace:
# either immortal objects or (for HybridGC) externally raw_malloc'ed
GCFLAG_EXTERNAL = first_gcflag << 1
GCFLAG_FINALIZATION_ORDERING = first_gcflag << 2
_GCFLAG_HASH_BASE = first_gcflag << 3
GCFLAG_HASHMASK = _GCFLAG_HASH_BASE * 0x3 # also consumes 'first_gcflag << 4'
# the two bits in GCFLAG_HASHMASK can have one of the following values:
# - nobody ever asked for the hash of the object
GC_HASH_NOTTAKEN = _GCFLAG_HASH_BASE * 0x0
# - someone asked, and we gave the address of the object
GC_HASH_TAKEN_ADDR = _GCFLAG_HASH_BASE * 0x1
# - someone asked, and we gave the address plus 'nursery_hash_base'
GC_HASH_TAKEN_NURS = _GCFLAG_HASH_BASE * 0x2
# - we have our own extra field to store the hash
GC_HASH_HASFIELD = _GCFLAG_HASH_BASE * 0x3
GCFLAG_EXTRA = first_gcflag << 5 # for RPython abuse only
memoryError = MemoryError()
class SemiSpaceGC(MovingGCBase):
_alloc_flavor_ = "raw"
inline_simple_malloc = True
inline_simple_malloc_varsize = True
malloc_zero_filled = True
first_unused_gcflag = first_gcflag << 6
gcflag_extra = GCFLAG_EXTRA
HDR = lltype.Struct('header', ('tid', lltype.Signed)) # XXX or rffi.INT?
typeid_is_in_field = 'tid'
FORWARDSTUB = lltype.GcStruct('forwarding_stub',
('forw', llmemory.Address))
FORWARDSTUBPTR = lltype.Ptr(FORWARDSTUB)
object_minimal_size = llmemory.sizeof(FORWARDSTUB)
# the following values override the default arguments of __init__ when
# translating to a real backend.
TRANSLATION_PARAMS = {'space_size': 8*1024*1024} # XXX adjust
def __init__(self, config, space_size=4096, max_space_size=sys.maxint//2+1,
**kwds):
self.param_space_size = space_size
self.param_max_space_size = max_space_size
MovingGCBase.__init__(self, config, **kwds)
def setup(self):
#self.total_collection_time = 0.0
self.total_collection_count = 0
self.space_size = self.param_space_size
self.max_space_size = self.param_max_space_size
self.red_zone = 0
#self.program_start_time = time.time()
self.tospace = llarena.arena_malloc(self.space_size, True)
ll_assert(bool(self.tospace), "couldn't allocate tospace")
self.top_of_space = self.tospace + self.space_size
self.fromspace = llarena.arena_malloc(self.space_size, True)
ll_assert(bool(self.fromspace), "couldn't allocate fromspace")
self.free = self.tospace
MovingGCBase.setup(self)
self.objects_with_finalizers = self.AddressDeque()
self.objects_with_light_finalizers = self.AddressStack()
self.objects_with_weakrefs = self.AddressStack()
def _teardown(self):
debug_print("Teardown")
llarena.arena_free(self.fromspace)
llarena.arena_free(self.tospace)
# This class only defines the malloc_{fixed,var}size_clear() methods
# because the spaces are filled with zeroes in advance.
def malloc_fixedsize_clear(self, typeid16, size,
has_finalizer=False,
is_finalizer_light=False,
contains_weakptr=False):
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
result = self.free
if raw_malloc_usage(totalsize) > self.top_of_space - result:
result = self.obtain_free_space(totalsize)
llarena.arena_reserve(result, totalsize)
self.init_gc_object(result, typeid16)
self.free = result + totalsize
#if is_finalizer_light:
# self.objects_with_light_finalizers.append(result + size_gc_header)
#else:
if has_finalizer:
from rpython.rtyper.lltypesystem import rffi
self.objects_with_finalizers.append(result + size_gc_header)
self.objects_with_finalizers.append(rffi.cast(llmemory.Address, -1))
if contains_weakptr:
self.objects_with_weakrefs.append(result + size_gc_header)
return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
def malloc_varsize_clear(self, typeid16, length, size, itemsize,
offset_to_length):
size_gc_header = self.gcheaderbuilder.size_gc_header
nonvarsize = size_gc_header + size
try:
varsize = ovfcheck(itemsize * length)
totalsize = ovfcheck(nonvarsize + varsize)
except OverflowError:
raise memoryError
result = self.free
if raw_malloc_usage(totalsize) > self.top_of_space - result:
result = self.obtain_free_space(totalsize)
llarena.arena_reserve(result, totalsize)
self.init_gc_object(result, typeid16)
(result + size_gc_header + offset_to_length).signed[0] = length
self.free = result + llarena.round_up_for_allocation(totalsize)
return llmemory.cast_adr_to_ptr(result+size_gc_header, llmemory.GCREF)
def shrink_array(self, addr, smallerlength):
size_gc_header = self.gcheaderbuilder.size_gc_header
if self._is_in_the_space(addr - size_gc_header):
typeid = self.get_type_id(addr)
totalsmallersize = (
size_gc_header + self.fixed_size(typeid) +
self.varsize_item_sizes(typeid) * smallerlength)
llarena.arena_shrink_obj(addr - size_gc_header, totalsmallersize)
#
offset_to_length = self.varsize_offset_to_length(typeid)
(addr + offset_to_length).signed[0] = smallerlength
return True
else:
return False
def register_finalizer(self, fq_index, gcobj):
from rpython.rtyper.lltypesystem import rffi
obj = llmemory.cast_ptr_to_adr(gcobj)
fq_index = rffi.cast(llmemory.Address, fq_index)
self.objects_with_finalizers.append(obj)
self.objects_with_finalizers.append(fq_index)
def obtain_free_space(self, needed):
# a bit of tweaking to maximize the performance and minimize the
# amount of code in an inlined version of malloc_fixedsize_clear()
if not self.try_obtain_free_space(needed):
raise memoryError
return self.free
obtain_free_space._dont_inline_ = True
def try_obtain_free_space(self, needed):
# XXX for bonus points do big objects differently
needed = raw_malloc_usage(needed)
if (self.red_zone >= 2 and self.space_size < self.max_space_size and
self.double_space_size()):
pass # collect was done during double_space_size()
else:
self.semispace_collect()
missing = needed - (self.top_of_space - self.free)
if missing <= 0:
return True # success
else:
# first check if the object could possibly fit
proposed_size = self.space_size
while missing > 0:
if proposed_size >= self.max_space_size:
return False # no way
missing -= proposed_size
proposed_size *= 2
# For address space fragmentation reasons, we double the space
# size possibly several times, moving the objects at each step,
# instead of going directly for the final size. We assume that
# it's a rare case anyway.
while self.space_size < proposed_size:
if not self.double_space_size():
return False
ll_assert(needed <= self.top_of_space - self.free,
"double_space_size() failed to do its job")
return True
def double_space_size(self):
self.red_zone = 0
old_fromspace = self.fromspace
newsize = self.space_size * 2
newspace = llarena.arena_malloc(newsize, True)
if not newspace:
return False # out of memory
llarena.arena_free(old_fromspace)
self.fromspace = newspace
# now self.tospace contains the existing objects and
# self.fromspace is the freshly allocated bigger space
self.semispace_collect(size_changing=True)
self.top_of_space = self.tospace + newsize
# now self.tospace is the freshly allocated bigger space,
# and self.fromspace is the old smaller space, now empty
llarena.arena_free(self.fromspace)
newspace = llarena.arena_malloc(newsize, True)
if not newspace:
# Complex failure case: we have in self.tospace a big chunk
# of memory, and the two smaller original spaces are already gone.
# Unsure if it's worth these efforts, but we can artificially
# split self.tospace in two again...
self.max_space_size = self.space_size # don't try to grow again,
# because doing arena_free(self.fromspace) would crash
self.fromspace = self.tospace + self.space_size
self.top_of_space = self.fromspace
ll_assert(self.free <= self.top_of_space,
"unexpected growth of GC space usage during collect")
return False # out of memory
self.fromspace = newspace
self.space_size = newsize
return True # success
def set_max_heap_size(self, size):
# Set the maximum semispace size.
# The size is rounded down to the next power of two. Also, this is
# the size of one semispace only, so actual usage can be the double
# during a collection. Finally, note that this will never shrink
# an already-allocated heap.
if size < 1:
size = 1 # actually, the minimum is 8MB in default translations
self.max_space_size = sys.maxint//2+1
while self.max_space_size > size:
self.max_space_size >>= 1
@classmethod
def JIT_minimal_size_in_nursery(cls):
return cls.object_minimal_size
def collect(self, gen=0):
self.debug_check_consistency()
self.semispace_collect()
# the indirection is required by the fact that collect() is referred
# to by the gc transformer, and the default argument would crash
# (this is also a hook for the HybridGC)
def semispace_collect(self, size_changing=False):
debug_start("gc-collect")
debug_print()
debug_print(".----------- Full collection ------------------")
start_usage = self.free - self.tospace
debug_print("| used before collection: ",
start_usage, "bytes")
#start_time = time.time()
#llop.debug_print(lltype.Void, 'semispace_collect', int(size_changing))
# Switch the spaces. We copy everything over to the empty space
# (self.fromspace at the beginning of the collection), and clear the old
# one (self.tospace at the beginning). Their purposes will be reversed
# for the next collection.
tospace = self.fromspace
fromspace = self.tospace
self.fromspace = fromspace
self.tospace = tospace
self.top_of_space = tospace + self.space_size
scan = self.free = tospace
self.starting_full_collect()
self.collect_roots()
self.copy_pending_finalizers(self.copy)
scan = self.scan_copied(scan)
if self.objects_with_light_finalizers.non_empty():
self.deal_with_objects_with_light_finalizers()
if self.objects_with_finalizers.non_empty():
scan = self.deal_with_objects_with_finalizers(scan)
if self.objects_with_weakrefs.non_empty():
self.invalidate_weakrefs()
self.update_objects_with_id()
self.finished_full_collect()
self.debug_check_consistency()
if not size_changing:
llarena.arena_reset(fromspace, self.space_size, True)
self.record_red_zone()
self.execute_finalizers()
#llop.debug_print(lltype.Void, 'collected', self.space_size, size_changing, self.top_of_space - self.free)
if have_debug_prints():
#end_time = time.time()
#elapsed_time = end_time - start_time
#self.total_collection_time += elapsed_time
self.total_collection_count += 1
#total_program_time = end_time - self.program_start_time
end_usage = self.free - self.tospace
debug_print("| used after collection: ",
end_usage, "bytes")
debug_print("| freed: ",
start_usage - end_usage, "bytes")
debug_print("| size of each semispace: ",
self.space_size, "bytes")
debug_print("| fraction of semispace now used: ",
end_usage * 100.0 / self.space_size, "%")
#ct = self.total_collection_time
cc = self.total_collection_count
debug_print("| number of semispace_collects: ",
cc)
#debug_print("| i.e.: ",
# cc / total_program_time, "per second")
#debug_print("| total time in semispace_collect: ",
# ct, "seconds")
#debug_print("| i.e.: ",
# ct * 100.0 / total_program_time, "%")
debug_print("`----------------------------------------------")
debug_stop("gc-collect")
def starting_full_collect(self):
pass # hook for the HybridGC
def finished_full_collect(self):
pass # hook for the HybridGC
def record_red_zone(self):
# red zone: if the space is more than 80% full, the next collection
# should double its size. If it is more than 66% full twice in a row,
# then it should double its size too. (XXX adjust)
# The goal is to avoid many repeated collection that don't free a lot
# of memory each, if the size of the live object set is just below the
# size of the space.
free_after_collection = self.top_of_space - self.free
if free_after_collection > self.space_size // 3:
self.red_zone = 0
else:
self.red_zone += 1
if free_after_collection < self.space_size // 5:
self.red_zone += 1
def get_size_incl_hash(self, obj):
size = self.get_size(obj)
hdr = self.header(obj)
if (hdr.tid & GCFLAG_HASHMASK) == GC_HASH_HASFIELD:
size += llmemory.sizeof(lltype.Signed)
return size
def scan_copied(self, scan):
while scan < self.free:
curr = scan + self.size_gc_header()
self.trace_and_copy(curr)
scan += self.size_gc_header() + self.get_size_incl_hash(curr)
return scan
def collect_roots(self):
self.root_walker.walk_roots(
SemiSpaceGC._collect_root, # stack roots
SemiSpaceGC._collect_root, # static in prebuilt non-gc structures
SemiSpaceGC._collect_root) # static in prebuilt gc objects
def _collect_root(self, root):
root.address[0] = self.copy(root.address[0])
def copy(self, obj):
if self.DEBUG:
self.debug_check_can_copy(obj)
if self.is_forwarded(obj):
#llop.debug_print(lltype.Void, obj, "already copied to", self.get_forwarding_address(obj))
return self.get_forwarding_address(obj)
else:
objsize = self.get_size(obj)
newobj = self.make_a_copy(obj, objsize)
#llop.debug_print(lltype.Void, obj, "copied to", newobj,
# "tid", self.header(obj).tid,
# "size", totalsize)
self.set_forwarding_address(obj, newobj, objsize)
return newobj
def _get_object_hash(self, obj, objsize, tid):
# Returns the hash of the object, which must not be GC_HASH_NOTTAKEN.
gc_hash = tid & GCFLAG_HASHMASK
if gc_hash == GC_HASH_HASFIELD:
obj = llarena.getfakearenaaddress(obj)
return (obj + objsize).signed[0]
elif gc_hash == GC_HASH_TAKEN_ADDR:
return llmemory.cast_adr_to_int(obj)
elif gc_hash == GC_HASH_TAKEN_NURS:
return self._compute_current_nursery_hash(obj)
else:
assert 0, "gc_hash == GC_HASH_NOTTAKEN"
def _make_a_copy_with_tid(self, obj, objsize, tid):
totalsize = self.size_gc_header() + objsize
newaddr = self.free
llarena.arena_reserve(newaddr, totalsize)
raw_memcopy(obj - self.size_gc_header(), newaddr, totalsize)
if tid & GCFLAG_HASHMASK:
hash = self._get_object_hash(obj, objsize, tid)
llarena.arena_reserve(newaddr + totalsize,
llmemory.sizeof(lltype.Signed))
(newaddr + totalsize).signed[0] = hash
tid |= GC_HASH_HASFIELD
totalsize += llmemory.sizeof(lltype.Signed)
self.free += totalsize
newhdr = llmemory.cast_adr_to_ptr(newaddr, lltype.Ptr(self.HDR))
newhdr.tid = tid
newobj = newaddr + self.size_gc_header()
return newobj
def make_a_copy(self, obj, objsize):
tid = self.header(obj).tid
return self._make_a_copy_with_tid(obj, objsize, tid)
def trace_and_copy(self, obj):
self.trace(obj, self._trace_copy, None)
def _trace_copy(self, pointer, ignored):
pointer.address[0] = self.copy(pointer.address[0])
def surviving(self, obj):
# To use during a collection. Check if the object is currently
# marked as surviving the collection. This is equivalent to
# self.is_forwarded() for all objects except the nonmoving objects
# created by the HybridGC subclass. In all cases, if an object
# survives, self.get_forwarding_address() returns its new address.
return self.is_forwarded(obj)
def is_forwarded(self, obj):
return self.header(obj).tid & GCFLAG_FORWARDED != 0
# note: all prebuilt objects also have this flag set
def get_forwarding_address(self, obj):
tid = self.header(obj).tid
if tid & GCFLAG_EXTERNAL:
self.visit_external_object(obj)
return obj # external or prebuilt objects are "forwarded"
# to themselves
else:
stub = llmemory.cast_adr_to_ptr(obj, self.FORWARDSTUBPTR)
return stub.forw
def visit_external_object(self, obj):
pass # hook for the HybridGC
def get_possibly_forwarded_type_id(self, obj):
tid = self.header(obj).tid
if self.is_forwarded(obj) and not (tid & GCFLAG_EXTERNAL):
obj = self.get_forwarding_address(obj)
return self.get_type_id(obj)
def set_forwarding_address(self, obj, newobj, objsize):
# To mark an object as forwarded, we set the GCFLAG_FORWARDED and
# overwrite the object with a FORWARDSTUB. Doing so is a bit
# long-winded on llarena, but it all melts down to two memory
# writes after translation to C.
size_gc_header = self.size_gc_header()
stubsize = llmemory.sizeof(self.FORWARDSTUB)
tid = self.header(obj).tid
ll_assert(tid & GCFLAG_EXTERNAL == 0, "unexpected GCFLAG_EXTERNAL")
ll_assert(tid & GCFLAG_FORWARDED == 0, "unexpected GCFLAG_FORWARDED")
# replace the object at 'obj' with a FORWARDSTUB.
hdraddr = obj - size_gc_header
llarena.arena_reset(hdraddr, size_gc_header + objsize, False)
llarena.arena_reserve(hdraddr, size_gc_header + stubsize)
hdr = llmemory.cast_adr_to_ptr(hdraddr, lltype.Ptr(self.HDR))
hdr.tid = tid | GCFLAG_FORWARDED
stub = llmemory.cast_adr_to_ptr(obj, self.FORWARDSTUBPTR)
stub.forw = newobj
def combine(self, typeid16, flags):
return llop.combine_ushort(lltype.Signed, typeid16, flags)
def get_type_id(self, addr):
tid = self.header(addr).tid
ll_assert(tid & (GCFLAG_FORWARDED|GCFLAG_EXTERNAL) != GCFLAG_FORWARDED,
"get_type_id on forwarded obj")
# Non-prebuilt forwarded objects are overwritten with a FORWARDSTUB.
# Although calling get_type_id() on a forwarded object works by itself,
# we catch it as an error because it's likely that what is then
# done with the typeid is bogus.
return llop.extract_ushort(llgroup.HALFWORD, tid)
def init_gc_object(self, addr, typeid16, flags=0):
hdr = llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
hdr.tid = self.combine(typeid16, flags)
def init_gc_object_immortal(self, addr, typeid16, flags=0):
hdr = llmemory.cast_adr_to_ptr(addr, lltype.Ptr(self.HDR))
flags |= GCFLAG_EXTERNAL | GCFLAG_FORWARDED | GC_HASH_TAKEN_ADDR
hdr.tid = self.combine(typeid16, flags)
# immortal objects always have GCFLAG_FORWARDED set;
# see get_forwarding_address().
def deal_with_objects_with_light_finalizers(self):
""" This is a much simpler version of dealing with finalizers
and an optimization - we can reasonably assume that those finalizers
don't do anything fancy and *just* call them. Among other things
they won't resurrect objects
"""
new_objects = self.AddressStack()
while self.objects_with_light_finalizers.non_empty():
obj = self.objects_with_light_finalizers.pop()
if self.surviving(obj):
new_objects.append(self.get_forwarding_address(obj))
else:
self.call_destructor(obj)
self.objects_with_light_finalizers.delete()
self.objects_with_light_finalizers = new_objects
def deal_with_objects_with_finalizers(self, scan):
# walk over list of objects with finalizers
# if it is not copied, add it to the list of to-be-called finalizers
# and copy it, to me make the finalizer runnable
# We try to run the finalizers in a "reasonable" order, like
# CPython does. The details of this algorithm are in
# pypy/doc/discussion/finalizer-order.txt.
new_with_finalizer = self.AddressDeque()
marked = self.AddressDeque()
pending = self.AddressStack()
self.tmpstack = self.AddressStack()
while self.objects_with_finalizers.non_empty():
x = self.objects_with_finalizers.popleft()
fq_nr = self.objects_with_finalizers.popleft()
ll_assert(self._finalization_state(x) != 1,
"bad finalization state 1")
if self.surviving(x):
new_with_finalizer.append(self.get_forwarding_address(x))
new_with_finalizer.append(fq_nr)
continue
marked.append(x)
marked.append(fq_nr)
pending.append(x)
while pending.non_empty():
y = pending.pop()
state = self._finalization_state(y)
if state == 0:
self._bump_finalization_state_from_0_to_1(y)
self.trace(y, self._append_if_nonnull, pending)
elif state == 2:
self._recursively_bump_finalization_state_from_2_to_3(y)
scan = self._recursively_bump_finalization_state_from_1_to_2(
x, scan)
while marked.non_empty():
x = marked.popleft()
fq_nr = marked.popleft()
state = self._finalization_state(x)
ll_assert(state >= 2, "unexpected finalization state < 2")
newx = self.get_forwarding_address(x)
if state == 2:
from rpython.rtyper.lltypesystem import rffi
fq_index = rffi.cast(lltype.Signed, fq_nr)
self.mark_finalizer_to_run(fq_index, newx)
# we must also fix the state from 2 to 3 here, otherwise
# we leave the GCFLAG_FINALIZATION_ORDERING bit behind
# which will confuse the next collection
self._recursively_bump_finalization_state_from_2_to_3(x)
else:
new_with_finalizer.append(newx)
new_with_finalizer.append(fq_nr)
self.tmpstack.delete()
pending.delete()
marked.delete()
self.objects_with_finalizers.delete()
self.objects_with_finalizers = new_with_finalizer
return scan
def _append_if_nonnull(pointer, stack):
stack.append(pointer.address[0])
_append_if_nonnull = staticmethod(_append_if_nonnull)
def _finalization_state(self, obj):
if self.surviving(obj):
newobj = self.get_forwarding_address(obj)
hdr = self.header(newobj)
if hdr.tid & GCFLAG_FINALIZATION_ORDERING:
return 2
else:
return 3
else:
hdr = self.header(obj)
if hdr.tid & GCFLAG_FINALIZATION_ORDERING:
return 1
else:
return 0
def _bump_finalization_state_from_0_to_1(self, obj):
ll_assert(self._finalization_state(obj) == 0,
"unexpected finalization state != 0")
hdr = self.header(obj)
hdr.tid |= GCFLAG_FINALIZATION_ORDERING
def _recursively_bump_finalization_state_from_2_to_3(self, obj):
ll_assert(self._finalization_state(obj) == 2,
"unexpected finalization state != 2")
newobj = self.get_forwarding_address(obj)
pending = self.tmpstack
ll_assert(not pending.non_empty(), "tmpstack not empty")
pending.append(newobj)
while pending.non_empty():
y = pending.pop()
hdr = self.header(y)
if hdr.tid & GCFLAG_FINALIZATION_ORDERING: # state 2 ?
hdr.tid &= ~GCFLAG_FINALIZATION_ORDERING # change to state 3
self.trace(y, self._append_if_nonnull, pending)
def _recursively_bump_finalization_state_from_1_to_2(self, obj, scan):
# recursively convert objects from state 1 to state 2.
# Note that copy() copies all bits, including the
# GCFLAG_FINALIZATION_ORDERING. The mapping between
# state numbers and the presence of this bit was designed
# for the following to work :-)
self.copy(obj)
return self.scan_copied(scan)
def invalidate_weakrefs(self):
# walk over list of objects that contain weakrefs
# if the object it references survives then update the weakref
# otherwise invalidate the weakref
new_with_weakref = self.AddressStack()
while self.objects_with_weakrefs.non_empty():
obj = self.objects_with_weakrefs.pop()
if not self.surviving(obj):
continue # weakref itself dies
obj = self.get_forwarding_address(obj)
offset = self.weakpointer_offset(self.get_type_id(obj))
pointing_to = (obj + offset).address[0]
# XXX I think that pointing_to cannot be NULL here
if pointing_to:
if self.surviving(pointing_to):
(obj + offset).address[0] = self.get_forwarding_address(
pointing_to)
new_with_weakref.append(obj)
else:
(obj + offset).address[0] = NULL
self.objects_with_weakrefs.delete()
self.objects_with_weakrefs = new_with_weakref
def _is_external(self, obj):
return (self.header(obj).tid & GCFLAG_EXTERNAL) != 0
def _is_in_the_space(self, obj):
return self.tospace <= obj < self.free
def debug_check_object(self, obj):
"""Check the invariants about 'obj' that should be true
between collections."""
tid = self.header(obj).tid
if tid & GCFLAG_EXTERNAL:
ll_assert(tid & GCFLAG_FORWARDED != 0, "bug: external+!forwarded")
ll_assert(not (self.tospace <= obj < self.free),
"external flag but object inside the semispaces")
else:
ll_assert(not (tid & GCFLAG_FORWARDED), "bug: !external+forwarded")
ll_assert(self.tospace <= obj < self.free,
"!external flag but object outside the semispaces")
ll_assert(not (tid & GCFLAG_FINALIZATION_ORDERING),
"unexpected GCFLAG_FINALIZATION_ORDERING")
def debug_check_can_copy(self, obj):
ll_assert(not (self.tospace <= obj < self.free),
"copy() on already-copied object")
STATISTICS_NUMBERS = 0
def is_in_nursery(self, addr):
# overridden in generation.py.
return False
def _compute_current_nursery_hash(self, obj):
# overridden in generation.py.
raise AssertionError("should not be called")
def identityhash(self, gcobj):
# The following loop should run at most twice.
while 1:
obj = llmemory.cast_ptr_to_adr(gcobj)
hdr = self.header(obj)
if hdr.tid & GCFLAG_HASHMASK:
break
# It's the first time we ask for a hash, and it's not an
# external object. Shrink the top of space by the extra
# hash word that will be needed after a collect.
shrunk_top = self.top_of_space - llmemory.sizeof(lltype.Signed)
if shrunk_top < self.free:
# Cannot shrink! Do a collection, asking for at least
# one word of free space, and try again. May raise
# MemoryError. Obscure: not called directly, but
# across an llop, to make sure that there is the
# correct push_roots/pop_roots around the call...
llop.gc_obtain_free_space(llmemory.Address,
llmemory.sizeof(lltype.Signed))
continue
else:
# Now we can have side-effects: lower the top of space
# and set one of the GC_HASH_TAKEN_xxx flags.
self.top_of_space = shrunk_top
if self.is_in_nursery(obj):
hdr.tid |= GC_HASH_TAKEN_NURS
else:
hdr.tid |= GC_HASH_TAKEN_ADDR
break
# Now we can return the result
objsize = self.get_size(obj)
return self._get_object_hash(obj, objsize, hdr.tid)
def track_heap_parent(self, obj, parent):
addr = obj.address[0]
parent_idx = llop.get_member_index(lltype.Signed,
self.get_type_id(parent))
idx = llop.get_member_index(lltype.Signed, self.get_type_id(addr))
self._ll_typeid_map[parent_idx].links[idx] += 1
self.track_heap(addr)
def track_heap(self, adr):
if self._tracked_dict.contains(adr):
return
self._tracked_dict.add(adr)
idx = llop.get_member_index(lltype.Signed, self.get_type_id(adr))
self._ll_typeid_map[idx].count += 1
totsize = self.get_size(adr) + self.size_gc_header()
self._ll_typeid_map[idx].size += llmemory.raw_malloc_usage(totsize)
self.trace(adr, self.track_heap_parent, adr)
@staticmethod
def _track_heap_root(obj, self):
self.track_heap(obj)
def heap_stats(self):
self._tracked_dict = self.AddressDict()
max_tid = self.root_walker.gcdata.max_type_id
ll_typeid_map = lltype.malloc(ARRAY_TYPEID_MAP, max_tid, zero=True)
for i in range(max_tid):
ll_typeid_map[i] = lltype.malloc(TYPEID_MAP, max_tid, zero=True)
self._ll_typeid_map = ll_typeid_map
self._tracked_dict.add(llmemory.cast_ptr_to_adr(ll_typeid_map))
i = 0
while i < max_tid:
self._tracked_dict.add(llmemory.cast_ptr_to_adr(ll_typeid_map[i]))
i += 1
self.enumerate_all_roots(SemiSpaceGC._track_heap_root, self)
self._ll_typeid_map = lltype.nullptr(ARRAY_TYPEID_MAP)
self._tracked_dict.delete()
return ll_typeid_map
|