From d73f22407657709aba05a643facd43ff1060d109 Mon Sep 17 00:00:00 2001
From: Sam Yates <yates@cscs.ch>
Date: Wed, 30 Nov 2016 09:20:54 +0100
Subject: [PATCH] Work around xlC 13.1.4 compiler bugs. (#115)

Addresses in part issue #113.

* Make compatibility wrappers/functions that dance around xlC
  bugs. Wrappers are provied in `util/compat.hpp` and live in
  the `compat` namespace.
      - `compat::end` reimplements `std::end` but in a way that
	apparently does not trigger the xlC bug.
      - `compat::compiler_barrier_if_xlc_leq()` inserts a compiler
	reordering barrier if the compiler is xlC and the version
	less than or equal to that specified. Name is deliberately
	verbose.
      - `compat::isinf()` is an inline wrapper around `std::isinf()`,
	which apparently is sufficient to defuse an evaluation
	order bug with `std::isinf()` in switch statements.
* Use `compat::compiler_barrier_if_xlc_leq()` in `util::unitialized`
  reference access methods to avoid improper reordering with -O2.
* Use `compat::isinf()` in `test_math.cpp` to defuse improper
  reordering within `EXPECT_EQ` gtest macro of `std::isinf()`.
* Use `compat::end()` in `util::back()` and `util::cend()` to avoid
  incorrect `std::end()` behaviour with -O2.
* Use `util::cend()` in `algorithms::sum()`, again to avoid
  incorrect `std::end()` behaviour with -O2.
---
 src/algorithms.hpp         |  2 +-
 src/util/compat.hpp        | 37 +++++++++++++++++++++++++++++++++++++
 src/util/iterutil.hpp      |  4 +++-
 src/util/meta.hpp          |  7 +++++--
 src/util/uninitialized.hpp | 15 +++++++++++++--
 tests/unit/test_math.cpp   | 11 ++++++++---
 6 files changed, 67 insertions(+), 9 deletions(-)
 create mode 100644 src/util/compat.hpp

diff --git a/src/algorithms.hpp b/src/algorithms.hpp
index 43893b31..9a3ecdf7 100644
--- a/src/algorithms.hpp
+++ b/src/algorithms.hpp
@@ -29,7 +29,7 @@ typename util::sequence_traits<C>::value_type
 sum(C const& c)
 {
     using value_type = typename util::sequence_traits<C>::value_type;
-    return std::accumulate(std::begin(c), std::end(c), value_type{0});
+    return std::accumulate(util::cbegin(c), util::cend(c), value_type{0});
 }
 
 template <typename C>
diff --git a/src/util/compat.hpp b/src/util/compat.hpp
new file mode 100644
index 00000000..c288effe
--- /dev/null
+++ b/src/util/compat.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+/* Collection of compatibility workarounds to deal with compiler defects */
+
+#include <cstddef>
+#include <cmath>
+
+namespace compat {
+
+// std::end() broken with (at least) xlC 13.1.4.
+
+template <typename T>
+auto end(T& x) -> decltype(x.end()) { return x.end(); }
+
+template <typename T, std::size_t N>
+T* end(T (&x)[N]) { return &x[0]+N; }
+
+template <typename T, std::size_t N>
+const T* end(const T (&x)[N]) { return &x[0]+N; }
+
+// workaround bad optimization reordering in xlC 13.1.4
+
+inline void compiler_barrier_if_xlc_leq(unsigned ver) {
+#if defined(__xlC__)
+    if (__xlC__<=ver) {
+        asm volatile ("" ::: "memory");
+    }
+#endif
+}
+
+// Work around bad ordering of std::isinf() (sometimes) within switch, xlC 13.1.4;
+// wrapping the call within another function appears to be sufficient.
+
+template <typename X>
+inline constexpr bool isinf(X x) { return std::isinf(x); }
+
+}
diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp
index 8c327d41..00e52d62 100644
--- a/src/util/iterutil.hpp
+++ b/src/util/iterutil.hpp
@@ -10,6 +10,7 @@
 #include <type_traits>
 #include <utility>
 
+#include <util/compat.hpp>
 #include <util/meta.hpp>
 
 namespace nest {
@@ -78,7 +79,8 @@ auto front(Seq& seq) -> decltype(*std::begin(seq)) {
 
 template <typename Seq>
 auto back(Seq& seq) -> decltype(*std::begin(seq)) {
-    return *upto(std::begin(seq), std::end(seq));
+    // COMPAT: use own `end` implementation to work around xlC 13.1 bug.
+    return *upto(std::begin(seq), compat::end(seq));
 }
 
 /*
diff --git a/src/util/meta.hpp b/src/util/meta.hpp
index 3bc8d941..34b809bb 100644
--- a/src/util/meta.hpp
+++ b/src/util/meta.hpp
@@ -6,6 +6,8 @@
 #include <iterator>
 #include <type_traits>
 
+#include <util/compat.hpp>
+
 namespace nest {
 namespace mc {
 namespace util {
@@ -36,8 +38,9 @@ constexpr auto cbegin(const T& c) -> decltype(std::begin(c)) {
 }
 
 template <typename T>
-constexpr auto cend(const T& c) -> decltype(std::end(c)) {
-    return std::end(c);
+constexpr auto cend(const T& c) -> decltype(compat::end(c)) {
+    // COMPAT: use own `end` implementation to work around xlC 13.1 bug.
+    return compat::end(c);
 }
 
 template <typename T>
diff --git a/src/util/uninitialized.hpp b/src/util/uninitialized.hpp
index a13842ac..f9c1bf36 100644
--- a/src/util/uninitialized.hpp
+++ b/src/util/uninitialized.hpp
@@ -12,6 +12,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "util/compat.hpp"
 #include "util/meta.hpp"
 
 namespace nest {
@@ -33,8 +34,18 @@ public:
     using reference = X&;
     using const_reference= const X&;
 
-    pointer ptr() { return static_cast<X*>(static_cast<void*>(&data)); }
-    const_pointer cptr() const { return static_cast<const X*>(static_cast<const void*>(&data)); }
+    pointer ptr() {
+        // COMPAT: xlC 13.1.4 workaround:
+        // should be equivalent to `return reinterpret_cast<X*>(&data)`.
+        compat::compiler_barrier_if_xlc_leq(0x0d01);
+        return static_cast<X*>(static_cast<void*>(&data));
+    }
+    const_pointer cptr() const {
+        // COMPAT: xlC 13.1.4 workaround:
+        // should be equivalent to `return reinterpret_cast<const X*>(&data)`
+        compat::compiler_barrier_if_xlc_leq(0x0d01);
+        return static_cast<const X*>(static_cast<const void*>(&data));
+    }
 
     reference ref() { return *ptr(); }
     const_reference cref() const { return *cptr(); }
diff --git a/tests/unit/test_math.cpp b/tests/unit/test_math.cpp
index 2bae462d..1a5f442f 100644
--- a/tests/unit/test_math.cpp
+++ b/tests/unit/test_math.cpp
@@ -2,7 +2,9 @@
 #include <limits>
 
 #include "../gtest.h"
+
 #include <math.hpp>
+#include <util/compat.hpp>
 
 using namespace nest::mc::math;
 
@@ -82,17 +84,20 @@ TEST(math, infinity) {
     // check values for float, double, long double
     auto finf = infinity<float>();
     EXPECT_TRUE((std::is_same<float, decltype(finf)>::value));
-    EXPECT_TRUE(std::isinf(finf));
+    // COMPAT: use compatibility wrapper for isinf() thanks to xlC 13.1 bug.
+    EXPECT_TRUE(compat::isinf(finf));
     EXPECT_GT(finf, 0.f);
 
     auto dinf = infinity<double>();
     EXPECT_TRUE((std::is_same<double, decltype(dinf)>::value));
-    EXPECT_TRUE(std::isinf(dinf));
+    // COMPAT: use compatibility wrapper for isinf() thanks to xlC 13.1 bug.
+    EXPECT_TRUE(compat::isinf(dinf));
     EXPECT_GT(dinf, 0.0);
 
     auto ldinf = infinity<long double>();
     EXPECT_TRUE((std::is_same<long double, decltype(ldinf)>::value));
-    EXPECT_TRUE(std::isinf(ldinf));
+    // COMPAT: use compatibility wrapper for isinf() thanks to xlC 13.1 bug.
+    EXPECT_TRUE(compat::isinf(ldinf));
     EXPECT_GT(ldinf, 0.0l);
 
     // check default value promotes correctly (i.e., acts like INFINITY)
-- 
GitLab