Merge remote-tracking branch 'redux/master' into sh4-pool
[tamarin-stm.git] / extensions / ST_vmbase_concurrency.st
blob90faddaddfd0aedb39620049d4f3e1b1b94dc460
1 // -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
2 // vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 //
4 // ***** BEGIN LICENSE BLOCK *****
5 // Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 //
7 // The contents of this file are subject to the Mozilla Public License Version
8 // 1.1 (the "License"); you may not use this file except in compliance with
9 // the License. You may obtain a copy of the License at
10 // http://www.mozilla.org/MPL/
12 // Software distributed under the License is distributed on an "AS IS" basis,
13 // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 // for the specific language governing rights and limitations under the
15 // License.
17 // The Original Code is [Open Source Virtual Machine.].
19 // The Initial Developer of the Original Code is
20 // Adobe System Incorporated.
21 // Portions created by the Initial Developer are Copyright (C) 2004-2006
22 // the Initial Developer. All Rights Reserved.
24 // Contributor(s):
25 //   Adobe AS3 Team
27 // Alternatively, the contents of this file may be used under the terms of
28 // either the GNU General Public License Version 2 or later (the "GPL"), or
29 // the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 // in which case the provisions of the GPL or the LGPL are applicable instead
31 // of those above. If you wish to allow use of your version of this file only
32 // under the terms of either the GPL or the LGPL, and not to allow others to
33 // use your version of this file under the terms of the MPL, indicate your
34 // decision by deleting the provisions above and replace them with the notice
35 // and other provisions required by the GPL or the LGPL. If you do not delete
36 // the provisions above, a recipient may use your version of this file under
37 // the terms of any one of the MPL, the GPL or the LGPL.
39 // ***** END LICENSE BLOCK ***** */
41 %%component vmbase
42 %%category  concurrency
44 %%prefix
45 using namespace MMgc;
46 using namespace vmbase;
48 // We use the same testing method as that for ST_vmpi_threads:
49 // Each construct is tested by (1) using it in the implementation of a mutator that
50 // modifies a counter for a fixed number of iterations, and then (2) running
51 // duplicates of that mutator in parallel. The final counter value ends up in
52 // sharedCounter, which is guarded by m_monitor (except for in CASTest).
53 // Each test checks that the sharedCounter ends up with a statically determined
54 // end value.
55 // (Description courtesy of Felix)
57 // We need a specific namespace as ST_vmpi_threads uses the same test class names
58 namespace selftestconcurrency {
60     class ThreadTestBase : public Runnable {
61         public:
62             ThreadTestBase(int iterations) : m_iterations(iterations), sharedCounter(0) {
63             }
64             virtual ~ThreadTestBase() {
65             }
67         protected:
68             WaitNotifyMonitor m_monitor;
69             const int m_iterations;
71         public:
72             int sharedCounter;
73     };
75     class TestRunner {
76         public:
77             struct ThreadRecord {
78                 VMThread* thread;
79                 bool startupOk;
80             };
82             TestRunner(int threadQty, bool doJoin = true) : m_threadQty(threadQty), m_doJoin(doJoin) {
83             }
84             virtual ~TestRunner() {
85             }
87             void runTest(ThreadTestBase& test) {
89                 m_threads = mmfx_new_array(ThreadRecord, m_threadQty);
91                 // Start up the threads
92                 for (int i = 0; i < m_threadQty; i++) {
93                     m_threads[i].thread = mmfx_new(VMThread(&test));
94                     m_threads[i].startupOk = m_threads[i].thread->start();
95                 }
97                 // ...then block until they all terminate
98                 for (int i = 0; i < m_threadQty; i++) {
99                     if (m_doJoin && m_threads[i].startupOk) {
100                         m_threads[i].thread->join();
101                     }
102                     mmfx_delete(m_threads[i].thread);
103                 }
105                 mmfx_delete_array(m_threads);
106             }
108         private:
109             const int m_threadQty;
110             ThreadRecord* m_threads;
111             bool m_doJoin;
112     };
114     class MutexTest : public ThreadTestBase {
115         public:
116             MutexTest(int iterations) : ThreadTestBase(iterations) {}
117             virtual ~MutexTest() {}
119             virtual void run() {
120                 AvmAssert(m_iterations % 2 == 0);
121                 for (int i = 0; i < m_iterations/2; i++) {
122                     SCOPE_LOCK(m_monitor) {
123                         SCOPE_LOCK(m_monitor) {
124                             sharedCounter++;
125                         }
126                     }
127                 }
128                 for (int i = 0; i < m_iterations/2; i++) {
129                     SCOPE_LOCK_NAMED(locker, m_monitor) {
130                         SCOPE_LOCK_NAMED(locker, m_monitor) {
131                             sharedCounter++;
132                         }
133                     }
134                 }
135             }
136     };
137     class ConditionTest : public ThreadTestBase {
138         public:
139             ConditionTest(int iterations, int threadQty) : ThreadTestBase(iterations), m_threadQty(threadQty) {}
140             virtual ~ConditionTest() {}
142             virtual void run() {
144                 AvmAssert(m_threadQty >= 2);
146                 for (int i = 0; i < m_iterations; i++) {
147                     SCOPE_LOCK_NAMED(locker, m_monitor) {
148                         sharedCounter++;
149                         // If there's another thread still active then wait.
150                         if (m_threadQty > 1) {
151                             locker.notify();
152                             locker.wait();
153                         }
154                         // This thread has finished, so let's wake everyone else up
155                         if (i == m_iterations - 1) {
156                             --m_threadQty;
157                             locker.notifyAll();
158                         }
159                     }
161                 }
163             }
165         private:
166             int m_threadQty;
167     };
169     class AtomicCounterTest : public ThreadTestBase {
170         public:
171             AtomicCounterTest(int iterations, int threadQty) : ThreadTestBase(iterations), m_threadQty(threadQty) {}
172             virtual ~AtomicCounterTest() {}
174             virtual void run() {
176                 AvmAssert(m_iterations % 4 == 0);
178                 for (int i = 0; i < m_iterations/4; i++) {
179                     m_counter++;
180                 }
181                 for (int i = 0; i < m_iterations/4; i++) {
182                     m_counter--;
183                 }
184                 for (int i = 0; i < m_iterations/4; i++) {
185                     ++m_counter;
186                 }
187                 for (int i = 0; i < m_iterations/4; i++) {
188                     --m_counter;
189                 }
191                 SCOPE_LOCK(m_monitor) {
192                     if (--m_threadQty == 0) {
193                         sharedCounter = m_counter;
194                     }
195                 }
196             }
197         private:
198             AtomicCounter32 m_counter;
199             int m_threadQty;
200     };
202     class CASTest : public ThreadTestBase {
203         public:
204             CASTest(int iterations, bool withBarrier) : ThreadTestBase(iterations), m_withBarrier(withBarrier) {}
205             virtual ~CASTest() {}
207             virtual void run() {
208                 if (m_withBarrier) {
209                     for (int i = 0; i < m_iterations; i++) {
210                         int32_t current, next;
211                         do {
212                             current = sharedCounter;
213                             next = current + 1;
214                         } while (!AtomicOps::compareAndSwap32WithBarrier(current, next, &sharedCounter));
215                     }
216                 } else {
217                     for (int i = 0; i < m_iterations; i++) {
218                         int32_t current, next;
219                         do {
220                             current = sharedCounter;
221                             next = current + 1;
222                         } while (!AtomicOps::compareAndSwap32(current, next, &sharedCounter));
223                     }
224                 }
225             }
226         private:
227             bool m_withBarrier;
228     };
230     /**
231      * We protect a shared counter with a Dekker-style lock that has been made
232      * sequentially consistent with memory barriers.
233      *
234      * The idea is that if the barriers are correct, then two threads can compete
235      * to update the counter n times each, so that the final counter value is 2n. If
236      * the final value is not 2n, then the barriers have failed to ensure sequential
237      * consistency.
238      *
239      * FIXME: bug 609943
240      * This seems way too complicated. We have to be confident in the algorithm
241      * before considering the barrier implementations, and I'm not convinced as yet.
242      * Is there something simpler?
243      * Note that the barriers below are extremely conservative.
244      *
245      * This is test is not actually run. The verifyPass below just returns true.
246      */
247     class MemoryBarrierTest : public ThreadTestBase {
248         public:
249             MemoryBarrierTest(int iterations) : ThreadTestBase(iterations), m_thread0(0), m_thread1(0), m_turn(NULL) {}
250             virtual ~MemoryBarrierTest() {}
252             virtual void run() {
254                 volatile int* me;
255                 volatile int* other;
256                 volatile int* const counterp = &sharedCounter;
258                 SCOPE_LOCK(m_monitor) {
259                     if (m_turn == NULL) {
260                         me = &m_thread0;
261                         other = &m_thread1;
262                         m_turn = me;
263                     } else {
264                         me = &m_thread1;
265                         other = &m_thread0;
266                     }
267                 }
269                 for (int i = 0; i < m_iterations; i++) {
270                     // Dekker lock
271                     *me = 1;
272                     MemoryBarrier::readWrite();
273                     while (*other == 1) {
274                         MemoryBarrier::readWrite();
275                         if (m_turn == other) {
276                             MemoryBarrier::readWrite();
277                             *me = 0;
278                             MemoryBarrier::readWrite();
279                             while (m_turn == other) {
280                                 MemoryBarrier::readWrite();
281                             }
282                             MemoryBarrier::readWrite();
283                             *me = 1;
284                             MemoryBarrier::readWrite();
285                         }
286                     }
287                     MemoryBarrier::readWrite();
288                     (*counterp)++;
289                     MemoryBarrier::readWrite();
290                     m_turn = other;
291                     MemoryBarrier::readWrite();
292                     *me = 0;
293                     MemoryBarrier::readWrite();
294                 }
295             }
296         private:
297             volatile int m_thread0;
298             volatile int m_thread1;
299             volatile int* volatile m_turn;
300     };
302     class ConditionWithWaitTest : public ThreadTestBase {
303         public:
304             ConditionWithWaitTest(int iterations) : ThreadTestBase(iterations) {}
305             virtual ~ConditionWithWaitTest() {}
307             virtual void run() {
308                 for (int i = 0; i < m_iterations; i++) {
309                     SCOPE_LOCK_NAMED(locker, m_monitor) {
310                         sharedCounter++;
311                         locker.wait(1);
312                     }
313                 }
314             }
315     };
317     class SleepTest : public ThreadTestBase {
318         public:
319             SleepTest(int iterations) : ThreadTestBase(iterations) {}
320             virtual ~SleepTest() {}
322             virtual void run() {
323                 for (int i = 0; i < m_iterations; i++) {
324                     SCOPE_LOCK(m_monitor) {
325                         sharedCounter++;
326                     }
327                     VMThread::sleep(1);
328                 }
329             }
330     };
332     class VMThreadLocalTest : public ThreadTestBase {
333         public:
334             VMThreadLocalTest(int iterations) : ThreadTestBase(iterations) {}
335             virtual ~VMThreadLocalTest() {}
337             virtual void run() {
338                 for (int i = 0; i < m_iterations; i++) {
339                     m_localCounter.set(m_localCounter.get() + 1);
340                 }
341                 SCOPE_LOCK(m_monitor) {
342                     sharedCounter += (int)m_localCounter;
343                 }
344             }
345         private:
346             VMThreadLocal<uintptr_t> m_localCounter;
347     };
350 // This needs to be at least 2 for ConditionTest
351 #define THREAD_QTY 4
352 #define ITERATIONS 100000
354 using namespace selftestconcurrency;
356 %%test mutexes
357 #ifndef UNDER_CE
358     TestRunner runner(THREAD_QTY);
359     MutexTest test(ITERATIONS);
360     runner.runTest(test);
361     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
362 #endif
364 %%test conditions
365 #ifndef UNDER_CE
366     TestRunner runner(THREAD_QTY);
367     ConditionTest test(ITERATIONS, THREAD_QTY);
368     runner.runTest(test);
369     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
370 #endif
372 %%test atomic_counter
373 #ifndef UNDER_CE
374     TestRunner runner(THREAD_QTY);
375     AtomicCounterTest test(ITERATIONS, THREAD_QTY);
376     runner.runTest(test);
377     %%verify test.sharedCounter == 0
378 #endif
380 %%test compare_and_swap_without_barrier
381 #ifndef UNDER_CE
382     TestRunner runner(THREAD_QTY);
383     CASTest test(ITERATIONS, false);
384     runner.runTest(test);
385     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
386 #endif
388 %%test compare_and_swap_with_barrier
389 #ifndef UNDER_CE
390     TestRunner runner(THREAD_QTY);
391     CASTest test(ITERATIONS, true);
392     runner.runTest(test);
393     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
394 #endif
396 %%test memory_barrier
397 #ifndef UNDER_CE
398     /* This test is failing on Windows and Mac OSX 10.4.
399      * For Windows, see bug 609820.
400      * For Mac, are the 10.4 APIs not reliable?
401      * It could also be the test, or the compiler!
402      * FIXME: bug 609943 Selftests to stress memory barriers (fences)
404     // Note that the memory barrier test is based on a Dekker lock, so we
405     // only ever use 2 threads.
406     TestRunner runner(2);
407     MemoryBarrierTest test(ITERATIONS);
408     runner.runTest(test);
409     %%verify test.sharedCounter == 2 * ITERATIONS
410     */
411     
412     %%verify true
413 #endif
415 %%test condition_with_wait
416 #ifndef UNDER_CE
417     TestRunner runner(THREAD_QTY);
418     ConditionWithWaitTest test(2000); // Use 2000 iterations with a 1 ms wait
419     runner.runTest(test);
420     %%verify test.sharedCounter == THREAD_QTY * 2000
421 #endif
423 %%test sleep
424 #ifndef UNDER_CE
425     TestRunner runner(THREAD_QTY);
426     SleepTest test(2000); // Use 2000 iterations with a 1 ms sleep
427     runner.runTest(test);
428     %%verify test.sharedCounter == THREAD_QTY * 2000
429 #endif
431 %%test vmthreadlocal
432 #ifndef UNDER_CE
433     TestRunner runner(THREAD_QTY);
434     VMThreadLocalTest test(ITERATIONS);
435     runner.runTest(test);
436     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
437 #endif
439 %%test join
440 #ifndef UNDER_CE
441     // We should be able to run the dtor of a non-started VMThread.
442     {
443         VMThread vmthread;
444     }
445     // Run the mutex test but call the VMThread dtors without joining first
446     TestRunner runner(THREAD_QTY, false);
447     MutexTest test(ITERATIONS);
448     runner.runTest(test);
449     %%verify test.sharedCounter == THREAD_QTY * ITERATIONS
450 #endif