Files
Luke SandbergandGitHub ee39f8f47f 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 -->
2026-05-07 17:05:05 +00:00

27 lines
806 B
TOML

[package]
name = "turbo-rcstr-macros"
version = "0.1.0"
edition = "2024"
license = "MIT"
[lib]
proc-macro = true
bench = false
[features]
# Mirror the threshold-affecting features of `turbo-rcstr`. The consuming
# crate routes them via `turbo-rcstr/atom_size_*` -> `turbo-rcstr-macros/atom_size_*`
# so the proc macro can compute the correct `MAX_INLINE_LEN` at expansion time
# instead of leaving an ambiguous range that needs a runtime const branch.
atom_size_64 = []
atom_size_128 = []
[lints]
workspace = true
[dependencies]
# Intentionally minimal: the only dependency is the standard `proc_macro`
# API. Dropping `syn` and `quote` removes ~hundreds of milliseconds of
# per-invocation overhead and a large slice of the proc-macro crate's
# build time. See the crate-level docs for the reasoning.