diff --git a/AUTHORS b/AUTHORS index c1e91a8342f9f..f5cac4a6f7294 100644 --- a/AUTHORS +++ b/AUTHORS @@ -245,3 +245,6 @@ a license to everyone to use it as detailed in LICENSE.) * Pieter Vantorre * Maher Sallam * Andrey Burov +* Holland Schutte +* Kerby Geffrard + diff --git a/emscripten-version.txt b/emscripten-version.txt index 66566adfeb390..fdd5c62a3bd55 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -"1.36.2" +"1.36.3" diff --git a/src/library_openal.js b/src/library_openal.js index 01ffb63a977c3..17b750533b7e9 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -251,6 +251,10 @@ var LibraryOpenAL = { var gain = ctx.createGain(); gain.connect(ctx.destination); + // Extend the Web Audio API AudioListener object with a few tracking values of our own. + ctx.listener._position = [0, 0, 0]; + ctx.listener._velocity = [0, 0, 0]; + ctx.listener._orientation = [0, 0, 0, 0, 0, 0]; var context = { ctx: ctx, err: 0, @@ -353,6 +357,9 @@ var LibraryOpenAL = { queue: [], loop: false, playbackRate: 1, + _position: [0, 0, 0], + _velocity: [0, 0, 0], + _direction: [0, 0, 0], get refDistance() { return this._refDistance || 1; }, @@ -375,24 +382,30 @@ var LibraryOpenAL = { if (this.panner) this.panner.rolloffFactor = val; }, get position() { - return this._position || [0, 0, 0]; + return this._position; }, set position(val) { - this._position = val; + this._position[0] = val[0]; + this._position[1] = val[1]; + this._position[2] = val[2]; if (this.panner) this.panner.setPosition(val[0], val[1], val[2]); }, get velocity() { - return this._velocity || [0, 0, 0]; + return this._velocity; }, set velocity(val) { - this._velocity = val; + this._velocity[0] = val[0]; + this._velocity[1] = val[1]; + this._velocity[2] = val[2]; if (this.panner) this.panner.setVelocity(val[0], val[1], val[2]); }, get direction() { - return this._direction || [0, 0, 0]; + return this._direction; }, set direction(val) { - this._direction = val; + this._direction[0] = val[0]; + this._direction[1] = val[1]; + this._direction[2] = val[2]; if (this.panner) this.panner.setOrientation(val[0], val[1], val[2]); }, get coneOuterGain() { @@ -615,13 +628,19 @@ var LibraryOpenAL = { } switch (param) { case 0x1004 /* AL_POSITION */: - src.position = [v1, v2, v3]; + src.position[0] = v1; + src.position[1] = v2; + src.position[2] = v3; break; case 0x1005 /* AL_DIRECTION */: - src.direction = [v1, v2, v3]; + src.direction[0] = v1; + src.direction[1] = v2; + src.direction[2] = v3; break; case 0x1006 /* AL_VELOCITY */: - src.velocity = [v1, v2, v3]; + src.velocity[0] = v1; + src.velocity[1] = v2; + src.velocity[2] = v3; break; default: #if OPENAL_DEBUG @@ -1230,19 +1249,19 @@ var LibraryOpenAL = { } switch (pname) { case 0x1004 /* AL_POSITION */: - var position = AL.currentContext.ctx.listener._position || [0,0,0]; + var position = AL.currentContext.ctx.listener._position; {{{ makeSetValue('values', '0', 'position[0]', 'float') }}} {{{ makeSetValue('values', '4', 'position[1]', 'float') }}} {{{ makeSetValue('values', '8', 'position[2]', 'float') }}} break; case 0x1006 /* AL_VELOCITY */: - var velocity = AL.currentContext.ctx.listener._velocity || [0,0,0]; + var velocity = AL.currentContext.ctx.listener._velocity; {{{ makeSetValue('values', '0', 'velocity[0]', 'float') }}} {{{ makeSetValue('values', '4', 'velocity[1]', 'float') }}} {{{ makeSetValue('values', '8', 'velocity[2]', 'float') }}} break; case 0x100F /* AL_ORIENTATION */: - var orientation = AL.currentContext.ctx.listener._orientation || [0,0,0,0,0,0]; + var orientation = AL.currentContext.ctx.listener._orientation; {{{ makeSetValue('values', '0', 'orientation[0]', 'float') }}} {{{ makeSetValue('values', '4', 'orientation[1]', 'float') }}} {{{ makeSetValue('values', '8', 'orientation[2]', 'float') }}} @@ -1339,11 +1358,15 @@ var LibraryOpenAL = { } switch (param) { case 0x1004 /* AL_POSITION */: - AL.currentContext.ctx.listener._position = [v1, v2, v3]; + AL.currentContext.ctx.listener._position[0] = v1; + AL.currentContext.ctx.listener._position[1] = v2; + AL.currentContext.ctx.listener._position[2] = v3; AL.currentContext.ctx.listener.setPosition(v1, v2, v3); break; case 0x1006 /* AL_VELOCITY */: - AL.currentContext.ctx.listener._velocity = [v1, v2, v3]; + AL.currentContext.ctx.listener._velocity[0] = v1; + AL.currentContext.ctx.listener._velocity[1] = v2; + AL.currentContext.ctx.listener._velocity[2] = v3; AL.currentContext.ctx.listener.setVelocity(v1, v2, v3); break; default: @@ -1367,14 +1390,18 @@ var LibraryOpenAL = { var x = {{{ makeGetValue('values', '0', 'float') }}}; var y = {{{ makeGetValue('values', '4', 'float') }}}; var z = {{{ makeGetValue('values', '8', 'float') }}}; - AL.currentContext.ctx.listener._position = [x, y, z]; + AL.currentContext.ctx.listener._position[0] = x; + AL.currentContext.ctx.listener._position[1] = y; + AL.currentContext.ctx.listener._position[2] = z; AL.currentContext.ctx.listener.setPosition(x, y, z); break; case 0x1006 /* AL_VELOCITY */: var x = {{{ makeGetValue('values', '0', 'float') }}}; var y = {{{ makeGetValue('values', '4', 'float') }}}; var z = {{{ makeGetValue('values', '8', 'float') }}}; - AL.currentContext.ctx.listener._velocity = [x, y, z]; + AL.currentContext.ctx.listener._velocity[0] = x; + AL.currentContext.ctx.listener._velocity[1] = y; + AL.currentContext.ctx.listener._velocity[2] = z; AL.currentContext.ctx.listener.setVelocity(x, y, z); break; case 0x100F /* AL_ORIENTATION */: @@ -1384,7 +1411,12 @@ var LibraryOpenAL = { var x2 = {{{ makeGetValue('values', '12', 'float') }}}; var y2 = {{{ makeGetValue('values', '16', 'float') }}}; var z2 = {{{ makeGetValue('values', '20', 'float') }}}; - AL.currentContext.ctx.listener._orientation = [x, y, z, x2, y2, z2]; + AL.currentContext.ctx.listener._orientation[0] = x; + AL.currentContext.ctx.listener._orientation[1] = y; + AL.currentContext.ctx.listener._orientation[2] = z; + AL.currentContext.ctx.listener._orientation[3] = x2; + AL.currentContext.ctx.listener._orientation[4] = y2; + AL.currentContext.ctx.listener._orientation[5] = z2; AL.currentContext.ctx.listener.setOrientation(x, y, z, x2, y2, z2); break; default: diff --git a/src/library_pthread.js b/src/library_pthread.js index e94f2abd7187e..7888d2bdcbd62 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -1,6 +1,6 @@ var LibraryPThread = { $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();', - $PThread__deps: ['$PROCINFO', '_register_pthread_ptr'], + $PThread__deps: ['$PROCINFO', '_register_pthread_ptr', 'emscripten_main_thread_process_queued_calls'], $PThread: { MAIN_THREAD_ID: 1, // A special constant that identifies the main JS thread ID. mainThreadInfo: { @@ -242,6 +242,8 @@ var LibraryPThread = { Module['print']('Thread ' + e.data.threadId + ': ' + e.data.text); } else if (e.data.cmd === 'printErr') { Module['printErr']('Thread ' + e.data.threadId + ': ' + e.data.text); + } else if (e.data.cmd == 'alert') { + alert('Thread ' + e.data.threadId + ': ' + e.data.text); } else if (e.data.cmd === 'exit') { // todo } else if (e.data.cmd === 'cancelDone') { @@ -498,7 +500,7 @@ var LibraryPThread = { if (canceled == 2) throw 'Canceled!'; }, - pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js'], + pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls'], pthread_join: function(thread, status) { if (!thread) { Module['printErr']('pthread_join attempted on a null thread pointer!'); @@ -771,7 +773,7 @@ var LibraryPThread = { _main_thread_futex_wait_address: '; if (ENVIRONMENT_IS_PTHREAD) __main_thread_futex_wait_address = PthreadWorkerInit.__main_thread_futex_wait_address; else PthreadWorkerInit.__main_thread_futex_wait_address = __main_thread_futex_wait_address = allocate(1, "i32*", ALLOC_STATIC)', // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error. - emscripten_futex_wait__deps: ['_main_thread_futex_wait_address'], + emscripten_futex_wait__deps: ['_main_thread_futex_wait_address', 'emscripten_main_thread_process_queued_calls'], emscripten_futex_wait: function(addr, val, timeout) { if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}}; // dump('futex_wait addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n'); @@ -803,7 +805,8 @@ var LibraryPThread = { // Register globally which address the main thread is simulating to be waiting on. When zero, main thread is not waiting on anything, // and on nonzero, the contents of address pointed by __main_thread_futex_wait_address tell which address the main thread is simulating its wait on. Atomics.store(HEAP32, __main_thread_futex_wait_address >> 2, addr); - while (addr) { + var ourWaitAddress = addr; // We may recursively re-enter this function while processing queued calls, in which case we'll do a spurious wakeup of the older wait operation. + while (addr == ourWaitAddress) { tNow = performance.now(); if (tNow > tEnd) { #if PTHREADS_PROFILING @@ -811,6 +814,7 @@ var LibraryPThread = { #endif return -{{{ cDefine('ETIMEDOUT') }}}; } + _emscripten_main_thread_process_queued_calls(); // We are performing a blocking loop here, so must pump any pthreads if they want to perform operations that are proxied. addr = Atomics.load(HEAP32, __main_thread_futex_wait_address >> 2); // Look for a worker thread waking us up. } #if PTHREADS_PROFILING diff --git a/src/library_workerfs.js b/src/library_workerfs.js index 9b26213f855b0..56b339458d8ce 100644 --- a/src/library_workerfs.js +++ b/src/library_workerfs.js @@ -15,8 +15,15 @@ mergeInto(LibraryManager.library, { var parent = root; for (var i = 0; i < parts.length-1; i++) { var curr = parts.slice(0, i+1).join('/'); + // Issue 4254: Using curr as a node name will prevent the node + // from being found in FS.nameTable when FS.open is called on + // a path which holds a child of this node, + // given that all FS functions assume node names + // are just their corresponding parts within their given path, + // rather than incremental aggregates which include their parent's + // directories. if (!createdParents[curr]) { - createdParents[curr] = WORKERFS.createNode(parent, curr, WORKERFS.DIR_MODE, 0); + createdParents[curr] = WORKERFS.createNode(parent, parts[i], WORKERFS.DIR_MODE, 0); } parent = createdParents[curr]; } diff --git a/src/pthread-main.js b/src/pthread-main.js index b3a68e1fbc20b..e392db154ad01 100644 --- a/src/pthread-main.js +++ b/src/pthread-main.js @@ -31,7 +31,10 @@ function threadPrintErr() { var text = Array.prototype.slice.call(arguments).join(' '); postMessage({cmd: 'printErr', text: text, threadId: selfThreadId}); } - +function threadAlert() { + var text = Array.prototype.slice.call(arguments).join(' '); + postMessage({cmd: 'alert', text: text, threadId: selfThreadId}); +} Module['print'] = threadPrint; Module['printErr'] = threadPrintErr; @@ -41,6 +44,8 @@ console = { error: threadPrintErr }; +this.alert = threadAlert; + this.onmessage = function(e) { if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. buffer = e.data.buffer; diff --git a/tests/pthread/test_pthread_proxying_in_futex_wait.cpp b/tests/pthread/test_pthread_proxying_in_futex_wait.cpp new file mode 100644 index 0000000000000..9cdf7ece46af9 --- /dev/null +++ b/tests/pthread/test_pthread_proxying_in_futex_wait.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +unsigned int main_thread_wait_val = 1; +int result = 1; + +void *ThreadMain(void *arg) +{ + for(int i = 0; i < 10; ++i) + { + char str[256]; + sprintf(str, "file%d.txt", i); + printf("Writing file %s..\n", str); // Prints out to page console, this is a proxied operation. + FILE *handle = fopen(str, "w"); // fopen, fputs and fclose are currently proxied operations too (although hopefully not in the future) + fputs(str, handle); + fclose(handle); + } + emscripten_atomic_store_u32(&main_thread_wait_val, 0); + emscripten_futex_wake(&main_thread_wait_val, 1); + emscripten_atomic_store_u32(&result, 0); + pthread_exit(0); +} + +int main() +{ + if (!emscripten_has_threading_support()) + { +#ifdef REPORT_RESULT + result = 0; + REPORT_RESULT(); +#endif + printf("Skipped: Threading is not supported.\n"); + return 0; + } + + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + int rc = pthread_create(&thread, &attr, ThreadMain, 0); + assert(rc == 0); + rc = emscripten_futex_wait(&main_thread_wait_val, 1, 15 * 1000); + if (rc != 0) + { + printf("ERROR! futex wait timed out!\n"); + result = 2; +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + exit(1); + } + pthread_attr_destroy(&attr); + pthread_join(thread, 0); + +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif +} diff --git a/tests/sdl2_timer.c b/tests/sdl2_timer.c new file mode 100644 index 0000000000000..8ed3827d2deef --- /dev/null +++ b/tests/sdl2_timer.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +Uint32 SDLCALL report_result(Uint32 interval, void *param) { + SDL_Quit(); + int result = *(int *)param; + printf("%p %d\n", param, result); + REPORT_RESULT(); + return 0; +} + +void nop(void) {} + +int main(int argc, char** argv) { + SDL_Init(SDL_INIT_TIMER); + + Uint32 ticks1 = SDL_GetTicks(); + SDL_Delay(5); // busy-wait + Uint32 ticks2 = SDL_GetTicks(); + assert(ticks2 >= ticks1 + 5); + + int badret = 4; + int goodret = 5; + + SDL_TimerID badtimer = SDL_AddTimer(500, report_result, &badret); + SDL_TimerID goodtimer = SDL_AddTimer(1000, report_result, &goodret); + SDL_RemoveTimer(badtimer); + + emscripten_set_main_loop(nop, 0, 0); + + return 0; +} diff --git a/tests/test_browser.py b/tests/test_browser.py index a4205a99cb1ad..f256c4416de4a 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -2576,6 +2576,9 @@ def test_sdl2_pumpevents(self): ''') self.btest('sdl2_pumpevents.c', expected='7', args=['--pre-js', 'pre.js', '-s', 'USE_SDL=2']) + def test_sdl2_timer(self): + self.btest('sdl2_timer.c', expected='5', args=['-s', 'USE_SDL=2']) + def test_sdl2_canvas_size(self): self.btest('sdl2_canvas_size.c', expected='1', args=['-s', 'USE_SDL=2']) @@ -3008,6 +3011,10 @@ def test_pthread_custom_pthread_main_url(self): try_delete('pthread-main.js') self.run_browser('test2.html', '', '/report_result?1') + # Test that if the main thread is performing a futex wait while a pthread needs it to do a proxied operation (before that pthread would wake up the main thread), that it's not a deadlock. + def test_pthread_proxying_in_futex_wait(self): + self.btest(path_from_root('tests', 'pthread', 'test_pthread_proxying_in_futex_wait.cpp'), expected='0', args=['-O3', '-s', 'USE_PTHREADS=2', '-s', 'PTHREAD_POOL_SIZE=1', '--separate-asm'], timeout=30) + # test atomicrmw i64 def test_atomicrmw_i64(self): Popen([PYTHON, EMCC, path_from_root('tests', 'atomicrmw_i64.ll'), '-s', 'USE_PTHREADS=1', '-s', 'IN_TEST_HARNESS=1', '-o', 'test.html']).communicate() diff --git a/tools/file_packager.py b/tools/file_packager.py index 114cbf614a660..4c5a7501df23e 100644 --- a/tools/file_packager.py +++ b/tools/file_packager.py @@ -442,7 +442,7 @@ def was_seen(name): var that = this; %s this.requests[this.name] = null; - }, + } }; %s ''' % ('' if not crunch else ''' diff --git a/tools/ports/sdl.py b/tools/ports/sdl.py index 3ceecd6e5f240..48b637843d66e 100644 --- a/tools/ports/sdl.py +++ b/tools/ports/sdl.py @@ -1,6 +1,6 @@ import os, shutil, logging -TAG = 'version_10' +TAG = 'version_11' def get_with_configure(ports, settings, shared): # not currently used; no real need for configure on emscripten users' machines! if settings.USE_SDL == 2: