[pypy-commit] pypy stm-gc: Read and write barriers.

arigo noreply at buildbot.pypy.org
Thu Feb 2 16:46:39 CET 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: stm-gc
Changeset: r52033:28f4cfffe4db
Date: 2012-02-02 16:46 +0100
http://bitbucket.org/pypy/pypy/changeset/28f4cfffe4db/

Log:	Read and write barriers.

diff --git a/pypy/rpython/memory/gc/stmgc.py b/pypy/rpython/memory/gc/stmgc.py
--- a/pypy/rpython/memory/gc/stmgc.py
+++ b/pypy/rpython/memory/gc/stmgc.py
@@ -3,6 +3,7 @@
 from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage
 from pypy.rpython.memory.gc.base import GCBase
 from pypy.rlib.rarithmetic import LONG_BIT
+from pypy.rlib.debug import ll_assert
 
 
 WORD = LONG_BIT // 8
@@ -53,7 +54,9 @@
         GCBase.__init__(self, config, **kwds)
         self.stm_operations = stm_operations
         self.max_nursery_size = max_nursery_size
-
+        #
+        self.declare_readers()
+        self.declare_write_barrier()
 
     def setup(self):
         """Called at run-time to initialize the GC."""
@@ -71,7 +74,7 @@
 
     def setup_thread(self, in_main_thread):
         tls = lltype.malloc(self.GCTLS, flavor='raw')
-        self.stm_operations.set_tls(llmemory.cast_ptr_to_adr(tls))
+        self.stm_operations.set_tls(self, llmemory.cast_ptr_to_adr(tls))
         tls.nursery_start = self._alloc_nursery()
         tls.nursery_size  = self.max_nursery_size
         tls.nursery_free  = tls.nursery_start
@@ -89,7 +92,7 @@
 
     def teardown_thread(self):
         tls = self.get_tls()
-        self.stm_operations.set_tls(NULL)
+        self.stm_operations.set_tls(self, NULL)
         self._free_nursery(tls.nursery_start)
         lltype.free(tls, flavor='raw')
 
@@ -142,6 +145,17 @@
         return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
 
+    def _malloc_local_raw(self, size):
+        # for _stm_write_barrier_global(): a version of malloc that does
+        # no initialization of the malloc'ed object
+        size_gc_header = self.gcheaderbuilder.size_gc_header
+        totalsize = size_gc_header + size
+        result = self.allocate_bump_pointer(totalsize)
+        llarena.arena_reserve(result, totalsize)
+        obj = result + size_gc_header
+        return obj
+
+
     @always_inline
     def combine(self, typeid16, flags):
         return llop.combine_ushort(lltype.Signed, typeid16, flags)
@@ -150,3 +164,95 @@
     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 declare_readers(self):
+        # Reading functions.  Defined here to avoid the extra burden of
+        # passing 'self' explicitly.
+        stm_operations = self.stm_operations
+        #
+        @always_inline
+        def read_signed(obj, offset):
+            if self.header(obj).tid & GCFLAG_GLOBAL == 0:
+                return (obj + offset).signed[0]    # local obj: read directly
+            else:
+                return _read_word_global(obj, offset)   # else: call a helper
+        self.read_signed = read_signed
+        #
+        @dont_inline
+        def _read_word_global(obj, offset):
+            hdr = self.header(obj)
+            if hdr.tid & GCFLAG_WAS_COPIED != 0:
+                #
+                # Look up in the thread-local dictionary.
+                localobj = stm_operations.tldict_lookup(obj)
+                if localobj:
+                    ll_assert(self.header(localobj).tid & GCFLAG_GLOBAL == 0,
+                              "stm_read: tldict_lookup() -> GLOBAL obj")
+                    return (localobj + offset).signed[0]
+            #
+            return stm_operations.stm_read_word(obj, offset)
+
+
+    def declare_write_barrier(self):
+        # Write barrier.  Defined here to avoid the extra burden of
+        # passing 'self' explicitly.
+        stm_operations = self.stm_operations
+        #
+        @always_inline
+        def write_barrier(obj):
+            if self.header(obj).tid & GCFLAG_GLOBAL != 0:
+                obj = _stm_write_barrier_global(obj)
+            return obj
+        self.write_barrier = write_barrier
+        #
+        @dont_inline
+        def _stm_write_barrier_global(obj):
+            # we need to find of make a local copy
+            hdr = self.header(obj)
+            if hdr.tid & GCFLAG_WAS_COPIED == 0:
+                # in this case, we are sure that we don't have a copy
+                hdr.tid |= GCFLAG_WAS_COPIED
+                # ^^^ non-protected write, but concurrent writes should
+                #     have the same effect, so fine
+            else:
+                # in this case, we need to check first
+                localobj = stm_operations.tldict_lookup(obj)
+                if localobj:
+                    hdr = self.header(localobj)
+                    ll_assert(hdr.tid & GCFLAG_GLOBAL == 0,
+                              "stm_write: tldict_lookup() -> GLOBAL obj")
+                    ll_assert(hdr.tid & GCFLAG_WAS_COPIED != 0,
+                              "stm_write: tldict_lookup() -> non-COPIED obj")
+                    return localobj
+            #
+            # Here, we need to really make a local copy
+            size = self.get_size(obj)
+            try:
+                localobj = self._malloc_local_raw(size)
+            except MemoryError:
+                # XXX
+                fatalerror("MemoryError in _stm_write_barrier_global -- sorry")
+                return llmemory.NULL
+            #
+            # Initialize the copy by doing an stm raw copy of the bytes
+            stm_operations.stm_copy_transactional_to_raw(obj, localobj, size)
+            #
+            # The raw copy done above includes all header fields.
+            # Check at least the gc flags of the copy.
+            hdr = self.header(obj)
+            localhdr = self.header(localobj)
+            GCFLAGS = (GCFLAG_GLOBAL | GCFLAG_WAS_COPIED)
+            ll_assert(hdr.tid & GCFLAGS == GCFLAGS,
+                      "stm_write: bogus flags on source object")
+            ll_assert(localhdr.tid & GCFLAGS == GCFLAGS,
+                      "stm_write: flags not copied!")
+            #
+            # Remove the GCFLAG_GLOBAL from the copy
+            localhdr.tid &= ~GCFLAG_GLOBAL
+            #
+            # Register the object as a valid copy
+            stm_operations.tldict_add(obj, localobj)
+            #
+            return localobj
diff --git a/pypy/rpython/memory/gc/test/test_stmgc.py b/pypy/rpython/memory/gc/test/test_stmgc.py
--- a/pypy/rpython/memory/gc/test/test_stmgc.py
+++ b/pypy/rpython/memory/gc/test/test_stmgc.py
@@ -1,15 +1,71 @@
 from pypy.rpython.lltypesystem import lltype, llmemory
 from pypy.rpython.memory.gc.stmgc import StmGC
-from pypy.rpython.memory.gc.stmgc import GCFLAG_GLOBAL
+from pypy.rpython.memory.gc.stmgc import GCFLAG_GLOBAL, GCFLAG_WAS_COPIED
+
+
+S = lltype.GcStruct('S', ('a', lltype.Signed), ('b', lltype.Signed))
+ofs_a = llmemory.offsetof(S, 'a')
 
 
 class FakeStmOperations:
-    def set_tls(self, tls):
+    threadnum = 0          # 0 = main thread; 1,2,3... = transactional threads
+
+    def set_tls(self, gc, tls):
         assert lltype.typeOf(tls) == llmemory.Address
-        self._tls = tls
+        if self.threadnum == 0:
+            assert not hasattr(self, '_tls_dict')
+            assert not hasattr(self, '_gc')
+            self._tls_dict = {0: tls}
+            self._tldicts = {0: {}}
+            self._gc = gc
+            self._transactional_copies = []
+        else:
+            assert self._gc is gc
+            self._tls_dict[self.threadnum] = tls
+            self._tldicts[self.threadnum] = {}
 
     def get_tls(self):
-        return self._tls
+        return self._tls_dict[self.threadnum]
+
+    def tldict_lookup(self, obj):
+        assert lltype.typeOf(obj) == llmemory.Address
+        assert obj
+        tldict = self._tldicts[self.threadnum]
+        return tldict.get(obj, llmemory.NULL)
+
+    def tldict_add(self, obj, localobj):
+        assert lltype.typeOf(obj) == llmemory.Address
+        tldict = self._tldicts[self.threadnum]
+        assert obj not in tldict
+        tldict[obj] = localobj
+
+    class stm_read_word:
+        def __init__(self, obj, offset):
+            self.obj = obj
+            self.offset = offset
+        def __repr__(self):
+            return 'stm_read_word(%r, %r)' % (self.obj, self.offset)
+        def __eq__(self, other):
+            return (type(self) is type(other) and
+                    self.__dict__ == other.__dict__)
+        def __ne__(self, other):
+            return not (self == other)
+
+    def stm_copy_transactional_to_raw(self, srcobj, dstobj, size):
+        sizehdr = self._gc.gcheaderbuilder.size_gc_header
+        srchdr = srcobj - sizehdr
+        dsthdr = dstobj - sizehdr
+        llmemory.raw_memcopy(srchdr, dsthdr, sizehdr)
+        llmemory.raw_memcopy(srcobj, dstobj, size)
+        self._transactional_copies.append((srcobj, dstobj))
+
+
+def fake_get_size(obj):
+    TYPE = obj.ptr._TYPE.TO
+    if isinstance(TYPE, lltype.GcStruct):
+        return llmemory.sizeof(TYPE)
+    else:
+        assert 0
 
 
 class TestBasic:
@@ -21,8 +77,27 @@
         self.gc = self.GCClass(config, FakeStmOperations(),
                                translated_to_c=False)
         self.gc.DEBUG = True
+        self.gc.get_size = fake_get_size
         self.gc.setup()
 
+    def teardown_method(self, meth):
+        for key in self.gc.stm_operations._tls_dict.keys():
+            if key != 0:
+                self.gc.stm_operations.threadnum = key
+                self.gc.teardown_thread()
+
+    # ----------
+    # test helpers
+    def malloc(self, STRUCT):
+        gcref = self.gc.malloc_fixedsize_clear(123, llmemory.sizeof(STRUCT))
+        realobj = lltype.cast_opaque_ptr(lltype.Ptr(STRUCT), gcref)
+        addr = llmemory.cast_ptr_to_adr(realobj)
+        return realobj, addr
+    def select_thread(self, threadnum):
+        self.gc.stm_operations.threadnum = threadnum
+        if threadnum not in self.gc.stm_operations._tls_dict:
+            self.gc.setup_thread(False)
+
     def test_gc_creation_works(self):
         pass
 
@@ -36,7 +111,6 @@
         assert a6 - a5 == 5
 
     def test_malloc_fixedsize_clear(self):
-        S = lltype.GcStruct('S', ('a', lltype.Signed), ('b', lltype.Signed))
         gcref = self.gc.malloc_fixedsize_clear(123, llmemory.sizeof(S))
         s = lltype.cast_opaque_ptr(lltype.Ptr(S), gcref)
         assert s.a == 0
@@ -45,13 +119,77 @@
         assert gcref2 != gcref
 
     def test_malloc_main_vs_thread(self):
-        S = lltype.GcStruct('S', ('a', lltype.Signed), ('b', lltype.Signed))
         gcref = self.gc.malloc_fixedsize_clear(123, llmemory.sizeof(S))
         obj = llmemory.cast_ptr_to_adr(gcref)
-        assert (self.gc.header(obj).tid & GCFLAG_GLOBAL) != 0
+        assert self.gc.header(obj).tid & GCFLAG_GLOBAL != 0
         #
-        self.gc.setup_thread(False)
+        self.select_thread(1)
         gcref = self.gc.malloc_fixedsize_clear(123, llmemory.sizeof(S))
         obj = llmemory.cast_ptr_to_adr(gcref)
-        assert (self.gc.header(obj).tid & GCFLAG_GLOBAL) == 0
-        self.gc.teardown_thread()
+        assert self.gc.header(obj).tid & GCFLAG_GLOBAL == 0
+
+    def test_reader_direct(self):
+        s, s_adr = self.malloc(S)
+        assert self.gc.header(s_adr).tid & GCFLAG_GLOBAL != 0
+        s.a = 42
+        value = self.gc.read_signed(s_adr, ofs_a)
+        assert value == FakeStmOperations.stm_read_word(s_adr, ofs_a)
+        #
+        self.select_thread(1)
+        s, s_adr = self.malloc(S)
+        assert self.gc.header(s_adr).tid & GCFLAG_GLOBAL == 0
+        self.gc.header(s_adr).tid |= GCFLAG_WAS_COPIED   # should be ignored
+        s.a = 42
+        value = self.gc.read_signed(s_adr, ofs_a)
+        assert value == 42
+
+    def test_reader_through_dict(self):
+        s, s_adr = self.malloc(S)
+        s.a = 42
+        #
+        self.select_thread(1)
+        t, t_adr = self.malloc(S)
+        t.a = 84
+        #
+        self.gc.header(s_adr).tid |= GCFLAG_WAS_COPIED
+        self.gc.stm_operations._tldicts[1][s_adr] = t_adr
+        #
+        value = self.gc.read_signed(s_adr, ofs_a)
+        assert value == 84
+
+    def test_write_barrier_exists(self):
+        self.select_thread(1)
+        t, t_adr = self.malloc(S)
+        obj = self.gc.write_barrier(t_adr)     # local object
+        assert obj == t_adr
+        #
+        self.select_thread(0)
+        s, s_adr = self.malloc(S)
+        #
+        self.select_thread(1)
+        self.gc.header(s_adr).tid |= GCFLAG_WAS_COPIED
+        self.gc.header(t_adr).tid |= GCFLAG_WAS_COPIED
+        self.gc.stm_operations._tldicts[1][s_adr] = t_adr
+        obj = self.gc.write_barrier(s_adr)     # global copied object
+        assert obj == t_adr
+        assert self.gc.stm_operations._transactional_copies == []
+
+    def test_write_barrier_new(self):
+        self.select_thread(0)
+        s, s_adr = self.malloc(S)
+        s.a = 12
+        s.b = 34
+        #
+        self.select_thread(1)
+        t_adr = self.gc.write_barrier(s_adr) # global object, not copied so far
+        assert t_adr != s_adr
+        t = t_adr.ptr
+        assert t.a == 12
+        assert t.b == 34
+        assert self.gc.stm_operations._transactional_copies == [(s_adr, t_adr)]
+        #
+        u_adr = self.gc.write_barrier(s_adr)  # again
+        assert u_adr == t_adr
+        #
+        u_adr = self.gc.write_barrier(u_adr)  # local object
+        assert u_adr == t_adr


More information about the pypy-commit mailing list