90612b2
commit a829b747cf2ce18ba869ea60f67975c484bf2769
90612b2
Author: Istvan Kurucsai <pistukem@gmail.com>
90612b2
Date:   Fri Jan 12 15:34:13 2018 +0100
90612b2
90612b2
    Implement allocate_once for atomic initialization with allocation
90612b2
90612b2
diff --git a/include/allocate_once.h b/include/allocate_once.h
90612b2
new file mode 100644
90612b2
index 0000000000000000..26902dde7c1a3255
90612b2
--- /dev/null
90612b2
+++ b/include/allocate_once.h
90612b2
@@ -0,0 +1,95 @@
90612b2
+/* Allocate and initialize an object once, in a thread-safe fashion.
90612b2
+   Copyright (C) 2018 Free Software Foundation, Inc.
90612b2
+   This file is part of the GNU C Library.
90612b2
+
90612b2
+   The GNU C Library is free software; you can redistribute it and/or
90612b2
+   modify it under the terms of the GNU Lesser General Public
90612b2
+   License as published by the Free Software Foundation; either
90612b2
+   version 2.1 of the License, or (at your option) any later version.
90612b2
+
90612b2
+   The GNU C Library is distributed in the hope that it will be useful,
90612b2
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
90612b2
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
90612b2
+   Lesser General Public License for more details.
90612b2
+
90612b2
+   You should have received a copy of the GNU Lesser General Public
90612b2
+   License along with the GNU C Library; if not, see
90612b2
+   <http://www.gnu.org/licenses/>.  */
90612b2
+
90612b2
+#ifndef _ALLOCATE_ONCE_H
90612b2
+#define _ALLOCATE_ONCE_H
90612b2
+
90612b2
+#include <atomic.h>
90612b2
+
90612b2
+/* Slow path for allocate_once; see below.  */
90612b2
+void *__libc_allocate_once_slow (void **__place,
90612b2
+                                 void *(*__allocate) (void *__closure),
90612b2
+                                 void (*__deallocate) (void *__closure,
90612b2
+                                                       void *__ptr),
90612b2
+                                 void *__closure);
90612b2
+
90612b2
+/* Return an a pointer to an allocated and initialized data structure.
90612b2
+   If this function returns a non-NULL value, the caller can assume
90612b2
+   that pointed-to data has been initialized according to the ALLOCATE
90612b2
+   function.
90612b2
+
90612b2
+   It is expected that callers define an inline helper function which
90612b2
+   adds type safety, like this.
90612b2
+
90612b2
+   struct foo { ... };
90612b2
+   struct foo *global_foo;
90612b2
+   static void *allocate_foo (void *closure);
90612b2
+   static void *deallocate_foo (void *closure, void *ptr);
90612b2
+
90612b2
+   static inline struct foo *
90612b2
+   get_foo (void)
90612b2
+   {
90612b2
+     return allocate_once (&global_foo, allocate_foo, free_foo, NULL);
90612b2
+   }
90612b2
+
90612b2
+   (Note that the global_foo variable is initialized to zero.)
90612b2
+   Usage of this helper function looks like this:
90612b2
+
90612b2
+   struct foo *local_foo = get_foo ();
90612b2
+   if (local_foo == NULL)
90612b2
+      report_allocation_failure ();
90612b2
+
90612b2
+   allocate_once first performs an acquire MO load on *PLACE.  If the
90612b2
+   result is not null, it is returned.  Otherwise, ALLOCATE (CLOSURE)
90612b2
+   is called, yielding a value RESULT.  If RESULT equals NULL,
90612b2
+   allocate_once returns NULL, and does not modify *PLACE (but another
90612b2
+   thread may concurrently perform an allocation which succeeds,
90612b2
+   updating *PLACE).  If RESULT does not equal NULL, the function uses
90612b2
+   a CAS with acquire-release MO to update the NULL value in *PLACE
90612b2
+   with the RESULT value.  If it turns out that *PLACE was updated
90612b2
+   concurrently, allocate_once calls DEALLOCATE (CLOSURE, RESULT) to
90612b2
+   undo the effect of ALLOCATE, and returns the new value of *PLACE
90612b2
+   (after an acquire MO load).  If DEALLOCATE is NULL, free (RESULT)
90612b2
+   is called instead.
90612b2
+
90612b2
+   Compared to __libc_once, allocate_once has the advantage that it
90612b2
+   does not need separate space for a control variable, and that it is
90612b2
+   safe with regards to cancellation and other forms of exception
90612b2
+   handling if the supplied callback functions are safe in that
90612b2
+   regard.  allocate_once passes a closure parameter to the allocation
90612b2
+   function, too.  */
90612b2
+static inline void *
90612b2
+allocate_once (void **__place, void *(*__allocate) (void *__closure),
90612b2
+               void (*__deallocate) (void *__closure, void *__ptr),
90612b2
+               void *__closure)
90612b2
+{
90612b2
+  /* Synchronizes with the release MO CAS in
90612b2
+     __allocate_once_slow.  */
90612b2
+  void *__result = atomic_load_acquire (__place);
90612b2
+  if (__result != NULL)
90612b2
+    return __result;
90612b2
+  else
90612b2
+    return __libc_allocate_once_slow (__place, __allocate, __deallocate,
90612b2
+                                      __closure);
90612b2
+}
90612b2
+
90612b2
+#ifndef _ISOMAC
90612b2
+libc_hidden_proto (__libc_allocate_once_slow)
90612b2
+#endif
90612b2
+
90612b2
+#endif /* _ALLOCATE_ONCE_H */
90612b2
diff --git a/misc/Makefile b/misc/Makefile
90612b2
index a5076b36728749d6..96afd6d890bd06f3 100644
90612b2
--- a/misc/Makefile
90612b2
+++ b/misc/Makefile
90612b2
@@ -70,9 +70,11 @@ routines := brk sbrk sstk ioctl \
90612b2
 	    getloadavg getclktck \
90612b2
 	    fgetxattr flistxattr fremovexattr fsetxattr getxattr \
90612b2
 	    listxattr lgetxattr llistxattr lremovexattr lsetxattr \
90612b2
-	    removexattr setxattr getauxval ifunc-impl-list makedev
90612b2
+	    removexattr setxattr getauxval ifunc-impl-list makedev \
90612b2
+	    allocate_once
90612b2
 
90612b2
-generated += tst-error1.mtrace tst-error1-mem.out
90612b2
+generated += tst-error1.mtrace tst-error1-mem.out \
90612b2
+  tst-allocate_once.mtrace tst-allocate_once-mem.out
90612b2
 
90612b2
 aux := init-misc
90612b2
 install-lib := libg.a
90612b2
@@ -84,11 +86,12 @@ tests := tst-dirname tst-tsearch tst-fdset tst-efgcvt tst-mntent tst-hsearch \
90612b2
 	 tst-preadvwritev tst-preadvwritev64 tst-makedev tst-empty \
90612b2
 	 tst-preadvwritev2 tst-preadvwritev64v2
90612b2
 
90612b2
-tests-internal := tst-atomic tst-atomic-long
90612b2
+tests-internal := tst-atomic tst-atomic-long tst-allocate_once
90612b2
 tests-static := tst-empty
90612b2
 
90612b2
 ifeq ($(run-built-tests),yes)
90612b2
-tests-special += $(objpfx)tst-error1-mem.out
90612b2
+tests-special += $(objpfx)tst-error1-mem.out \
90612b2
+  $(objpfx)tst-allocate_once-mem.out
90612b2
 endif
90612b2
 
90612b2
 CFLAGS-select.c += -fexceptions -fasynchronous-unwind-tables
90612b2
@@ -137,3 +140,8 @@ tst-error1-ARGS = $(objpfx)tst-error1.out
90612b2
 $(objpfx)tst-error1-mem.out: $(objpfx)tst-error1.out
90612b2
 	$(common-objpfx)malloc/mtrace $(objpfx)tst-error1.mtrace > $@; \
90612b2
 	$(evaluate-test)
90612b2
+
90612b2
+tst-allocate_once-ENV = MALLOC_TRACE=$(objpfx)tst-allocate_once.mtrace
90612b2
+$(objpfx)tst-allocate_once-mem.out: $(objpfx)tst-allocate_once.out
90612b2
+	$(common-objpfx)malloc/mtrace $(objpfx)tst-allocate_once.mtrace > $@; \
90612b2
+	$(evaluate-test)
90612b2
diff --git a/misc/Versions b/misc/Versions
90612b2
index bfbda505e4dd8743..900e4ffb798a9e13 100644
90612b2
--- a/misc/Versions
90612b2
+++ b/misc/Versions
90612b2
@@ -165,5 +165,6 @@ libc {
90612b2
     __tdelete; __tfind; __tsearch; __twalk;
90612b2
     __mmap; __munmap; __mprotect;
90612b2
     __sched_get_priority_min; __sched_get_priority_max;
90612b2
+    __libc_allocate_once_slow;
90612b2
   }
90612b2
 }
90612b2
diff --git a/misc/allocate_once.c b/misc/allocate_once.c
90612b2
new file mode 100644
90612b2
index 0000000000000000..2108014604cb026e
90612b2
--- /dev/null
90612b2
+++ b/misc/allocate_once.c
90612b2
@@ -0,0 +1,59 @@
90612b2
+/* Concurrent allocation and initialization of a pointer.
90612b2
+   Copyright (C) 2018 Free Software Foundation, Inc.
90612b2
+   This file is part of the GNU C Library.
90612b2
+
90612b2
+   The GNU C Library is free software; you can redistribute it and/or
90612b2
+   modify it under the terms of the GNU Lesser General Public
90612b2
+   License as published by the Free Software Foundation; either
90612b2
+   version 2.1 of the License, or (at your option) any later version.
90612b2
+
90612b2
+   The GNU C Library is distributed in the hope that it will be useful,
90612b2
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
90612b2
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
90612b2
+   Lesser General Public License for more details.
90612b2
+
90612b2
+   You should have received a copy of the GNU Lesser General Public
90612b2
+   License along with the GNU C Library; if not, see
90612b2
+   <http://www.gnu.org/licenses/>.  */
90612b2
+
90612b2
+#include <allocate_once.h>
90612b2
+#include <stdlib.h>
90612b2
+#include <stdbool.h>
90612b2
+
90612b2
+void *
90612b2
+__libc_allocate_once_slow (void **place, void *(*allocate) (void *closure),
90612b2
+                           void (*deallocate) (void *closure, void *ptr),
90612b2
+                           void *closure)
90612b2
+{
90612b2
+  void *result = allocate (closure);
90612b2
+  if (result == NULL)
90612b2
+    return NULL;
90612b2
+
90612b2
+  /* This loop implements a strong CAS on *place, with acquire-release
90612b2
+     MO semantics, from a weak CAS with relaxed-release MO.  */
90612b2
+  while (true)
90612b2
+    {
90612b2
+      /* Synchronizes with the acquire MO load in allocate_once.  */
90612b2
+      void *expected = NULL;
90612b2
+      if (atomic_compare_exchange_weak_release (place, &expected, result))
90612b2
+        return result;
90612b2
+
90612b2
+      /* The failed CAS has relaxed MO semantics, so perform another
90612b2
+         acquire MO load.  */
90612b2
+      void *other_result = atomic_load_acquire (place);
90612b2
+      if (other_result == NULL)
90612b2
+        /* Spurious failure.  Try again.  */
90612b2
+        continue;
90612b2
+
90612b2
+      /* We lost the race.  Free what we allocated and return the
90612b2
+         other result.  */
90612b2
+      if (deallocate == NULL)
90612b2
+        free (result);
90612b2
+      else
90612b2
+        deallocate (closure, result);
90612b2
+      return other_result;
90612b2
+    }
90612b2
+
90612b2
+  return result;
90612b2
+}
90612b2
+libc_hidden_def (__libc_allocate_once_slow)
90612b2
diff --git a/misc/tst-allocate_once.c b/misc/tst-allocate_once.c
90612b2
new file mode 100644
90612b2
index 0000000000000000..89277b33b769732b
90612b2
--- /dev/null
90612b2
+++ b/misc/tst-allocate_once.c
90612b2
@@ -0,0 +1,181 @@
90612b2
+/* Test the allocate_once function.
90612b2
+   Copyright (C) 2018 Free Software Foundation, Inc.
90612b2
+   This file is part of the GNU C Library.
90612b2
+
90612b2
+   The GNU C Library is free software; you can redistribute it and/or
90612b2
+   modify it under the terms of the GNU Lesser General Public
90612b2
+   License as published by the Free Software Foundation; either
90612b2
+   version 2.1 of the License, or (at your option) any later version.
90612b2
+
90612b2
+   The GNU C Library is distributed in the hope that it will be useful,
90612b2
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
90612b2
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
90612b2
+   Lesser General Public License for more details.
90612b2
+
90612b2
+   You should have received a copy of the GNU Lesser General Public
90612b2
+   License along with the GNU C Library; if not, see
90612b2
+   <http://www.gnu.org/licenses/>.  */
90612b2
+
90612b2
+#include <allocate_once.h>
90612b2
+#include <mcheck.h>
90612b2
+#include <string.h>
90612b2
+#include <support/check.h>
90612b2
+#include <support/support.h>
90612b2
+
90612b2
+/* Allocate a new string.  */
90612b2
+static void *
90612b2
+allocate_string (void *closure)
90612b2
+{
90612b2
+  return xstrdup (closure);
90612b2
+}
90612b2
+
90612b2
+/* Allocation and deallocation functions which are not expected to be
90612b2
+   called.  */
90612b2
+
90612b2
+static void *
90612b2
+allocate_not_called (void *closure)
90612b2
+{
90612b2
+  FAIL_EXIT1 ("allocation function called unexpectedly (%p)", closure);
90612b2
+}
90612b2
+
90612b2
+static void
90612b2
+deallocate_not_called (void *closure, void *ptr)
90612b2
+{
90612b2
+  FAIL_EXIT1 ("deallocate function called unexpectedly (%p, %p)",
90612b2
+              closure, ptr);
90612b2
+}
90612b2
+
90612b2
+/* Counter for various function calls.  */
90612b2
+static int function_called;
90612b2
+
90612b2
+/* An allocation function which returns NULL and records that it has
90612b2
+   been called.  */
90612b2
+static void *
90612b2
+allocate_return_null (void *closure)
90612b2
+{
90612b2
+  /* The function should only be called once.  */
90612b2
+  TEST_COMPARE (function_called, 0);
90612b2
+  ++function_called;
90612b2
+  return NULL;
90612b2
+}
90612b2
+
90612b2
+
90612b2
+/* The following is used to check the retry logic, by causing a fake
90612b2
+   race condition.  */
90612b2
+static void *fake_race_place;
90612b2
+static char fake_race_region[3]; /* To obtain unique addresses.  */
90612b2
+
90612b2
+static void *
90612b2
+fake_race_allocate (void *closure)
90612b2
+{
90612b2
+  TEST_VERIFY (closure == &fake_race_region[0]);
90612b2
+  TEST_COMPARE (function_called, 0);
90612b2
+  ++function_called;
90612b2
+  /* Fake allocation by another thread.  */
90612b2
+  fake_race_place = &fake_race_region[1];
90612b2
+  return &fake_race_region[2];
90612b2
+}
90612b2
+
90612b2
+static void
90612b2
+fake_race_deallocate (void *closure, void *ptr)
90612b2
+{
90612b2
+  /* Check that the pointer returned from fake_race_allocate is
90612b2
+     deallocated (and not the one stored in fake_race_place).  */
90612b2
+  TEST_VERIFY (ptr == &fake_race_region[2]);
90612b2
+
90612b2
+  TEST_VERIFY (fake_race_place == &fake_race_region[1]);
90612b2
+  TEST_VERIFY (closure == &fake_race_region[0]);
90612b2
+  TEST_COMPARE (function_called, 1);
90612b2
+  ++function_called;
90612b2
+}
90612b2
+
90612b2
+/* Similar to fake_race_allocate, but expects to be paired with free
90612b2
+   as the deallocation function.  */
90612b2
+static void *
90612b2
+fake_race_allocate_for_free (void *closure)
90612b2
+{
90612b2
+  TEST_VERIFY (closure == &fake_race_region[0]);
90612b2
+  TEST_COMPARE (function_called, 0);
90612b2
+  ++function_called;
90612b2
+  /* Fake allocation by another thread.  */
90612b2
+  fake_race_place = &fake_race_region[1];
90612b2
+  return xstrdup ("to be freed");
90612b2
+}
90612b2
+
90612b2
+static int
90612b2
+do_test (void)
90612b2
+{
90612b2
+  mtrace ();
90612b2
+
90612b2
+  /* Simple allocation.  */
90612b2
+  void *place1 = NULL;
90612b2
+  char *string1 = allocate_once (&place1, allocate_string,
90612b2
+                                   deallocate_not_called,
90612b2
+                                   (char *) "test string 1");
90612b2
+  TEST_VERIFY_EXIT (string1 != NULL);
90612b2
+  TEST_VERIFY (strcmp ("test string 1", string1) == 0);
90612b2
+  /* Second call returns the first pointer, without calling any
90612b2
+     callbacks.  */
90612b2
+  TEST_VERIFY (string1
90612b2
+               == allocate_once (&place1, allocate_not_called,
90612b2
+                                 deallocate_not_called,
90612b2
+                                 (char *) "test string 1a"));
90612b2
+
90612b2
+  /* Different place should result in another call.  */
90612b2
+  void *place2 = NULL;
90612b2
+  char *string2 = allocate_once (&place2, allocate_string,
90612b2
+                                 deallocate_not_called,
90612b2
+                                 (char *) "test string 2");
90612b2
+  TEST_VERIFY_EXIT (string2 != NULL);
90612b2
+  TEST_VERIFY (strcmp ("test string 2", string2) == 0);
90612b2
+  TEST_VERIFY (string1 != string2);
90612b2
+
90612b2
+  /* Check error reporting (NULL return value from the allocation
90612b2
+     function).  */
90612b2
+  void *place3 = NULL;
90612b2
+  char *string3 = allocate_once (&place3, allocate_return_null,
90612b2
+                                 deallocate_not_called, NULL);
90612b2
+  TEST_VERIFY (string3 == NULL);
90612b2
+  TEST_COMPARE (function_called, 1);
90612b2
+
90612b2
+  /* Check that the deallocation function is called if the race is
90612b2
+     lost.  */
90612b2
+  function_called = 0;
90612b2
+  TEST_VERIFY (allocate_once (&fake_race_place,
90612b2
+                              fake_race_allocate,
90612b2
+                              fake_race_deallocate,
90612b2
+                              &fake_race_region[0])
90612b2
+               == &fake_race_region[1]);
90612b2
+  TEST_COMPARE (function_called, 2);
90612b2
+  function_called = 3;
90612b2
+  TEST_VERIFY (allocate_once (&fake_race_place,
90612b2
+                              fake_race_allocate,
90612b2
+                              fake_race_deallocate,
90612b2
+                              &fake_race_region[0])
90612b2
+               == &fake_race_region[1]);
90612b2
+  TEST_COMPARE (function_called, 3);
90612b2
+
90612b2
+  /* Similar, but this time rely on that free is called.  */
90612b2
+  function_called = 0;
90612b2
+  fake_race_place = NULL;
90612b2
+  TEST_VERIFY (allocate_once (&fake_race_place,
90612b2
+                                fake_race_allocate_for_free,
90612b2
+                                NULL,
90612b2
+                                &fake_race_region[0])
90612b2
+               == &fake_race_region[1]);
90612b2
+  TEST_COMPARE (function_called, 1);
90612b2
+  function_called = 3;
90612b2
+  TEST_VERIFY (allocate_once (&fake_race_place,
90612b2
+                              fake_race_allocate_for_free,
90612b2
+                              NULL,
90612b2
+                              &fake_race_region[0])
90612b2
+               == &fake_race_region[1]);
90612b2
+  TEST_COMPARE (function_called, 3);
90612b2
+
90612b2
+  free (place2);
90612b2
+  free (place1);
90612b2
+
90612b2
+  return 0;
90612b2
+}
90612b2
+
90612b2
+#include <support/test-driver.c>