From 84eee0a9d902d1c1a09fe750eb404a3d486e12ea Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 14 Jan 2025 11:43:47 +0100 Subject: [PATCH 1/3] Start by renaming test --- ..._emscripten_futex_wake.cpp => audioworklet_emscripten_locks.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/webaudio/{audioworklet_emscripten_futex_wake.cpp => audioworklet_emscripten_locks.c} (100%) diff --git a/test/webaudio/audioworklet_emscripten_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_locks.c similarity index 100% rename from test/webaudio/audioworklet_emscripten_futex_wake.cpp rename to test/webaudio/audioworklet_emscripten_locks.c From 0d98211ee005792d7b23c453ad065004a402729d Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 14 Jan 2025 12:32:24 +0100 Subject: [PATCH 2/3] Locks use AW compatible get_now(), added tests --- system/lib/pthread/emscripten_thread_state.S | 2 +- system/lib/wasm_worker/library_wasm_worker.c | 6 +- test/test_interactive.py | 6 +- test/webaudio/audioworklet_emscripten_locks.c | 152 ++++++++++++++---- 4 files changed, 131 insertions(+), 35 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_interactive.py b/test/test_interactive.py index 511ba785ce831..dbf2019994fcb 100644 --- a/test/test_interactive.py +++ b/test/test_interactive.py @@ -293,10 +293,10 @@ def test_audio_worklet(self): self.btest('webaudio/audioworklet.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '--preload-file', test_file('hello_world.c') + '@/']) self.btest('webaudio/audioworklet.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread']) - # Tests AudioWorklet with emscripten_futex_wake(). + # Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends. @also_with_minimal_runtime - 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']) + def test_audio_worklet_emscripten_locks(self): + self.btest_exit('webaudio/audioworklet_emscripten_locks.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2']) # Tests a second AudioWorklet example: sine wave tone generator. def test_audio_worklet_tone_generator(self): diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 65d0c8dd60234..5e88127a6ea13 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -1,29 +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. +// 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() -int futexLocation = 0; -int testSuccess = 0; +// Internal, found in 'system/lib/pthread/threading_internal.h' +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\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\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\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\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\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\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), { @@ -37,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; } @@ -51,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 de5e8ec750eb099d66bc8dfaa4c2ee697e2e6b7a Mon Sep 17 00:00:00 2001 From: Carl Woffenden Date: Tue, 14 Jan 2025 12:51:01 +0100 Subject: [PATCH 3/3] Print expected results --- test/webaudio/audioworklet_emscripten_locks.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c index 5e88127a6ea13..c6e0485f0733c 100644 --- a/test/webaudio/audioworklet_emscripten_locks.c +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -52,27 +52,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\n", result); + 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\n", result); + 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\n", result); + 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\n", result); + printf("TEST_WAIT_ACQUIRE: %d (expect: 1)\n", result); assert(result); whichTest = TEST_RELEASE; break; @@ -80,7 +80,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\n", result); + printf("TEST_RELEASE: %d (expect: 1)\n", result); assert(result); whichTest = TEST_WAIT_INFINTE_1; break; @@ -93,7 +93,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\n", result); + printf("TEST_GET_NOW: %d (expect: > 0)\n", result); assert(result > 0); whichTest = TEST_DONE; case TEST_DONE: