[m-rev.] for review: Add support for weak pointers to the C RTS

Paul Bone paul at bone.id.au
Tue Apr 15 22:20:01 AEST 2014


For review by anyone.

Branches: master, version-14.01-branch

---

Add support for weak pointers to the C RTS

A weak pointer (aka weak reference) is a pointer to some garbage collector
(GC) allocated object or memory that is not considered by the GC when
deciding if the object is garbage or not.  Additionally, if the object is
reclaimed by the GC the GC will zero-out this pointer so that other code can
test if this object is still available.  Weak pointer can be useful when
implementing caches or cyclic data structures (my motivation for this
change).

This change introduces a weak pointer type (a typedef) in Mercury's runtime
system for the C backends.  Two macros are provided to create and deference
these weak pointers.  Extra documentation, and constraints on how these can
be used are described in mercury_memory.h.

runtime/mercury_memory.c:
runtime/mercury_memory.h:
    As above.

tests/general/weak_ptr.m:
tests/general/Mmakefile:
    Add a test for the C RTS's weak pointer support

tests/general/weak_ptr.exp:
    Expected output for the test.  This isn't the real program's output.
    weak_ptr's output is too volatile to use reliably (see weak_ptr.m).
---
 runtime/mercury_memory.c   |  19 +++++-
 runtime/mercury_memory.h   |  85 ++++++++++++++++++++++-
 tests/general/Mmakefile    |  11 ++-
 tests/general/weak_ptr.exp |   1 +
 tests/general/weak_ptr.m   | 163 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 276 insertions(+), 3 deletions(-)
 create mode 100644 tests/general/weak_ptr.exp
 create mode 100644 tests/general/weak_ptr.m

diff --git a/runtime/mercury_memory.c b/runtime/mercury_memory.c
index 8251696..76769cf 100644
--- a/runtime/mercury_memory.c
+++ b/runtime/mercury_memory.c
@@ -2,7 +2,7 @@
 ** vim: ts=4 sw=4 expandtab
 */
 /*
-** Copyright (C) 1994-2000,2002-2004, 2006, 2008, 2011 The University of Melbourne.
+** Copyright (C) 1994-2000,2002-2004, 2006, 2008, 2011, 2014 The University of Melbourne.
 ** This file may only be copied under the terms of the GNU Library General
 ** Public License - see the file COPYING.LIB in the Mercury distribution.
 */
@@ -381,6 +381,23 @@ MR_GC_realloc(void *old_ptr, size_t num_bytes)
     return ptr;
 }
 
+#ifdef MR_BOEHM_GC
+void*
+MR_weak_ptr_read_unsafe(void* weak_ptr_) {
+    MR_weak_ptr *weak_ptr = weak_ptr_;
+
+    /*
+    ** Even though we check for NULL in the macro we must re-check here
+    ** while holding the GC's allocation lock.
+    */
+    if (MR_NULL_WEAK_PTR != *weak_ptr) {
+        return GC_REVEAL_POINTER(*weak_ptr);
+    } else {
+        return NULL;
+    }
+}
+#endif
+
 /*---------------------------------------------------------------------------*/
 
 void *
diff --git a/runtime/mercury_memory.h b/runtime/mercury_memory.h
index bc1c1ed..862d1fa 100644
--- a/runtime/mercury_memory.h
+++ b/runtime/mercury_memory.h
@@ -1,5 +1,5 @@
 /*
-** Copyright (C) 1994-2000,2002, 2004, 2006, 2008 The University of Melbourne.
+** Copyright (C) 1994-2000,2002, 2004, 2006, 2008, 2014 The University of Melbourne.
 ** This file may only be copied under the terms of the GNU Library General
 ** Public License - see the file COPYING.LIB in the Mercury distribution.
 */
