Skip to content

Commit 273f021

Browse files
authored
[AUDIO_WORKLET] Enable AW spinlocks, verify them in the browser tests (#23729)
- spinlocks fixed so they work in audio worklets - adds tests for various spinlock cases - test converted to C-only (still in a C++ file for now, see below) This was unmerged last year (#22995), visited again now the browser tests are running with audio. It's been moved over to `btest_exit()` and verified that it runs (and will only exit after all tests have run or asserted). The only change I'd make is to rename the original test file, which has grown beyond its original use. A `git mv` command won't survive multiple commits, so I want to either leave it until last, live with the resulting deletion, or rename as a separate PR. Fixes: #22962
1 parent 1cebdf5 commit 273f021

File tree

4 files changed

+138
-41
lines changed

4 files changed

+138
-41
lines changed

system/lib/pthread/emscripten_thread_state.S

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ emscripten_is_main_browser_thread:
5353
global.get is_main_thread
5454
end_function
5555

56-
# Semantically the same as testing "!ENVIRONMENT_IS_WEB" in JS
56+
# Semantically the same as testing "!ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_AUDIO_WORKLET" in JS
5757
.globl _emscripten_thread_supports_atomics_wait
5858
_emscripten_thread_supports_atomics_wait:
5959
.functype _emscripten_thread_supports_atomics_wait () -> (i32)

system/lib/wasm_worker/library_wasm_worker.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,17 @@ void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) {
105105
}
106106

107107
bool emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) {
108+
// TODO: we changed the performance_now calls to get_now, which can be applied
109+
// to the remaining code (since all calls defer to the best internal option).
108110
emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
109111
if (!val) return true;
110112

111-
double t = emscripten_performance_now();
113+
double t = emscripten_get_now();
112114
double waitEnd = t + maxWaitMilliseconds;
113115
while (t < waitEnd) {
114116
val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
115117
if (!val) return true;
116-
t = emscripten_performance_now();
118+
t = emscripten_get_now();
117119
}
118120
return false;
119121
}

test/test_browser.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5516,12 +5516,13 @@ def test_audio_worklet_params_mixing(self, args):
55165516
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
55175517
self.btest_exit('webaudio/audioworklet_params_mixing.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DTEST_AND_EXIT'] + args)
55185518

5519-
# Tests AudioWorklet with emscripten_futex_wake().
5519+
# Tests AudioWorklet with emscripten_lock_busyspin_wait_acquire() and friends
5520+
@no_wasm64('https://github.com/emscripten-core/emscripten/pull/23508')
5521+
@no_2gb('https://github.com/emscripten-core/emscripten/pull/23508')
55205522
@requires_sound_hardware
55215523
@also_with_minimal_runtime
5522-
@disabled('https://github.com/emscripten-core/emscripten/issues/22962')
5523-
def test_audio_worklet_emscripten_futex_wake(self):
5524-
self.btest('webaudio/audioworklet_emscripten_futex_wake.cpp', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread', '-sPTHREAD_POOL_SIZE=2'])
5524+
def test_audio_worklet_emscripten_locks(self):
5525+
self.btest_exit('webaudio/audioworklet_emscripten_futex_wake.cpp', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-pthread'])
55255526

55265527
def test_error_reporting(self):
55275528
# Test catching/reporting Error objects
Lines changed: 128 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,105 @@
1-
#include <emscripten/webaudio.h>
21
#include <emscripten/threading.h>
3-
#include <stdio.h>
4-
#include <stdlib.h>
2+
#include <emscripten/wasm_worker.h>
3+
#include <emscripten/webaudio.h>
54
#include <assert.h>
65

7-
// Tests that
8-
// - _emscripten_thread_supports_atomics_wait() returns true in a Wasm Audio Worklet.
9-
// - emscripten_futex_wake() does not crash in a Wasm Audio Worklet.
10-
// - emscripten_futex_wait() does not crash in a Wasm Audio Worklet.
11-
// - emscripten_get_now() does not crash in a Wasm Audio Worklet.
12-
13-
int futexLocation = 0;
14-
int testSuccess = 0;
6+
// Tests that these audio worklet compatible functions work, details in comments below:
7+
//
8+
// - _emscripten_thread_supports_atomics_wait()
9+
// - emscripten_lock_init()
10+
// - emscripten_lock_try_acquire()
11+
// - emscripten_lock_busyspin_wait_acquire()
12+
// - emscripten_lock_busyspin_waitinf_acquire()
13+
// - emscripten_lock_release()
14+
// - emscripten_get_now()
1515

16-
// Internal, found in 'system/lib/pthread/threading_internal.h'
17-
extern "C" int _emscripten_thread_supports_atomics_wait();
16+
// Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread)
17+
extern "C" int _emscripten_thread_supports_atomics_wait(void);
1818

19-
bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) {
20-
int supportsAtomicWait = _emscripten_thread_supports_atomics_wait();
21-
printf("supportsAtomicWait: %d\n", supportsAtomicWait);
22-
assert(!supportsAtomicWait);
23-
emscripten_futex_wake(&futexLocation, 1);
24-
printf("%f\n", emscripten_get_now());
19+
typedef enum {
20+
// No wait support in audio worklets
21+
TEST_HAS_WAIT,
22+
// Acquired in main, fail in process
23+
TEST_TRY_ACQUIRE,
24+
// Keep acquired so time-out
25+
TEST_WAIT_ACQUIRE_FAIL,
26+
// Release in main, succeed in process
27+
TEST_WAIT_ACQUIRE,
28+
// Release in process after above
29+
TEST_RELEASE,
30+
// Released in process above, spin in main
31+
TEST_WAIT_INFINTE_1,
32+
// Release in process to stop spinning in main
33+
TEST_WAIT_INFINTE_2,
34+
// Call emscripten_get_now() in process
35+
TEST_GET_NOW,
36+
// Test finished
37+
TEST_DONE
38+
} Test;
2539

26-
emscripten_futex_wait(&futexLocation, 1, /*maxWaitMs=*/2);
27-
testSuccess = 1;
40+
// Lock used in all the tests
41+
emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;
42+
// Which test is running (sometimes in the worklet, sometimes in the main thread)
43+
_Atomic Test whichTest = TEST_HAS_WAIT;
44+
// Time at which the test starts taken in main()
45+
double startTime = 0;
2846

29-
return false;
47+
bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) {
48+
int result = 0;
49+
switch (whichTest) {
50+
case TEST_HAS_WAIT:
51+
// Should not have wait support here
52+
result = _emscripten_thread_supports_atomics_wait();
53+
emscripten_outf("TEST_HAS_WAIT: %d (expect: 0)", result);
54+
assert(!result);
55+
whichTest = TEST_TRY_ACQUIRE;
56+
break;
57+
case TEST_TRY_ACQUIRE:
58+
// Was locked after init, should fail to acquire
59+
result = emscripten_lock_try_acquire(&testLock);
60+
emscripten_outf("TEST_TRY_ACQUIRE: %d (expect: 0)", result);
61+
assert(!result);
62+
whichTest = TEST_WAIT_ACQUIRE_FAIL;
63+
break;
64+
case TEST_WAIT_ACQUIRE_FAIL:
65+
// Still locked so we fail to acquire
66+
result = emscripten_lock_busyspin_wait_acquire(&testLock, 100);
67+
emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result);
68+
assert(!result);
69+
whichTest = TEST_WAIT_ACQUIRE;
70+
case TEST_WAIT_ACQUIRE:
71+
// Will get unlocked in main thread, so should quickly acquire
72+
result = emscripten_lock_busyspin_wait_acquire(&testLock, 100);
73+
emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result);
74+
assert(result);
75+
whichTest = TEST_RELEASE;
76+
break;
77+
case TEST_RELEASE:
78+
// Unlock, check the result
79+
emscripten_lock_release(&testLock);
80+
result = emscripten_lock_try_acquire(&testLock);
81+
emscripten_outf("TEST_RELEASE: %d (expect: 1)", result);
82+
assert(result);
83+
whichTest = TEST_WAIT_INFINTE_1;
84+
break;
85+
case TEST_WAIT_INFINTE_1:
86+
// Still locked when we enter here but move on in the main thread
87+
break;
88+
case TEST_WAIT_INFINTE_2:
89+
emscripten_lock_release(&testLock);
90+
whichTest = TEST_GET_NOW;
91+
break;
92+
case TEST_GET_NOW:
93+
result = (int) (emscripten_get_now() - startTime);
94+
emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", result);
95+
assert(result > 0);
96+
whichTest = TEST_DONE;
97+
case TEST_DONE:
98+
return false;
99+
default:
100+
break;
101+
}
102+
return true;
30103
}
31104

32105
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), {
@@ -40,34 +113,55 @@ EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), {
40113
};
41114
});
42115

43-
bool PollTestSuccess(double, void *) {
44-
if (testSuccess) {
45-
printf("Test success!\n");
46-
#ifdef REPORT_RESULT
47-
REPORT_RESULT(0);
48-
#endif
116+
bool MainLoop(double time, void* data) {
117+
switch (whichTest) {
118+
case TEST_WAIT_ACQUIRE:
119+
// Release here to acquire in process
120+
emscripten_lock_release(&testLock);
121+
break;
122+
case TEST_WAIT_INFINTE_1:
123+
// Spin here until released in process (but don't change test until we know this case ran)
124+
whichTest = TEST_WAIT_INFINTE_2;
125+
emscripten_lock_busyspin_waitinf_acquire(&testLock);
126+
emscripten_out("TEST_WAIT_INFINTE (from main)");
127+
break;
128+
case TEST_DONE:
129+
// Finished, exit from the main thread
130+
emscripten_out("Test success");
131+
emscripten_force_exit(0);
49132
return false;
133+
default:
134+
break;
50135
}
51136
return true;
52137
}
53138

54139
void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) {
55140
int outputChannelCounts[1] = { 1 };
56141
EmscriptenAudioWorkletNodeCreateOptions options = { .numberOfInputs = 0, .numberOfOutputs = 1, .outputChannelCounts = outputChannelCounts };
57-
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, 0);
142+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, NULL);
58143
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
59144
InitHtmlUi(audioContext);
60145
}
61146

62147
void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) {
63148
WebAudioWorkletProcessorCreateOptions opts = { .name = "noise-generator" };
64-
emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, 0);
149+
emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, NULL);
65150
}
66151

67-
uint8_t wasmAudioWorkletStack[4096];
152+
uint8_t wasmAudioWorkletStack[2048];
68153

69154
int main() {
70-
emscripten_set_timeout_loop(PollTestSuccess, 10, 0);
71-
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(0);
72-
emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, 0);
155+
// Main thread init and acquire (work passes to the processor)
156+
emscripten_lock_init(&testLock);
157+
int hasLock = emscripten_lock_busyspin_wait_acquire(&testLock, 0);
158+
assert(hasLock);
159+
160+
startTime = emscripten_get_now();
161+
162+
emscripten_set_timeout_loop(MainLoop, 10, NULL);
163+
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL);
164+
emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL);
165+
166+
emscripten_exit_with_live_runtime();
73167
}

0 commit comments

Comments
 (0)