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_futex_wake.cpp b/test/webaudio/audioworklet_emscripten_futex_wake.cpp deleted file mode 100644 index 65d0c8dd60234..0000000000000 --- a/test/webaudio/audioworklet_emscripten_futex_wake.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#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; - -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()); - - emscripten_futex_wait(&futexLocation, 1, /*maxWaitMs=*/2); - testSuccess = 1; - - return false; -} - -EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), { - let startButton = document.createElement('button'); - startButton.innerHTML = 'Start playback'; - document.body.appendChild(startButton); - - audioContext = emscriptenGetAudioObject(audioContext); - startButton.onclick = () => { - audioContext.resume(); - }; -}); - -bool PollTestSuccess(double, void *) { - if (testSuccess) { - printf("Test success!\n"); -#ifdef REPORT_RESULT - REPORT_RESULT(0); -#endif - return false; - } - return true; -} - -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_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); -} - -uint8_t wasmAudioWorkletStack[4096]; - -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); -} diff --git a/test/webaudio/audioworklet_emscripten_locks.c b/test/webaudio/audioworklet_emscripten_locks.c new file mode 100644 index 0000000000000..c6e0485f0733c --- /dev/null +++ b/test/webaudio/audioworklet_emscripten_locks.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include + +// 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' +int _emscripten_thread_supports_atomics_wait(void); + +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; + +// 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; + +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), { + let startButton = document.createElement('button'); + startButton.innerHTML = 'Start playback'; + document.body.appendChild(startButton); + + audioContext = emscriptenGetAudioObject(audioContext); + startButton.onclick = () => { + audioContext.resume(); + }; +}); + +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; +} + +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, 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, NULL); +} + +uint8_t wasmAudioWorkletStack[2048]; + +int main() { + // 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); +}