Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r93:2d0f4e06ce3a
Date: 2013-06-11 22:49 +0200
http://bitbucket.org/pypy/stmgc/changeset/2d0f4e06ce3a/

Log:    Fixes. Now I have a clear idea about which thread can change
        exactly which parts of which objects.

diff --git a/c4/doc-objects.txt b/c4/doc-objects.txt
--- a/c4/doc-objects.txt
+++ b/c4/doc-objects.txt
@@ -245,3 +245,28 @@
     make a stub with h_revision = private object | 2
 
     after a CPU write barrier, make the public h_revision to point to the stub
+
+
+
+Change to the flags and h_revision
+----------------------------------
+
+The flags are in `h_tid`.  Changes to this field and `h_revision` must
+not occur uncontrolled:
+
+- private copies: the thread that owns the private copy can change
+freely the `h_tid` and `h_revision` fields.  The other threads must not
+touch them, and must read them carefully.  This concerns only stealing
+threads, on GCFLAG_PRIVATE_FROM_PROTECTED objects.  The flag
+GCFLAG_PRIVATE_FROM_PROTECTED itself is only changed when the owning
+thread has got its collection_lock, and as long as it is set, h_revision
+points to the backup copy.
+
+- protected copies (includes backup copies): any change requires the
+owning thread's collection_lock.  During stealing, other threads
+might add (with the collection_lock) the flags GCFLAG_PUBLIC or
+GCFLAG_PUBLIC_TO_PRIVATE.
+
+- public copies: must be changed carefully: `h_tid` is only modified to
+add GCFLAG_PUBLIC_TO_PRIVATE; and `h_revision` changes are done with
+bool_cas() in a thread-controlled way.
diff --git a/c4/et.c b/c4/et.c
--- a/c4/et.c
+++ b/c4/et.c
@@ -75,9 +75,11 @@
   gcptr P = G;
   revision_t v;
 
+ restart_all:
   if (P->h_tid & GCFLAG_PRIVATE_FROM_PROTECTED)
     {
-    private_from_protected:
+      assert(!(P->h_revision & 1));   /* pointer to the backup copy */
+
       /* check P->h_revision->h_revision: if a pointer, then it means
          the backup copy has been stolen into a public object and then
          modified by some other thread.  Abort. */
@@ -86,15 +88,20 @@
 
       goto add_in_recent_reads_cache;
     }
+  /* else, for the rest of this function, we can assume that P was not
+     a private copy */
 
   if (P->h_tid & GCFLAG_PUBLIC)
     {
       /* follow the chained list of h_revision's as long as they are
-         regular pointers */
-    retry:
+         regular pointers.  We will only find more public objects
+         along this chain.
+      */
+    restart_all_public:
       v = ACCESS_ONCE(P->h_revision);
       if (!(v & 1))  // "is a pointer", i.e.
         {            //      "has a more recent revision"
+        retry:
           if (v & 2)
             goto follow_stub;
 
@@ -114,23 +121,33 @@
                  doing this write occasionally based on a counter in d */
               P_prev->h_revision = v;
               P = (gcptr)v;
-              goto retry;
+              v = ACCESS_ONCE(P->h_revision);
+              if (!(v & 1))  // "is a pointer", i.e. "has a more recent rev"
+                goto retry;
+            }
+
+          /* We reach this point if P != G only.  Check again the
+             read_barrier_cache: if P now hits the cache, just return it
+          */
+          if (FXCACHE_AT(P) == P)
+            {
+              fprintf(stderr, "read_barrier: %p -> %p fxcache\n", G, P);
+              return P;
             }
         }
 
-      /* if we land on a P in read_barrier_cache: just return it */
-      if (FXCACHE_AT(P) == P)
-        {
-          fprintf(stderr, "read_barrier: %p -> %p fxcache\n", G, P);
-          return P;
-        }
-
+      /* If we land on a P with GCFLAG_PUBLIC_TO_PRIVATE, it might be
+         because *we* have an entry in d->public_to_private.  (It might
+         also be someone else.)
+      */
       if (P->h_tid & GCFLAG_PUBLIC_TO_PRIVATE)
         {
           wlog_t *item;
         retry_public_to_private:;
           G2L_FIND(d->public_to_private, P, item, goto no_private_obj);
 
+          /* We have a key in 'public_to_private'.  The value is the
+             corresponding private object. */
           P = item->val;
           assert(!(P->h_tid & GCFLAG_PUBLIC));
           assert(is_private(P));
@@ -138,6 +155,8 @@
           return P;
 
         no_private_obj:
+          /* Key not found.  It might be because there really is none, or
+             because we still have it waiting in 'stolen_objects'. */
           if (d->public_descriptor->stolen_objects.size > 0)
             {
               spinlock_acquire(d->public_descriptor->collection_lock, 'N');
@@ -147,7 +166,8 @@
             }
         }
 
-      if (UNLIKELY(v > d->start_time))   // object too recent?
+      /* The answer is a public object.  Is it too recent? */
+      if (UNLIKELY(v > d->start_time))
         {
           if (v >= LOCKED)
             {
@@ -161,10 +181,17 @@
     }
   else
     {
+      /* Not private and not public: it's a protected object
+       */
       fprintf(stderr, "read_barrier: %p -> %p protected\n", G, P);
+
+      /* The risks are not high, but in parallel it's possible for the
+         object to be stolen by another thread and become public, after
+         which it can be outdated by another commit.  So the following
+         assert can actually fail in that case. */
+      /*assert(P->h_revision & 1);*/
     }
 
- register_in_list_of_read_objects:
   gcptrlist_insert(&d->list_of_read_objects, P);
 
  add_in_recent_reads_cache:
@@ -172,43 +199,32 @@
   return P;
 
  follow_stub:;
+  /* We know that P is a stub object, because only stubs can have
+     an h_revision that is == 2 mod 4.
+  */
   struct tx_public_descriptor *foreign_pd = STUB_THREAD(P);
   if (foreign_pd == d->public_descriptor)
     {
-      /* same thread */
+      /* Same thread: dereference the pointer directly.  It's possible
+         we reach any kind of object, even a public object, in case it
+         was stolen.  So we just repeat the whole procedure. */
       P = (gcptr)(v - 2);
-      assert(!(P->h_tid & GCFLAG_PUBLIC));
-      if (P->h_revision == stm_private_rev_num)
-        {
-          fprintf(stderr, "read_barrier: %p -> %p handle "
-                  "private\n", G, P);
-          return P;
-        }
-      else if (P->h_tid & GCFLAG_PRIVATE_FROM_PROTECTED)
-        {
-          fprintf(stderr, "read_barrier: %p -> %p handle "
-                  "private_from_protected\n", G, P);
-          goto private_from_protected;
-        }
-      else if (FXCACHE_AT(P) == P)
-        {
-          fprintf(stderr, "read_barrier: %p -> %p handle "
-                  "protected fxcache\n", G, P);
-          return P;
-        }
-      else
-        {
-          fprintf(stderr, "read_barrier: %p -> %p handle "
-                  "protected\n", G, P);
-          goto register_in_list_of_read_objects;
-        }
+      fprintf(stderr, "read_barrier: %p -> %p via stub\n  ", G, P);
+
+      if (UNLIKELY((P->h_revision != stm_private_rev_num) &&
+                   (FXCACHE_AT(P) != P)))
+        goto restart_all;
+
+      return P;
     }
   else
     {
       /* stealing */
       fprintf(stderr, "read_barrier: %p -> stealing %p...\n  ", G, P);
       stm_steal_stub(P);
-      goto retry;
+
+      assert(P->h_tid & GCFLAG_PUBLIC);
+      goto restart_all_public;
     }
 }
 
@@ -327,21 +343,12 @@
   return L;
 }
 
-static gcptr LocalizePublic(struct tx_descriptor *d, gcptr R);
-
 static gcptr LocalizeProtected(struct tx_descriptor *d, gcptr P)
 {
   gcptr B;
-  spinlock_acquire(d->public_descriptor->collection_lock, 'L');
-
-  if (P->h_tid & GCFLAG_PUBLIC)
-    {
-      /* became PUBLIC while waiting for the collection_lock */
-      spinlock_release(d->public_descriptor->collection_lock);
-      return LocalizePublic(d, P);
-    }
 
   assert(P->h_revision != stm_private_rev_num);
+  assert(P->h_revision & 1);
   assert(!(P->h_tid & GCFLAG_PUBLIC_TO_PRIVATE));
   assert(!(P->h_tid & GCFLAG_BACKUP_COPY));
   assert(!(P->h_tid & GCFLAG_STUB));
@@ -355,7 +362,6 @@
 
   gcptrlist_insert(&d->private_from_protected, P);
 
-  spinlock_release(d->public_descriptor->collection_lock);
   return P;
 }
 
@@ -406,11 +412,17 @@
   if (is_private(R))
     return R;
 
+  spinlock_acquire(d->public_descriptor->collection_lock, 'L');
+  if (d->public_descriptor->stolen_objects.size != 0)
+    stm_normalize_stolen_objects(d);
+
   if (R->h_tid & GCFLAG_PUBLIC)
     W = LocalizePublic(d, R);
   else
     W = LocalizeProtected(d, R);
 
+  spinlock_release(d->public_descriptor->collection_lock);
+
   fprintf(stderr, "write_barrier: %p -> %p -> %p\n", P, R, W);
 
   return W;
diff --git a/c4/steal.c b/c4/steal.c
--- a/c4/steal.c
+++ b/c4/steal.c
@@ -55,25 +55,60 @@
 
     gcptr L = (gcptr)(v - 2);
 
+    /* L might be a private_from_protected, or just a protected copy.
+       To know which case it is, read GCFLAG_PRIVATE_FROM_PROTECTED.
+    */
     if (L->h_tid & GCFLAG_PRIVATE_FROM_PROTECTED) {
         gcptr B = (gcptr)L->h_revision;     /* the backup copy */
+
+        /* B is now a backup copy, i.e. a protected object, and we own
+           the foreign thread's collection_lock, so we can read/write the
+           flags
+        */
+        assert(B->h_tid & GCFLAG_BACKUP_COPY);
         B->h_tid &= ~GCFLAG_BACKUP_COPY;
 
-        /* add {B: L} in 'public_to_private', but lazily, because we don't
-           want to walk over the feet of the foreign thread */
-        B->h_tid |= GCFLAG_PUBLIC_TO_PRIVATE;
-        gcptrlist_insert2(&foreign_pd->stolen_objects, B, L);
-
+        if (B->h_tid & GCFLAG_PUBLIC_TO_PRIVATE) {
+            /* already stolen */
+        }
+        else {
+            B->h_tid |= GCFLAG_PUBLIC_TO_PRIVATE;
+            /* add {B: L} in 'public_to_private', but lazily, because we
+               don't want to walk over the feet of the foreign thread
+            */
+            gcptrlist_insert2(&foreign_pd->stolen_objects, B, L);
+        }
         fprintf(stderr, "stolen: %p -> %p - - -> %p\n", P, B, L);
-
         L = B;
     }
 
-    /* change L from protected to public */
+    /* Here L is a protected (or backup) copy, and we own the foreign
+       thread's collection_lock, so we can read/write the flags.  Change
+       it from protected to public.
+    */
     L->h_tid |= GCFLAG_PUBLIC;
 
-    smp_wmb();      /* the following update must occur "after" the flag
-                       GCFLAG_PUBLIC was added, for other threads */
+    /* Note that all protected or backup copies have a h_revision that
+       is odd.
+    */
+    assert(L->h_revision & 1);
+
+    /* At this point, the object can only be seen by its owning foreign
+       thread and by us.  No 3rd thread can see it as long as we own
+       the foreign thread's collection_lock.  For the foreign thread,
+       it might suddenly see the GCFLAG_PUBLIC being added to L
+       (but it may not do any change to the flags itself, because
+       it cannot grab its own collection_lock).  L->h_revision is an
+       odd number that is also valid on a public up-to-date object.
+    */
+
+    /* If another thread (the foreign or a 3rd party) does a read
+       barrier from P, it must only reach L if all writes to L are
+       visible; i.e. it must not see P->h_revision => L that still
+       doesn't have the GCFLAG_PUBLIC.  So we need a CPU write
+       barrier here.
+    */
+    smp_wmb();
 
     /* update the original P->h_revision to point directly to L */
     P->h_revision = (revision_t)L;
@@ -92,6 +127,8 @@
         gcptr L = items[i + 1];
 
         assert(L->h_tid & GCFLAG_PRIVATE_FROM_PROTECTED);
+        assert(!(B->h_tid & GCFLAG_BACKUP_COPY));  /* already removed */
+
         g2l_insert(&d->public_to_private, B, L);
     }
     gcptrlist_clear(&d->public_descriptor->stolen_objects);
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to