Enable wasm-bindgen feature with target wasm32-unknown-emscripten - #1349
Conversation
roderickvd
left a comment
There was a problem hiding this comment.
That's great! Not so long ago we removed the old Emscripten host that had become defunct. This seems like a light-weight manner to get Emscripten support back.
Beyond the changes requested in the review points, please also consider updating README.md with Emscripten support.
ccac245 to
1620436
Compare
|
Thank you for your swift response! I have responded to the comments, and additionally updated the tables in the README to reflect support for the Emscripten target. |
roderickvd
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround. Here's a few points, hopefully the last.
1620436 to
40c1b2b
Compare
Enable the WebAudio host (and its wasm-bindgen/js-sys/web-sys deps) for `wasm32-unknown-emscripten`, gated on `any(target_os = "emscripten", target_os = "unknown")` so nothing wasm-bindgen-related is pulled in for `wasm32-wasip1`/`wasip2`. The AudioWorklet host stays `wasm32-unknown-unknown`-only. The three WebAudio JS callbacks keep using `Closure::wrap`. Dropping the `as Box<dyn FnMut(_)>` cast keeps the closures concrete, so their captures (all `UnwindSafe`) satisfy the `panic=unwind` bound on Emscripten without `wrap_aborting` -- a callback panic still surfaces as a JS exception rather than aborting the instance. Minimum `wasm-bindgen` stays at 0.2. README: document the `wasm32-unknown-emscripten` target (Emscripten 6.0.3, wasm-bindgen 0.2.127) and list it under the `wasm-bindgen` feature. Adds an Emscripten CI job. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
40c1b2b to
3846df3
Compare
572e408 to
31691df
Compare
roderickvd
left a comment
There was a problem hiding this comment.
Thanks for the iteration. Some more points inside.
Additionally:
- would you add
wasm-emscriptento thepublish-cpalgate? - add a CI path for threading and the proxying?
| if is_main_thread() { | ||
| Some(run(func)) | ||
| } else { | ||
| None |
There was a problem hiding this comment.
None maps to ErrorKind::UnsupportedConfig but not being able to proxy seems more like an UnsupportedOperation. Maybe return a Resultrather thanOption` here?
There was a problem hiding this comment.
In the old code path, when attempting to create an input/output stream from a worker thread, it would fail on this line:
https://github.com/RustAudio/cpal/blob/master/src/host/webaudio/mod.rs#L324-L330
So I chose that error variant because it would preserve the existing behavior. But I like the idea of returning a Result here - much more explicit. I'll go ahead and make that change.
There was a problem hiding this comment.
The switch to the Result looks good, but the Rustdoc is stale and still talks about returning Some or None with both try_run implementations.
There was a problem hiding this comment.
I should have caught that. Thanks. Fixed in 411f5cc
| (&raw mut slot).cast(), | ||
| ); | ||
|
|
||
| assert!( |
There was a problem hiding this comment.
How does this not panic with try_run?
Both this assert!() and the .expect() below.
There was a problem hiding this comment.
My intended semantics for try_run are:
- If it's possible to run the closure on the main browser thread, do it
- If it wasn't possible, return
null
My intention was that try_run would always return Some on Emscripten targets, since Emscripten always has a main thread. The only case where emscripten_proxy_sync returns false is when the target thread is null or cancelled, but I don't think that can reasonably happen for the UI thread? So I added these asserts to document invariants that I would never expect to fail. The main point of having try_run return None was for wasm32-unknown-unknown, where proxying isn't possible.
There was a problem hiding this comment.
Since (according to my best understanding) these expects should never fail - it should always be possible to proxy something to Emscripten's runtime thread - I would push to leave them as is.
There was a problem hiding this comment.
I understand. Keeping the expect indeed is better than simply unwrapping, but then why do we still need the assertion?
There was a problem hiding this comment.
Good point - I have left the expects, but the assert is unnecessary, because if the function didn't run then the expect on the very next line will fail. Removed the extra check in 411f5cc
583ef9e to
3492db2
Compare
|
I added the To cover threading, I modified the Let me know if you had something more in mind - to really exercise this code path, we should add an example. But |
|
Your CI rationale sounds reasonable: let's keep it like that for now. Thanks for linking to those headers. Reading through
But what we're proxying now can block and isn't entirely safe: reating an AudioContext, getUserMedia, buffer allocation, mutex acquisition. That is neither nonblocking nor signal-handler-safe. Is this something we should be using |
The Emscripten runtime does some magic to provide a synchronous API for stuff like network and file IO. Javascript only provides async versions of such functions, so when a normal thread attempts to read a file or write a socket, it will block that thread and proxy to the main one (much as we are doing here). I believe that this is what the documentation means when it says nonblocking and safe to run. Creating an The one instance where I could imagine an issue is allocation: in the past, there have been deadlock bugs related to using
Considering this, I think the warning is just meant to say "hey, don't expect Emscripten functions that are already proxied to work, such as file IO." I haven't been able to come up with a concrete failure path for us, so my instinct is to leave it. But I'm happy to allocate a separate queue in a thread local if you'd feel better about it. I also want to note that with a proxying approach like this, it's always possible for incorrect user code create a deadlock, assuming that user code is using mutexes of its own. That's an unavoidable consequence of this architecture. |
| ok, | ||
| "emscripten_proxy_sync to the browser main thread failed" | ||
| ); | ||
| Ok(slot.ret.take().expect("proxied task did not run")) |
There was a problem hiding this comment.
Nitpick: you can use .ok_or() to transform an Option into a Result.
There was a problem hiding this comment.
From the way I understand your suggestion, this would look like:
slot.ret.ok_or_else(|| panic!("proxied task did not run"))I'm not totally convinced that the control flow is more obvious here. With the current code, you can look and see the expect followed by the Ok, and see that it's an unwrap (or panic) followed by putting the value into a Result. With ok_or_else, the reader needs to parse the panic! and then the fact that it only happens in this conditionally-executing closure.
Of course, we could properly return an Err here for the exceptional case:
slot.ret.ok_or_else(|| Error::with_message(
ErrorKind::BackendError,
"proxied task did not run",
))But from our previous conversations here I thought that we were keeping the panic-on-error.
I did notice that the .take() is useless here though, so I'll remove that in any case.
There was a problem hiding this comment.
Yes, I meant that we might as well defensively return Err if we've got that function signature anyway.
|
Thanks, that makes sense. Just dropped a comment which I think should be the last, if you're still up to iterating over that. Should be good to go then! |
|
Thanks, merged! |
|
Just want to say thank you for iterating with me! Your dedication and thoroughness as a maintainer are very apparent :) |
Summary
The
wasm-bindgentool is finally getting support for integration with Emscripten. The feature is still quite new, but this means that most of Rust's web ecosystem can now work with the Emscripten target. I'd like to usecpalin a WASM/Emscripten project, but right now thewasm32-unknown-emscriptentarget is hard-coded to use the null backend. This PR eliminates the feature gate to make thewasm-bindgenfeature work withwasm32-unknown-emscriptentoo.This PR exposes the
webaudiobackend but not theaudioworkletbackend. That backend relies on re-instantiating the WASM module, but the way Emscripten modules get instantiated is different, so it wouldn't work without more changes.Changes
#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "wasm-bindgen"))]with#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]for thewebaudiobackendClosure::wrap_abortinginstead ofClosure::wrapso that the code properly compiles on WASM targets withpanic=unwindwasm-bindgendependency to0.2.110in order to useClosure::wrap_abortingTesting
In my own project, I have gotten
cpalaudio working with a Rust/Emscripten WASM module in Chrome. This PR also adds CI checks to ensure that compilation is successful.Related issues
#92 #413 #810