@@ -175,6 +175,18 @@ extern	void 	MR_ensure_big_enough_buffer(char **buffer_ptr,
 **      XXX this interface is subject to change.
 **
 ** Note: consider using the _attrib variants below.
+**
+** MR_new_weak_ptr(ptr, object):
+**  Create a weak pointer to object and store it in the memory pointed to by
+**  ptr (a double pointer).  object must have been allocated using one of
+**  the MR_GC methods.  Weak pointers only work with the Boehm collector
+**  (.gc grades).  In other grades this is an ordinary pointer.
+**
+** MR_weak_ptr_read(weak_ptr):
+**  Dereference a weak pointer.  Returns NULL of the pointed to object has
+**  been deallocated.  If weak_ptr is NULL then NULL is returned, so the
+**  programmer doesn't need to do an extra NULL check incase their pointer
+**  is deliberatly NULL;
 */
 
 extern	void	*MR_GC_malloc(size_t num_bytes);
@@ -209,6 +221,77 @@ typedef void 	(*MR_GC_finalizer)(void *ptr, void *data);
 #endif
 
 /*
+** Don't dereference a weak pointer directly, it won't work as the pointer
+** is hidden from the GC by storing its negated bits.
+*/
+#ifdef  MR_BOEHM_GC
+#define MR_NULL_WEAK_PTR    0
+typedef GC_hidden_pointer   MR_weak_ptr;
+#else
+#define MR_NULL_WEAK_PTR    NULL
+typedef void*               MR_weak_ptr;
+#endif
+
+/*
+** Create a weak pointer to obj and store the pointer in the memory pointed
+** to by weak_ptr, which must be a pointer to an MR_weak_ptr.  obj must not
+** be an internal pointer and weak_ptr must be located within a heap object
+** managed by the GC.
+*/
+#ifdef  MR_BOEHM_GC
+#define MR_new_weak_ptr(weak_ptr, obj)                              \
+    do {                                                                \
+        int result;                                                     \
+                                                                        \
+        *(weak_ptr) = GC_HIDE_POINTER((obj));                           \
+        /*                                                              \
+        ** This call takes a double pointer, so it can clear the        \
+        ** user's pointer. Recall that *weak_ptr is a hidden pointer    \
+        ** a pointer cast to an int.                                    \
+        */                                                              \
+        result =                                                        \
+            GC_general_register_disappearing_link((void**)(weak_ptr), (obj)); \
+                                                                        \
+        if (GC_DUPLICATE == result) {                                   \
+            MR_fatal_error(                                             \
+                "Error registering weak pointer: already registered");  \
+        } else if (GC_NO_MEMORY == result) {                            \
+            MR_fatal_error(                                             \
+                "Error registering weak pointer: out of memory");       \
+        }                                                               \
+    } while(0)
+#else
+#define MR_new_weak_ptr(weak_ptr, obj)                              \
+    do {                                                                \
+        *(weak_ptr) = (obj);                                                \
+    } while(0)
+#endif
+
+/*
+** Don't call this directly, it must be protected by Boehm's allocation
+** lock, see MR_weak_ptr_read below
+*/
+#ifdef MR_BOEHM_GC
+extern void*
+MR_weak_ptr_read_unsafe(void* weak_ptr);
+#endif
+
+/*
+** Use this before dereferencing a weak pointer.  weak_ptr must be a pointer
+** to an MR_weak_ptr.  This returns the real pointer that the weak pointer
+** represents.
+*/
+#ifdef MR_BOEHM_GC
+#define MR_weak_ptr_read(weak_ptr) \
+    ((MR_NULL_WEAK_PTR != *(weak_ptr)) ?                                      \
+        GC_call_with_alloc_lock(MR_weak_ptr_read_unsafe, (weak_ptr)) :   \
+        NULL)
+#else
+#define MR_weak_ptr_read(weak_ptr) \
+    (*(weak_ptr))
+#endif
+
+/*
 ** MR_GC_NEW_ATTRIB(type, attrib):
 ** MR_GC_NEW_UNCOLLECTABLE_ATTRIB(type, attrib):
 ** MR_GC_NEW_ARRAY_ATTRIB(type, attrib):
diff --git a/tests/general/Mmakefile b/tests/general/Mmakefile
index b83817f..035afe2 100644
--- a/tests/general/Mmakefile
+++ b/tests/general/Mmakefile
@@ -74,7 +74,8 @@ ORDINARY_PROGS=	\
 		test_string_to_int_overflow \
 		test_univ \
 		unreachable \
-		unsafe_uniqueness
+		unsafe_uniqueness \
+		weak_ptr
 
 EXCEPTION_PROGS = \
 		map_corresponding \
@@ -150,4 +151,12 @@ string_format_test_3.out: string_format_test_3
 io_foldl.out: io_foldl io_foldl.exp
 	./io_foldl < io_foldl.exp > io_foldl.out 2>&1
 
+# weak_ptr's output is extreamly volatile, it depends on Boehm GC's behavour
+# which can vary depending on many different things.  All we can hope to do
+# is check that it doesn't crash.
+
+weak_ptr.out: weak_ptr
+	./weak_ptr
+	echo "Output is not part of test, see Mmakefile" > $@
+
 #-----------------------------------------------------------------------------#
diff --git a/tests/general/weak_ptr.exp b/tests/general/weak_ptr.exp
new file mode 100644
index 0000000..44d96fa
--- /dev/null
+++ b/tests/general/weak_ptr.exp
@@ -0,0 +1 @@
+Output is not part of test, see Mmakefile
diff --git a/tests/general/weak_ptr.m b/tests/general/weak_ptr.m
new file mode 100644
index 0000000..466e08d
--- /dev/null
+++ b/tests/general/weak_ptr.m
@@ -0,0 +1,163 @@
+%
+% Test the RTS' weak pointer support.
+%
+% Running this with GC_PRINT_STATS=1 GC_PRINT_VERBOSE_STATS=1 in the
+% environment will show that this support is working.
+%
+
+% The output of this test is extreamly volatile.  It depends on the garbage
+% collector's state (which objects get collected when) which can depend on a
+% lot of other things.  Therefore we don't compare it's output with expected
+% output.  The best we can do is test that it exits cleanly.
+
+:- module weak_ptr.
+:- interface.
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+:- implementation.
+
+main(!IO) :-
+    test(!IO).
+
+:- pragma foreign_decl("C", local,
+"
+#include \"gc.mh\"
+
+struct list {
+    int             item;
+    struct list*    next;
+    MR_weak_ptr     prev;
+};
+
+typedef struct list list;
+
+static list* build_list(void);
+
+static void traverse_forwards(list* list);
+
+static void traverse_backwards(list* list);
+
+static list* get_tail(list* list);
+
+static list* drop(int n, list* list);
+").
+
+:- pragma foreign_code("C",
+"
+static list*
+build_list(void)
+{
+    list*   head;
+    list*   cur;
+    list*   prev;
+    int     i;
+
+    cur = MR_GC_malloc(sizeof(struct list));
+    cur->item = 0;
+    cur->next = NULL;
+    cur->prev = MR_NULL_WEAK_PTR;
+    prev = cur;
+    head = cur;
+
+    for (i = 1; i < 10000; i++) {
+        cur = MR_GC_malloc(sizeof(struct list));
+        cur->item = i;
+        cur->next = NULL;
+
+        prev->next = cur;
+        MR_new_weak_ptr(&(cur->prev), prev);
+
+        prev = cur;
+    }
+
+    /* Help the GC */
+    cur = NULL;
+    prev = NULL;
+
+    return head;
+}
+
+static void
+traverse_forwards(list* cur)
+{
+    printf(\"Forwards: \");
+
+    while (NULL != cur) {
+        printf(\"%d \", cur->item);
+        cur = cur->next;
+    }
+
+    printf(\"\\n\");
+}
+
+static void
+traverse_backwards(list* cur)
+{
+    printf(\"Backwards: \");
+
+    while (NULL != cur) {
+        printf(\"%d \", cur->item);
+        cur = MR_weak_ptr_read(&(cur->prev));
+    }
+
+    printf(\"\\n\");
+}
+
+static list*
+get_tail(list* cur)
+{
+    list *prev = NULL;
+
+    while (NULL != cur) {
+        prev = cur;
+        cur = cur->next;
+    }
+
+    return prev;
+}
+
+static list*
+drop(int n, list* cur)
+{
+    int i;
+
+    for (i = 0; (i < n) && (cur != NULL); i++) {
+        cur = cur->next;
+    }
+
+    return cur;
+}
+").
+
+:- pred test(io::di, io::uo) is det.
+
+:- pragma foreign_proc("C", test(_IO0::di, _IO::uo),
+    [will_not_throw_exception, thread_safe, promise_pure],
+    "
+        list*   list_head;
+        list*   list_tail;
+
+        ML_garbage_collect();
+
+        list_head = build_list();
+        ML_garbage_collect();
+
+        list_tail = get_tail(list_head);
+        traverse_forwards(list_head);
+        ML_garbage_collect();
+
+        traverse_backwards(list_tail);
+        ML_garbage_collect();
+
+        list_head = drop(9000, list_head);
+        ML_garbage_collect();
+        traverse_forwards(list_head);
+        traverse_backwards(list_tail);
+
+    ").
+
+test(!IO) :-
+    io.write_string("Test not supported in this grade.\n").
+
-- 
1.9.1




More information about the reviews mailing list