1 /*-------------------------------------------------------------------------
2  * drawElements C++ Base Library
3  * -----------------------------
4  *
5  * Copyright 2015 The Android Open Source Project
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  *//*!
20  * \file
21  * \brief Cross-thread barrier.
22  *//*--------------------------------------------------------------------*/
23 
24 #include "deSpinBarrier.hpp"
25 #include "deThread.hpp"
26 #include "deRandom.hpp"
27 #include "deInt32.h"
28 
29 #include <vector>
30 
31 namespace de
32 {
33 
SpinBarrier(deInt32 numThreads)34 SpinBarrier::SpinBarrier (deInt32 numThreads)
35 	: m_numThreads	(numThreads)
36 	, m_numEntered	(0)
37 	, m_numLeaving	(0)
38 {
39 	DE_ASSERT(numThreads > 0);
40 }
41 
~SpinBarrier(void)42 SpinBarrier::~SpinBarrier (void)
43 {
44 	DE_ASSERT(m_numEntered == 0 && m_numLeaving == 0);
45 }
46 
sync(WaitMode mode)47 void SpinBarrier::sync (WaitMode mode)
48 {
49 	DE_ASSERT(mode == WAIT_MODE_YIELD || mode == WAIT_MODE_BUSY);
50 
51 	deMemoryReadWriteFence();
52 
53 	if (m_numLeaving > 0)
54 	{
55 		for (;;)
56 		{
57 			if (m_numLeaving == 0)
58 				break;
59 
60 			if (mode == WAIT_MODE_YIELD)
61 				deYield();
62 		}
63 	}
64 
65 	if (deAtomicIncrement32(&m_numEntered) == m_numThreads)
66 	{
67 		m_numLeaving = m_numThreads;
68 		deMemoryReadWriteFence();
69 		m_numEntered = 0;
70 	}
71 	else
72 	{
73 		for (;;)
74 		{
75 			if (m_numEntered == 0)
76 				break;
77 
78 			if (mode == WAIT_MODE_YIELD)
79 				deYield();
80 		}
81 	}
82 
83 	deAtomicDecrement32(&m_numLeaving);
84 	deMemoryReadWriteFence();
85 }
86 
87 namespace
88 {
89 
singleThreadTest(SpinBarrier::WaitMode mode)90 void singleThreadTest (SpinBarrier::WaitMode mode)
91 {
92 	SpinBarrier barrier(1);
93 
94 	barrier.sync(mode);
95 	barrier.sync(mode);
96 	barrier.sync(mode);
97 }
98 
99 class TestThread : public de::Thread
100 {
101 public:
TestThread(SpinBarrier & barrier,volatile deInt32 * sharedVar,int numThreads,int threadNdx,bool busyOk)102 	TestThread (SpinBarrier& barrier, volatile deInt32* sharedVar, int numThreads, int threadNdx, bool busyOk)
103 		: m_barrier		(barrier)
104 		, m_sharedVar	(sharedVar)
105 		, m_numThreads	(numThreads)
106 		, m_threadNdx	(threadNdx)
107 		, m_busyOk		(busyOk)
108 	{
109 	}
110 
run(void)111 	void run (void)
112 	{
113 		const int	numIters	= 10000;
114 		de::Random	rnd			(deInt32Hash(m_numThreads) ^ deInt32Hash(m_threadNdx));
115 
116 		for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
117 		{
118 			// Phase 1: count up
119 			deAtomicIncrement32(m_sharedVar);
120 
121 			// Verify
122 			m_barrier.sync(getWaitMode(rnd));
123 
124 			DE_TEST_ASSERT(*m_sharedVar == m_numThreads);
125 
126 			m_barrier.sync(getWaitMode(rnd));
127 
128 			// Phase 2: count down
129 			deAtomicDecrement32(m_sharedVar);
130 
131 			// Verify
132 			m_barrier.sync(getWaitMode(rnd));
133 
134 			DE_TEST_ASSERT(*m_sharedVar == 0);
135 
136 			m_barrier.sync(getWaitMode(rnd));
137 		}
138 	}
139 
140 private:
141 	SpinBarrier&		m_barrier;
142 	volatile deInt32*	m_sharedVar;
143 	int					m_numThreads;
144 	int					m_threadNdx;
145 	bool				m_busyOk;
146 
getWaitMode(de::Random & rnd)147 	SpinBarrier::WaitMode getWaitMode (de::Random& rnd)
148 	{
149 		if (m_busyOk && rnd.getBool())
150 			return SpinBarrier::WAIT_MODE_BUSY;
151 		else
152 			return SpinBarrier::WAIT_MODE_YIELD;
153 	}
154 };
155 
multiThreadTest(int numThreads)156 void multiThreadTest (int numThreads)
157 {
158 	SpinBarrier					barrier		(numThreads);
159 	volatile deInt32			sharedVar	= 0;
160 	std::vector<TestThread*>	threads		(numThreads, static_cast<TestThread*>(DE_NULL));
161 
162 	// Going over logical cores with busy-waiting will cause priority inversion and make tests take
163 	// excessive amount of time. Use busy waiting only when number of threads is at most one per
164 	// core.
165 	const bool					busyOk		= (deUint32)numThreads <= deGetNumAvailableLogicalCores();
166 
167 	for (int ndx = 0; ndx < numThreads; ndx++)
168 	{
169 		threads[ndx] = new TestThread(barrier, &sharedVar, numThreads, ndx, busyOk);
170 		DE_TEST_ASSERT(threads[ndx]);
171 		threads[ndx]->start();
172 	}
173 
174 	for (int ndx = 0; ndx < numThreads; ndx++)
175 	{
176 		threads[ndx]->join();
177 		delete threads[ndx];
178 	}
179 
180 	DE_TEST_ASSERT(sharedVar == 0);
181 }
182 
183 } // namespace
184 
SpinBarrier_selfTest(void)185 void SpinBarrier_selfTest (void)
186 {
187 	singleThreadTest(SpinBarrier::WAIT_MODE_YIELD);
188 	singleThreadTest(SpinBarrier::WAIT_MODE_BUSY);
189 	multiThreadTest(1);
190 	multiThreadTest(2);
191 	multiThreadTest(4);
192 	multiThreadTest(8);
193 	multiThreadTest(16);
194 }
195 
196 } // de
197