Skip to content

Commit 0792e7c

Browse files
committed
Complete rethink of the test
1 parent 97840e6 commit 0792e7c

File tree

1 file changed

+154
-130
lines changed

1 file changed

+154
-130
lines changed
Lines changed: 154 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#include <emscripten/threading.h>
21
#include <emscripten/wasm_worker.h>
32
#include <emscripten/webaudio.h>
43
#include <assert.h>
@@ -7,180 +6,205 @@
76
//
87
// - _emscripten_thread_supports_atomics_wait()
98
// - emscripten_lock_init()
10-
// - emscripten_lock_try_acquire()
119
// - emscripten_lock_busyspin_wait_acquire()
12-
// - emscripten_lock_busyspin_waitinf_acquire()
1310
// - emscripten_lock_release()
14-
// - emscripten_get_now()
11+
// - emscripten_get_now() in AW
12+
13+
// Build with emcc -sAUDIO_WORKLET -sWASM_WORKERS -pthread -O1 -g -o index.html audioworklet_emscripten_locks.c
14+
15+
// Values -1.5373, 77.2259, -251.4728
16+
// Values -0.9080, -42.4902, -250.6685
17+
18+
// Marks a function to be kept in the Module and exposed to script (instead of adding to EXPORTED_FUNCTIONS)
19+
#ifndef KEEP_IN_MODULE
20+
#define KEEP_IN_MODULE __attribute__((used, visibility("default")))
21+
#endif
22+
23+
// This needs to be big enough for a stereo output (1024 with a 128 frame) + working stack
24+
#define AUDIO_STACK_SIZE 2048
1525

1626
// Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread)
1727
int _emscripten_thread_supports_atomics_wait(void);
1828

1929
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,
30+
// The test hasn't yet started
31+
TEST_NOT_STARTED,
32+
// Worklet ready and running the test
33+
TEST_RUNNING,
34+
// Main thread is finished, wait on worklet
35+
TEST_DONE_MAIN,
3636
// Test finished
3737
TEST_DONE
3838
} Test;
3939

40+
// Global audio context
41+
EMSCRIPTEN_WEBAUDIO_T context;
4042
// Lock used in all the tests
4143
emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;
4244
// Which test is running (sometimes in the worklet, sometimes in the main thread)
43-
_Atomic Test whichTest = TEST_HAS_WAIT;
45+
_Atomic Test whichTest = TEST_NOT_STARTED;
4446
// Time at which the test starts taken in main()
4547
double startTime = 0;
4648

