Skip to content

Add no_std support specifically for wasm32v1-none - #4630

Open
bushrat011899 wants to merge 30 commits into
rust-windowing:masterfrom
bushrat011899:no_std
Open

Add no_std support specifically for wasm32v1-none#4630
bushrat011899 wants to merge 30 commits into
rust-windowing:masterfrom
bushrat011899:no_std

Conversation

@bushrat011899

@bushrat011899 bushrat011899 commented Jul 14, 2026

Copy link
Copy Markdown

Description

Details

This PR demonstrates adding no_std support to winit specifically for wasm32v1-none, affecting winit-core, winit-web, and winit.

Note that this currently requires a patch for web-time:

[patch.crates-io]
web-time = { git = "https://github.com/daxpedda/web-time" }

The published version does not include support for wasm32v1-none, but it is implemented on the repository's main branch. The changes in this PR are still usable regardless, but actual compilation on wasm32v1-none requires the above patch.

Since this PR doesn't target general no_std support, no new feature gates are required.

Notable Changes

winit-core

  • SurfaceSizeWriter has been refactored to use PhysicalSize<AtomicU32> instead of a Mutex, but that change is abstracted around a new SurfaceSizeWriterHandle type. I believe this simplifies the API for users while also making no_std support simpler.
  • Added libm exclusively for all(target_family = "wasm", target_os = "none") (wasm32v1-none). It's technically required for all no_std platforms, but this is the only no_std target that this PR unblocks, so no need to add a feature gate for it.
  • wasm32v1-none will mark BadIcon as non-exhaustive to avoid dealing with the OsError variant. This can be removed once core::io is stable.
  • wasm32v1-none marks WindowEvent as non_exhaustive as the DataTransferReceived variant requires dyn TypedData, which requires IO, which is currently nightly-only for no_std.

winit-web

  • Now includes once_cell as a dependency. This is acceptable in my opinion, as once_cell was already a transitive dependency through wasm-bindgen (and others).
  • async::channel has been modified to use ConcurrentQueue rather than std::mpsc::channel.
  • async::Dispatcher uses a channel to return values rather than a Mutex/Condvar combo. Since Wasm has a stubbed std I don't believe thread yielding is actually more performant that busy waiting.
  • Several uses of OnceLock have been replaced with once_cell::race::{OnceBox, OnceBool, OnceRef}, and OnceCell where appropriate.
  • Uses of thread_local have either been removed entirely where it is relatively cheap to just recompute the stored value or replaced with a global static where it is appropriate to share said value between threads.

winit

  • winit's cfg_aliases have been updated to include wasm32v1-none as a web_platform.

Testing

Currently tested using cargo check -p winit --target wasm32v1-none --all-features.

Checklist

  • Tested on all platforms changed
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
    • Unsure if needed. Only the change to SurfaceSizeWriter is really visible, and actual compilation on wasm32v1-none still requires a patch.
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality
    • An example would require the patch, so unsure if desirable?

Notes

  • No AI tooling of any kind was used during the creation of this PR.

@bushrat011899
bushrat011899 force-pushed the no_std branch 2 times, most recently from 2b8b9f8 to f9526f6 Compare July 14, 2026 10:05
@bushrat011899 bushrat011899 changed the title [Draft]: Add no_std support specifically for wasm32v1-none Add no_std support specifically for wasm32v1-none Jul 14, 2026
@bushrat011899
bushrat011899 marked this pull request as ready for review July 14, 2026 10:52

@ogoffart ogoffart left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work.

I'm thinking it might not be wise to remove all the caching. One can use the once_cell::race that you use anyway.

Currently tested using cargo check -p winit --target wasm32v1-none --all-features.

Can we add that on the CI?

Comment thread winit-core/src/libm.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a question: why do we need this module instead of always using libm directly?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I expect Winit would want to avoid using libm on platforms which have std (since the std implementations are what are used right now and can have differences in performance and precision). This stub module makes it clear what methods are actually needed from libm and avoid polluting the use sites with conditional compilation logic.

Comment thread winit-core/src/icon.rs
impl_dyn_casting!(IconProvider);

#[derive(Debug)]
#[cfg_attr(all(target_family = "wasm", target_os = "none"), non_exhaustive)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason not to make it non_exhaustive in all cases?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Purely to preserve the API as-is for std equipped platforms. I think it would make sense to leave it permanently non_exhaustive. But, IO in core is already on nightly, so this may be able to be removed in a not-too-distant MSRV bump anyway.

Comment thread winit-web/src/web_sys/event.rs Outdated
loop {
match receiver.try_recv() {
Ok(Some(value)) => break value,
_ => core::hint::spin_loop(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is quite suspicious.
Is there really no way to block here?
I think we can still have threads with Web Worker, and spinlock doesn't seem like a good idea.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

core and alloc provide no mechanism to block or yield execution, but web-sys might provide something we could use (I think a timeout causing a yield, but there might be something more direct). I'll look into this a bit more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ok so I believe this is the best we can do on web. The std::sync::Mutex implementation internally using a futex with atomics enabled, or just a boolean otherwise (arguing that the platform has no threads and therefore cannot contest). The futex internally just calls spin_loop while waiting to unlock, and without atomics yield_now is an empty function (again, no threads, so nothing to yield to).

The only way to enter into this loop is if you're in a worker and call Dispatcher::queue. Once you call that function, the work must be performed on the main thread, and the function must return the value produced by the work done on the main thread. Since the worker is blocked on the main thread, there's nothing we can do other than spin. The use of Condvar doesn't change the equation here either, since that internally just calls try_unlock in a loop on Wasm without atomics.

I do agree that it's not ideal, but this is a natural consequence of the function signature of queue.

Comment thread winit-web/src/web_sys/mod.rs Outdated
Comment thread winit-web/src/lock.rs
Comment thread winit-web/src/web_sys/schedule.rs Outdated
Comment thread winit-core/src/event.rs

#[derive(Debug)]
struct SharedSurfaceSize {
inner: PhysicalSize<AtomicU32>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice that it works. But now it is two different atomic and so it is possible for one thread to read partial result (the width has been set from one thread, but not the height)

It might not matter if all the read and right are expected to be on the same thread, as i don't think in practice, SurfaceSizeWriter is often being sent to a different thread. But maybe then it should be changed to a RefCell and remove the Send bound.

Or use an AtomicU64 instead of a PhysicalSize

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I originally used a packed AtomicU64 for this, but there are quite a few targets which currently don't support 64-bit atomics but do support 32-bit. I summarised that for foldhash here if you'd like to see exactly what I'm referring to. Since winit-core currently only uses AtomicUsize, adding a use of AtomicU64 would break any of those targets (which may or may not be currently supported). I don't believe there's a risk of actually observing unmatched width and height values, since the current logic already assumes the call to window_event will cause the SurfaceSize to be correctly updated. Since there's no synchronisation logic that actually ensures that is the case, winit must already be resilient to the surface size potentially not being updated correctly after window_event returns. So the worst case scenario remains a two-step resize, with the new possibility of updating either the width or height early.

@bushrat011899
bushrat011899 force-pushed the no_std branch 2 times, most recently from 31af106 to 67ed1f7 Compare July 21, 2026 23:36
@bushrat011899

bushrat011899 commented Jul 21, 2026

Copy link
Copy Markdown
Author

Can we add that on the CI?

I would like to, but because web-time still hasn't published an updated version with wasm32v1-none support (only available on their Git repo), it would require using a patch, which I'm unsure if is desirable.

EDIT: Added wasm32v1-none as a configuration to test in CI. It does requiring disabling steps that build the test configuration or documentation, as they aren't made no_std compatible. It still requires a patch but I worked out how to pass it as a CLI configuration.

@bushrat011899
bushrat011899 force-pushed the no_std branch 2 times, most recently from 0daa2d6 to be2d802 Compare July 22, 2026 00:00
@bushrat011899

Copy link
Copy Markdown
Author

cargo deny failure is due to syn 3 being released and is unrelated to this PR.

@bushrat011899
bushrat011899 force-pushed the no_std branch 11 times, most recently from c0376ca to 528308d Compare July 22, 2026 04:31
Certain `f64` methods are only available with `std`, so `libm` is required to fill the gaps.
Since the navigator is available and provides consistent results across the main thread and workers, a global value is appropriate. Using a `OnceBox` would be appropriate, but a `OnceRef` avoids the one-off allocation at the expense of a bit of verbosity.
`Document` is only available from the main thread, so this function can only be called in the context of the main thread. Therefore, the `thread_local` that was originally caching the result can be replaced with a global static instead.
Requires a patch to `web-time` at time of writing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Support wasm32v1-none

2 participants