Add de::SpinBarrier

SpinBarrier provides convenient cross-thread barriers.

Change-Id: I70eac2ed07b2c123d9709ecf5bbe284f35771204
diff --git a/Android.mk b/Android.mk
index 2115e14..9ace5f1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -98,6 +98,7 @@
 	framework/delibs/decpp/deSemaphore.cpp \
 	framework/delibs/decpp/deSharedPtr.cpp \
 	framework/delibs/decpp/deSocket.cpp \
+	framework/delibs/decpp/deSpinBarrier.cpp \
 	framework/delibs/decpp/deSTLUtil.cpp \
 	framework/delibs/decpp/deStringUtil.cpp \
 	framework/delibs/decpp/deThread.cpp \
diff --git a/framework/delibs/decpp/CMakeLists.txt b/framework/delibs/decpp/CMakeLists.txt
index f5120ef..974c385 100644
--- a/framework/delibs/decpp/CMakeLists.txt
+++ b/framework/delibs/decpp/CMakeLists.txt
@@ -55,6 +55,8 @@
 	deThreadSafeRingBuffer.hpp
 	deUniquePtr.cpp
 	deUniquePtr.hpp
+	deSpinBarrier.cpp
+	deSpinBarrier.hpp
 	)
 
 add_library(decpp STATIC ${DECPP_SRCS})
diff --git a/framework/delibs/decpp/deSpinBarrier.cpp b/framework/delibs/decpp/deSpinBarrier.cpp
new file mode 100644
index 0000000..150bbc2
--- /dev/null
+++ b/framework/delibs/decpp/deSpinBarrier.cpp
@@ -0,0 +1,196 @@
+/*-------------------------------------------------------------------------
+ * drawElements C++ Base Library
+ * -----------------------------
+ *
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Cross-thread barrier.
+ *//*--------------------------------------------------------------------*/
+
+#include "deSpinBarrier.hpp"
+#include "deThread.hpp"
+#include "deRandom.hpp"
+#include "deInt32.h"
+
+#include <vector>
+
+namespace de
+{
+
+SpinBarrier::SpinBarrier (deInt32 numThreads)
+	: m_numThreads	(numThreads)
+	, m_numEntered	(0)
+	, m_numLeaving	(0)
+{
+	DE_ASSERT(numThreads > 0);
+}
+
+SpinBarrier::~SpinBarrier (void)
+{
+	DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0);
+}
+
+void SpinBarrier::sync (WaitMode mode)
+{
+	DE_ASSERT(mode == WAIT_MODE_YIELD || mode == WAIT_MODE_BUSY);
+
+	deMemoryReadWriteFence();
+
+	if (m_numLeaving > 0)
+	{
+		for (;;)
+		{
+			if (m_numLeaving == 0)
+				break;
+
+			if (mode == WAIT_MODE_YIELD)
+				deYield();
+		}
+	}
+
+	if (deAtomicIncrement32(&m_numEntered) == m_numThreads)
+	{
+		m_numLeaving = m_numThreads;
+		deMemoryReadWriteFence();
+		m_numEntered = 0;
+	}
+	else
+	{
+		for (;;)
+		{
+			if (m_numEntered == 0)
+				break;
+
+			if (mode == WAIT_MODE_YIELD)
+				deYield();
+		}
+	}
+
+	deAtomicDecrement32(&m_numLeaving);
+	deMemoryReadWriteFence();
+}
+
+namespace
+{
+
+void singleThreadTest (SpinBarrier::WaitMode mode)
+{
+	SpinBarrier barrier(1);
+
+	barrier.sync(mode);
+	barrier.sync(mode);
+	barrier.sync(mode);
+}
+
+class TestThread : public de::Thread
+{
+public:
+	TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx, bool busyOk)
+		: m_barrier		(barrier)
+		, m_sharedVar	(sharedVar)
+		, m_numThreads	(numThreads)
+		, m_threadNdx	(threadNdx)
+		, m_busyOk		(busyOk)
+	{
+	}
+
+	void run (void)
+	{
+		const int	numIters	= 10000;
+		de::Random	rnd			(deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx));
+
+		for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
+		{
+			// Phase 1: count up
+			deAtomicIncrement32(m_sharedVar);
+
+			// Verify
+			m_barrier.sync(getWaitMode(rnd));
+
+			DE_TEST_ASSERT(*m_sharedVar == m_numThreads);
+
+			m_barrier.sync(getWaitMode(rnd));
+
+			// Phase 2: count down
+			deAtomicDecrement32(m_sharedVar);
+
+			// Verify
+			m_barrier.sync(getWaitMode(rnd));
+
+			DE_TEST_ASSERT(*m_sharedVar == 0);
+
+			m_barrier.sync(getWaitMode(rnd));
+		}
+	}
+
+private:
+	SpinBarrier&		m_barrier;
+	volatile deInt32*	m_sharedVar;
+	int					m_numThreads;
+	int					m_threadNdx;
+	bool				m_busyOk;
+
+	SpinBarrier::WaitMode getWaitMode (de::Random& rnd)
+	{
+		if (m_busyOk && rnd.getBool())
+			return SpinBarrier::WAIT_MODE_BUSY;
+		else
+			return SpinBarrier::WAIT_MODE_YIELD;
+	}
+};
+
+void multiThreadTest (int numThreads)
+{
+	SpinBarrier					barrier		(numThreads);
+	volatile deInt32			sharedVar	= 0;
+	std::vector<TestThread*>	threads		(numThreads, static_cast<TestThread*>(DE_NULL));
+
+	// Going over logical cores with busy-waiting will cause priority inversion and make tests take
+	// excessive amount of time. Use busy waiting only when number of threads is at most one per
+	// core.
+	const bool					busyOk		= (deUint32)numThreads <= deGetNumAvailableLogicalCores();
+
+	for (int ndx = 0; ndx < numThreads; ndx++)
+	{
+		threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx, busyOk);
+		DE_TEST_ASSERT(threads[ndx]);
+		threads[ndx]->start();
+	}
+
+	for (int ndx = 0; ndx < numThreads; ndx++)
+	{
+		threads[ndx]->join();
+		delete threads[ndx];
+	}
+
+	DE_TEST_ASSERT(sharedVar == 0);
+}
+
+} // namespace
+
+void SpinBarrier_selfTest (void)
+{
+	singleThreadTest(SpinBarrier::WAIT_MODE_YIELD);
+	singleThreadTest(SpinBarrier::WAIT_MODE_BUSY);
+	multiThreadTest(1);
+	multiThreadTest(2);
+	multiThreadTest(4);
+	multiThreadTest(8);
+	multiThreadTest(16);
+}
+
+} // de
diff --git a/framework/delibs/decpp/deSpinBarrier.hpp b/framework/delibs/decpp/deSpinBarrier.hpp
new file mode 100644
index 0000000..6679f8d
--- /dev/null
+++ b/framework/delibs/decpp/deSpinBarrier.hpp
@@ -0,0 +1,68 @@
+#ifndef _DESPINBARRIER_HPP
+#define _DESPINBARRIER_HPP
+/*-------------------------------------------------------------------------
+ * drawElements C++ Base Library
+ * -----------------------------
+ *
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief Cross-thread barrier.
+ *//*--------------------------------------------------------------------*/
+
+#include "deDefs.hpp"
+#include "deAtomic.h"
+
+namespace de
+{
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Cross-thread barrier
+ *
+ * SpinBarrier provides barrier implementation that uses spin loop for
+ * waiting for other threads. Threads may choose to wait in tight loop
+ * (WAIT_MODE_BUSY) or yield between iterations (WAIT_MODE_YIELD).
+ *//*--------------------------------------------------------------------*/
+class SpinBarrier
+{
+public:
+	enum WaitMode
+	{
+		WAIT_MODE_BUSY = 0,
+		WAIT_MODE_YIELD,
+
+		WAIT_MODE_LAST
+	};
+
+						SpinBarrier		(deInt32 numThreads);
+						~SpinBarrier	(void);
+
+	void				sync			(WaitMode mode);
+
+private:
+						SpinBarrier		(const SpinBarrier&);
+	SpinBarrier			operator=		(const SpinBarrier&);
+
+	const deInt32		m_numThreads;
+	volatile deInt32	m_numEntered;
+	volatile deInt32	m_numLeaving;
+};
+
+void	SpinBarrier_selfTest	(void);
+
+} // de
+
+#endif // _DESPINBARRIER_HPP
diff --git a/modules/internal/ditDelibsTests.cpp b/modules/internal/ditDelibsTests.cpp
index bdda4ff..df47341 100644
--- a/modules/internal/ditDelibsTests.cpp
+++ b/modules/internal/ditDelibsTests.cpp
@@ -52,6 +52,7 @@
 #include "deCommandLine.hpp"
 #include "deArrayBuffer.hpp"
 #include "deStringUtil.hpp"
+#include "deSpinBarrier.hpp"
 
 namespace dit
 {
@@ -159,6 +160,7 @@
 		addChild(new SelfCheckCase(m_testCtx, "commandline",				"de::cmdline::selfTest()",				de::cmdline::selfTest));
 		addChild(new SelfCheckCase(m_testCtx, "array_buffer",				"de::ArrayBuffer_selfTest()",			de::ArrayBuffer_selfTest));
 		addChild(new SelfCheckCase(m_testCtx, "string_util",				"de::StringUtil_selfTest()",			de::StringUtil_selfTest));
+		addChild(new SelfCheckCase(m_testCtx, "spin_barrier",				"de::SpinBarrier_selfTest()",			de::SpinBarrier_selfTest));
 	}
 };