diff --git a/emcc b/emcc index 4b9caf2481e3f..025daaf9dcecc 100755 --- a/emcc +++ b/emcc @@ -1000,6 +1000,28 @@ try: if js_opts: shared.Settings.RUNNING_JS_OPTS = 1 + if shared.Settings.USE_PTHREADS: + if not any(s.startswith('PTHREAD_POOL_SIZE=') for s in settings_changes): + settings_changes.append('PTHREAD_POOL_SIZE=0') + js_libraries.append(shared.path_from_root('src', 'library_pthread.js')) + newargs.append('-D__EMSCRIPTEN_PTHREADS__=1') + else: + js_libraries.append(shared.path_from_root('src', 'library_pthread_stub.js')) + + if shared.Settings.USE_PTHREADS: + if shared.Settings.PROXY_TO_WORKER: + logging.error('-s PROXY_TO_WORKER=1 is not yet supported with -s USE_PTHREADS=1!') + exit(1) + if shared.Settings.LINKABLE: + logging.error('-s LINKABLE=1 is not supported with -s USE_PTHREADS=1!') + exit(1) + if shared.Settings.SIDE_MODULE: + logging.error('-s SIDE_MODULE=1 is not supported with -s USE_PTHREADS=1!') + exit(1) + if shared.Settings.MAIN_MODULE: + logging.error('-s MAIN_MODULE=1 is not supported with -s USE_PTHREADS=1!') + exit(1) + shared.Settings.EMSCRIPTEN_VERSION = shared.EMSCRIPTEN_VERSION shared.Settings.OPT_LEVEL = opt_level shared.Settings.DEBUG_LEVEL = debug_level @@ -1351,6 +1373,9 @@ try: final += '.mem.js' src = None + if shared.Settings.USE_PTHREADS: + shutil.copyfile(shared.path_from_root('src', 'pthread-main.js'), os.path.join(os.path.dirname(os.path.abspath(target)), 'pthread-main.js')) + log_time('source transforms') # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing @@ -1606,7 +1631,7 @@ try: + {{{ SCRIPT }}} + + diff --git a/tests/pthread/test_pthread_mutex.cpp b/tests/pthread/test_pthread_mutex.cpp new file mode 100644 index 0000000000000..9009a4ff5ff44 --- /dev/null +++ b/tests/pthread/test_pthread_mutex.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +#define NUM_THREADS 8 + +int numThreadsToCreateTotal = 50; + +pthread_t thread[NUM_THREADS] = {}; + +volatile int counter = 0; // Shared data +pthread_mutex_t lock; + +void sleep(int msecs) +{ + // Test two different variants of sleeping to verify + // against bug https://bugzilla.mozilla.org/show_bug.cgi?id=1131757 +#ifdef SPINLOCK_TEST + double t0 = emscripten_get_now(); + double t1 = t0 + (double)msecs; + while(emscripten_get_now() < t1) + ; +#else + usleep(msecs*1000); +#endif +} +void *ThreadMain(void *arg) +{ + pthread_mutex_lock(&lock); + int c = counter; + sleep(100); // Create contention on the lock. + ++c; + counter = c; + pthread_mutex_unlock(&lock); + pthread_exit(0); +} + +void CreateThread(int i, int n) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_attr_setstacksize(&attr, 4*1024); + int rc = pthread_create(&thread[i], &attr, ThreadMain, 0); + if (rc != 0 || thread[i] == 0) + printf("Failed to create thread!\n"); + pthread_attr_destroy(&attr); +} + +int threadNum = 0; +void WaitToJoin() +{ + int threadsRunning = 0; + // Join all threads. + for(int i = 0; i < NUM_THREADS; ++i) + { + if (thread[i]) + { + void *status; + int rc = pthread_join(thread[i], &status); + if (rc == 0) + { + thread[i] = 0; + if (threadNum < numThreadsToCreateTotal) + { + CreateThread(i, threadNum++); + ++threadsRunning; + } + } + else + ++threadsRunning; + } + } + if (!threadsRunning) + { + if (counter == numThreadsToCreateTotal) + EM_ASM_INT( { console.log('All threads finished. Counter = ' + $0 + ' as expected.'); }, counter); + else + EM_ASM_INT( { console.error('All threads finished, but counter = ' + $0 + ' != ' + $1 + '!'); }, counter, numThreadsToCreateTotal); +#ifdef REPORT_RESULT + int result = counter; + REPORT_RESULT(); +#endif + emscripten_cancel_main_loop(); + } +} + +int main() +{ + pthread_mutex_init(&lock, NULL); + + // Create new threads in parallel. + for(int i = 0; i < NUM_THREADS; ++i) + CreateThread(i, threadNum++); + + emscripten_set_main_loop(WaitToJoin, 0, 0); +} diff --git a/tests/pthread/test_pthread_nested_spawns.cpp b/tests/pthread/test_pthread_nested_spawns.cpp new file mode 100644 index 0000000000000..482278e590935 --- /dev/null +++ b/tests/pthread/test_pthread_nested_spawns.cpp @@ -0,0 +1,30 @@ +#include +#include + +int result = 0; + +static void *thread2_func(void *vptr_args) { + puts("c"); + result = 1; + return NULL; +} + +static void *thread_func(void *vptr_args) { + pthread_t thread; + puts("b"); + pthread_create(&thread, NULL, thread2_func, NULL); + pthread_join(thread, NULL); + return NULL; +} + +int main(void) { + pthread_t thread; + puts("a"); + pthread_create(&thread, NULL, thread_func, NULL); + pthread_join(thread, NULL); + +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + return 0; +} diff --git a/tests/pthread/test_pthread_num_logical_cores.cpp b/tests/pthread/test_pthread_num_logical_cores.cpp new file mode 100644 index 0000000000000..ce843e802a52c --- /dev/null +++ b/tests/pthread/test_pthread_num_logical_cores.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main() +{ + printf("emscripten_num_logical_cores returns %d.\n", (int)emscripten_num_logical_cores()); +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif +} diff --git a/tests/pthread/test_pthread_once.cpp b/tests/pthread/test_pthread_once.cpp new file mode 100644 index 0000000000000..4cb5434764f27 --- /dev/null +++ b/tests/pthread/test_pthread_once.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +volatile int numInitialized = 0; + +void once_init() +{ + emscripten_atomic_add_u32((void*)&numInitialized, 1); +} + +#define NUM_THREADS 8 + +void *thread_main(void *arg) +{ + static pthread_once_t control = PTHREAD_ONCE_INIT; + pthread_once(&control, &once_init); + assert(numInitialized == 1); + pthread_exit(0); +} + +pthread_t thread[NUM_THREADS]; + +int main() +{ + assert(numInitialized == 0); + for(int i = 0; i < NUM_THREADS; ++i) pthread_create(&thread[i], NULL, thread_main, 0); + for(int i = 0; i < NUM_THREADS; ++i) pthread_join(thread[i], NULL); + assert(numInitialized == 1); + +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif +} diff --git a/tests/pthread/test_pthread_printf.cpp b/tests/pthread/test_pthread_printf.cpp new file mode 100644 index 0000000000000..488e3463723da --- /dev/null +++ b/tests/pthread/test_pthread_printf.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +void *ThreadMain(void *arg) +{ + printf("Hello from thread, string: %s, int: %d, double: %g\n", "str", 5, 42.0); +} + +int numThreadsToCreate = 1000; + +int main() +{ + malloc(4); // Work around bug https://github.com/kripken/emscripten/issues/2621 + + pthread_t thread; + int rc = pthread_create(&thread, NULL, ThreadMain, 0); + assert(rc == 0); + + rc = pthread_join(thread, NULL); + assert(rc == 0); + + printf("The thread should print 'Hello from thread, string: str, int: 5, double: 42.0'\n"); + +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif +} diff --git a/tests/pthread/test_pthread_setspecific_mainthread.cpp b/tests/pthread/test_pthread_setspecific_mainthread.cpp new file mode 100644 index 0000000000000..76f9f258baf10 --- /dev/null +++ b/tests/pthread/test_pthread_setspecific_mainthread.cpp @@ -0,0 +1,18 @@ +#include +#include + +int main() +{ + pthread_key_t key; + pthread_key_create(&key, NULL); + void *val = pthread_getspecific(key); + assert(val == 0); + pthread_setspecific(key, (void*)1); + val = pthread_getspecific(key); + assert(val == (void*)1); + +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif +} diff --git a/tests/pthread/test_pthread_spawns.cpp b/tests/pthread/test_pthread_spawns.cpp new file mode 100644 index 0000000000000..42d4b411ce91b --- /dev/null +++ b/tests/pthread/test_pthread_spawns.cpp @@ -0,0 +1,23 @@ +#include + +#define NUM_THREADS 2 + +void *thread_main(void *arg) +{ + pthread_exit(0); +} + +pthread_t thread[NUM_THREADS]; + +int main() +{ + for(int x = 0; x < 1000; ++x) + { + for(int i = 0; i < NUM_THREADS; ++i) pthread_create(&thread[i], NULL, thread_main, 0); + for(int i = 0; i < NUM_THREADS; ++i) pthread_join(thread[i], NULL); + } +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif +} diff --git a/tests/pthread/test_pthread_thread_local_storage.cpp b/tests/pthread/test_pthread_thread_local_storage.cpp new file mode 100644 index 0000000000000..3ae98f9974f42 --- /dev/null +++ b/tests/pthread/test_pthread_thread_local_storage.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include + +#define NUM_THREADS 8 +#define NUM_KEYS 16 +#define NUM_ITERS 100 + +pthread_key_t keys[NUM_KEYS]; +void *ThreadMain(void *arg) +{ + uintptr_t local_keys[NUM_KEYS]; + for(int iter = 0; iter < NUM_ITERS; ++iter) + { + for(int i = 0; i < NUM_KEYS; ++i) + { + local_keys[i] = (uintptr_t)pthread_getspecific(keys[i]); +// EM_ASM_INT( { Module['printErr']('Thread ' + $0 + ': Read value ' + $1 + ' from TLS for key at index ' + $2); }, pthread_self(), (int)local_keys[i], i); + } + + for(int i = 0; i < NUM_KEYS; ++i) + ++local_keys[i]; + + for(int i = 0; i < NUM_KEYS; ++i) + pthread_setspecific(keys[i], (void*)local_keys[i]); + } + + for(int i = 0; i < NUM_KEYS; ++i) + { + local_keys[i] = (uintptr_t)pthread_getspecific(keys[i]); +// EM_ASM_INT( { Module['printErr']('Thread ' + $0 + ' final verify: Read value ' + $1 + ' from TLS for key at index ' + $2); }, pthread_self(), (int)local_keys[i], i); + if (local_keys[i] != NUM_ITERS) + pthread_exit((void*)1); + } + pthread_exit(0); +} + +pthread_t thread[NUM_THREADS]; + +int numThreadsToCreate = 32; + +void CreateThread(int i) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + int rc = pthread_create(&thread[i], &attr, ThreadMain, (void*)i); + assert(rc == 0); + pthread_attr_destroy(&attr); +} + +int main() +{ + malloc(4); // Work around bug https://github.com/kripken/emscripten/issues/2621 + + for(int i = 0; i < NUM_KEYS; ++i) + pthread_key_create(&keys[i], NULL); + + // Create initial threads. + for(int i = 0; i < NUM_THREADS; ++i) + CreateThread(i); + + // Join all threads and create more. + for(int i = 0; i < NUM_THREADS; ++i) + { + if (thread[i]) + { + int status; + int rc = pthread_join(thread[i], (void**)&status); + assert(rc == 0); + EM_ASM_INT( { Module['printErr']('Main: Joined thread idx ' + $0 + ' with status ' + $1); }, i, (int)status); + assert(status == 0); + thread[i] = 0; + if (numThreadsToCreate > 0) + { + --numThreadsToCreate; + CreateThread(i); + } + } + } +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif + + for(int i = 0; i < NUM_KEYS; ++i) + pthread_key_delete(keys[i]); +} diff --git a/tests/pthread/test_pthread_volatile.cpp b/tests/pthread/test_pthread_volatile.cpp new file mode 100644 index 0000000000000..0b4b09ac0adf9 --- /dev/null +++ b/tests/pthread/test_pthread_volatile.cpp @@ -0,0 +1,37 @@ +#include +#include +#include + +// Toggle to use two different methods for updating shared data (C++03 volatile vs explicit atomic ops). +// Note that using a volatile variable explicitly depends on x86 strong memory model semantics. +//#define USE_C_VOLATILE + +volatile int sharedVar = 0; + +static void *thread_start(void *arg) // thread: just flip the shared flag and quit. +{ +#ifdef USE_C_VOLATILE + sharedVar = 1; +#else + emscripten_atomic_store_u32((void*)&sharedVar, 1); +#endif + pthread_exit(0); +} + +int main() +{ + pthread_t thr; + pthread_create(&thr, NULL, thread_start, (void*)0); + +#ifdef USE_C_VOLATILE + while(sharedVar == 0) + ; +#else + while(emscripten_atomic_load_u32((void*)&sharedVar) == 0) {} +#endif + +#ifdef REPORT_RESULT + int result = sharedVar; + REPORT_RESULT(); +#endif +} diff --git a/tests/runner.py b/tests/runner.py index 199bbd73eb714..98336b7c76911 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -17,6 +17,7 @@ from subprocess import Popen, PIPE, STDOUT import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, re, difflib, webbrowser, hashlib, threading, platform, BaseHTTPServer, SimpleHTTPServer, multiprocessing, functools, stat, string, random +from urllib import unquote # Setup @@ -611,8 +612,10 @@ def run_browser(self, html_file, message, expectedResult=None): output = queue.get() break time.sleep(0.1) - - self.assertIdentical(expectedResult, output) + if output.startswith('/report_result?skipped:'): + self.skip(unquote(output[len('/report_result?skipped:'):]).strip()) + else: + self.assertIdentical(expectedResult, output) finally: server.terminate() time.sleep(0.1) # see comment about Windows above @@ -747,7 +750,7 @@ def btest(self, filename, expected=None, reference=None, force_c=False, referenc self.reftest(path_from_root('tests', reference)) if not manual_reference: args = args + ['--pre-js', 'reftest.js', '-s', 'GL_TESTING=1'] - all_args = [PYTHON, EMCC, temp_filepath, '-o', outfile] + args + all_args = [PYTHON, EMCC, '-s', 'IN_TEST_HARNESS=1', temp_filepath, '-o', outfile] + args #print 'all args:', all_args try_delete(outfile) Popen(all_args).communicate() diff --git a/tests/sigalrm.cpp b/tests/sigalrm.cpp new file mode 100644 index 0000000000000..6f9219e45aeeb --- /dev/null +++ b/tests/sigalrm.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +void alarm_handler(int dummy) +{ + printf("Received alarm!\n"); +#ifdef REPORT_RESULT + int result = 0; + REPORT_RESULT(); +#endif + exit(0); +} + +int main() +{ + if (signal(SIGALRM, alarm_handler) == SIG_ERR) + { + printf("Error in signal()!\n"); +#ifdef REPORT_RESULT + int result = 1; + REPORT_RESULT(); +#endif + exit(1); + } + alarm(5); +} diff --git a/tests/test_browser.py b/tests/test_browser.py index 37fa8c5315c76..d38771e55d85c 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -2514,3 +2514,126 @@ def test_dynamic_link_glemu(self): self.btest(self.in_dir('main.cpp'), '1', args=['-s', 'MAIN_MODULE=1', '-O2', '-s', 'LEGACY_GL_EMULATION=1', '--pre-js', 'pre.js']) + # Test that the emscripten_ atomics api functions work. + def test_pthread_atomics(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_atomics.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test 64-bit atomics. + def test_pthread_64bit_atomics(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_64bit_atomics.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test the old GCC atomic __sync_fetch_and_op builtin operations. + def test_pthread_gcc_atomic_fetch_and_op(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_atomic_fetch_and_op.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # 64 bit version of the above test. + def test_pthread_gcc_64bit_atomic_fetch_and_op(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_64bit_atomic_fetch_and_op.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test the old GCC atomic __sync_op_and_fetch builtin operations. + def test_pthread_gcc_atomic_op_and_fetch(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_atomic_op_and_fetch.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # 64 bit version of the above test. + def test_pthread_gcc_64bit_atomic_op_and_fetch(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_64bit_atomic_op_and_fetch.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Tests the rest of the remaining GCC atomics after the two above tests. + def test_pthread_gcc_atomics(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_atomics.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test the __sync_lock_test_and_set and __sync_lock_release primitives. + def test_pthread_gcc_spinlock(self): + for arg in [[], ['-DUSE_EMSCRIPTEN_INTRINSICS']]: + self.btest(path_from_root('tests', 'pthread', 'test_pthread_gcc_spinlock.cpp'), expected='800', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8'] + arg) + + # Test that basic thread creation works. + def test_pthread_create(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_create.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test that a pthread can spawn another pthread of its own. + def test_pthread_create_pthread(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_create_pthread.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=2', '-s', 'NO_EXIT_RUNTIME=1']) + + # Test another case of pthreads spawning pthreads, but this time the callers immediately join on the threads they created. + def test_pthread_nested_spawns(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_nested_spawns.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=2']) + + # Test that main thread can wait for a pthread to finish via pthread_join(). + def test_pthread_join(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_join.cpp'), expected='6765', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test pthread_cancel() operation + def test_pthread_cancel(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_cancel.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test pthread_kill() operation + def test_pthread_kill(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_kill.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test that pthread cleanup stack (pthread_cleanup_push/_pop) works. + def test_pthread_cleanup(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_cleanup.cpp'), expected='907640832', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Tests the pthread mutex api. + def test_pthread_mutex(self): + for arg in [[], ['-DSPINLOCK_TEST']]: + self.btest(path_from_root('tests', 'pthread', 'test_pthread_mutex.cpp'), expected='50', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8'] + arg) + + # Test that memory allocation is thread-safe. + def test_pthread_malloc(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_malloc.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Stress test pthreads allocating memory that will call to sbrk(), and main thread has to free up the data. + def test_pthread_malloc_free(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_malloc_free.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8', '-s', 'TOTAL_MEMORY=268435456']) + + # Test that the pthread_barrier API works ok. + def test_pthread_barrier(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_barrier.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test the pthread_once() function. + def test_pthread_once(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_once.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test against a certain thread exit time handling bug by spawning tons of threads. + def test_pthread_spawns(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_spawns.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # It is common for code to flip volatile global vars for thread control. This is a bit lax, but nevertheless, test whether that + # kind of scheme will work with Emscripten as well. + def test_pthread_volatile(self): + for arg in [[], ['-DUSE_C_VOLATILE']]: + self.btest(path_from_root('tests', 'pthread', 'test_pthread_volatile.cpp'), expected='1', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8'] + arg) + + # Test thread-specific data (TLS). + def test_pthread_thread_local_storage(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_thread_local_storage.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test the pthread condition variable creation and waiting. + def test_pthread_condition_variable(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_condition_variable.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=8']) + + # Test that pthreads are able to do printf. + def test_pthread_printf(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_printf.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=1']) + + # Test that pthreads are able to do cout. Failed due to https://bugzilla.mozilla.org/show_bug.cgi?id=1154858. + def test_pthread_iostream(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_iostream.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=1']) + + # Test that the main thread is able to use pthread_set/getspecific. + def test_pthread_setspecific_mainthread(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_setspecific_mainthread.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1']) + + # Test the -s PTHREAD_HINT_NUM_CORES=x command line variable. + def test_pthread_num_logical_cores(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_num_logical_cores.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_HINT_NUM_CORES=2']) + + # Test that pthreads have access to filesystem. + def test_pthread_file_io(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_file_io.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=1', '-s', 'PTHREAD_POOL_SIZE=1']) + + # Test that it is possible to send a signal via calling alarm(timeout), which in turn calls to the signal handler set by signal(SIGALRM, func); + def test_sigalrm(self): + self.btest(path_from_root('tests', 'sigalrm.cpp'), expected='0', args=['-O3']) diff --git a/tests/unistd/sysconf.out b/tests/unistd/sysconf.out index c54370ceb79f5..fa7bb8fe3919a 100644 --- a/tests/unistd/sysconf.out +++ b/tests/unistd/sysconf.out @@ -88,7 +88,7 @@ errno: 0 _SC_THREAD_PRIO_PROTECT: 200809 errno: 0 -_SC_THREAD_PRIORITY_SCHEDULING: 200809 +_SC_THREAD_PRIORITY_SCHEDULING: 0 errno: 0 _SC_THREAD_PROCESS_SHARED: 200809 diff --git a/tools/shared.py b/tools/shared.py index 302e261957e61..0ed58048437d4 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1885,5 +1885,16 @@ def safe_copy(src, dst): if dst == '/dev/null': return shutil.copyfile(src, dst) -import js_optimizer +def read_and_preprocess(filename): + f = open(filename, 'r').read() + pos = 0 + include_pattern = re.compile('^#include\s*["<](.*)[">]\s?$', re.MULTILINE) + while(1): + m = include_pattern.search(f, pos) + if not m: + return f + included_file = open(os.path.join(os.path.dirname(filename), m.groups(0)[0]), 'r').read() + + f = f[:m.start(0)] + included_file + f[m.end(0):] +import js_optimizer diff --git a/tools/system_libs.py b/tools/system_libs.py index 89bf7235af279..c7dbf1bb2a748 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1,4 +1,4 @@ -import os, json, logging, zipfile +import os, json, logging, zipfile, glob import shared from subprocess import Popen, CalledProcessError import multiprocessing @@ -51,6 +51,7 @@ def read_symbols(path, exclude=None): libcxx_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libcxx', 'symbols'), exclude=libc_symbols) libcxxabi_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libcxxabi', 'symbols'), exclude=libc_symbols) gl_symbols = read_symbols(shared.path_from_root('system', 'lib', 'gl.symbols')) + pthreads_symbols = read_symbols(shared.path_from_root('system', 'lib', 'pthreads.symbols')) # XXX we should disable EMCC_DEBUG when building libs, just like in the relooper @@ -92,9 +93,6 @@ def build_libcxx(src_dirname, lib_filename, files, lib_opts, has_noexcept_versio # libc def create_libc(libname): logging.debug(' building libc for cache') - libc_files = [ - 'dlmalloc.c', - ] musl_files = [ ['ctype', [ 'isdigit.c', @@ -266,10 +264,18 @@ def create_libc(libname): 'strncmp.c', ]] ] + libc_files = [] for directory, sources in musl_files: libc_files += [os.path.join('libc', 'musl', 'src', directory, source) for source in sources] + return build_libc(libname, libc_files, ['-O2']) + def create_pthreads(libname): + # Add pthread files. + pthreads_files = [os.path.join('pthread', 'library_pthread.c')] + pthreads_files += glob.glob(shared.path_from_root('system/lib/libc/musl/src/thread/*.c')) + return build_libc(libname, pthreads_files, ['-O2']) + # libcextra def create_libcextra(libname): logging.debug('building libcextra for cache') @@ -635,6 +641,17 @@ def create_gl(libname): # libname is ignored, this is just one .o file check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'gl.c'), '-o', o]) return o + def create_dlmalloc(out_name, clflags): + o = in_temp(out_name) + check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-o', o] + clflags) + return o + + def create_dlmalloc_singlethreaded(libname): + return create_dlmalloc(libname, ['-O2']) + + def create_dlmalloc_multithreaded(libname): + return create_dlmalloc(libname, ['-O2', '-s', 'USE_PTHREADS=1']) + # Setting this in the environment will avoid checking dependencies and make building big projects a little faster # 1 means include everything; otherwise it can be the name of a lib (libcxx, etc.) # You can provide 1 to include everything, or a comma-separated list with the ones you want @@ -702,6 +719,23 @@ class Dummy: for symbols in symbolses: all_needed.difference_update(symbols.defs) + system_libs = [('libcxx', 'a', create_libcxx, libcxx_symbols, ['libcextra', 'libcxxabi'], True), + ('libcextra', 'bc', create_libcextra, libcextra_symbols, ['libc'], False), + ('libcxxabi', 'bc', create_libcxxabi, libcxxabi_symbols, ['libc'], False), + ('gl', 'bc', create_gl, gl_symbols, ['libc'], False), + ('libc', 'bc', create_libc, libc_symbols, [], False)] + + # malloc dependency is force-added, so when using pthreads, it must be force-added + # as well, since malloc needs to be thread-safe, so it depends on mutexes. + if shared.Settings.USE_PTHREADS: + system_libs += [('pthreads', 'bc', create_pthreads, pthreads_symbols, ['libc'], False), + ('dlmalloc_threadsafe', 'bc', create_dlmalloc_multithreaded, [], [], False)] + force.add('pthreads') + force.add('dlmalloc_threadsafe') + else: + system_libs += [('dlmalloc', 'bc', create_dlmalloc_singlethreaded, [], [], False)] + force.add('dlmalloc') + # Go over libraries to figure out which we must include def maybe_noexcept(name): if shared.Settings.DISABLE_EXCEPTION_CATCHING: @@ -709,11 +743,8 @@ def maybe_noexcept(name): return name ret = [] has = need = None - for shortname, suffix, create, library_symbols, deps, can_noexcept in [('libcxx', 'a', create_libcxx, libcxx_symbols, ['libcextra', 'libcxxabi'], True), - ('libcextra', 'bc', create_libcextra, libcextra_symbols, ['libc'], False), - ('libcxxabi', 'bc', create_libcxxabi, libcxxabi_symbols, ['libc'], False), - ('gl', 'bc', create_gl, gl_symbols, ['libc'], False), - ('libc', 'bc', create_libc, libc_symbols, [], False)]: + + for shortname, suffix, create, library_symbols, deps, can_noexcept in system_libs: force_this = force_all or shortname in force if can_noexcept: shortname = maybe_noexcept(shortname) if force_this: