| // Copyright 2015 The Emscripten Authors. All rights reserved. |
| // Emscripten is available under two separate licenses, the MIT license and the |
| // University of Illinois/NCSA Open Source License. Both these licenses can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <memory.h> |
| #include <pthread.h> |
| #include <emscripten.h> |
| #include <emscripten/threading.h> |
| #include <assert.h> |
| |
| #if !defined(__EMSCRIPTEN_PTHREADS__) || __EMSCRIPTEN_PTHREADS__ != 1 |
| #error __EMSCRIPTEN_PTHREADS__ should have been defined to be equal to 1 when building with pthreads support enabled! |
| #endif |
| |
| #define NUM_THREADS 8 |
| |
| volatile unsigned char globalUchar = 0; |
| volatile unsigned short globalUshort = 0; |
| volatile unsigned int globalUint = 0; |
| volatile float globalFloat = 0.0f; |
| volatile double globalDouble = 0.0; |
| |
| const int N = 10; |
| int sharedData[N] = {}; |
| |
| struct Test |
| { |
| int op; |
| int threadId; |
| }; |
| |
| uint64_t threadCasAccumulatedWrittenData[NUM_THREADS] = {}; |
| uint64_t threadCasAccumulatedReadData[NUM_THREADS] = {}; |
| |
| int rand_32() |
| { |
| return (int)(emscripten_random() * 0x3FFFFFFF); |
| } |
| |
| void *ThreadMain(void *arg) |
| { |
| assert(pthread_self() != 0); |
| assert(globalUchar == 5); |
| assert(globalUshort == 5); |
| assert(globalUint == 5); |
| assert(globalFloat == 5.0f); |
| assert(globalDouble == 5.0); |
| struct Test *t = (struct Test*)arg; |
| EM_ASM(out('Thread ' + $0 + ' for test ' + $1 + ': starting computation.'), t->threadId, t->op); |
| |
| for(int i = 0; i < 99999; ++i) |
| for(int j = 0; j < N; ++j) |
| { |
| switch(t->op) |
| { |
| case 0: emscripten_atomic_add_u32(&sharedData[j], 1); break; |
| case 1: emscripten_atomic_sub_u32(&sharedData[j], 1); break; |
| case 2: emscripten_atomic_and_u32(&sharedData[j], ~(1UL << t->threadId)); break; |
| case 3: emscripten_atomic_or_u32(&sharedData[j], 1UL << t->threadId); break; |
| case 4: emscripten_atomic_xor_u32(&sharedData[j], 1UL << t->threadId); break; |
| case 5: |
| { |
| // Atomically load and store data, and test that each individual u8 is the same. |
| int data = emscripten_atomic_load_u32(&sharedData[j]); |
| uint8_t dataU8[4]; |
| memcpy(dataU8, &data, 4); |
| assert(dataU8[0] >= 10 && dataU8[0] < 10+NUM_THREADS); |
| assert(dataU8[0] == dataU8[1] && dataU8[0] == dataU8[2] && dataU8[0] == dataU8[3]); |
| dataU8[0] = dataU8[1] = dataU8[2] = dataU8[3] = 10 + t->threadId; |
| memcpy(&data, dataU8, 4); |
| emscripten_atomic_store_u32(&sharedData[j], data); |
| } |
| break; |
| case 6: |
| { |
| int newData = rand_32(); |
| int data; |
| int prevData; |
| do { |
| data = emscripten_atomic_load_u32(&sharedData[j]); |
| prevData = emscripten_atomic_cas_u32(&sharedData[j], data, newData); |
| } while(prevData != data); |
| threadCasAccumulatedReadData[t->threadId] += data; |
| threadCasAccumulatedWrittenData[t->threadId] += newData; |
| } |
| break; |
| } |
| } |
| EM_ASM(out('Thread ' + $0 + ' for test ' + $1 + ': finished, exit()ing.'), t->threadId, t->op); |
| pthread_exit(0); |
| } |
| |
| struct Test t[NUM_THREADS] = {}; |
| pthread_t thread[NUM_THREADS]; |
| |
| void RunTest(int test) |
| { |
| pthread_attr_t attr; |
| pthread_attr_init(&attr); |
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); |
| pthread_attr_setstacksize(&attr, 4*1024); |
| |
| printf("Main thread has thread ID %d\n", (int)pthread_self()); |
| assert(pthread_self() != 0); |
| |
| switch(test) |
| { |
| case 2: memset(sharedData, 0xFF, sizeof(sharedData)); break; |
| case 5: memset(sharedData, 0x10, sizeof(sharedData)); break; |
| default: memset(sharedData, 0, sizeof(sharedData)); break; |
| } |
| |
| EM_ASM(out('Main: Starting test ' + $0), test); |
| |
| for(int i = 0; i < NUM_THREADS; ++i) |
| { |
| t[i].op = test; |
| t[i].threadId = i; |
| int rc = pthread_create(&thread[i], &attr, ThreadMain, &t[i]); |
| assert(rc == 0); |
| } |
| |
| pthread_attr_destroy(&attr); |
| |
| for(int i = 0; i < NUM_THREADS; ++i) |
| { |
| int status = 1; |
| int rc = pthread_join(thread[i], (void**)&status); |
| assert(rc == 0); |
| assert(status == 0); |
| } |
| |
| int val = sharedData[0]; |
| EM_ASM(out('Main: Test ' + $0 + ' finished. Result: ' + $1), test, val); |
| if (test != 6) |
| { |
| for(int i = 1; i < N; ++i) |
| assert(sharedData[i] == sharedData[0]); |
| } |
| } |
| |
| int main() |
| { |
| globalUchar = 4; |
| globalUshort = 4; |
| globalUint = 4; |
| globalFloat = 5.0f; |
| globalDouble = 5.0; |
| |
| uint8_t prevUchar = emscripten_atomic_add_u8((void*)&globalUchar, 1); assert(prevUchar == 4); |
| uint16_t prevUshort = emscripten_atomic_add_u16((void*)&globalUshort, 1); assert(prevUshort == 4); |
| uint32_t prevUint = emscripten_atomic_add_u32((void*)&globalUint, 1); assert(prevUint == 4); |
| |
| emscripten_atomic_fence(); |
| __sync_synchronize(); |
| |
| if (!emscripten_has_threading_support()) |
| { |
| #ifdef REPORT_RESULT |
| REPORT_RESULT(0); |
| #endif |
| printf("Skipped: Threading is not supported.\n"); |
| return 0; |
| } |
| |
| for(int i = 0; i < 7; ++i) |
| RunTest(i); |
| |
| uint64_t totalRead = 0; |
| uint64_t totalWritten = 0; |
| for(int i = 0; i < NUM_THREADS; ++i) |
| { |
| totalRead += threadCasAccumulatedReadData[i]; |
| totalWritten += threadCasAccumulatedWrittenData[i]; |
| } |
| for(int i = 0; i < N; ++i) |
| totalRead += sharedData[i]; |
| if (totalRead == totalWritten) |
| printf("totalRead: %llu, totalWritten: %llu\n", totalRead, totalWritten); |
| else |
| printf("32-bit CAS test failed! totalRead != totalWritten (%llu != %llu)\n", totalRead, totalWritten); |
| #ifdef REPORT_RESULT |
| int result = (totalRead != totalWritten) ? 1 : 0; |
| REPORT_RESULT(result); |
| #else |
| EM_ASM(out('Main: Test successfully finished.')); |
| #endif |
| } |