[pypy-commit] pypy concurrent-marksweep: The first few tests pass :-)
arigo
noreply at buildbot.pypy.org
Fri Oct 7 16:17:33 CEST 2011
Author: Armin Rigo <arigo at tunes.org>
Branch: concurrent-marksweep
Changeset: r47855:2ee9ca05d5df
Date: 2011-10-07 15:54 +0200
http://bitbucket.org/pypy/pypy/changeset/2ee9ca05d5df/
Log: The first few tests pass :-)
diff --git a/pypy/rpython/memory/gc/concurrentms.py b/pypy/rpython/memory/gc/concurrentms.py
--- a/pypy/rpython/memory/gc/concurrentms.py
+++ b/pypy/rpython/memory/gc/concurrentms.py
@@ -1,8 +1,7 @@
import time, sys
from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup, rffi
-from pypy.rpython.lltypesystem.lloperation import llop
from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage
-from pypy.rlib.objectmodel import we_are_translated, specialize
+from pypy.rlib.objectmodel import we_are_translated, running_on_llinterp
from pypy.rlib.debug import ll_assert
from pypy.rlib.rarithmetic import LONG_BIT, r_uint
from pypy.rpython.memory.gc.base import GCBase
@@ -27,10 +26,17 @@
WORD_POWER_2 = {32: 2, 64: 3}[LONG_BIT]
assert 1 << WORD_POWER_2 == WORD
size_of_addr = llmemory.sizeof(llmemory.Address)
-first_gcflag = 1 << (LONG_BIT//2)
-GCFLAG_MARK_TOGGLE = first_gcflag << 0
-GCFLAG_FINALIZATION_ORDERING = first_gcflag << 1
+# XXX assumes little-endian machines for now: the byte at offset 0 in
+# the object is either a mark byte (equal to an odd value), or if the
+# location is free, it is the low byte of a pointer to the next free
+# location (and then it is an even value, by pointer alignment).
+assert sys.byteorder == 'little'
+
+
+MARK_VALUE_1 = 'M' # 77, 0x4D
+MARK_VALUE_2 = 'k' # 107, 0x6B
+GCFLAG_WITH_HASH = 0x01
class MostlyConcurrentMarkSweepGC(GCBase):
@@ -40,10 +46,13 @@
needs_write_barrier = True
prebuilt_gc_objects_are_static_roots = False
malloc_zero_filled = True
- gcflag_extra = GCFLAG_FINALIZATION_ORDERING
+ #gcflag_extra = GCFLAG_FINALIZATION_ORDERING
- HDR = lltype.Struct('header', ('tid', lltype.Signed))
- typeid_is_in_field = 'tid'
+ HDR = lltype.Struct('header', ('mark', lltype.Char), # MARK_VALUE_{1,2}
+ ('flags', lltype.Char),
+ ('typeid16', llgroup.HALFWORD))
+ typeid_is_in_field = 'typeid16'
+ withhash_flag_is_in_field = 'flags', GCFLAG_WITH_HASH
TRANSLATION_PARAMS = {'page_size': 4096,
'small_request_threshold': 35*WORD,
@@ -59,20 +68,27 @@
self.small_request_threshold = small_request_threshold
self.page_size = page_size
self.free_pages = NULL
- length = small_request_threshold // WORD + 1
- self.free_lists = lltype.malloc(rffi.CArray(llmemory.Address),
- length, flavor='raw', zero=True,
- immortal=True)
- self.current_mark = 0
+ self.pagelists_length = small_request_threshold // WORD + 1
+ #
+ def list_of_addresses_per_small_size():
+ return lltype.malloc(rffi.CArray(llmemory.Address),
+ self.pagelists_length, flavor='raw',
+ zero=True, immortal=True)
+ self.nonfree_pages = list_of_addresses_per_small_size()
+ self.collect_pages = list_of_addresses_per_small_size()
+ self.free_lists = list_of_addresses_per_small_size()
+ self.collect_heads = list_of_addresses_per_small_size()
+ self.collect_tails = list_of_addresses_per_small_size()
+ self.current_mark = MARK_VALUE_1
#
# When the mutator thread wants to trigger the next collection,
# it scans its own stack roots and prepares everything, then
- # sets 'collection_running' to True, and releases
+ # sets 'collection_running' to 1, and releases
# 'ready_to_start_lock'. This triggers the collector thread,
# which re-acquires 'ready_to_start_lock' and does its job.
# When done it releases 'finished_lock'. The mutator thread is
- # responsible for resetting 'collection_running' to False.
- self.collection_running = False
+ # responsible for resetting 'collection_running' to 0.
+ self.collection_running = 0
self.ready_to_start_lock = ll_thread.allocate_lock()
self.finished_lock = ll_thread.allocate_lock()
#
@@ -109,11 +125,7 @@
self.collect()
def get_type_id(self, obj):
- tid = self.header(obj).tid
- return llop.extract_ushort(llgroup.HALFWORD, tid)
-
- def combine(self, typeid16, flags):
- return llop.combine_ushort(lltype.Signed, typeid16, flags)
+ return self.header(obj).typeid16
def malloc_fixedsize_clear(self, typeid, size,
needs_finalizer=False, contains_weakptr=False):
@@ -131,7 +143,9 @@
llarena.arena_reset(result, size_of_addr, 0)
llarena.arena_reserve(result, totalsize)
hdr = llmemory.cast_adr_to_ptr(result, lltype.Ptr(self.HDR))
- hdr.tid = self.combine(typeid, self.current_mark)
+ hdr.typeid16 = typeid
+ hdr.mark = self.current_mark
+ hdr.flags = '\x00'
#
obj = result + size_gc_header
return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
@@ -151,16 +165,21 @@
self.allocate_next_arena()
newpage = self.free_pages
self.free_pages = newpage.address[0]
- llarena.arena_reset(newpage, size_of_addr, 0)
+ #
+ # Put the free page in the list 'nonfree_pages[n]'. This is
+ # a linked list chained through the first word of each page.
+ n = (rawtotalsize + WORD - 1) >> WORD_POWER_2
+ newpage.address[0] = self.nonfree_pages[n]
+ self.nonfree_pages[n] = newpage
#
# Initialize the free page to contain objects of the given
# size. This requires setting up all object locations in the
# page, linking them in the free list.
- n = (rawtotalsize + WORD - 1) >> WORD_POWER_2
head = self.free_lists[n]
ll_assert(not head, "_malloc_slowpath: unexpected free_lists[n]")
i = self.page_size - rawtotalsize
- while i >= rawtotalsize:
+ limit = rawtotalsize + raw_malloc_usage(size_of_addr)
+ while i >= limit:
llarena.arena_reserve(newpage + i, size_of_addr)
(newpage + i).address[0] = head
head = newpage + i
@@ -168,14 +187,17 @@
self.free_lists[n] = head
result = head - rawtotalsize
#
- # Done: all object locations are linked, apart from 'result',
- # which is the first object location in the page. Note that
- # if the size is not a multiple of 2, there are a few wasted
- # WORDs, which we place at the start of the page rather than
- # at the end (Hans Boehm, xxx ref).
+ # Done: all object locations are linked, apart from
+ # 'result', which is the first object location in the page.
+ # Note that if the size is not an exact divisor of
+ # 4096-WORD, there are a few wasted WORDs, which we place at
+ # the start of the page rather than at the end (Hans Boehm,
+ # xxx ref).
llarena.arena_reserve(result, totalsize)
hdr = llmemory.cast_adr_to_ptr(result, lltype.Ptr(self.HDR))
- hdr.tid = self.combine(typeid, self.current_mark)
+ hdr.typeid16 = typeid
+ hdr.mark = self.current_mark
+ hdr.flags = '\x00'
#
obj = result + size_gc_header
return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
@@ -196,19 +218,23 @@
def write_barrier(self, newvalue, addr_struct):
- flag = self.header(addr_struct).tid & GCFLAG_MARK_TOGGLE
- if flag != self.current_mark:
+ mark = self.header(addr_struct).mark
+ if mark != self.current_mark:
self.force_scan(addr_struct)
def _init_writebarrier_logic(self):
#
def force_scan(obj):
self.mutex_lock.acquire(True)
- if self.current_mark:
- self.set_mark_flag(obj, GCFLAG_MARK_TOGGLE)
- else:
- self.set_mark_flag(obj, 0)
- self.trace(obj, self._barrier_add_extra, None)
+ if not self.is_marked(obj, self.current_mark):
+ # it is only possible to reach this point if there is
+ # a collection running in collector_mark(), before it
+ # does mutex_lock itself. Check this:
+ ll_assert(self.collection_running == 1,
+ "write barrier: wrong call?")
+ #
+ self.set_mark(obj, self.current_mark)
+ self.trace(obj, self._barrier_add_extra, None)
self.mutex_lock.release()
#
force_scan._dont_inline_ = True
@@ -218,24 +244,35 @@
self.extra_objects_to_mark.append(root.address[0])
+ def wait_for_the_end_of_collection(self):
+ """In the mutator thread: wait for the collection currently
+ running (if any) to finish."""
+ if self.collection_running != 0:
+ self.acquire(self.finished_lock)
+ self.collection_running = 0
+ #
+ # Check invariants
+ ll_assert(not self.extra_objects_to_mark.non_empty(),
+ "objs left behind in extra_objects_to_mark")
+ ll_assert(not self.gray_objects.non_empty(),
+ "objs left behind in gray_objects")
+ #
+ # Grab the results of the last collection: read the collector's
+ # 'collect_heads/collect_tails' and merge them with the mutator's
+ # 'free_lists'.
+ n = 1
+ while n < self.pagelists_length:
+ if self.collect_tails[n] != NULL:
+ self.collect_tails[n].address[0] = self.free_lists[n]
+ self.free_lists[n] = self.collect_heads[n]
+ n += 1
+
+
def collect(self, gen=0):
"""Trigger a complete collection, and wait for it to finish."""
self.trigger_next_collection()
self.wait_for_the_end_of_collection()
- def wait_for_the_end_of_collection(self):
- """In the mutator thread: wait for the collection currently
- running (if any) to finish."""
- if self.collection_running:
- self.acquire(self.finished_lock)
- self.collection_running = False
- #
- # It's possible that an object was added to 'extra_objects_to_mark'
- # by the write barrier but not taken out by the collector thread,
- # because it finished in the meantime. The result is still
- # correct, but we need to clear the list.
- self.extra_objects_to_mark.clear()
-
def trigger_next_collection(self):
"""In the mutator thread: triggers the next collection."""
#
@@ -250,10 +287,19 @@
#
# Invert this global variable, which has the effect that on all
# objects' state go instantly from "marked" to "non marked"
- self.current_mark ^= GCFLAG_MARK_TOGGLE
+ self.current_mark = self.other_mark(self.current_mark)
+ #
+ # Copy a few 'mutator' fields to 'collector' fields:
+ # 'collect_pages' make linked lists of all nonfree pages at the
+ # start of the collection (unlike the 'nonfree_pages' lists, which
+ # the mutator will continue to grow).
+ n = 1
+ while n < self.pagelists_length:
+ self.collect_pages[n] = self.nonfree_pages[n]
+ n += 1
#
# Start the collector thread
- self.collection_running = True
+ self.collection_running = 1
self.ready_to_start_lock.release()
def _add_stack_root(self, root):
@@ -291,23 +337,29 @@
#
# Mark
self.collector_mark()
+ self.collection_running = 2
#
# Sweep
self.collector_sweep()
+ self.finished_lock.release()
+ #
except Exception, e:
print 'Crash!', e.__class__.__name__, e
self._exc_info = sys.exc_info()
- @specialize.arg(2)
+ def other_mark(self, mark):
+ ll_assert(mark == MARK_VALUE_1 or mark == MARK_VALUE_2,
+ "bad mark value")
+ return chr(ord(mark) ^ (ord(MARK_VALUE_1) ^ ord(MARK_VALUE_2)))
+
def is_marked(self, obj, current_mark):
- return (self.header(obj).tid & GCFLAG_MARK_TOGGLE) == current_mark
+ mark = self.header(obj).mark
+ ll_assert(mark == MARK_VALUE_1 or mark == MARK_VALUE_2,
+ "bad mark byte in object")
+ return mark == current_mark
- @specialize.arg(2)
- def set_mark_flag(self, obj, current_mark):
- if current_mark:
- self.header(obj).tid |= GCFLAG_MARK_TOGGLE
- else:
- self.header(obj).tid &= ~GCFLAG_MARK_TOGGLE
+ def set_mark(self, obj, current_mark):
+ self.header(obj).mark = current_mark
def collector_mark(self):
while True:
@@ -315,10 +367,7 @@
# Do marking. The following function call is interrupted
# if the mutator's write barrier adds new objects to
# 'extra_objects_to_mark'.
- if self.current_mark:
- self._collect_mark(GCFLAG_MARK_TOGGLE)
- else:
- self._collect_mark(0)
+ self._collect_mark()
#
# Move the objects from 'extra_objects_to_mark' to
# 'gray_objects'. This requires the mutex lock.
@@ -339,12 +388,12 @@
if not self.gray_objects.non_empty():
return
- @specialize.arg(1)
- def _collect_mark(self, current_mark):
+ def _collect_mark(self):
+ current_mark = self.current_mark
while self.gray_objects.non_empty():
obj = self.gray_objects.pop()
if not self.is_marked(obj, current_mark):
- self.set_mark_flag(obj, current_mark)
+ self.set_mark(obj, current_mark)
self.trace(obj, self._collect_add_pending, None)
#
# Interrupt early if the mutator's write barrier adds stuff
@@ -360,4 +409,49 @@
self.gray_objects.append(root.address[0])
def collector_sweep(self):
- xxx
+ n = 1
+ while n < self.pagelists_length:
+ self._collect_sweep_pages(n)
+ n += 1
+
+ def _collect_sweep_pages(self, n):
+ # sweep all pages from the linked list starting at 'page',
+ # containing objects of fixed size 'object_size'.
+ page = self.collect_pages[n]
+ object_size = n << WORD_POWER_2
+ linked_list = NULL
+ first_freed_object = NULL
+ nonmarked = self.other_mark(self.current_mark)
+ while page != llmemory.NULL:
+ i = self.page_size - object_size
+ limit = raw_malloc_usage(size_of_addr)
+ while i >= limit:
+ hdr = page + i
+ #
+ if maybe_read_mark_byte(hdr) == nonmarked:
+ # the location contains really an object (and is not just
+ # part of a linked list of free locations), and moreover
+ # the object is still not marked. Free it by inserting
+ # it into the linked list.
+ llarena.arena_reset(hdr, object_size, 0)
+ llarena.arena_reserve(hdr, size_of_addr)
+ hdr.address[0] = linked_list
+ linked_list = hdr
+ if first_freed_object == NULL:
+ first_freed_object = hdr
+ # XXX detect when the whole page is freed again
+ #
+ i -= object_size
+ #
+ page = page.address[0]
+ #
+ self.collect_heads[n] = linked_list
+ self.collect_tails[n] = first_freed_object
+
+
+def maybe_read_mark_byte(addr):
+ "NOT_RPYTHON"
+ try:
+ return addr.ptr.mark
+ except AttributeError:
+ return '\x00'
More information about the pypy-commit
mailing list