Patchwork [6/7] Add elision test cases

login
register
mail settings
Submitter Andi Kleen
Date Dec. 19, 2014, 7:37 a.m.
Message ID <1418974667-32587-7-git-send-email-andi@firstfloor.org>
Download mbox | patch
Permalink /patch/4361/
State New
Headers show

Comments

Andi Kleen - Dec. 19, 2014, 7:37 a.m.
From: Andi Kleen <ak@linux.intel.com>

Add the elision test cases that didn't make it last time.
I use this to the test the environment variables.

2014-12-17  Andi Kleen  <ak@linux.intel.com>

	* Makefile (tst-elision1, tst-elision2, tst-elision1b, tst-elision1c):
	Add.
	* tst-elision1.c: New file to test mutex and rwlock elision.
	* tst-elision2.c: New file to test trylock elision
	* tst-elision-common.c: New file with common test code.
	* tst-elision1b.c: Stub to allow env var enable testing.
	* tst-elision1c.c: Stub to allow env var disable testing.
---
 nptl/Makefile             |  11 +-
 nptl/tst-elision-common.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++
 nptl/tst-elision1.c       | 133 +++++++++++++++++++++++
 nptl/tst-elision1b.c      |   1 +
 nptl/tst-elision1c.c      |   1 +
 nptl/tst-elision2.c       |  84 +++++++++++++++
 nptl/tst-elision2b.c      |   1 +
 nptl/tst-elision2c.c      |   1 +
 8 files changed, 495 insertions(+), 1 deletion(-)
 create mode 100644 nptl/tst-elision-common.c
 create mode 100644 nptl/tst-elision1.c
 create mode 100644 nptl/tst-elision1b.c
 create mode 100644 nptl/tst-elision1c.c
 create mode 100644 nptl/tst-elision2.c
 create mode 100644 nptl/tst-elision2b.c
 create mode 100644 nptl/tst-elision2c.c

Patch

diff --git a/nptl/Makefile b/nptl/Makefile
index 3d61ec1..78a3f90 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -272,7 +272,8 @@  tests = tst-typesizes \
 	tst-getpid1 tst-getpid2 tst-getpid3 \
 	tst-setuid3 \
 	tst-initializers1 $(addprefix tst-initializers1-,c89 gnu89 c99 gnu99) \
-	tst-bad-schedattr
+	tst-bad-schedattr \
+	tst-elision1 tst-elision2 tst-elision1b tst-elision2b tst-elision1c tst-elision2c
 xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
 	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
 test-srcs = tst-oddstacklimit
@@ -469,6 +470,14 @@  tst-umask1-ARGS = $(objpfx)tst-umask1.temp
 # resolution. Avoid this race by disabling lazy binding. BZ #11214.
 tst-getpid2-ENV = LD_BIND_NOW=1
 
+# test if disabling elision works through environment arguments
+tst-elision1b-ENV = GLIBC_PTHREAD_MUTEX=none GLIBC_PTHREAD_RWLOCK=none
+tst-elision2b-ENV = GLIBC_PTHREAD_MUTEX=none GLIBC_PTHREAD_RWLOCK=none
+# test if enabling elision works
+# XXX requires env file
+tst-elision1c-ENV = GLIBC_PTHREAD_MUTEX=elision GLIBC_PTHREAD_RWLOCK=elision
+tst-elision2c-ENV = GLIBC_PTHREAD_MUTEX=elision GLIBC_PTHREAD_RWLOCK=elision
+
 $(objpfx)tst-atfork2: $(libdl) $(shared-thread-library)
 LDFLAGS-tst-atfork2 = -rdynamic
 tst-atfork2-ENV = MALLOC_TRACE=$(objpfx)tst-atfork2.mtrace
