Skip to content

Enable getrandom support for Emscripten - #2877

Open
guybedford wants to merge 1 commit into
briansmith:mainfrom
guybedford:emscripten
Open

guybedford wants to merge 1 commit into
briansmith:mainfrom
guybedford:emscripten

Conversation

@guybedford

Copy link
Copy Markdown

This enables SystemRandom on wasm32-unknown-emscripten.

getrandom 0.4 implements Emscripten via libc getentropy, which Emscripten's runtime backs with crypto.getRandomValues, so this is the environment's CSPRNG rather than a custom or rdrand implementation, matching the criterion for the existing target_os list.

  • Adds target_os = "emscripten" to the SystemRandom cfg in src/rand.rs.

No build.rs changes were needed; the existing wasm32 C build works as-is with CC=emcc AR=emar.

Tested locally with Emscripten 6.0.10 and Node 22: cargo test --target wasm32-unknown-emscripten with CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node passes the full suite including rand_tests and doctests.

crowlKats added a commit to jsr-io/jsr that referenced this pull request Oct 10, 2026
Moves the branch off every local checkout and onto published toolchains:

* [patch.crates-io] now carries the workers-rs emscripten tokio patchset
  verbatim from cloudflare/workers-rs examples/emscripten-tokio (guybedford
  tokio/mio tags + rust-lang/libc), and the whole wasm-bindgen family comes
  from crates.io 0.2.129 — the sibling ../workers-rs forks are gone, as are
  the 3 tokio methods that fork needed.
* `worker-build --emscripten --release` (workers-rs 0.8.7) is the build
  driver, via api/build-worker.sh and wrangler's [build] command. It
  provisions emsdk 6.0.10 and applies the emscripten frontend patches itself,
  so the hand-patched emscripten checkout and the import-source workaround
  are no longer needed. It also requires the `worker` SDK crate in the graph,
  which is what makes it pass the tokio/wasm-bindgen unstable cfgs.
