diff --git a/emcc b/emcc index 4b9caf248..025daaf9d 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 000000000..9009a4ff5 --- /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 000000000..482278e59 --- /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 000000000..ce843e802 --- /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 000000000..4cb543476 --- /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 000000000..488e34637 --- /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 000000000..76f9f258b --- /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 000000000..42d4b411c --- /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 000000000..3ae98f997 --- /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 000000000..0b4b09ac0 --- /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 199bbd73e..98336b7c7 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -17,6 +17,7 @@ so you may prefer to use fewer cores here. 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 @@ class BrowserCore(RunnerCore): 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 @@ class BrowserCore(RunnerCore): 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 000000000..6f9219e45 --- /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 37fa8c531..d38771e55 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -2514,3 +2514,126 @@ window.close = function() { 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 c54370ceb..fa7bb8fe3 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 302e26195..0ed580484 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 89bf7235a..c7dbf1bb2 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 calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): 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 calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): # 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 calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): '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 calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): 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 @@ def calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): 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 calculate(temp_files, in_temp, stdout_, stderr_, forced=[]): 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: