AstNodes on the stack, and only chain links in the arena (#27963)
Stop allocating every `AstNode` wrapper in the arena, following the approach of #21931. ### What changes - `AstNode<'parent, 'a, T> { inner, parent, following_span_start }` is `Copy` and has no `&Allocator`. - `'parent` is the lifetime of the parent `AstNode` that `parent` refers to. - Generated child accessors return an owned `AstNode` that borrows its parent. - The parent's `write` keeps its children on its stack frame. - The unsafe lifetime extension (`transmute_self`) is gone. - The arena-backed `as_ast_nodes()` is replaced by a generated owned `AstNodeKind` (`as_kind()`). - `NeedsParentheses for AstNode<Expression>` is generated as one direct match. ### Sites that are not a plain recursive descent A loop that walks down a chain cannot keep each node on the stack, as the next node refers to it as its parent. #21931 hit this at about 10 sites. - Chain walks (binary-like flattening, member chains, arrow chains, assignment-like, nested namespaces) keep their loops. - Only the links that the walk continues past go into the arena, through `AstNode::in_arena` / `AstNodeKind::in_arena`. - The rule is documented once on `AstNode`. - Checks that never ask for a parent read the AST directly, with no wrapper. - Left sides: one table, `LeftSide::of`, which `left_span_of`, `leftmost` and the `AstNode` walk `left_of` derive from. - The shape checks of assignment-like chains. - The binary-like split and the flattened logical expression are loops too, so long chains need no more stack than on `main`. ### Compared to #21931 (Jim's PoC) - Enum nodes resolve through the generated `as_kind()`, so call sites keep the `match` shape of `as_ast_nodes()`. - PoC rewrote each of them by hand, as a `match` on `inner` with `with_inner`. - The arena is used only through `in_arena`, and only for the links a chain walk continues past. - PoC put an `f.allocator().alloc(..)` at each site that broke, kept the recursion, and allocated at the entry of a flatten. - Checks that never ask for a parent use no wrapper. - PoC allocated a wrapper at each step of e.g. `get_innermost_expression`. - `ImportExpression::arguments` takes the allocator, instead of leaving `to_arguments` as a TODO. ### Alternatives tried Both follow directions suggested in #21931. - #27958: no arena at all - As suggested "replace loop with recursive function call". - The chain walks recurse, and a walk that needs the whole chain keeps it in a `StackList` on the stack, consumed through continuation callbacks. - The recursion uses stack in proportion to the chain length: with 2 MiB workers, `return a.b().b()…` overflows at 4000 calls, where `main` formats 32000. - So it needs a workaround, e.g. 8 MiB `oxfmt` workers, which still leaves the napi API and the LSP on default stacks. - About 0.5 points faster than this PR on CodSpeed, but more code. - #27959: no parent pointer - As suggested "Alternative approach". - Ancestors on a `NonEmptyStack` like `Traverse`, and `following_span_start` computed on demand. - 2-4% more instructions than #27958, quadratic on long chains, and the ancestry is only checked in debug builds.
⚓ Oxc
/oʊ ɛks siː/
The Oxidation Compiler is a collection of high-performance tools for JavaScript and TypeScript written in Rust.
Oxc is part of VoidZero's vision for a unified, high-performance toolchain for JavaScript. It powers Rolldown (Vite's bundler) and enables the next generation of ultra-fast development tools that work seamlessly together.
For more information, check out our website at oxc.rs.
* Oxidation is the chemical process that creates rust
🙋 Who's using Oxc?
Rolldown and Nuxt use Oxc for parsing. Rolldown also uses Oxc for transformation and minification. Nova, swc-node, and knip use oxc_resolver for module resolution. Preact, Shopify, ByteDance, and Shopee use oxlint for linting.
🔧 Lint or Format a Codebase
🧰 Build Tooling on Top of Oxc
- Parse JavaScript and TypeScript: Parser
- Transform TypeScript, JSX, and modern JavaScript: Transformer
- Minify JavaScript for production builds: Minifier
- Resolve modules for JavaScript and TypeScript: Resolver
✍️ Contribute
Check out some of the good first issues or ask us on Discord.
See CONTRIBUTING.md for guidance, or read the complete contributing guide on our website →
If you are unable to contribute by code, you can still participate by:
- Add a GitHub Star to the project
- Join us on Discord
- Follow me on X and post about this project
📚 Other Resources
Sponsored By
📖 License
Oxc is free and open-source software licensed under the MIT License.
Thank you to namespace.so for powering our CI/CD pipelines with fast, free macOS and Linux runners.
Oxc ports or copies code from other open source projects, their licenses are listed in Third-party library licenses.