* The fetch export is `#[wasm_bindgen(experimental_tokio = "isolated")]`
  (upstream's name) and drops `js_namespace = ["default"]`: worker-build's
  generated shim provides the default export as a WorkerEntrypoint subclass.
* tokio can no longer use `features = ["full"]` on wasm — the patched tokio
  hard-errors on process/signal/rt-multi-thread for emscripten — so the
  shared dep lists a wasm-buildable set and native re-adds `full`.
* Release profile for the worker: opt-level = "s" + fat LTO, 36.6MB -> 24.2MB
  wasm. opt-level = "z" reaches 22.0MB but triples latency and starves the
  per-request pool under load; opt-level = 3 + LTO does not link (wasm-bindgen
  cannot parse the exnref output). See EMSCRIPTEN_EXPERIMENT.md#size.
* Four patches retired by upgrading instead of patching (libc from the git
  patchset, and socket2 0.6 / deno_doc / tar from the vendored set), leaving the
  tokio+mio git patchset and six vendored cfg fixes:
  - libc: the emscripten declarations shipped in 0.2.190, so it comes from
    crates.io now. The release alone was not enough - the mio fork hardcodes
    `libc = { git = "rust-lang/libc", branch = "libc-0.2" }`, so a
    [patch."https://github.com/rust-lang/libc"] entry redirects that source at the
    published crate, otherwise the graph carries two libc copies.
  - socket2 0.6: 0.6.5 has the emscripten cfg upstream (tokio uses it unpatched);
    socket2 0.5 refreshed 0.5.7 -> 0.5.10.
  - deno_doc: 0.208.0 ships denoland/deno_doc#857. Needs deno_graph =0.109.0 ->
    =0.111.0 (two deno_graph versions in one graph are different types), and with
    it: BuildOptions::unstable_text_imports is gone (0.111 allows
    `with { type: "text" }` unconditionally - jsr had it false, and there is no
    flag left to keep it off), BuildOptions gained unstable_config_imports and
    prefer_cached_jsr_versions (both false = previous behaviour), GenerateOptions
    gained symbol_listing_limit (None = render everything, as before).
  - tar: 0.4.46 builds for emscripten unpatched. The blocker was jsr's own
    fixture - big_file/big.txt is 21MB of zeros stored sparsely, and tar >= 0.4.46
    writes a sparse file as a GNUSparse entry, which the reader rejects as
    invalidEntryType before the size check the test is about. tar.sparse(false) in
    the test helpers fixes it and stops the fixture depending on the checkout's
    filesystem.
  The six fixes that remain are deliberately NOT in this commit. jsr does not
  carry local patches of third-party crates, so the committed [patch] table is
  just the git tags above, and as a consequence the wasm32-unknown-emscripten
  link does not succeed from a clean checkout until those six land upstream.
  Native builds are unaffected: `cargo test` is 173/173 with no patch directory
  present at all, and Cargo.lock resolves those crates from crates.io at main's
  versions (reqwest 0.12.12 and astral-tokio-tar 0.6.3 pinned back so re-resolving
  does not drag in unrelated drift).
  EMSCRIPTEN_EXPERIMENT.md records what each fix is - 2 lines for ring, 3 for
  slug, 4 for socket2, ~10 for astral-tokio-tar, 110 mechanical ones for reqwest -
  and the blocker for each: ring
  needs the tree-sitter grammar bump (tree-sitter-javascript 0.21.4 is the last
  crate pinning cc ~1.0.90) before briansmith/ring#2877 can be used; socket2 0.5
  is pulled only by routerify's hyper "tcp" feature; reqwest is stuck in two
  different ways (below); slug is a mandatory comrak 0.29 dependency that
  deno_doc pins; astral-tokio-tar has no upstream fix in 0.7.0.
* .gitignore picks up .wrangler/, api/build/ and .emscripten-patches/, and
  .licenserc.json / deno.json exclude .emscripten-patches/ as well, so a locally
  reconstructed copy of those crates cannot be committed by accident and does not
  trip the license-header check (it globs **/*.rs and flagged 418 files).

Also fixes a regression from the first experiment commit: the native-only
rust-s3 dep had lost `default-features = false`, which re-enabled rust-s3's
`fail-on-err` and turned every missing-object 404 into a 500 (3 failing tests).

Upstream re-checked 2026-10-08: mio#1969 is merged but unreleased (mio 1.2.4
predates it), tokio#8484 and ring#2877 are still open, rust-s3 0.38.0 is still on
reqwest 0.12, and comrak 0.56 drops slug to a dev-dependency but deno_doc still
pins comrak 0.29.

reqwest specifically (2026-10-10): the emscripten gate only ever landed in 0.13 -
0.12.28, the latest 0.12, still has the bare cfg(target_arch = "wasm32") - and
neither copy can reach it from jsr's side. The 0.12 copy belongs to rust-s3 and
opentelemetry (opentelemetry-http is still ^0.12 even at 0.31), and 0.13 cannot
satisfy ^0.12. The 0.11 copy is capped at 0.12 by oauth2 5.0.0, so the only
reachable step is consolidating 0.11 -> 0.12: it retires one of the two patches
and costs oauth2 4->5 plus postmark 0.10->2.0 in the auth/email path.

Verified: 173/173 + 8 native tests pass; the worker serves on stock
`wrangler dev` (no forked workerd) with 64/64 concurrent requests returning
200 and no cross-request-I/O errors.
crowlKats added a commit to crowlKats/jsr that referenced this pull request Oct 10, 2026
Bumps tree-sitter-highlight 0.22.6 -> 0.26.13 and the eleven grammars to their
current releases. They no longer have to move in lockstep with the core: the
grammar crates went over to tree-sitter-language 0.1 and don't depend on
tree-sitter itself any more.

The reason to do it now is the cc pin. tree-sitter-javascript 0.21.4 was the
last crate in the graph asking for cc ~1.0.90, which held cc at 1.0.106, and
that in turn held ring at 0.17.9 since 0.17.10 and up need cc ^1.2. Current
grammars ask for cc ^1.1/^1.2, so cc resolves to 1.7.0 and ring moves to
0.17.14. That also removes the last blocker on the emscripten branch's ring
patch, though the emscripten fix itself is still only in an open PR
(briansmith/ring#2877).

Highlight stays on 0.26.13 rather than 0.27.1, and tree-sitter-language is
pinned to 0.1.7, because 0.27/0.1.8 raised their MSRV to 1.90 and we pin
1.89.0.

Two API changes, both in api/src/tree_sitter.rs: grammars export a LanguageFn
constant (LANGUAGE, LANGUAGE_TYPESCRIPT, LANGUAGE_XML and so on) instead of a
language() function, and HtmlRenderer::render's attribute callback writes into
a buffer now instead of returning a slice.

Highlighting had no test coverage, so I compared the rendered output for ts,
jsx, rust, bash, json, css, xml, md, regex and html before and after the bump:
byte-identical. Added a test asserting a token gets a class in each language,
because nothing fails loudly when a grammar and CAPTURE_NAMES stop lining up -
the code just comes back as plain text.
crowlKats added a commit to crowlKats/jsr that referenced this pull request Oct 10, 2026
The tree-sitter bump took tree-sitter-javascript off `cc ~1.0.90`, so cc is at
1.7.0 and ring at 0.17.14. What's left for ring is the emscripten fix itself,
briansmith/ring#2877, which is open and lives on `guybedford:emscripten` rather
than in briansmith/ring, so using it would mean a git dep on a fork.
crowlKats added a commit to jsr-io/jsr that referenced this pull request Oct 10, 2026
Two of the emscripten patches disappeared in the merge, without any work on this
branch: main moved jsr's own HTTP client to reqwest 0.13, which has the
emscripten cfg upstream, so only rust-s3's 0.12 copy is left; and main did the
tree-sitter bump itself, which lifts the `cc ~1.0.90` pin that was holding ring
at 0.17.9. ring now comes from the branch briansmith/ring#2877 is opened from
rather than a local patch, so the remaining three (socket2 0.5, reqwest 0.12,
astral-tokio-tar) are the only ones left.

How the conflicts were resolved:

* api/Cargo.toml: main's dependency set, with the experiment's gating re-applied
  on top - tokio trimmed to a wasm-buildable feature set (native re-adds
  `full`), the OTLP stack and jemalloc moved into the native-only target block,
  rust-s3 split so wasm skips `tokio-rustls-tls`, async-tar swapped for
  astral-tokio-tar, and askalono's `gzip` codec. deno_doc stays at =0.209.0 and
  comrak at 0.56 (the slug fix); everything else takes main's version.
* jsonwebtoken is now declared per target: main's `aws_lc_rs` backend for
  native, the pure-Rust `rust_crypto` one for wasm, because aws-lc-sys does not
  build for emscripten and the two backends do not feature-unify. jsonwebkey is
  gone with main's move to jsonwebtoken 11.
* api/src/tracing.rs and api/src/tree_sitter.rs: main's rewrites taken whole -
  the opentelemetry 0.32 API, and the hand-rolled line renderer with source
  links - then re-gated/ported. tracing.rs keeps the wasm32 cfgs around the
  exporter stack and the tolerant `set_global_default`/`DISPATCH` (the worker
  rebuilds its router per request); tree_sitter.rs moves to comrak 0.56's
  `fmt::Write` adapters.
* api/src/analysis.rs and api/src/api/package.rs: main had already added the
  deno_graph 0.111 `BuildOptions` fields, so our copies of them are dropped.
  package.rs keeps main's specifier links and collects into a String, which is
  what comrak's `fmt::Write` adapters need.
* api/src/api/mod.rs: main's `/hooks` scope, then the cfg-gated jemalloc routes.
* api/src/docs.rs: main's `symbol_listing_limit: Some(SYMBOL_LISTING_LIMIT)`.
* api/src/publish.rs: main's wording for the `tar.sparse(false)` comment - it
  fixed the same sparse-fixture problem independently.
* Cargo.lock: regenerated from main's rather than hand-merged.

Native verified after the merge: cargo test 267/267, clippy and fmt clean.
@JustYannicc

Copy link
Copy Markdown

This is the same SystemRandom cfg omission we encountered with released Ring 0.17.14. A downstream focused probe reproduced the pristine compile failure, then passed compilation, linking and execution with only Emscripten added to this cfg.

Additional coverage was Emscripten 4.0.10, Rust nightly-2026-03-01, Node 24.18.0 with JSPI, getrandom 0.2.17 and Rustls 0.23.45. It used the existing getrandom → libc getentropy → Emscripten random_get route. This is older-release evidence; your PR uses current Ring/getrandom 0.4 and already reports the newer SDK/full suite.

The focused Rust calls are:

use ring::rand::{SecureRandom, SystemRandom};

let mut bytes = [0u8; 32];
SystemRandom::new().fill(&mut bytes).unwrap();
let provider = rustls::crypto::ring::default_provider();
provider.secure_random.fill(&mut bytes).unwrap();

Our synthetic host also checks entropy failure propagation. It proves the exercised RNG route, not a TLS handshake or a complete application. No duplicate PR is needed for this fix.

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.

2 participants