[pypy-commit] stmgc default: in-progress

arigo noreply at buildbot.pypy.org
Sat Jun 8 15:17:01 CEST 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r78:98c0b1dadf35
Date: 2013-06-08 15:14 +0200
http://bitbucket.org/pypy/stmgc/changeset/98c0b1dadf35/

Log:	in-progress

diff --git a/c4/doc-objects.txt b/c4/doc-objects.txt
--- a/c4/doc-objects.txt
+++ b/c4/doc-objects.txt
@@ -1,3 +1,22 @@
+
+Design goal
+-----------
+
+stm_read_barrier(P) -> P: the read barrier (containing a call in the
+slow path) can be applied on a pointer to an object, and returns a
+possibly different pointer.  Afterwards, any reads from the object can
+be done normally (using the returned pointer).
+
+stm_write_barrier(P) -> P: the same for writes (actually read/write mode).
+
+The returned pointers are valid until a potential transaction break ---
+with the exception that the result of stm_read_barrier() will be
+invalidated by a stm_write_barrier() done on the same object.
+
+This means we must not modify an object in-place from thread A when
+thread B might be reading from it!  It is the basis for the design
+outlined in the sequel, in which "protected" objects are seen by only
+one thread, whereas "public" objects are seen by all threads.
 
 
 
@@ -8,29 +27,19 @@
 
   Private freshly created
              \                Private, with backup