47-
bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) {
48-
assert(emscripten_current_thread_is_audio_worklet());
49+
// Counter for main, accessed only by main
50+
int howManyMain = 0;
51+
// Counter for the audio worklet, accessed only by the AW
52+
int howManyProc = 0;
53+
54+
// Our dummy container
55+
typedef struct Dummy {
56+
uint32_t val0;
57+
uint32_t val1;
58+
uint32_t val2;
59+
} Dummy;
60+
61+
// Start values
62+
void initDummy(Dummy* dummy) {
63+
dummy->val0 = 4;
64+
dummy->val1 = 1;
65+
dummy->val2 = 2;
66+
}
4967

50-
// Produce at few empty frames of audio before we start trying to interact
51-
// with the with main thread.
52-
// On chrome at least it appears the main thread completely blocks until
53-
// a few frames have been produced. This means it may not be safe to interact
54-
// with the main thread during initial frames?
55-
// In my experiments it seems like 5 was the magic number that I needed to
56-
// produce before the main thread could continue to run.
57-
// See https://github.com/emscripten-core/emscripten/issues/24213
58-
static int count = 0;
59-
if (count++ < 5) return true;
60-
61-
int result = 0;
62-
switch (whichTest) {
63-
case TEST_HAS_WAIT:
64-
// Should not have wait support here
65-
result = _emscripten_thread_supports_atomics_wait();
66-
emscripten_outf("TEST_HAS_WAIT: %d (expect: 0)", result);
67-
assert(!result);
68-
whichTest = TEST_TRY_ACQUIRE;
69-
break;
70-
case TEST_TRY_ACQUIRE:
71-
// Was locked after init, should fail to acquire
72-
result = emscripten_lock_try_acquire(&testLock);
73-
emscripten_outf("TEST_TRY_ACQUIRE: %d (expect: 0)", result);
74-
assert(!result);
75-
whichTest = TEST_WAIT_ACQUIRE_FAIL;
76-
break;
77-
case TEST_WAIT_ACQUIRE_FAIL:
78-
// Still locked so we fail to acquire
79-
result = emscripten_lock_busyspin_wait_acquire(&testLock, 100);
80-
emscripten_outf("TEST_WAIT_ACQUIRE_FAIL: %d (expect: 0)", result);
81-
assert(!result);
82-
whichTest = TEST_WAIT_ACQUIRE;
83-
case TEST_WAIT_ACQUIRE:
84-
// Will get unlocked in main thread, so should quickly acquire
85-
result = emscripten_lock_busyspin_wait_acquire(&testLock, 10000);
86-
emscripten_outf("TEST_WAIT_ACQUIRE: %d (expect: 1)", result);
87-
assert(result);
88-
whichTest = TEST_RELEASE;
89-
break;
90-
case TEST_RELEASE:
91-
// Unlock, check the result
68+
void printDummy(Dummy* dummy) {
69+
emscripten_outf("Values: %u, %u, %u", dummy->val0, dummy->val1, dummy->val2);
70+
}
71+
72+
// Run a simple calculation that will only be stable *if* all values are atomically updated
73+
void runCalcs(Dummy* dummy, int num) {
74+
for (int n = 0; n < num; n++) {
75+
int have = emscripten_lock_busyspin_wait_acquire(&testLock, 100);
76+
assert(have);
77+
dummy->val0 += dummy->val1 * dummy->val2;
78+
dummy->val1 += dummy->val2 * dummy->val0;
79+
dummy->val2 += dummy->val0 * dummy->val1;
80+
dummy->val0 /= 4;
81+
dummy->val1 /= 3;
82+
dummy->val2 /= 2;
9283
emscripten_lock_release(&testLock);
93-
result = emscripten_lock_try_acquire(&testLock);
94-
emscripten_outf("TEST_RELEASE: %d (expect: 1)", result);
95-
assert(result);
96-
whichTest = TEST_WAIT_INFINTE_1;
97-
break;
98-
case TEST_WAIT_INFINTE_1:
99-
// Still locked when we enter here but move on in the main thread
84+
}
85+
}
86+
87+
void stopping() {
88+
emscripten_out("Expect: 949807601, 1303780836, 243502614");
89+
emscripten_out("Ending test");
90+
emscripten_destroy_audio_context(context);
91+
emscripten_force_exit(0);
92+
}
93+
94+
// AW callback
95+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
96+
assert(emscripten_current_thread_is_audio_worklet());
97+
switch (whichTest) {
98+
case TEST_NOT_STARTED:
99+
whichTest = TEST_RUNNING;
100100
break;
101-
case TEST_WAIT_INFINTE_2:
102-
emscripten_lock_release(&testLock);
103-
whichTest = TEST_GET_NOW;
101+
case TEST_RUNNING:
102+
case TEST_DONE_MAIN:
103+
if (howManyProc-- > 0) {
104+
runCalcs((Dummy*) data, 250);
105+
} else {
106+
if (whichTest == TEST_DONE_MAIN) {
107+
// Both loops are finished
108+
whichTest = TEST_DONE;
109+
}
110+
}
104111
break;
105-
case TEST_GET_NOW:
106-
result = (int) (emscripten_get_now() - startTime);
107-
emscripten_outf("TEST_GET_NOW: %d (expect: > 0)", result);
108-
assert(result > 0);
109-
whichTest = TEST_DONE;
110112
case TEST_DONE:
113+
emscripten_outf("Took %dms (expect: > 0)", (int) (emscripten_get_now() - startTime));
111114
return false;
112-
default:
113-
break;
114115
}
115116
return true;
116117
}
117118

118-
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), {
119-
let startButton = document.createElement('button');
120-
startButton.innerHTML = 'Start playback';
121-
document.body.appendChild(startButton);
122-
123-
audioContext = emscriptenGetAudioObject(audioContext);
124-
startButton.onclick = () => {
125-
audioContext.resume();
126-
};
127-
});
128-
129-
bool MainLoop(double time, void* data) {
119+
// Main thread callback
120+
bool mainLoop(double time, void* data) {
130121
assert(!emscripten_current_thread_is_audio_worklet());
131-
static int didUnlock = false;
132122
switch (whichTest) {
133-
case TEST_WAIT_ACQUIRE:
134-
if (!didUnlock) {
135-
emscripten_out("main thread releasing lock");
136-
// Release here to acquire in process
137-
emscripten_lock_release(&testLock);
138-
didUnlock = true;
123+
case TEST_NOT_STARTED:
124+
break;
125+
case TEST_RUNNING:
126+
if (howManyMain-- > 0) {
127+
runCalcs((Dummy*) data, 1000);
128+
} else {
129+
// Done here, so signal to process()
130+
whichTest = TEST_DONE_MAIN;
139131
}
140132
break;
141-
case TEST_WAIT_INFINTE_1:
142-
// Spin here until released in process (but don't change test until we know this case ran)
143-
whichTest = TEST_WAIT_INFINTE_2;
144-
emscripten_lock_busyspin_waitinf_acquire(&testLock);
145-
emscripten_out("TEST_WAIT_INFINTE (from main)");
133+
case TEST_DONE_MAIN:
134+
// Wait for process() to finish
146135
break;
147136
case TEST_DONE:
148-
// Finished, exit from the main thread
149-
emscripten_out("Test success");
150-
emscripten_force_exit(0);
137+
printDummy((Dummy*) data);
138+
// 32-bit maths with locks *should* result in these:
139+
assert(((Dummy*) data)->val0 == 949807601
140+
&& ((Dummy*) data)->val1 == 1303780836
141+
&& ((Dummy*) data)->val2 == 243502614);
142+
stopping();
151143
return false;
152-
default:
153-
break;
154144
}
155145
return true;
156146
}
157147

158-
void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) {
159-
int outputChannelCounts[1] = { 1 };
160-
EmscriptenAudioWorkletNodeCreateOptions options = { .numberOfInputs = 0, .numberOfOutputs = 1, .outputChannelCounts = outputChannelCounts };
161-
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, NULL);
162-
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
163-
InitHtmlUi(audioContext);
148+
KEEP_IN_MODULE void startTest() {
149+
startTime = emscripten_get_now();
150+
if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) {
151+
emscripten_resume_audio_context_sync(context);
152+
}
153+
howManyMain = 200;
154+
howManyProc = 200;
164155
}
165156

166-
void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) {
167-
WebAudioWorkletProcessorCreateOptions opts = { .name = "noise-generator" };
168-
emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, NULL);
157+
// HTML button to manually run the test
158+
EM_JS(void, addButton, (), {
159+
var button = document.createElement("button");
160+
button.appendChild(document.createTextNode("Start Test"));
161+
document.body.appendChild(button);
162+
document.onclick = () => {
163+
if (globalThis._startTest) {
164+
_startTest();
165+
}
166+
};
167+
});
168+
169+
// Audio processor created, now register the audio callback
170+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) {
171+
assert(success && "Audio worklet failed in processorCreated()");
172+
emscripten_out("Audio worklet processor created");
173+
// Single stereo output
174+
int outputChannelCounts[1] = { 1 };
175+
EmscriptenAudioWorkletNodeCreateOptions opts = {
176+
.numberOfOutputs = 1,
177+
.outputChannelCounts = outputChannelCounts
178+
};
179+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(ctx, "locks-test", &opts, &process, data);
180+
emscripten_audio_node_connect(worklet, ctx, 0, 0);
169181
}
170182

171-
uint8_t wasmAudioWorkletStack[2048];
183+
// Worklet thread inited, now create the audio processor
184+
void initialised(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) {
185+
assert(success && "Audio worklet failed in initialised()");
186+
emscripten_out("Audio worklet initialised");
187+
WebAudioWorkletProcessorCreateOptions opts = {
188+
.name = "locks-test"
189+
};
190+
emscripten_create_wasm_audio_worklet_processor_async(ctx, &opts, &processorCreated, data);
191+
}
172192

173193
int main() {
174-
// Main thread init and acquire (work passes to the processor)
175194
emscripten_lock_init(&testLock);
176-
int hasLock = emscripten_lock_busyspin_wait_acquire(&testLock, 0);
177-
assert(hasLock);
178-
179-
startTime = emscripten_get_now();
180-
181-
emscripten_set_timeout_loop(MainLoop, 10, NULL);
182-
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL);
183-
emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, NULL);
195+
Dummy* dummy = (Dummy*) malloc(sizeof(Dummy));
196+
initDummy(dummy);
197+
198+
char* const workletStack = memalign(16, AUDIO_STACK_SIZE);
199+
assert(workletStack);
200+
// Audio processor callback setup
201+
context = emscripten_create_audio_context(NULL);
202+
assert(context);
203+
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, dummy);
204+
205+
emscripten_set_timeout_loop(mainLoop, 10, dummy);
206+
addButton();
207+
startTest(); // <-- May need a manual click to start
184208

185209
emscripten_exit_with_live_runtime();
186210
}

0 commit comments

Comments
 (0)