Skip to content

Rust I18N Rewrite - #7322

Open
logan-gatlin wants to merge 24 commits into
mainfrom
logan/rust-i18n
Open

logan-gatlin wants to merge 24 commits into
mainfrom
logan/rust-i18n

Conversation

@logan-gatlin

@logan-gatlin logan-gatlin commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Rewrites node I18N in Rust. The C++ implementation defers to ICU and simdutf for some conversions. ICU bindings are provided by rust_icu while simdutf bindings are written by hand (I filed a PR to the existing simdutf bindings to make their build work for us, but was ignored).

JSG Additions

  • Uint8Array::from_buffer
  • new_zeroed_with - allocates a buffer, zeroes it, then writes to it from a closure. This allows Rust to safely create and initialize buffers. Since the buffer is created in the same function it is written to, there are provably no other references to the buffer so mutating is safe.

@logan-gatlin
logan-gatlin requested review from a team as code owners September 11, 2026 15:34
@logan-gatlin logan-gatlin mentioned this pull request Sep 11, 2026
Comment thread src/rust/i18n/lib.rs Outdated
@ask-bonk

ask-bonk Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Since last review: 0 resolved, 0 still open, 0 new.
LGTM!

Not re-run: tests, docs, jsg-gc, kj-style, memory-safety, rust-first (no author changes in their files since the last review)


Reviewed commit: db9e3e66 · github run

@logan-gatlin

Copy link
Copy Markdown
Contributor Author

I'm Bonk, and I've done a quick review of your PR.

Rewrites Node I18N transcoding in Rust behind an autogate.

  1. High: src/rust/i18n/lib.rs:139 exposes uninitialized backing-store bytes through result.buffer. Posted one inline suggestion.

Targeted tests could not build because the runner lacks libc++ headers. Time for a pun! That buffer had too many loose bytes.

github run

fixed

@danlapid

Copy link
Copy Markdown
Collaborator

Still too much unsafe - can't we drop it down more? we really want our rust code to be safe

Comment thread src/rust/i18n/dispatch.rs Outdated
Comment thread src/rust/i18n/dispatch.rs Outdated
Comment thread src/rust/jsg/v8.rs Outdated
@codecov-commenter

codecov-commenter commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.79381% with 98 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.14%. Comparing base (e775a96) to head (db9e3e6).

Files with missing lines Patch % Lines
src/workerd/api/node/tests/buffer-nodejs-test.js 0.00% 72 Missing ⚠️
src/rust/i18n/ffi.rs 95.08% 9 Missing ⚠️
src/workerd/api/node/i18n.c++ 76.92% 2 Missing and 4 partials ⚠️
src/rust/i18n/dispatch.rs 96.29% 5 Missing ⚠️
src/rust/jsg/ffi.c++ 57.14% 2 Missing and 1 partial ⚠️
src/rust/jsg/v8.rs 94.73% 2 Missing ⚠️
src/rust/i18n/error.rs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7322      +/-   ##
==========================================
+ Coverage   41.04%   41.14%   +0.09%     
==========================================
  Files         986      990       +4     
  Lines      284720   285204     +484     
  Branches    27618    27628      +10     
==========================================
+ Hits       116877   117353     +476     
- Misses     151757   151761       +4     
- Partials    16086    16090       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@logan-gatlin

Copy link
Copy Markdown
Contributor Author

Still too much unsafe - can't we drop it down more? we really want our rust code to be safe

Yes we can! Most of the unsafe was bindings to icu and simdutf. We only use a small slice of either library, so I rewrote what we needed in safe Rust.

@logan-gatlin

logan-gatlin commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

After discussing with Mike and James, and running some benchmarks to compare simdutf with my Rust rewrite, I've decided to keep the C bindings to ICU and simdutf.

For ICU it is a compatibility concern, if the ICU C library changes to include new additions to unicode, we need to pick those up. A Rust rewrite puts that maintenance burden on us.

For simdutf it is a performance concern. Rust CAN achieve the same performance as C, but not without unsafe. The compiler intrinsics that simdutf uses are unsafe in Rust.

Comment thread src/workerd/api/node/i18n.c++ Outdated
Comment thread build/deps/rust.MODULE.bazel
Ports workerd::api::node::i18n::transcode to a new Rust crate, //src/rust/i18n, selected at runtime by a new NODEJS_I18N_RUST autogate. The C++ implementation is left in place, byte for byte, as the gate-off/rollback path.

The dispatch table, output-size computation, ICU substitute-character setup, empty-input handling, ISOLATE_LIMIT checks, and truncation all move to Rust (src/rust/i18n/dispatch.rs). The underlying codecs stay the same: a new C++ shim (src/rust/i18n/shim.{h,c++}) exposes the ICU ucnv_* primitives and the four simdutf functions the C++ path already uses, so both paths call identical codecs and cannot silently diverge.

i18n::transcode gets a gate branch at the top, following the createNodeException pattern: it maps api::node::Encoding to the bridge's Encoding enum via a fromImpl overload (kj-rs/convert.h idiom) whose switch has no default arm, rejecting BASE64/BASE64URL/HEX on either side (a deliberate divergence from the unmodified C++ dispatch, which only checks the from encoding). The Rust entry point returns a jsg::v8::ffi::MaybeLocal naming a Uint8Array; ffi-inl.h gains the missing Rust-to-C++ MaybeLocal conversion (maybe_local_from_ffi), mirroring the existing local_from_ffi.

10 Rust unit tests in dispatch.rs cover every (from, to) pair, empty input, unmappable-character substitution, the ASCII->UTF16LE latin1-widening quirk, the UTF8->UTF16LE zero-estimate quirk, invalid UTF-8, odd-length UTF-16LE input, and unpaired surrogates.
Point the src/rust/i18n shim's ICU dependency at :icuuc instead of the umbrella :icu target, per R7 (the shim only uses ucnv_* primitives from ICU's common library, not icui18n).

The bazel/BUILD.icu file vendored via V8's patch set only marks :icu and :icudata public; :icuuc is package-private. Add a small new V8 patch (0039) granting :icuuc public visibility, following the existing precedent of patch 0027 doing the same for :icudata.

Verified: just clippy i18n is clean; bazel test //src/rust/i18n:i18n_test, //src/workerd/api/node/tests:buffer-nodejs-test@, and @all-autogates all pass; full bazel test //... passes 1623/1623, observed running fresh (no cached results).
…ters

c_char is unsigned on aarch64, so the explicit i8 annotations caused a
type mismatch against the cxx-bridge declaration when building for ARM.
- Add a shared safe callback adapter for isolate and handle transitions
- Configure rust_icu_sys for Chromium ICU 78 symbol renaming
- Pass Rust compiler environment through wd_rust_crate
The transcode result exposes its complete ArrayBuffer through the Buffer\nwrapper, including bytes outside the shortened result view. Initialize the\nbacking store so those unwritten bytes cannot reveal allocator contents.\n\nAdd a node:buffer regression test that inspects both regions outside the\nvisible result.
- Remove the simdutf C++ dependency and FFI bindings
- Add Rust codec implementations with parity tests
- Remove the rust_icu_sys dependency
- Match ICU substitution and windows-1252 behavior
- Add C++ integration coverage
- Remove custom c_char16 support from the cxx bridge
- Simplify codec dispatch around the shared Encoding type
- Preserve boundary and Unicode coverage with fewer test inputs
- Reduce redundant exhaustive and pseudo-random cases
The test runs every corpus input through both implementations for all
16 encoding pairs, which exceeds the default 15s timeout on the Windows
CI runners.
- Move the C++ bridge and raw isolate handling into ffi.rs
- Extract codec, dispatch, error, and integration tests into separate files
- Replace safe-Rust codec implementations with ICU and simdutf wrappers
- Add rust_icu_sys configuration and propagate Rust environment settings
- Move codec coverage into FFI tests and initialize ICU in dispatch tests
- Mark FFI conversion results as must-use
- Clarify the encoding conversion ADL comment
- Restore the transcode test attribution
The nodejs-exceptions-rust autogate was removed, so the encoding conversion comment no longer points at it.
- Regenerate the ICU repository from the commit V8 pins
- Read U_ICU_VERSION_MAJOR_NUM from that commit and update RUST_ICU_MAJOR_VERSION_NUMBER in build/deps/rust.MODULE.bazel and src/rust/i18n/BUILD.bazel
- Document the version pin in the V8 update guide
The test takes about 60s on the Windows CI runners, which is the whole of the large timeout, so it intermittently times out there.
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.

4 participants