[Gc] Virtual dirty bits for Windows

Ben Hutchings ben.hutchings at businesswebsoftware.com
Fri Nov 12 07:15:35 PST 2004


Hans Boehm wrote (a little while back):
 > I believe incremental GC should basically work on Windows, at least
 > in the absence of threads.  I think there are the standard issues with
 > system calls that write to the heap.  And for some reason, incremental
 > collection is no longer tested by default with Windows threads.
 > I don't recall why that was turned off.  But I think single threaded
 > tests do run with incremental GC.

I've now tried enabling incremental collection in our (multithreaded) 
program on Windows and unfortunately it does not work.  The program 
crashes, apparently due to objects being erroneously collected and 
cleared.  The GC test program also crashes if built if optimisations are 
enabled and the GC is dynamically linked and multithreading is enabled, 
but it succeeds if any of these is not the case.

I've also attempted a virtual dirty bit implementation based on 
GetWriteWatch, but that fails similarly.  I've attached a patch (against 
something resembling version 6.1) for your inspection, in case there's 
an obvious bug in what I've written.

Ben.
-------------- next part --------------
diff -u -r1.2 os_dep.c
--- gc/os_dep.c	13 Oct 2004 13:41:59 -0000	1.2
+++ gc/os_dep.c	12 Nov 2004 14:59:52 -0000
@@ -1346,6 +1346,9 @@
 	/* increased fragmentation.  But better alternatives	*/
 	/* would require effort.				*/
         result = (ptr_t) VirtualAlloc(NULL, bytes + 1,
+#                                     ifdef GWW_VDB
+                                        MEM_WRITE_WATCH |
+#                                     endif
     				      MEM_COMMIT | MEM_RESERVE,
     				      PAGE_EXECUTE_READWRITE);
     }
@@ -1703,7 +1706,7 @@
 
 /*
  * Routines for accessing dirty  bits on virtual pages.
- * We plan to eventually implement four strategies for doing so:
+ * We implement five strategies for doing so:
  * DEFAULT_VDB:	A simple dummy implementation that treats every page
  *		as possibly dirty.  This makes incremental collection
  *		useless, but the implementation is still correct.
@@ -1720,10 +1723,121 @@
  *		call from doing so.  It is the clients responsibility to
  *		make sure that other system calls are similarly protected
  *		or write only to the stack.
+ * GWW_VDB:     Use the Win32 GetWriteWatch functions, if available, to
+ *              read dirty bits.  In case it is not available (because we
+ *              are running on Windows 95, Windows 2000 or earlier),
+ *              MPROTECT_VDB or DEFAULT_VDB must be defined, specifying a
+ *              fallback strategy.
  */
  
 GC_bool GC_dirty_maintained = FALSE;
 
+#if defined(PROC_VDB) || defined(GWW_VDB)
+
+/* Add all pages in pht2 to pht1 */
+void GC_or_pages(pht1, pht2)
+page_hash_table pht1, pht2;
+{
+    register int i;
+    
+    for (i = 0; i < PHT_SIZE; i++) pht1[i] |= pht2[i];
+}
+
+#endif
+
+#ifdef GWW_VDB
+
+# ifndef _BASETSD_H_
+    typedef ULONG * PULONG_PTR;
+# endif
+  typedef UINT (WINAPI * PGetWriteWatch)(
+    DWORD, PVOID, SIZE_T, PVOID*, PULONG_PTR, PULONG);
+  static PGetWriteWatch pGetWriteWatch;
+
+# define GC_GWW_BUF_LEN 1024
+  static PVOID gww_buf[GC_GWW_BUF_LEN];
+
+# define GC_GWW_INITIALIZED (pGetWriteWatch != NULL)
+
+  static void GC_gww_dirty_init(void)
+  {
+    pGetWriteWatch = (PGetWriteWatch)
+      GetProcAddress(GetModuleHandle("kernel32.dll"), "GetWriteWatch");
+  }
+
+  static void GC_gww_read_dirty(void)
+  {
+    if (pGetWriteWatch == NULL) {
+      memset(GC_grungy_pages, 0xff, sizeof(page_hash_table));
+      memset(GC_written_pages, 0xff, sizeof(page_hash_table));
+    } else {
+
+      word i;
+
+      BZERO(GC_grungy_pages, sizeof(GC_grungy_pages));
+
+      for (i = 0; i != GC_n_heap_sects; ++i) {
+        UINT more;
+
+        do {
+          PVOID * pages, * pages_end;
+          DWORD count;
+          DWORD page_size;
+
+          pages = gww_buf;
+          count = GC_GWW_BUF_LEN;
+          more = pGetWriteWatch(WRITE_WATCH_FLAG_RESET,
+                                GC_heap_sects[i].hs_start,
+                                GC_heap_sects[i].hs_bytes,
+                                pages,
+                                &count,
+                                &page_size);
+          /*
+           * GetWriteWatch is documented as returning non-zero when it fails,
+           * but the documentation doesn't explicitly say why it would fail or
+           * what its behaviour will be if it fails.  I think the only reason
+           * for it to fail would be that the output buffer is too small, which
+           * would mean the return value is a flag indicating that there's
+           * more to come, and I hope that in this case it fills the buffer and
+           * resets the flags for the pages that it has told us about.  Given
+           * that I'm not sure, for now I assert that the return value is zero.
+           */
+          GC_ASSERT(!more);
+          pages_end = pages + count;
+          while (pages != pages_end) {
+            struct hblk * h = (struct hblk *) *pages++;
+            struct hblk * h_end = (struct hblk *) ((char *) h + page_size);
+            do
+              set_pht_entry_from_index(GC_grungy_pages, PHT_HASH(h));
+            while (++h < h_end);
+          }
+        } while (more);
+      }
+
+      GC_or_pages(GC_written_pages, GC_grungy_pages);
+    }
+  }
+
+  GC_bool GC_gww_page_was_dirty(struct hblk * h)
+  {
+    return HDR(h) == 0 || get_pht_entry_from_index(GC_grungy_pages, PHT_HASH(h));
+  }
+
+  GC_bool GC_gww_page_was_ever_dirty(struct hblk * h)
+  {
+    return HDR(h) == 0 || get_pht_entry_from_index(GC_written_pages, PHT_HASH(h));
+  }
+
+# else /* !GWW_VDB */
+
+#   define GC_GWW_INITIALIZED FALSE
+#   define GC_gww_dirty_init()
+#   define GC_gww_read_dirty()
+#   define GC_gww_page_was_dirty(h) TRUE
+#   define GC_gww_page_was_ever_dirty(h) TRUE
+
+# endif /* GWW_VDB */
+
 # ifdef DEFAULT_VDB
 
 /* All of the following assume the allocation lock is held, and	*/
@@ -1735,13 +1849,16 @@
 /* Initialize virtual dirty bit implementation.			*/
 void GC_dirty_init()
 {
+    GC_gww_dirty_init();
     GC_dirty_maintained = TRUE;
 }
 
 /* Retrieve system dirty bits for heap to a local buffer.	*/
 /* Restore the systems notion of which pages are dirty.		*/
 void GC_read_dirty()
-{}
+{
+    GC_gww_read_dirty();
+}
 
 /* Is the HBLKSIZE sized page at h marked dirty in the local buffer?	*/
 /* If the actual page size is different, this returns TRUE if any	*/
@@ -1751,7 +1868,7 @@
 GC_bool GC_page_was_dirty(h)
 struct hblk *h;
 {
-    return(TRUE);
+    return GC_gww_page_was_dirty(h);
 }
 
 /*
@@ -1766,7 +1883,7 @@
 GC_bool GC_page_was_ever_dirty(h)
 struct hblk *h;
 {
-    return(TRUE);
+    return GC_gww_page_was_ever_dirty(h);
 }
 
 /* Reset the n pages starting at h to "was never dirty" status.	*/
@@ -2511,13 +2628,16 @@
       }
 #   endif /* MACOS || HPUX || LINUX */
 #   if defined(MSWIN32)
-      GC_old_segv_handler = SetUnhandledExceptionFilter(GC_write_fault_handler);
-      if (GC_old_segv_handler != NULL) {
-#	ifdef PRINTSTATS
-          GC_err_printf0("Replaced other UnhandledExceptionFilter\n");
-#	endif
-      } else {
+      GC_gww_dirty_init();
+      if (!GC_GWW_INITIALIZED) {
+        GC_old_segv_handler = SetUnhandledExceptionFilter(GC_write_fault_handler);
+        if (GC_old_segv_handler != NULL) {
+#         ifdef PRINTSTATS
+            GC_err_printf0("Replaced other UnhandledExceptionFilter\n");
+#         endif
+        } else {
           GC_old_segv_handler = SIG_DFL;
+        }
       }
 #   endif
 }
@@ -2600,18 +2720,26 @@
 /* bits while this is happenning (as in GC_enable_incremental).		*/
 void GC_read_dirty()
 {
-    BCOPY((word *)GC_dirty_pages, GC_grungy_pages,
-          (sizeof GC_dirty_pages));
-    BZERO((word *)GC_dirty_pages, (sizeof GC_dirty_pages));
-    GC_protect_heap();
+    if (GC_GWW_INITIALIZED) {
+      GC_gww_read_dirty();
+    } else {
+      BCOPY((word *)GC_dirty_pages, GC_grungy_pages,
+            (sizeof GC_dirty_pages));
+      BZERO((word *)GC_dirty_pages, (sizeof GC_dirty_pages));
+      GC_protect_heap();
+    }
 }
 
 GC_bool GC_page_was_dirty(h)
 struct hblk * h;
 {
-    register word index = PHT_HASH(h);
+    if (GC_GWW_INITIALIZED) {
+      return GC_gww_page_was_dirty(h);
+    } else {
+      register word index = PHT_HASH(h);
     
-    return(HDR(h) == 0 || get_pht_entry_from_index(GC_grungy_pages, index));
+      return(HDR(h) == 0 || get_pht_entry_from_index(GC_grungy_pages, index));
+    }
 }
 
 /*
@@ -2759,7 +2887,10 @@
 GC_bool GC_page_was_ever_dirty(h)
 struct hblk *h;
 {
-    return(TRUE);
+    if (GC_GWW_INITIALIZED)
+      return GC_gww_page_was_ever_dirty(h);
+    else
+      return(TRUE);
 }
 
 /* Reset the n pages starting at h to "was never dirty" status.	*/
@@ -2820,15 +2951,6 @@
 #   define PAGE_IS_FRESH(h) \
 	(GC_fresh_pages[FRESH_PAGE_SLOT(h)] == (h) && (h) != 0)
 #endif
-
-/* Add all pages in pht2 to pht1 */
-void GC_or_pages(pht1, pht2)
-page_hash_table pht1, pht2;
-{
-    register int i;
-    
-    for (i = 0; i < PHT_SIZE; i++) pht1[i] |= pht2[i];
-}
 
 int GC_proc_fd;
 
diff -u -r1.2 gc_priv.h
--- gc/include/private/gc_priv.h	3 Aug 2004 08:37:41 -0000	1.2
+++ gc/include/private/gc_priv.h	12 Nov 2004 14:59:52 -0000
@@ -934,7 +934,7 @@
         /* Stubborn object pages that were changes before last call to	*/
 	/* GC_read_changed.						*/
 # endif
-# if defined(PROC_VDB) || defined(MPROTECT_VDB)
+# if defined(PROC_VDB) || defined(MPROTECT_VDB) || defined(GWW_VDB)
     page_hash_table _grungy_pages; /* Pages that were dirty at last 	   */
 				     /* GC_read_dirty.			   */
 # endif
@@ -942,7 +942,7 @@
     VOLATILE page_hash_table _dirty_pages;	
 			/* Pages dirtied since last GC_read_dirty. */
 # endif
-# ifdef PROC_VDB
+# if defined(PROC_VDB) || defined(GWW_VDB)
     page_hash_table _written_pages;	/* Pages ever dirtied	*/
 # endif
 #ifndef MAX_HEAP_SECTS
@@ -1041,13 +1041,13 @@
 # define GC_excl_table GC_arrays._excl_table
 # define GC_all_nils GC_arrays._all_nils
 # define GC_top_index GC_arrays._top_index
-# if defined(PROC_VDB) || defined(MPROTECT_VDB)
+# if defined(PROC_VDB) || defined(MPROTECT_VDB) || defined(GWW_VDB)
 #   define GC_grungy_pages GC_arrays._grungy_pages
 # endif
 # ifdef MPROTECT_VDB
 #   define GC_dirty_pages GC_arrays._dirty_pages
 # endif
-# ifdef PROC_VDB
+# if defined(PROC_VDB) || defined(GWW_VDB)
 #   define GC_written_pages GC_arrays._written_pages
 # endif
 # ifdef GATHERSTATS
Index: gc/include/private/gcconfig.h
===================================================================
RCS file: /home/cvs/firmstep2/gc/include/private/gcconfig.h,v
retrieving revision 1.2
diff -u -r1.2 gcconfig.h
--- gc/include/private/gcconfig.h	19 Oct 2004 13:34:45 -0000	1.2
+++ gc/include/private/gcconfig.h	12 Nov 2004 14:59:52 -0000
@@ -1082,9 +1082,8 @@
 #	define OS_TYPE "MSWIN32"
 		/* STACKBOTTOM and DATASTART are handled specially in 	*/
 		/* os_dep.c.						*/
-#       ifndef __WATCOMC__
-#	  define MPROTECT_VDB
-#	endif
+#       define GWW_VDB
+#       define DEFAULT_VDB
 #       define DATAEND  /* not needed */
 #   endif
 #   ifdef MSWINCE
diff -u -r1.2 test.c
--- gc/tests/test.c	2 Apr 2003 15:59:56 -0000	1.2
+++ gc/tests/test.c	12 Nov 2004 14:59:52 -0000
@@ -1466,7 +1466,8 @@
 #   endif
     GC_INIT();	/* Only needed if gc is dynamic library.	*/
     (void) GC_set_warn_proc(warn_proc);
-#   if (defined(MPROTECT_VDB) || defined(PROC_VDB)) && !defined(MAKE_BACK_GRAPH)
+#   if (defined(MPROTECT_VDB) || defined(PROC_VDB) || defined(GWW_VDB)) \
+       && !defined(MAKE_BACK_GRAPH)
       GC_enable_incremental();
       (void) GC_printf0("Switched to incremental mode\n");
 #     if defined(MPROTECT_VDB)


More information about the Gc mailing list