-              \                ^  .          |  ^
-               \              /  .    commit |  |
-         commit \     modify /  .            |  |
-                 \          /  . commit      |  | modify
-                  V        /  V              |  |
-           Protected, no backup              V  |
+              \                ^           |  ^
+               \              /     commit |  |
+         commit \     modify /             |  |
+                 \          /              |  | modify
+                  V        /               |  |
+           Protected, no backup            V  |
                 ^    ^              Protected, with backup
                /     |       gc       |
        commit /      `----------------'
              /
             /
-    Private copy of               (the dotted arrow is followed if the
-    a public obj                   protected backup copy was stolen)
-
-
-
-     Protected backup copy
-                  \
-                   \
-           stealing \         commit of newer version
-                     \         ,-----------------.
-                      V        |                 V
-             Up-to-date public copy       Outdated public copy
+    Private copy of
+    a public obj
 
 
 
@@ -45,6 +54,7 @@
 Protected objects:
 - converted from fresh private obj                     (old PRN)
 - converted from a private obj with backup           ptr to backup
+- converted from a private obj from public                GT
 - backup copy of a private obj                    original h_revision
 - backup copy still attached to a protected               GT
 - original obj after GC killed the backup                 GT
@@ -73,13 +83,15 @@
 - the PRN (private revision number): odd, negative, changes for every
   transaction that commits
 
-- dict active_backup_copies = {private converted from protected: backup copy}
+- list active_backup_copies =
+  [(private converted from protected, backup copy)]
 
 - dict public_to_private = {public obj: private copy}
 
 - list read_set containing the objects in the read set, with possibly
   some duplicates (but hopefully not too many)
 
+- list stolen_objects = [(priv/prot object, public copy)]
 
 
 Kind of object copy            distinguishing feature
@@ -154,6 +166,8 @@
     update the original P->h_revision to point directly to the new
     public copy
 
+    add (P, new public copy) to stolen_objects
+
 
 
 Write barrier
diff --git a/c4/doc-stmgc.txt b/c4/doc-stmgc.txt
--- a/c4/doc-stmgc.txt
+++ b/c4/doc-stmgc.txt
@@ -304,3 +304,16 @@
 revision numbers of all threads, and theoretically compact each interval
 of numbers down to only one number, but still keep one active revision
 number per thread.
+
+
+Stealing
+--------
+
+This is done by the *stealing thread* in order to gain access to an
+object that is protected by the *foreign thread*.  Stealing is triggered
+when we, the stealing thread, follow a "handle" created by a foreign
+thread.  The handle has a reference to the normal protected/private
+object.  The process depends on the exact state of the protected/private
+copy.  As a general rule, we may carefully read, but not write, to the
+foreign copies during stealing.
+
diff --git a/c4/et.c b/c4/et.c
--- a/c4/et.c
+++ b/c4/et.c
@@ -13,11 +13,6 @@
    a transaction inevitable: we then add 1 to it. */
 static revision_t global_cur_time = 2;
 
-/* 'next_locked_value' is incremented by two for every thread that starts.
-   XXX it should be fixed at some point because right now the process will
-   die if we start more than 0x7fff threads. */
-static revision_t next_locked_value = (LOCKED + 1) | 1;
-
 /* a negative odd number that identifies the currently running
    transaction within the thread. */
 __thread revision_t stm_private_rev_num;
@@ -85,6 +80,11 @@
 }
 #endif
 
+static void steal(gcptr P)
+{
+  abort();
+}
+
 gcptr stm_DirectReadBarrier(gcptr G)
 {
   struct tx_descriptor *d = thread_descriptor;
@@ -165,9 +165,9 @@
   return P;
 
  old_to_young:;
-  revision_t target_lock;
-  target_lock = *(revision_t *)(v & ~(HANDLE_BLOCK_SIZE-1));
-  if (target_lock == d->my_lock)
+  revision_t target_descriptor_index;
+  target_descriptor_index = *(revision_t *)(v & ~(HANDLE_BLOCK_SIZE-1));
+  if (target_descriptor_index == d->descriptor_index)
     {
       P = (gcptr)(*(revision_t *)(v - 2));
       assert(!(P->h_tid & GCFLAG_PUBLIC));
@@ -194,6 +194,7 @@
     {
       /* stealing */
       fprintf(stderr, "read_barrier: %p -> stealing %p...", G, (gcptr)v);
+      steal(P);
       abort();
     }
 }
@@ -577,7 +578,7 @@
           "!!!!!!!!!!!!!!!!!!!!!  [%lx] abort %d\n"
           "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
           "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
-          "\n", (long)d->my_lock, num);
+          "\n", (long)d->descriptor_index, num);
   if (num != ABRT_MANUAL && d->max_aborts >= 0 && !d->max_aborts--)
     {
       fprintf(stderr, "unexpected abort!\n");
@@ -773,7 +774,7 @@
       handle_block = (revision_t *)
         ((((intptr_t)handle_block) + HANDLE_BLOCK_SIZE-1)
          & ~(HANDLE_BLOCK_SIZE-1));
-      handle_block[0] = d->my_lock;
+      handle_block[0] = d->descriptor_index;
       handle_block[1] = v;
 
       revision_t w = ((revision_t)(handle_block + 1)) + 2;
@@ -947,7 +948,7 @@
                 (XXX statically we should know when we're outside
                 a transaction) */
 
-  fprintf(stderr, "[%lx] inevitable: %s\n", (long)d->my_lock, why);
+  fprintf(stderr, "[%lx] inevitable: %s\n", (long)d->descriptor_index, why);
 
   cur_time = acquire_inev_mutex_and_mark_global_cur_time();
   if (d->start_time != cur_time)
@@ -1071,6 +1072,10 @@
 
 /************************************************************/
 
+struct tx_descriptor *stm_descriptor_array[MAX_THREADS] = {0};
+static revision_t descriptor_array_next = 0;
+static revision_t descriptor_array_lock = 0;
+
 int DescriptorInit(void)
 {
   if (GCFLAG_PREBUILT != PREBUILT_FLAGS)
@@ -1082,33 +1087,39 @@
 
   if (thread_descriptor == NULL)
     {
+      revision_t i;
       struct tx_descriptor *d = stm_malloc(sizeof(struct tx_descriptor));
       memset(d, 0, sizeof(struct tx_descriptor));
+      spinlock_acquire(descriptor_array_lock, 1);
 
-      /* initialize 'my_lock' to be a unique odd number > LOCKED */
-      while (1)
+      i = descriptor_array_next;
+      while (stm_descriptor_array[i] != NULL)
         {
-          d->my_lock = ACCESS_ONCE(next_locked_value);
-          if (d->my_lock > INTPTR_MAX - 2)
+          i++;
+          if (i == MAX_THREADS)
+            i = 0;
+          if (i == descriptor_array_next)
             {
-              /* XXX fix this limitation */
-              fprintf(stderr, "XXX error: too many threads ever created "
+              fprintf(stderr, "error: too many threads at the same time "
                               "in this process");
               abort();
             }
-          if (bool_cas(&next_locked_value, d->my_lock, d->my_lock + 2))
-            break;
         }
+      descriptor_array_next = i;
+      stm_descriptor_array[i] = d;
+      d->descriptor_index = i;
+      d->my_lock = LOCKED + 2 * i;
       assert(d->my_lock & 1);
-      assert(d->my_lock > LOCKED);
+      assert(d->my_lock >= LOCKED);
       stm_private_rev_num = -1;
       d->private_revision_ref = &stm_private_rev_num;
       d->max_aborts = -1;
       thread_descriptor = d;
 
       fprintf(stderr, "[%lx] pthread %lx starting\n",
-              (long)d->my_lock, (long)pthread_self());
+              (long)d->descriptor_index, (long)pthread_self());
 
+      spinlock_release(descriptor_array_lock);
       return 1;
     }
   else
@@ -1121,6 +1132,7 @@
     assert(d != NULL);
     assert(d->active == 0);
 
+    stm_descriptor_array[d->descriptor_index] = NULL;
     thread_descriptor = NULL;
 
     g2l_delete(&d->public_to_private);
@@ -1142,7 +1154,7 @@
         num_spinloops += d->num_spinloops[i];
 
     p += sprintf(p, "[%lx] finishing: %d commits, %d aborts ",
-                 (long)d->my_lock,
+                 (long)d->descriptor_index,
                  d->num_commits,
                  num_aborts);
 
diff --git a/c4/et.h b/c4/et.h
--- a/c4/et.h
+++ b/c4/et.h
@@ -11,7 +11,8 @@
 #define _SRCSTM_ET_H
 
 
-#define LOCKED  ((INTPTR_MAX - 0xffff) | 1)
+#define MAX_THREADS         1024
+#define LOCKED              (INTPTR_MAX - 2*(MAX_THREADS-1))
 
 #define WORD                sizeof(gcptr)
 #define HANDLE_BLOCK_SIZE   (2 * WORD)
@@ -103,6 +104,7 @@
 struct tx_descriptor {
   jmp_buf *setjmp_buf;
   revision_t start_time;
+  revision_t descriptor_index;
   revision_t my_lock;
   revision_t collection_lock;
   gcptr *shadowstack;
@@ -127,11 +129,11 @@
   long long longest_abort_info_time;
   struct FXCache recent_reads_cache;
   revision_t *private_revision_ref;
-  struct tx_descriptor *tx_next, *tx_prev;   /* a doubly linked list */
 };
 
 extern __thread struct tx_descriptor *thread_descriptor;
 extern __thread revision_t stm_private_rev_num;
+extern struct tx_descriptor *stm_descriptor_array[];
 
 /************************************************************/
 
diff --git a/c4/test/support.py b/c4/test/support.py
--- a/c4/test/support.py
+++ b/c4/test/support.py
@@ -84,7 +84,7 @@
     gcptr pseudoprebuilt(size_t size, int tid);
     revision_t get_private_rev_num(void);
     revision_t get_start_time(void);
-    revision_t get_my_lock(void);
+    revision_t get_descriptor_index(void);
 
     //gcptr *addr_of_thread_local(void);
     //int in_nursery(gcptr);
@@ -93,7 +93,6 @@
     /* some constants normally private that are useful in the tests */
     #define WORD                     ...
     #define GC_PAGE_SIZE             ...
-    #define LOCKED                   ...
     #define HANDLE_BLOCK_SIZE        ...
     #define GCFLAG_OLD               ...
     #define GCFLAG_VISITED           ...
@@ -207,9 +206,9 @@
         return thread_descriptor->start_time;
     }
 
-    revision_t get_my_lock(void)
+    revision_t get_descriptor_index(void)
     {
-        return thread_descriptor->my_lock;
+        return thread_descriptor->descriptor_index;
     }
 
     /*gcptr *addr_of_thread_local(void)
@@ -551,7 +550,7 @@
 def decode_handle(r):
     assert (r & 3) == 2
     p = r & ~(lib.HANDLE_BLOCK_SIZE-1)
-    my_lock = ffi.cast("revision_t *", p)[0]
-    assert my_lock >= lib.LOCKED
+    dindex = ffi.cast("revision_t *", p)[0]
+    assert 0 <= dindex < 20
     ptr = ffi.cast("gcptr *", r - 2)[0]
-    return ptr, my_lock
+    return ptr, dindex
diff --git a/c4/test/test_et.py b/c4/test/test_et.py
--- a/c4/test/test_et.py
+++ b/c4/test/test_et.py
@@ -138,7 +138,7 @@
     lib.stm_begin_inevitable_transaction()
     assert classify(p) == "public"
     assert classify(p2) == "protected"
-    assert decode_handle(p.h_revision) == (p2, lib.get_my_lock())
+    assert decode_handle(p.h_revision) == (p2, lib.get_descriptor_index())
     assert lib.rawgetlong(p, 0) == 28971289
     assert lib.rawgetlong(p2, 0) == 1289222
 
@@ -224,14 +224,36 @@
     assert p4 == p2
     assert list_of_read_objects() == [p2]
 
-def test_stealing_protected_without_backup():
+def test_stealing():
     p = palloc(HDR + WORD)
+    plist = [p]
     def f1(r):
-        lib.setlong(p, 0, 2782172)
+        assert (p.h_tid & GCFLAG_PUBLIC_TO_PRIVATE) == 0
+        p1 = lib.stm_write_barrier(p)   # private copy
+        assert p1 != p
+        assert classify(p) == "public"
+        assert classify(p1) == "private"
+        assert p.h_tid & GCFLAG_PUBLIC_TO_PRIVATE
+        lib.rawsetlong(p1, 0, 2782172)
         lib.stm_commit_transaction()
         lib.stm_begin_inevitable_transaction()
+        assert classify(p) == "public"
+        assert classify(p1) == "protected"
+        plist.append(p1)
+        # now p's most recent revision is protected
+        assert p.h_revision % 4 == 2    # a handle
         r.set(2)
+        r.wait(3)
+        assert lib.list_stolen_objects() == plist[-2:]
+        p2 = lib.stm_read_barrier(p1)
+        assert p2 == plist[-1]
     def f2(r):
         r.wait(2)
-        assert lib.getlong(p, 0) == 2782172
+        p2 = lib.stm_read_barrier(p)    # steals
+        assert lib.rawgetlong(p2, 0) == 2782172
+        assert p.h_revision == int(ffi.cast("revision_t", p2))
+        assert p2 == lib.stm_read_barrier(p)
+        assert p2 not in plist
+        plist.append(p2)
+        r.set(3)
     run_parallel(f1, f2)


More information about the pypy-commit mailing list