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..3b6f93735af07 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5516,12 +5516,13 @@ 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 + @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 - @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']) + def test_audio_worklet_emscripten_locks(self): + 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 diff --git a/test/webaudio/audioworklet_emscripten_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_futex_wake.cpp index 95aa2f6e87b25..f76e81234ffb9 100644 --- a/test/webaudio/audioworklet_emscripten_futex_wake.cpp +++ b/test/webaudio/audioworklet_emscripten_futex_wake.cpp @@ -1,32 +1,105 @@ -#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(); +// Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread) +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(); + 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); + 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); + 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); + emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result); + assert(result); + whichTest = TEST_RELEASE; + break; + case TEST_RELEASE: + // Unlock, check the result + emscripten_lock_release(&testLock); + result = emscripten_lock_try_acquire(&testLock); + emscripten_outf("TEST_RELEASE: %d (expect: 1)", 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); + emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", 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 +113,25 @@ 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); + 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; } return true; } @@ -54,20 +139,29 @@ 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); + + emscripten_exit_with_live_runtime(); }