Skip to content

Conversation

cwoffenden
Copy link
Contributor

@cwoffenden cwoffenden commented Aug 29, 2024

If building with -sAUDIO_WORKLET and --closure=1 the generated glue JS will minimise the property names passed to the worklet's message handler:

p.postMessage({'_wsc': d['callback'], 'x': [d['contextHandle'], 1/*EM_TRUE*/, d['userData']] }); // "WaSm Call"

E.g. these are received as:

A: 3,
B: 1,
F: 0,
s: "noise-generator"

No sound will be generated as a result, since calls to emscripten_create_wasm_audio_worklet_processor_async() will never call the passed function.

The quick fix being to add the names to the literal notation for the message (as per the commit). It might be worthwhile renaming the property names to something shorter though whilst making the changes.

Compiling any of the webaudio tests with --closure will reproduce the error:

emcc -O2 --closure=1 -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -o index.html tone_generator.c

Links here for precompiled before and after. On the before test the 'Toggle playback' button doesn't get added because AudioWorkletProcessorCreated never gets called.

@juj
Copy link
Collaborator

juj commented Aug 29, 2024

Looks great. Something that seems odd is that the browser test suite does have several AudioWorklet tests that do enable Closure, e.g.

# Tests the AudioWorklet demo
@parameterized({
'': ([],),
'memory64': (['-sMEMORY64', '-Wno-experimental'],),
'with_fs': (['--preload-file', test_file('hello_world.c') + '@/'],),
'closure': (['--closure', '1', '-Oz'],),
'asyncify': (['-sASYNCIFY'],),
'pthreads': (['-pthread', '-sPTHREAD_POOL_SIZE=2'],),
'pthreads_and_closure': (['-pthread', '--closure', '1', '-Oz'],),
'minimal_runtime': (['-sMINIMAL_RUNTIME'],),
'minimal_runtime_pthreads_and_closure': (['-sMINIMAL_RUNTIME', '-pthread', '--closure', '1', '-Oz'],),
'pthreads_es6': (['-pthread', '-sPTHREAD_POOL_SIZE=2', '-sEXPORT_ES6'],),
'es6': (['-sEXPORT_ES6'],),
'strict': (['-sSTRICT'],),
})
def test_audio_worklet(self, args):
if '-sMEMORY64' in args and is_firefox():
self.skipTest('https://github.com/emscripten-core/emscripten/issues/19161')
self.btest_exit('webaudio/audioworklet.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'] + args)
# Tests that audioworklets and workers can be used at the same time
def test_audio_worklet_worker(self):
self.btest('webaudio/audioworklet_worker.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'], expected='1')
# Tests that posting functions between the main thread and the audioworklet thread works
@parameterized({
'': ([],),
'closure': (['--closure', '1', '-Oz'],),
})
def test_audio_worklet_post_function(self, args):
self.btest('webaudio/audioworklet_post_function.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'] + args, expected='1')
@parameterized({
'': ([],),
'closure': (['--closure', '1', '-Oz'],),
})
def test_audio_worklet_modularize(self, args):
self.btest_exit('webaudio/audioworklet.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-sMODULARIZE=1', '-sEXPORT_NAME=MyModule', '--shell-file', test_file('shell_that_launches_modularize.html')] + args)

Any chance you'd be able to double check why those tests didn't catch this issue? And/or add/modify the tests there so that they would start failing before this fix?

The fix itself looks solid.

@cwoffenden
Copy link
Contributor Author

Any chance you'd be able to double check why those tests didn't catch this issue?

I'll take a look tomorrow (at a guess it silently fails, which is what the basic tests do).

@cwoffenden
Copy link
Contributor Author

cwoffenden commented Sep 2, 2024

@juj I took a look at the tests you linked to and some of the individual parameterised tests fail to initialise the audio without this fix, e.g.:

test/runner browser.test_audio_worklet_closure
test/runner browser.test_audio_worklet_pthreads_and_closure
test/runner browser.test_audio_worklet_minimal_runtime_pthreads_and_closure

And other tests fail for other reasons, e.g., missing stackAlloc:

test/runner browser.test_audio_worklet_minimal_runtime

But running the original code with the test runner, none seems to flag as a failure. Though if I look at a complete run output I see that the worklet doesn't appear to be loaded for any of the tests:

test_audio_worklet_closure (test_browser.browser) ... cache:INFO: generating system asset: symbol_lists/ed0c6582c55ebab55869b56f918759080dad520a.json... (this will be cached in "/root/cache/symbol_lists/ed0c6582c55ebab55869b56f918759080dad520a.json" for subsequent builds)
cache:INFO:  - ok
127.0.0.1 - - [29/Aug/2024 15:40:47] code 404, message File not found: /root/project/test.aw.js

In the end I ran each test individually, passed --save-dir to the test runner and used emrun to look at each.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 3, 2024

In the end I ran each test individually, passed --save-dir to the test runner and used emrun to look at each.

FYI, when you run just a single test, the output is always saved in out/test, so you don't need --save-dir in this case.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 3, 2024

@juj I took a look at the tests you linked to and some of the individual parameterised tests fail to initialise the audio without this fix, e.g.:

This seems bad, I guess we need to improve the robustness of these test. Its hard I suppose because I'm not sure that we can actually run any actual, audio tests in CI (we run the browser tests with EMTEST_LACKS_SOUND_HARDWARE in CI).