diff --git a/nptl/tst-elision-common.c b/nptl/tst-elision-common.c
new file mode 100644
index 0000000..5aaf0f6
--- /dev/null
+++ b/nptl/tst-elision-common.c
@@ -0,0 +1,264 @@ 
+/* tst-elision-common: Elision test harness.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "config.h"
+
+#define CPUID_FEATURE_RTM (1U << 11)
+
+static int
+cpu_has_rtm (void)
+{
+  if (__get_cpuid_max (0, NULL) >= 7)
+    {
+      unsigned a, b, c, d;
+
+      __cpuid_count (7, 0, a, b, c, d);
+      if (b & CPUID_FEATURE_RTM)
+	return 1;
+    }
+  return 0;
+}
+
+#define ITER 10
+#define MAXTRY 100
+
+pthread_mutex_t lock;
+
+#ifndef USE_TRYLOCK_ONLY
+static int
+pthread_mutex_timedlock_wrapper(pthread_mutex_t *l)
+{
+  struct timespec wait = { 0, 0 };
+  return pthread_mutex_timedlock (l, &wait);
+}
+#endif
+
+/* Note this test program will fail when single stepped.
+   It also assumes that simple transactions always work. There is no
+   guarantee in the architecture that this is the case. We do some
+   retries to handle random abort cases like interrupts. But it's
+   not fully guaranteed. However when this fails it is somewhat worrying. */
+
+int
+run_mutex (int expected, const char *name, int force)
+{
+  int i;
+  int try = 0;
+  int txn __attribute__((unused));
+  int err;
+
+#ifndef USE_TRYLOCK_ONLY
+  TESTLOCK(lock, pthread_mutex_lock, pthread_mutex_unlock, force);
+  TESTLOCK(lock, pthread_mutex_timedlock_wrapper, pthread_mutex_unlock, force);
+  TESTLOCK(lock, pthread_mutex_trylock, pthread_mutex_unlock, force);
+#else
+  TESTTRYLOCK(lock, pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, force);
+#endif
+
+  err = pthread_mutex_destroy (&lock);
+  if (err != 0)
+    {
+      printf ("destroy for %s failed: %d\n", name, err);
+      return 1;
+    }
+  return 0;
+}
+
+static int
+run_mutex_init (int iter, const char *name, int type, int has_type, int force,
+		int elision)
+{
+  pthread_mutexattr_t attr;
+
+  pthread_mutexattr_init (&attr);
+  if (type != -1)
+    pthread_mutexattr_settype (&attr, type);
+
+  pthread_mutex_init (&lock, has_type ? &attr : NULL);
+  return run_mutex (iter, name, force);
+}
+
+static int
+default_elision_enabled (void)
+{
+#ifdef ENABLE_LOCK_ELISION
+  return 1;
+#else
+  return 0;
+#endif
+}
+
+static int
+elision_override (const char *var, int iter)
+{
+  char *s = getenv(var);
+
+  s = getenv(var);
+  if (s && strstr (s, "none"))
+    return 0;
+  if (s && strstr (s, "elision"))
+    return ITER;
+  return iter;
+}
+
+int
+mutex_test (void)
+{
+  int ret = 0;
+  int default_iter = default_elision_enabled () ? ITER : 0;
+
+  default_iter = elision_override ("GLIBC_PTHREAD_MUTEX", default_iter);
+  printf("default_iter %d\n", default_iter);
+
+  lock = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
+  ret += run_mutex (default_iter, "default initializer timed", 0);
+
+#ifdef ADAPTIVE_SUPPORTS_ELISION
+  lock = (pthread_mutex_t) PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;
+  run_mutex (default_iter, "adaptive initializer default", 0);
+#endif
+
+  ret += run_mutex_init (default_iter, "timed init default", -1, 0, 0, -1);
+  ret += run_mutex_init (0, "normal no elision",
+			 PTHREAD_MUTEX_NORMAL, 1, 2, -1);
+
+#ifdef ADAPTIVE_SUPPORTS_ELISION
+  ret += run_mutex_init (default_iter, "adaptive init default",
+		         PTHREAD_MUTEX_ADAPTIVE_NP, 1, 0, -1);
+  ret += run_mutex_init (ITER, "adaptive init elision",
+                         PTHREAD_MUTEX_ADAPTIVE_NP, 1, 1, 1);
+  ret += run_mutex_init (0, "adaptive init no elision",
+		         PTHREAD_MUTEX_ADAPTIVE_NP,
+			  1, 2, 0);
+#endif
+
+  return ret;
+}
+
+pthread_rwlock_t rwlock;
+
+#ifndef USE_TRYLOCK_ONLY
+static int
+pthread_rwlock_timedwrlock_wrapper(pthread_rwlock_t *l)
+{
+  struct timespec wait = { 0, 0 };
+  return pthread_rwlock_timedwrlock (l, &wait);
+}
+
+static int
+pthread_rwlock_timedrdlock_wrapper(pthread_rwlock_t *l)
+{
+  struct timespec wait = { 0, 0 };
+  return pthread_rwlock_timedrdlock (l, &wait);
+}
+#endif
+
+int
+run_rwlock (int expected, const char *name, int force)
+{
+  int i;
+  int try = 0;
+  int txn __attribute__((unused));
+  int err;
+
+#ifndef USE_TRYLOCK_ONLY
+  TESTLOCK(rwlock, pthread_rwlock_rdlock, pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_wrlock, pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_rdlock, pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_tryrdlock, pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_trywrlock, pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_timedrdlock_wrapper,
+	   pthread_rwlock_unlock, force);
+  TESTLOCK(rwlock, pthread_rwlock_timedwrlock_wrapper,
+	   pthread_rwlock_unlock, force);
+#else
+  TESTTRYLOCK(rwlock, pthread_rwlock_wrlock, pthread_rwlock_trywrlock,
+	      pthread_rwlock_unlock, force);
+#endif
+
+  err = pthread_rwlock_destroy (&rwlock);
+  if (err != 0)
+    {
+      printf ("pthread_rwlock_destroy for %s failed: %d\n", name, err);
+      return 1;
+    }
+  return 0;
+}
+
+int
+run_rwlock_attr (int iter, const char *name, int type, int el, int force)
+{
+  pthread_rwlockattr_t attr;
+  pthread_rwlockattr_init (&attr);
+  pthread_rwlockattr_setkind_np (&attr, type);
+  pthread_rwlock_init (&rwlock, &attr);
+  return run_rwlock (iter, name, force);
+}
+
+int
+run_rwlock_attr_set (int iter, const char *extra, int flag, int force)
+{
+  char str[100];
+  int ret = 0;
+
+  snprintf(str, sizeof str, "rwlock attr prefer reader %s", extra);
+  ret += run_rwlock_attr (iter, str,
+                          PTHREAD_RWLOCK_PREFER_READER_NP, flag, force);
+  snprintf(str, sizeof str, "rwlock attr prefer writer %s", extra);
+  ret += run_rwlock_attr (iter, str,
+                          PTHREAD_RWLOCK_PREFER_WRITER_NP, flag, force);
+  snprintf(str, sizeof str, "rwlock attr prefer writer non recursive %s", extra);
+  ret += run_rwlock_attr (iter, str,
+		          PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, flag, force);
+  return ret;
+}
+
+
+int
+rwlock_test (void)
+{
+  int ret = 0;
+  int default_iter = default_elision_enabled () ? ITER : 0;
+
+  default_iter = elision_override ("GLIBC_PTHREAD_RWLOCK", default_iter);
+
+  pthread_rwlock_init (&rwlock, NULL);
+  ret += run_rwlock (default_iter, "rwlock created", 0);
+
+  rwlock = (pthread_rwlock_t)PTHREAD_RWLOCK_INITIALIZER;
+  ret += run_rwlock (default_iter, "rwlock initialized", 0);
+
+  rwlock = (pthread_rwlock_t)PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP;
+  ret += run_rwlock (default_iter, "rwlock initialized writer non recursive", 0);
+
+  rwlock = (pthread_rwlock_t)PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP;
+  ret += run_rwlock (default_iter, "rwlock initialized writer non recursive", 0);
+
+#ifdef PTHREAD_RWLOCK_WRITER_INITIALIZER_NP
+  // XXX includes are missing PTHREAD_RWLOCK_WRITER_INITIALIZER_NP
+  rwlock = (pthread_rwlock_t)PTHREAD_RWLOCK_WRITER_INITIALIZER_NP;
+  ret += run_rwlock (default_iter, "rwlock initialized writer", 0);
+#endif
+
+  ret += run_rwlock_attr_set (default_iter, "", 0, 0);
+
+  return ret;
+}
diff --git a/nptl/tst-elision1.c b/nptl/tst-elision1.c
new file mode 100644
index 0000000..5460863
--- /dev/null
+++ b/nptl/tst-elision1.c
@@ -0,0 +1,133 @@ 
+/* tst-elision1: Test basic elision success.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* To use on other architectures you would need an own version
+   of cpu_has_rtm and _xtest.  */
+#if defined(__i386__) || defined(__x86_64__)
+# include <elision-conf.h>
+# include <pthread.h>
+# include <stdlib.h>
+# include <string.h>
+# include <stdio.h>
+# include <hle.h>
+# include <config.h>
+
+int disabled;
+int forced;
+
+int
+check (const char *name, const char *lock, int try, int txn, int max,
+       int override)
+{
+  int should_run = 1;
+
+  if (override == 0)
+    should_run = disabled == 0;
+  else if (override == 1)
+    should_run = 1;
+  else if (override == 2)
+    should_run = 0;
+
+  /* forced noop right now, so not tested. But test if the defaults change.  */
+  if (!should_run)
+    {
+      if (txn != 0)
+	{
+	  printf ("%s %s transaction run unexpected txn:%d\n", name, lock, txn);
+	  return 1;
+	}
+    }
+  else
+    {
+      if (try == max)
+	{
+	  printf ("%s %s no transactions when expected\n", name, lock);
+	  return 1;
+	}
+    }
+  return 0;
+}
+
+# define TESTLOCK(l, lock, unlock, force)\
+  do					\
+    {					\
+      txn = 0;				\
+      for (i = 0; i < ITER; i++)	\
+	{				\
+	  lock (&l);			\
+	  if (_xtest ())		\
+	    txn++;			\
+	  unlock (&l);			\
+	}				\
+    }						\
+  while (try++ < MAXTRY && txn != expected);	\
+  if (check (name, #lock, try, txn, MAXTRY, force))	\
+    return 1;
+
+# include "tst-elision-common.c"
+
+static void
+set_default (const char *var)
+{
+  char *s = getenv (var);
+
+  if (s != NULL)
+    {
+      if (strstr (s, "elision"))
+	forced = 1;
+      else if (strstr (s, "none"))
+	disabled = 1;
+    }
+  else
+    {
+#ifdef ENABLE_LOCK_ELISION
+      forced = 1;
+#else
+      disabled = 1;
+#endif
+    }
+}
+
+int
+do_test (void)
+{
+  if (cpu_has_rtm () == 0)
+    {
+      printf ("elision test requires RTM capable CPU. not tested\n");
+      return 0;
+    }
+
+  set_default ("GLIBC_PTHREAD_MUTEX");
+  if (mutex_test ())
+    return 1;
+
+  set_default ("GLIBC_PTHREAD_RWLOCK");
+  if (rwlock_test ())
+    return 1;
+
+  return 0;
+}
+#else
+int do_test (void)
+{
+  return 0;
+}
+#endif
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nptl/tst-elision1b.c b/nptl/tst-elision1b.c
new file mode 100644
index 0000000..9f5ec3d
--- /dev/null
+++ b/nptl/tst-elision1b.c
@@ -0,0 +1 @@ 
+#include "tst-elision1.c"
diff --git a/nptl/tst-elision1c.c b/nptl/tst-elision1c.c
new file mode 100644
index 0000000..9f5ec3d
--- /dev/null
+++ b/nptl/tst-elision1c.c
@@ -0,0 +1 @@ 
+#include "tst-elision1.c"
diff --git a/nptl/tst-elision2.c b/nptl/tst-elision2.c
new file mode 100644
index 0000000..4ca0bed
--- /dev/null
+++ b/nptl/tst-elision2.c
@@ -0,0 +1,84 @@ 
+/* tst-elision2: Test elided trylock semantics
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* To use on other architectures you would need an own version of
+   cpu_has_rtm.  */
+#if defined(__i386__) || defined(__x86_64__)
+# include <pthread.h>
+# include <stdio.h>
+# include <hle.h>
+# include <cpuid.h>
+
+int check (int success, int force)
+{
+  /* Any nested trylock disabled right now. */
+  return success == 0;
+}
+
+# define TESTTRYLOCK(l, lock, trylock, unlock, force)	\
+   do					\
+    {					\
+      txn = 0;				\
+      for (i = 0; i < ITER; i++)	\
+	{				\
+	  lock (&l);			\
+	  if (trylock (&l) == 0)	\
+	    {				\
+	      txn++;			\
+	      unlock (&l);		\
+	    }				\
+	  unlock (&l);			\
+	}				\
+    }					\
+   while (try++ < MAXTRY && txn != ITER);				\
+   if (!check (txn == ITER, force))					\
+     {									\
+       printf ("%s %s nested trylock check failed txn:%d iter:%d force:%d\n", \
+					name, #lock, txn, ITER, force);	\
+       return 1;							\
+     }
+
+# define USE_TRYLOCK_ONLY 1
+# include "tst-elision-common.c"
+
+int
+do_test (void)
+{
+  if (cpu_has_rtm () == 0)
+    {
+      printf ("elision test requires RTM capable CPU. not tested\n");
+      return 0;
+    }
+
+  if (mutex_test ())
+    return 1;
+
+  if (rwlock_test ())
+    return 1;
+
+  return 0;
+}
+#else
+int do_test (void)
+{
+  return 0;
+}
+#endif
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nptl/tst-elision2b.c b/nptl/tst-elision2b.c
new file mode 100644
index 0000000..499c98f
--- /dev/null
+++ b/nptl/tst-elision2b.c
@@ -0,0 +1 @@ 
+#include "tst-elision2.c"
diff --git a/nptl/tst-elision2c.c b/nptl/tst-elision2c.c
new file mode 100644
index 0000000..499c98f
--- /dev/null
+++ b/nptl/tst-elision2c.c
@@ -0,0 +1 @@ 
+#include "tst-elision2.c"