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 -->