From e0053b2bd6e0dbfd5dc095255d5d5437e4845c85 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Sat, 22 Feb 2025 15:29:31 +0100 Subject: [PATCH 1/6] Original PR had drifted too far --- system/lib/pthread/emscripten_thread_state.S | 2 +- system/lib/wasm_worker/library_wasm_worker.c | 6 +- test/test_browser.py | 5 +- .../audioworklet_emscripten_futex_wake.cpp | 153 ++++++++++++++---- 4 files changed, 129 insertions(+), 37 deletions(-) diff --git a/system/lib/pthread/emscripten_thread_state.S b/system/lib/pthread/emscripten_thread_state.S index 57f800c6594d4..9d0296bb6062e 100644 --- a/system/lib/pthread/emscripten_thread_state.S +++ b/system/lib/pthread/emscripten_thread_state.S @@ -53,7 +53,7 @@ emscripten_is_main_browser_thread: global.get is_main_thread end_function -# Semantically the same as testing "!ENVIRONMENT_IS_WEB" in JS +# Semantically the same as testing "!ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_AUDIO_WORKLET" in JS .globl _emscripten_thread_supports_atomics_wait _emscripten_thread_supports_atomics_wait: .functype _emscripten_thread_supports_atomics_wait () -> (i32) diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index c5d90f36407f7..0cd0180c94b3f 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -105,15 +105,17 @@ void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) { } bool emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) { + // TODO: we changed the performance_now calls to get_now, which can be applied + // to the remaining code (since all calls defer to the best internal option). emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; - double t = emscripten_performance_now(); + double t = emscripten_get_now(); double waitEnd = t + maxWaitMilliseconds; while (t < waitEnd) { val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; - t = emscripten_performance_now(); + t = emscripten_get_now(); } return false; } diff --git a/test/test_browser.py b/test/test_browser.py index 02da5d68072f0..47b90a8cba9ed 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5516,12 +5516,11 @@ def test_audio_worklet_params_mixing(self, args): shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/') self.btest_exit('webaudio/audioworklet_params_mixing.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DTEST_AND_EXIT'] + args) - # Tests AudioWorklet with emscripten_futex_wake(). + # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends. @requires_sound_hardware @also_with_minimal_runtime - @disabled('https://github.com/emscripten-core/emscripten/issues/22962') def test_audio_worklet_emscripten_futex_wake(self): - self.btest('webaudio/audioworklet_emscripten_futex_wake.cpp', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2']) + self.btest_exit('webaudio/audioworklet_emscripten_futex_wake.cpp', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2']) def test_error_reporting(self): # Test catching/reporting Error objects diff --git a/test/webaudio/audioworklet_emscripten_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_futex_wake.cpp index 95aa2f6e87b25..02f56e178fa66 100644 --- a/test/webaudio/audioworklet_emscripten_futex_wake.cpp +++ b/test/webaudio/audioworklet_emscripten_futex_wake.cpp @@ -1,32 +1,107 @@ -#include #include +#include +#include #include #include #include -// Tests that -// - _emscripten_thread_supports_atomics_wait() returns true in a Wasm Audio Worklet. -// - emscripten_futex_wake() does not crash in a Wasm Audio Worklet. -// - emscripten_futex_wait() does not crash in a Wasm Audio Worklet. -// - emscripten_get_now() does not crash in a Wasm Audio Worklet. - -int futexLocation = 0; -int testSuccess = 0; +// Tests that these audio worklet compatible functions work, details in comments below: +// +// - _emscripten_thread_supports_atomics_wait() +// - emscripten_lock_init() +// - emscripten_lock_try_acquire() +// - emscripten_lock_busyspin_wait_acquire() +// - emscripten_lock_busyspin_waitinf_acquire() +// - emscripten_lock_release() +// - emscripten_get_now() // Internal, found in 'system/lib/pthread/threading_internal.h' -extern "C" int _emscripten_thread_supports_atomics_wait(); +extern "C" int _emscripten_thread_supports_atomics_wait(void); -bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) { - int supportsAtomicWait = _emscripten_thread_supports_atomics_wait(); - printf("supportsAtomicWait: %d\n", supportsAtomicWait); - assert(!supportsAtomicWait); - emscripten_futex_wake(&futexLocation, 1); - printf("%f\n", emscripten_get_now()); +typedef enum { + // No wait support in audio worklets + TEST_HAS_WAIT, + // Acquired in main, fail in process + TEST_TRY_ACQUIRE, + // Keep acquired so time-out + TEST_WAIT_ACQUIRE_FAIL, + // Release in main, succeed in process + TEST_WAIT_ACQUIRE, + // Release in process after above + TEST_RELEASE, + // Released in process above, spin in main + TEST_WAIT_INFINTE_1, + // Release in process to stop spinning in main + TEST_WAIT_INFINTE_2, + // Call emscripten_get_now() in process + TEST_GET_NOW, + // Test finished + TEST_DONE +} Test; - emscripten_futex_wait(&futexLocation, 1, /*maxWaitMs=*/2); - testSuccess = 1; +// Lock used in all the tests +emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER; +// Which test is running (sometimes in the worklet, sometimes in the main thread) +_Atomic Test whichTest = TEST_HAS_WAIT; +// Time at which the test starts taken in main() +double startTime = 0; - return false; +bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) { + int result = 0; + switch (whichTest) { + case TEST_HAS_WAIT: + // Should not have wait support here + result = _emscripten_thread_supports_atomics_wait(); + printf("TEST_HAS_WAIT: %d (expect: 0)\n", result); + assert(!result); + whichTest = TEST_TRY_ACQUIRE; + break; + case TEST_TRY_ACQUIRE: + // Was locked after init, should fail to acquire + result = emscripten_lock_try_acquire(&testLock); + printf("TEST_TRY_ACQUIRE: %d (expect: 0)\n", result); + assert(!result); + whichTest = TEST_WAIT_ACQUIRE_FAIL; + break; + case TEST_WAIT_ACQUIRE_FAIL: + // Still locked so we fail to acquire + result = emscripten_lock_busyspin_wait_acquire(&testLock, 100); + printf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)\n", result); + assert(!result); + whichTest = TEST_WAIT_ACQUIRE; + case TEST_WAIT_ACQUIRE: + // Will get unlocked in main thread, so should quickly acquire + result = emscripten_lock_busyspin_wait_acquire(&testLock, 100); + printf("TEST_WAIT_ACQUIRE: %d (expect: 1)\n", result); + assert(result); + whichTest = TEST_RELEASE; + break; + case TEST_RELEASE: + // Unlock, check the result + emscripten_lock_release(&testLock); + result = emscripten_lock_try_acquire(&testLock); + printf("TEST_RELEASE: %d (expect: 1)\n", result); + assert(result); + whichTest = TEST_WAIT_INFINTE_1; + break; + case TEST_WAIT_INFINTE_1: + // Still locked when we enter here but move on in the main thread + break; + case TEST_WAIT_INFINTE_2: + emscripten_lock_release(&testLock); + whichTest = TEST_GET_NOW; + break; + case TEST_GET_NOW: + result = (int) (emscripten_get_now() - startTime); + printf("TEST_GET_NOW: %d (expect: > 0)\n", result); + assert(result > 0); + whichTest = TEST_DONE; + case TEST_DONE: + return false; + default: + break; + } + return true; } EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), { @@ -40,13 +115,22 @@ EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), { }; }); -bool PollTestSuccess(double, void *) { - if (testSuccess) { - printf("Test success!\n"); -#ifdef REPORT_RESULT - REPORT_RESULT(0); -#endif +bool MainLoop(double time, void* data) { + switch (whichTest) { + case TEST_WAIT_ACQUIRE: + // Release here to acquire in process + emscripten_lock_release(&testLock); + break; + case TEST_WAIT_INFINTE_1: + // Spin here until released in process (but don't change test until we know this case ran) + whichTest = TEST_WAIT_INFINTE_2; + emscripten_lock_busyspin_waitinf_acquire(&testLock); + printf("TEST_WAIT_INFINTE (from main)\n"); + break; + case TEST_DONE: return false; + default: + break; } return true; } @@ -54,20 +138,27 @@ bool PollTestSuccess(double, void *) { void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { int outputChannelCounts[1] = { 1 }; EmscriptenAudioWorkletNodeCreateOptions options = { .numberOfInputs = 0, .numberOfOutputs = 1, .outputChannelCounts = outputChannelCounts }; - EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, 0); + EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, NULL); emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0); InitHtmlUi(audioContext); } void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { WebAudioWorkletProcessorCreateOptions opts = { .name = "noise-generator" }; - emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, 0); + emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, NULL); } -uint8_t wasmAudioWorkletStack[4096]; +uint8_t wasmAudioWorkletStack[2048]; int main() { - emscripten_set_timeout_loop(PollTestSuccess, 10, 0); - EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(0); - emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, 0); + // Main thread init and acquire (work passes to the processor) + emscripten_lock_init(&testLock); + int hasLock = emscripten_lock_busyspin_wait_acquire(&testLock, 0); + assert(hasLock); + + startTime = emscripten_get_now(); + + emscripten_set_timeout_loop(MainLoop, 10, NULL); + EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); + emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); } From d62157985169697492ff48e1b03cfdc7f75b4974 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Sat, 22 Feb 2025 15:38:01 +0100 Subject: [PATCH 2/6] Rename test --- test/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 47b90a8cba9ed..02d0d625455d7 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5519,7 +5519,7 @@ def test_audio_worklet_params_mixing(self, args): # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends. @requires_sound_hardware @also_with_minimal_runtime - def test_audio_worklet_emscripten_futex_wake(self): + def test_audio_worklet_emscripten_locks(self): self.btest_exit('webaudio/audioworklet_emscripten_futex_wake.cpp', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2']) def test_error_reporting(self): From 5467649b53afdf7d11343f23df74b15409a9478c Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Sat, 22 Feb 2025 16:36:53 +0100 Subject: [PATCH 3/6] Fixed for btest_exit() --- .../audioworklet_emscripten_futex_wake.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_futex_wake.cpp index 02f56e178fa66..eb37a21b05480 100644 --- a/test/webaudio/audioworklet_emscripten_futex_wake.cpp +++ b/test/webaudio/audioworklet_emscripten_futex_wake.cpp @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include // Tests that these audio worklet compatible functions work, details in comments below: @@ -52,27 +50,27 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, case TEST_HAS_WAIT: // Should not have wait support here result = _emscripten_thread_supports_atomics_wait(); - printf("TEST_HAS_WAIT: %d (expect: 0)\n", result); + emscripten_outf("TEST_HAS_WAIT: %d (expect: 0)", result); assert(!result); whichTest = TEST_TRY_ACQUIRE; break; case TEST_TRY_ACQUIRE: // Was locked after init, should fail to acquire result = emscripten_lock_try_acquire(&testLock); - printf("TEST_TRY_ACQUIRE: %d (expect: 0)\n", result); + emscripten_outf("TEST_TRY_ACQUIRE: %d (expect: 0)", result); assert(!result); whichTest = TEST_WAIT_ACQUIRE_FAIL; break; case TEST_WAIT_ACQUIRE_FAIL: // Still locked so we fail to acquire result = emscripten_lock_busyspin_wait_acquire(&testLock, 100); - printf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)\n", result); + emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result); assert(!result); whichTest = TEST_WAIT_ACQUIRE; case TEST_WAIT_ACQUIRE: // Will get unlocked in main thread, so should quickly acquire result = emscripten_lock_busyspin_wait_acquire(&testLock, 100); - printf("TEST_WAIT_ACQUIRE: %d (expect: 1)\n", result); + emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result); assert(result); whichTest = TEST_RELEASE; break; @@ -80,7 +78,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, // Unlock, check the result emscripten_lock_release(&testLock); result = emscripten_lock_try_acquire(&testLock); - printf("TEST_RELEASE: %d (expect: 1)\n", result); + emscripten_outf("TEST_RELEASE: %d (expect: 1)", result); assert(result); whichTest = TEST_WAIT_INFINTE_1; break; @@ -93,7 +91,7 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, break; case TEST_GET_NOW: result = (int) (emscripten_get_now() - startTime); - printf("TEST_GET_NOW: %d (expect: > 0)\n", result); + emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", result); assert(result > 0); whichTest = TEST_DONE; case TEST_DONE: @@ -125,9 +123,12 @@ bool MainLoop(double time, void* data) { // Spin here until released in process (but don't change test until we know this case ran) whichTest = TEST_WAIT_INFINTE_2; emscripten_lock_busyspin_waitinf_acquire(&testLock); - printf("TEST_WAIT_INFINTE (from main)\n"); + emscripten_out("TEST_WAIT_INFINTE (from main)"); break; case TEST_DONE: + // Finished, exit from the main thread + emscripten_out("Test success"); + emscripten_force_exit(0); return false; default: break; @@ -161,4 +162,6 @@ int main() { emscripten_set_timeout_loop(MainLoop, 10, NULL); EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL); emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL); + + emscripten_exit_with_live_runtime(); } From 349792bb5bc6122f6620a9fe9a263556cbfe06e9 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Sat, 22 Feb 2025 16:38:57 +0100 Subject: [PATCH 4/6] Doesn't yet work with 2GB or wasm64 --- test/test_browser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 02d0d625455d7..61d8749785059 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5516,7 +5516,9 @@ def test_audio_worklet_params_mixing(self, args): shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/') self.btest_exit('webaudio/audioworklet_params_mixing.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DTEST_AND_EXIT'] + args) - # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends. + # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends + @no_wasm64('https://github.com/emscripten-core/emscripten/pull/23508') + @no_2gb('https://github.com/emscripten-core/emscripten/pull/23508') @requires_sound_hardware @also_with_minimal_runtime def test_audio_worklet_emscripten_locks(self): From eed0ca20fa776e023187762692fbc24cb4962972 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 25 Feb 2025 09:12:48 +0100 Subject: [PATCH 5/6] Removed thread pool --- test/test_browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_browser.py b/test/test_browser.py index 61d8749785059..3b6f93735af07 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5522,7 +5522,7 @@ def test_audio_worklet_params_mixing(self, args): @requires_sound_hardware @also_with_minimal_runtime def test_audio_worklet_emscripten_locks(self): - self.btest_exit('webaudio/audioworklet_emscripten_futex_wake.cpp', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2']) + self.btest_exit('webaudio/audioworklet_emscripten_futex_wake.cpp', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread']) def test_error_reporting(self): # Test catching/reporting Error objects From e27b24f6d9ddf01580e757966d3fce02e124bc41 Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 25 Feb 2025 09:18:56 +0100 Subject: [PATCH 6/6] Added note --- test/webaudio/audioworklet_emscripten_futex_wake.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/webaudio/audioworklet_emscripten_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_futex_wake.cpp index eb37a21b05480..f76e81234ffb9 100644 --- a/test/webaudio/audioworklet_emscripten_futex_wake.cpp +++ b/test/webaudio/audioworklet_emscripten_futex_wake.cpp @@ -13,7 +13,7 @@ // - emscripten_lock_release() // - emscripten_get_now() -// Internal, found in 'system/lib/pthread/threading_internal.h' +// Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) extern "C" int _emscripten_thread_supports_atomics_wait(void); typedef enum {