Allow multiple C finalizers to be attached to a Weak#
authorTakano Akio <aljee@hyper.cx>
Thu, 18 Apr 2013 09:30:23 +0000 (18:30 +0900)
committerIan Lynagh <ian@well-typed.com>
Sat, 15 Jun 2013 15:23:09 +0000 (16:23 +0100)
The commit replaces mkWeakForeignEnv# with addCFinalizerToWeak#.
This new primop mutates an existing Weak# object and adds a new
C finalizer to it.

This change removes an invariant in MarkWeak.c, namely that the relative
order of Weak# objects in the list needs to be preserved across GC. This
makes it easier to split the list into per-generation structures.

The patch also removes a race condition between two threads calling
finalizeWeak# on the same WEAK object at that same time.

compiler/prelude/primops.txt.pp
includes/rts/storage/Closures.h
includes/stg/MiscClosures.h
rts/Linker.c
rts/PrimOps.cmm
rts/StgMiscClosures.cmm
rts/Weak.c
rts/Weak.h
rts/sm/Compact.c
rts/sm/MarkWeak.c
utils/deriveConstants/DeriveConstants.hs

index 4547281..7203c11 100644 (file)
@@ -1871,8 +1871,15 @@ primop  MkWeakNoFinalizerOp "mkWeakNoFinalizer#" GenPrimOp
    has_side_effects = True
    out_of_line      = True
 
-primop  MkWeakForeignEnvOp "mkWeakForeignEnv#" GenPrimOp
-   o -> b -> Addr# -> Addr# -> Int# -> Addr# -> State# RealWorld -> (# State# RealWorld, Weak# b #)
+primop  AddCFinalizerToWeakOp "addCFinalizerToWeak#" GenPrimOp
+   Addr# -> Addr# -> Int# -> Addr# -> Weak# b
+          -> State# RealWorld -> (# State# RealWorld, Int# #)
+   { {\tt addCFinalizerToWeak# fptr ptr flag eptr w} attaches a C
+     function pointer {\tt fptr} to a weak pointer {\tt w} as a finalizer. If
+     {\tt flag} is zero, {\tt fptr} will be called with one argument,
+     {\tt ptr}. Otherwise, it will be called with two arguments,
+     {\tt eptr} and {\tt ptr}. {\tt addCFinalizerToWeak#} returns
+     1 on success, or 0 if {\tt w} is already dead. }
    with
    has_side_effects = True
    out_of_line      = True
index 1eef182..09e7021 100644 (file)
@@ -191,17 +191,21 @@ typedef struct _StgStableName {
 
 typedef struct _StgWeak {      /* Weak v */
   StgHeader header;
-  StgClosure *cfinalizer;
+  StgClosure *cfinalizers;
   StgClosure *key;
   StgClosure *value;           /* v */
   StgClosure *finalizer;
   struct _StgWeak *link;
 } StgWeak;
 
-typedef struct _StgDeadWeak {  /* Weak v */
+typedef struct _StgCFinalizerList {
   StgHeader header;
-  struct _StgWeak *link;
-} StgDeadWeak;
+  StgClosure *link;
+  void (*fptr)(void);
+  void *ptr;
+  void *eptr;
+  StgWord flag; /* has environment (0 or 1) */
+} StgCFinalizerList;
 
 /* Byte code objects.  These are fixed size objects with pointers to
  * four arrays, designed so that a BCO can be easily "re-linked" to
index b9b4f23..db0a32e 100644 (file)
@@ -412,7 +412,7 @@ RTS_FUN_DECL(stg_threadStatuszh);
 RTS_FUN_DECL(stg_mkWeakzh);
 RTS_FUN_DECL(stg_mkWeakNoFinalizzerzh);
 RTS_FUN_DECL(stg_mkWeakForeignzh);
-RTS_FUN_DECL(stg_mkWeakForeignEnvzh);
+RTS_FUN_DECL(stg_addCFinalizzerToWeakzh);
 RTS_FUN_DECL(stg_finalizzeWeakzh);
 RTS_FUN_DECL(stg_deRefWeakzh);
 
index 47eb6b0..43edde2 100644 (file)
@@ -321,7 +321,7 @@ typedef struct _RtsSymbolVal {
 
 #define Maybe_Stable_Names      SymI_HasProto(stg_mkWeakzh)                     \
                                 SymI_HasProto(stg_mkWeakNoFinalizzerzh)         \
-                                SymI_HasProto(stg_mkWeakForeignEnvzh)           \
+                                SymI_HasProto(stg_addCFinalizzerToWeakzh)       \
                                 SymI_HasProto(stg_makeStableNamezh)             \
                                 SymI_HasProto(stg_finalizzeWeakzh)
 
index 01339b2..8d2bc2f 100644 (file)
@@ -374,14 +374,10 @@ stg_mkWeakzh ( gcptr key,
   w = Hp - SIZEOF_StgWeak + WDS(1);
   SET_HDR(w, stg_WEAK_info, CCCS);
 
-  // We don't care about cfinalizer here.
-  // Should StgWeak_cfinalizer(w) be stg_NO_FINALIZER_closure or
-  // something else?
-
-  StgWeak_key(w)        = key;
-  StgWeak_value(w)      = value;
-  StgWeak_finalizer(w)  = finalizer;
-  StgWeak_cfinalizer(w) = stg_NO_FINALIZER_closure;
+  StgWeak_key(w)         = key;
+  StgWeak_value(w)       = value;
+  StgWeak_finalizer(w)   = finalizer;
+  StgWeak_cfinalizers(w) = stg_NO_FINALIZER_closure;
 
   ACQUIRE_LOCK(sm_mutex);
   StgWeak_link(w)       = W_[weak_ptr_list];
@@ -398,61 +394,62 @@ stg_mkWeakNoFinalizzerzh ( gcptr key, gcptr value )
   jump stg_mkWeakzh (key, value, stg_NO_FINALIZER_closure);
 }
 
-stg_mkWeakForeignEnvzh ( gcptr key,
-                         gcptr val,
-                         W_ fptr,   // finalizer
-                         W_ ptr,
-                         W_ flag,   // has environment (0 or 1)
-                         W_ eptr )
+STRING(stg_cfinalizer_msg,"Adding a finalizer to %p\n")
+
+stg_addCFinalizzerToWeakzh ( W_ fptr,   // finalizer
+                             W_ ptr,
+                             W_ flag,   // has environment (0 or 1)
+                             W_ eptr,
+                             gcptr w )
 {
-  W_ payload_words, words;
-  gcptr w, p;
+  W_ c, info;
 
-  ALLOC_PRIM (SIZEOF_StgWeak);
+  ALLOC_PRIM (SIZEOF_StgCFinalizerList)
 
-  w = Hp - SIZEOF_StgWeak + WDS(1);
-  SET_HDR(w, stg_WEAK_info, CCCS);
+  c = Hp - SIZEOF_StgCFinalizerList + WDS(1);
+  SET_HDR(c, stg_C_FINALIZER_LIST_info, CCCS);
 
-  payload_words = 4;
-  words         = BYTES_TO_WDS(SIZEOF_StgArrWords) + payload_words;
-  ("ptr" p)     = ccall allocate(MyCapability() "ptr", words);
+  StgCFinalizerList_fptr(c) = fptr;
+  StgCFinalizerList_ptr(c) = ptr;
+  StgCFinalizerList_eptr(c) = eptr;
+  StgCFinalizerList_flag(c) = flag;
 
-  TICK_ALLOC_PRIM(SIZEOF_StgArrWords,WDS(payload_words),0);
-  SET_HDR(p, stg_ARR_WORDS_info, CCCS);
+  ("ptr" info) = ccall lockClosure(w "ptr");
 
-  StgArrWords_bytes(p)     = WDS(payload_words);
-  StgArrWords_payload(p,0) = fptr;
-  StgArrWords_payload(p,1) = ptr;
-  StgArrWords_payload(p,2) = eptr;
-  StgArrWords_payload(p,3) = flag;
+  if (info == stg_DEAD_WEAK_info) {
+    // Already dead.
+    unlockClosure(w, info);
+    return (0);
+  }
 
-  // We don't care about the value here.
-  // Should StgWeak_value(w) be stg_NO_FINALIZER_closure or something else?
+  StgCFinalizerList_link(c) = StgWeak_cfinalizers(w);
+  StgWeak_cfinalizers(w) = c;
 
-  StgWeak_key(w)        = key;
-  StgWeak_value(w)      = val;
-  StgWeak_finalizer(w)  = stg_NO_FINALIZER_closure;
-  StgWeak_cfinalizer(w) = p;
+  unlockClosure(w, info);
 
-  ACQUIRE_LOCK(sm_mutex);
-  StgWeak_link(w)   = W_[weak_ptr_list];
-  W_[weak_ptr_list] = w;
-  RELEASE_LOCK(sm_mutex);
+  recordMutable(w);
 
-  IF_DEBUG(weak, ccall debugBelch(stg_weak_msg,w));
+  IF_DEBUG(weak, ccall debugBelch(stg_cfinalizer_msg,w));
 
-  return (w);
+  return (1);
 }
 
 stg_finalizzeWeakzh ( gcptr w )
 {
-  gcptr f, arr;
+  gcptr f, list;
+  W_ info;
+
+  ("ptr" info) = ccall lockClosure(w "ptr");
 
   // already dead?
-  if (GET_INFO(w) == stg_DEAD_WEAK_info) {
+  if (info == stg_DEAD_WEAK_info) {
+      unlockClosure(w, info);
       return (0,stg_NO_FINALIZER_closure);
   }
 
+  f    = StgWeak_finalizer(w);
+  list = StgWeak_cfinalizers(w);
+
   // kill it
 #ifdef PROFILING
   // @LDV profiling
@@ -469,19 +466,12 @@ stg_finalizzeWeakzh ( gcptr w )
   //
   // Todo: maybe use SET_HDR() and remove LDV_recordCreate()?
   //
-  SET_INFO(w,stg_DEAD_WEAK_info);
-  LDV_RECORD_CREATE(w);
+  unlockClosure(w, stg_DEAD_WEAK_info);
 
-  f   = StgWeak_finalizer(w);
-  arr = StgWeak_cfinalizer(w);
-
-  StgDeadWeak_link(w) = StgWeak_link(w);
+  LDV_RECORD_CREATE(w);
 
-  if (arr != stg_NO_FINALIZER_closure) {
-    ccall runCFinalizer(StgArrWords_payload(arr,0),
-                              StgArrWords_payload(arr,1),
-                              StgArrWords_payload(arr,2),
-                              StgArrWords_payload(arr,3));
+  if (list != stg_NO_FINALIZER_closure) {
+    ccall runCFinalizers(list);
   }
 
   /* return the finalizer */
@@ -494,10 +484,21 @@ stg_finalizzeWeakzh ( gcptr w )
 
 stg_deRefWeakzh ( gcptr w )
 {
-  W_ code;
+  W_ code, info;
   gcptr val;
 
-  if (GET_INFO(w) == stg_WEAK_info) {
+  info = GET_INFO(w);
+
+  if (info == stg_WHITEHOLE_info) {
+    // w is locked by another thread. Now it's not immediately clear if w is
+    // alive or not. We use lockClosure to wait for the info pointer to become
+    // something other than stg_WHITEHOLE_info.
+
+    ("ptr" info) = ccall lockClosure(w "ptr");
+    unlockClosure(w, info);
+  }
+
+  if (info == stg_WEAK_info) {
     code = 1;
     val = StgWeak_value(w);
   } else {
index 28a41ad..9484031 100644 (file)
@@ -439,6 +439,15 @@ INFO_TABLE_CONSTR(stg_DEAD_WEAK,0,5,0,CONSTR,"DEAD_WEAK","DEAD_WEAK")
 { foreign "C" barf("DEAD_WEAK object entered!") never returns; }
 
 /* ----------------------------------------------------------------------------
+   C finalizer lists
+
+   Singly linked lists that chain multiple C finalizers on a weak pointer.
+   ------------------------------------------------------------------------- */
+
+INFO_TABLE_CONSTR(stg_C_FINALIZER_LIST,1,4,0,CONSTR,"C_FINALIZER_LIST","C_FINALIZER_LIST")
+{ foreign "C" barf("C_FINALIZER_LIST object entered!") never returns; }
+
+/* ----------------------------------------------------------------------------
    NO_FINALIZER
 
    This is a static nullary constructor (like []) that we use to mark an empty
index 5546514..e7a1257 100644 (file)
 #include "Prelude.h"
 #include "Trace.h"
 
-// ForeignPtrs with C finalizers rely on weak pointers inside weak_ptr_list
-// to always be in the same order.
-
 StgWeak *weak_ptr_list;
 
 void
-runCFinalizer(void *fn, void *ptr, void *env, StgWord flag)
+runCFinalizers(StgCFinalizerList *list)
 {
-    if (flag)
-       ((void (*)(void *, void *))fn)(env, ptr);
-    else
-       ((void (*)(void *))fn)(ptr);
+    StgCFinalizerList *head;
+    for (head = list;
+        (StgClosure *)head != &stg_NO_FINALIZER_closure;
+        head = (StgCFinalizerList *)head->link)
+    {
+        if (head->flag)
+            ((void (*)(void *, void *))head->fptr)(head->eptr, head->ptr);
+        else
+            ((void (*)(void *))head->fptr)(head->ptr);
+    }
 }
 
 void
@@ -42,15 +45,7 @@ runAllCFinalizers(StgWeak *list)
     }
 
     for (w = list; w; w = w->link) {
-       StgArrWords *farr;
-
-       farr = (StgArrWords *)UNTAG_CLOSURE(w->cfinalizer);
-
-       if ((StgClosure *)farr != &stg_NO_FINALIZER_closure)
-           runCFinalizer((void *)farr->payload[0],
-                         (void *)farr->payload[1],
-                         (void *)farr->payload[2],
-                         farr->payload[3]);
+       runCFinalizers((StgCFinalizerList *)w->cfinalizers);
     }
 
     if (task != NULL) {
@@ -91,8 +86,6 @@ scheduleFinalizers(Capability *cap, StgWeak *list)
     // count number of finalizers, and kill all the weak pointers first...
     n = 0;
     for (w = list; w; w = w->link) { 
-       StgArrWords *farr;
-
        // Better not be a DEAD_WEAK at this stage; the garbage
        // collector removes DEAD_WEAKs from the weak pointer list.
        ASSERT(w->header.info != &stg_DEAD_WEAK_info);
@@ -101,13 +94,7 @@ scheduleFinalizers(Capability *cap, StgWeak *list)
            n++;
        }
 
-       farr = (StgArrWords *)UNTAG_CLOSURE(w->cfinalizer);
-
-       if ((StgClosure *)farr != &stg_NO_FINALIZER_closure)
-           runCFinalizer((void *)farr->payload[0],
-                         (void *)farr->payload[1],
-                         (void *)farr->payload[2],
-                         farr->payload[3]);
+       runCFinalizers((StgCFinalizerList *)w->cfinalizers);
 
 #ifdef PROFILING
         // A weak pointer is inherently used, so we do not need to call
index 9b230f9..7892277 100644 (file)
@@ -16,7 +16,7 @@
 extern rtsBool running_finalizers;
 extern StgWeak * weak_ptr_list;
 
-void runCFinalizer(void *fn, void *ptr, void *env, StgWord flag);
+void runCFinalizers(StgCFinalizerList *list);
 void runAllCFinalizers(StgWeak *w);
 void scheduleFinalizers(Capability *cap, StgWeak *w);
 void markWeakList(void);
index 7c89418..ffa355a 100644 (file)
@@ -620,7 +620,7 @@ thread_obj (StgInfoTable *info, StgPtr p)
     case WEAK:
     {
        StgWeak *w = (StgWeak *)p;
-       thread(&w->cfinalizer);
+       thread(&w->cfinalizers);
        thread(&w->key);
        thread(&w->value);
        thread(&w->finalizer);
index d57f7a0..f8ccaad 100644 (file)
@@ -122,7 +122,7 @@ traverseWeakPtrList(void)
           * called on a live weak pointer object.  Just remove it.
           */
          if (w->header.info == &stg_DEAD_WEAK_info) {
-             next_w = ((StgDeadWeak *)w)->link;
+             next_w = w->link;
              *last_w = next_w;
              continue;
          }
@@ -144,7 +144,6 @@ traverseWeakPtrList(void)
                   next_w  = w->link;
 
                   // and put it on the new weak ptr list.
-                  // NB. we must retain the order of the weak_ptr_list (#7160)
                   if (weak_ptr_list == NULL) {
                       weak_ptr_list = w;
                   } else {
@@ -332,7 +331,7 @@ markWeakPtrList ( void )
       evacuate((StgClosure **)last_w);
       w = *last_w;
       if (w->header.info == &stg_DEAD_WEAK_info) {
-          last_w = &(((StgDeadWeak*)w)->link);
+          last_w = &(w->link);
       } else {
           last_w = &(w->link);
       }
index 78233a5..c731b9e 100644 (file)
@@ -469,10 +469,14 @@ wanteds = concat
           ,closureField C "StgWeak" "key"
           ,closureField C "StgWeak" "value"
           ,closureField C "StgWeak" "finalizer"
-          ,closureField C "StgWeak" "cfinalizer"
-
-          ,closureSize  C "StgDeadWeak"
-          ,closureField C "StgDeadWeak" "link"
+          ,closureField C "StgWeak" "cfinalizers"
+
+          ,closureSize  C "StgCFinalizerList"
+          ,closureField C "StgCFinalizerList" "link"
+          ,closureField C "StgCFinalizerList" "fptr"
+          ,closureField C "StgCFinalizerList" "ptr"
+          ,closureField C "StgCFinalizerList" "eptr"
+          ,closureField C "StgCFinalizerList" "flag"
 
           ,closureSize  C "StgMVar"
           ,closureField C "StgMVar" "head"