@juj
Copy link
Collaborator

juj commented Sep 4, 2024

some of the individual parameterised tests fail to initialise

none seems to flag as a failure

Yikes, something has definitely gone wrong then. I'll try to look at this tomorrow on how to make this more robust. Was this Closure change enough to get those tests to pass for you?

@cwoffenden
Copy link
Contributor Author

Was this Closure change enough to get those tests to pass for you?

The minimal runtime ones also fail, I’ll try to look at that tomorrow and roll it into this PR.

@juj
Copy link
Collaborator

juj commented Sep 5, 2024

I see there are multiple regressions to Audio Worklet usage from different PRs in the past months. I've got them fixed now in local tree, with interactive tests passing... I'll see what a good way to land these fixes will be.

@cwoffenden cwoffenden force-pushed the cw_audioworklet_closure branch from d3833d1 to 7fb6ddf Compare September 6, 2024 18:12
'ap': audioParams,
'ch': contextHandle,
'cb': callback,
'ud': userData
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes me a bit sad that we have to do this in the source code rather than have the minifier tool do it... but I guess we don't have a better way.

Do we have any codesize test for audioworklets that can means that code size improvement here? If not maybe we should add one.

I wonder if we should do this in the pthreads code too? @juj what do you think of this technique?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine to me.

In general end users should never interact/snoop these postMessage()s, so their format should be strictly internal to the implementation. The only thing here is to make sure we generate messages that are not easy to confuse with user-sent messages - which I think manually mangled names make pretty clear it won't be anything that a user might have written.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, IIUC, there is no difference between these mangled names and the existing _wsc and _wpn mangled names. Can we be consistent and use underscores either for all of them, or none?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One difference was that the _wsc and _wpn fields were the "keys" of the data object, used to identify what the postMessaged struct was. These other fields are the satellite data, that are assumed to be present when that one key field matches. That is why I initially only mangled and underscored that one field to make it clear that "that's the key, others are satellite" disposition.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, lgtm

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm still a little confused. It looks like _wsc and _wpn are both fields the same object that ap and ch are part of.

For example I see registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor(d['ap'])); where they are both fields of d.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the importance of _wpn is that no other message is going to have such a property. There's a test, equivalent to

if (msg.data['_wpn']) {

I can imagine minified properties being called ap, which might trigger a false positive, but it's unlikely there'd be a _wpn.

So my take, I can see reasoning for the underscore here, but for anything else it's just extra characters.

// 'cb' is the callback function
// 'ch' the context handle
// 'ud' the passed user data
p.postMessage({'_wsc': d['cb'], 'x': [d['ch'], 1/*EM_TRUE*/, d['ud']] }); // "WaSm Call"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the trailing comment here up above too?

Also, should we use a leading underscore for all of these or none of them? @juj do you remember why you add the underscore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't _wpn part of the message name so could clash? Same as _wpn, the worklet processor name.

There was already an existing x in there so I didn't add underscores.

I did tidy the comments though because they repeat the same thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juj do you remember why you add the underscore?

Commented in #22472 (comment)

@cwoffenden cwoffenden force-pushed the cw_audioworklet_closure branch from 11af402 to 4136966 Compare September 6, 2024 18:43
@cwoffenden
Copy link
Contributor Author

@juj Fixes also added for MINIMAL_RUNTIME (dependencies plus missing float heap view).

Tested manually but this works with combinations of Closure and minimal runtime.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 10, 2024

This change actually inspired me to do exactly the opposite here for pthreads: #22555

If we even manage to implement the single-file approach like #21701 for wasm workers then we can do that same here too.

@cwoffenden
Copy link
Contributor Author

This change actually inspired me to do exactly the opposite here for pthreads: #22555

A better solution all round, sure.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to see a test for this in browser_tests.py. Can we wait on this change until @juj has updated the audio_worklet tests to be more robust/accurate. IIUC they are currently passing erroneously despite some recent breakages.

lgtm though, @juj if you want to land this first thats fine with me to.

@cwoffenden cwoffenden force-pushed the cw_audioworklet_closure branch 2 times, most recently from beea3b9 to f76433b Compare September 11, 2024 21:22
@juj
Copy link
Collaborator

juj commented Sep 12, 2024

This GitHub/CircleCI keeps on giving..

there was a test failure on build-test and test-browser-chrome due to the single offset converter test failure, which we know to be flaky.

So I opened the test page, and clicked on "Re-run failed tests" to rerun the test-browser-chrome suite again.

However that caused the test runner to re-run tons of other (already passing) suites, except that failing test-browser-chrome-suite that I wanted to rerun.

image

And now other suites are failing with HTTP 500 No Fun for You.

image

To me it seems that all relevant tests are passing, I'd be happy to land this PR.

@cwoffenden cwoffenden force-pushed the cw_audioworklet_closure branch from 37a2737 to f78e26e Compare September 15, 2024 11:21
@cwoffenden cwoffenden force-pushed the cw_audioworklet_closure branch from f78e26e to a5eb47f Compare September 17, 2024 20:48
@cwoffenden
Copy link
Contributor Author

This GitHub/CircleCI keeps on giving..

Run it enough times and it finally all passed!

@sbc100 sbc100 merged commit dd2add0 into emscripten-core:main Sep 18, 2024
28 checks passed
@cwoffenden cwoffenden deleted the cw_audioworklet_closure branch September 18, 2024 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants