next.js
ee39f8f4 - turbo-rcstr: replace `rcstr!` macro_rules with proc macro (#93551)

Commit
96 days ago
turbo-rcstr: replace `rcstr!` macro_rules with proc macro (#93551) ### What? Replaces the `rcstr!` macro in `turbo-rcstr` with a proc-macro implementation in a new `turbo-rcstr-macros` crate. The proc macro inspects the literal at expansion time and emits only the relevant arm — no dead `else` branch. ### Why? The previous `macro_rules!` macro emitted both an inline arm and a static + `inventory::submit!` arm at every call site, expecting `const` evaluation to discard one. `inventory::submit!` adds `#[used]` to its constructor, so the dead arm shipped in the binary even when const eval determined the live arm was the inline one. Across the workspace there are ~852 inlinable `rcstr!` literals (≤ 7 bytes); each previously emitted a wasted `PrehashedString` static, an inventory node, and a startup `ctor`. ### How? A `#[proc_macro] rcstr` in the new `turbo-rcstr-macros` crate decides at expansion time: - inlinable literal → `inline_atom(lit).unwrap()`. No static, no inventory entry, no dead branch. - non-inlinable literal → static + `inventory::submit!` + `from_static`, no inline arm. - constant identifiers / `concat!(…)` / non-literal inputs → fall back to the original both-branches expansion so const eval picks the arm. ~11 such call sites. `turbo-rcstr`'s `atom_size_64` / `atom_size_128` features forward to the proc-macro crate so the threshold is exact for every build configuration without a runtime const branch. The proc macro deliberately avoids `syn`, `quote`, and `proc-macro2`. It pattern-matches on `proc_macro::TokenTree` directly, asks the compiler for the literal's unescaped value via `Literal::str_value` (gated on the unstable `proc_macro_value` feature), and emits the chosen expansion by parsing a string template via `TokenStream::from_str`. The proc-macro crate compiles in ~0.7 s with zero third-party dependencies. ### Compile time Clean rebuild of `turbopack-core` in release mode (143 `rcstr!` call sites). Dependencies cached; only `turbopack-core` itself rebuilt each iteration. `darwin-aarch64`. | | n | min | mean | median | stdev | | ------------------ | ---:| --------:| --------:| --------:| --------:| | canary | 7 | 27.11 s | 28.00 s | 28.12 s | 0.61 s | | `rcstr_inventory` | 7 | 26.60 s | 27.99 s | 27.96 s | 0.80 s | | **delta** | | | **−0.01 s (−0.06 %)** | −0.16 s | | Within noise. The proc-macro per-invocation overhead is offset by reduced emitted-token volume (~40 % less code across all `rcstr!` sites — the 852 inlinable literals collapse from ~80 tokens to ~6 tokens each), so the compiler does materially less parsing and type-checking on the macro output. ### Binary size Release build of `next-napi-bindings` cdylib on `darwin-aarch64`: | | canary | `rcstr_inventory` | delta | | ---------------------------- | ------------------------------- | ------------------------------- | ------------------------------ | | Release `.dylib` (raw) | 123,699,312 B (117.97 MiB) | 123,349,424 B (117.64 MiB) | **−349,888 B (−0.28 %)** | | Stripped (`strip -x`) | 83,502,264 B (79.63 MiB) | 83,434,984 B (79.57 MiB) | **−67,280 B (−0.08 %)** | | Stripped + `gzip -9` | 28,967,762 B (27.63 MiB) | 28,958,198 B (27.62 MiB) | **−9,564 B (−0.03 %)** | The raw release build shrinks by ~342 KiB — the 852 dead `static PrehashedString` + inventory node + ctor entries no longer being emitted for inlinable call sites. Most of that compresses well (dead data is stripped and gzip handles the rest), so the user-facing impact in the gzipped npm tarball is ~9 KiB. <!-- NEXT_JS_LLM_PR -->
Author
Parents
Loading