Skip to content

feat!: use SWC Next for stripping and remove transform mode - #507

Draft
kdy1 wants to merge 6 commits into
nodejs:mainfrom
kdy1:kdy1/try-swc-next
Draft

kdy1 wants to merge 6 commits into
nodejs:mainfrom
kdy1:kdy1/try-swc-next

Conversation

@kdy1

@kdy1 kdy1 commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Switch TypeScript type stripping from swc_ts_fast_strip to the SWC Next crates published on crates.io, pinned to 0.5.0. Preserve fixed-width stripping, the transformSync API, and the existing Node.js REPL helpers.

Remove mode: "transform", the amaro/transform loader, transformation-only options, and their dependencies and tests. Requests for the removed mode now fail instead of falling back to stripping. This is a breaking change, consistent with the removal of transformation support in nodejs/node#61803.

The adapters preserve Amaro's diagnostic codes and location fields, while messages and highlighted ranges come from SWC Next. Existing SWC crates remain for REPL helpers and diagnostic formatting.

Validation

Local validation at 9981c731d with SWC Next 0.5.0:

  • cargo test --manifest-path crates/amaro/Cargo.toml --locked: 18 passed.
  • wasm-pack build crates/amaro --out-name wasm --release --target nodejs -- --locked: passed.
  • npm run build, npm run typecheck, and npm run ci: passed.
  • npm test: 99 passed, including removed-mode rejection, loader behavior, source positions, and REPL helpers.

Draft status

Additional bug analysis of the related swc-next components is currently in progress to look for potential upstream bugs. This PR remains a draft while that analysis continues; the passing tests above do not establish complete compatibility or prove that swc-next is free of bugs.

AI assistance and human review

AI was used to prepare these changes. Human-reviewed by @kdy1.

  • Harness: T3 Code (Codex harness)
  • Model: gpt-6.1-sol
  • Reasoning Effort: medium

@marco-ippolito

Copy link
Copy Markdown
Member

Amazingg work, does this boost the performance?

@kdy1

kdy1 commented Oct 31, 2026

Copy link
Copy Markdown
Member Author
image

It's currently mixed, so I'll optimize the swc-next first

@kdy1

kdy1 commented Nov 1, 2026

Copy link
Copy Markdown
Member Author
image

It's now faster for all cases! The swc-next commit I used is swc-project/swc-next@95e2394, and I'll try to find and fix any remaining bugs before merging.

kdy1 and others added 2 commits November 2, 2026 18:59
`deprecatedTsModuleAsError` is enabled by default, and the check parsed the
whole input a second time when the text contained "module". Most real files
contain that word in `module.exports`, `node:module`, or comments, so most
inputs were parsed twice.

Run the second parse only when a `module` word is followed by a possible
declaration name and that word is erased in the stripped output.
`strip_types` erases every `module` declaration as a whole statement, so a
keyword that survives unchanged is in a comment, a string, or an
expression. Inputs with strip diagnostics keep the previous behavior.

Results for 442 corpus variants, including injected declarations, are
identical to the previous build. This includes error codes, messages, and
locations. Median `transformSync` time in the release WASM build:

- hono-types.ts (76 KB): 1.11 ms -> 0.68 ms
- checker.ts (3 MB): 34.7 ms -> 21.0 ms
- typescript.js (8 MB): 100.7 ms -> 62.9 ms
- astro-config.ts (125 KB): 0.97 ms -> 1.05 ms (the parse is still needed
  because a comment in an erased interface contains "module ...")

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kdy1 added a commit to swc-project/swc-next that referenced this pull request Nov 2, 2026
## Motivation

On targets without a native SIMD backend, including
`wasm32-unknown-unknown` without `simd128`, `swc_next_simd` processed
each 16-byte vector one lane at a time. Loops written with `u8x16` were
then much slower than byte loops. Block comment scanning in the lexer
and blanking in `strip_types` were affected the most. For example, on a
1 MiB single-line block comment, swc-next parse took 5.4 ms, and the old
`swc_ecma_parser` took 0.35 ms. Node.js amaro and the swc-next npm
WebAssembly bindings build for this target without `simd128`.

## Changes

- The scalar backend now processes each vector as two `u64` words
(SWAR). The public API, the per-lane results, and the x86_64, aarch64,
and wasm32 `simd128` backends are unchanged. Call sites are unchanged.
- The scalar module is now compiled for tests on every host. New tests
place every byte pair in every lane position and compare each operation
with a per-lane reference. They also cover lane boundaries, `first_set`,
and `any`.
- On wasm32 without `simd128` (`opt-level = "z"`, fat LTO), compared
with `main`:
  - Parse of a 1 MiB single-line block comment: 5.39 ms → 0.79 ms.
  - Parse of 1 MiB of JSDoc comments: 2.16 ms → 0.45 ms.
- `strip_types` on `tasks/benchmark/files/astro-config.ts`: 0.57 ms →
0.11 ms.
  - No input is slower.
- The `strip_types` output and diagnostics are identical to `main` for
22 inputs: the benchmark files, the inputs from #1418, and edge cases
for line terminators, `0xE2` sequences, unterminated comments, and
16-byte boundaries.

## Related Links

- Close #1418
- nodejs/amaro#507

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`strip_types` erases every `module` declaration, so compare the input
with the stripped output and check only `module` words in erased text.
This replaces the full-text `match_indices` search and the UTF-16 offset
mapping. Also skip a `module` word that follows a non-keyword word on
the same line, because it cannot start a statement. This avoids the
second parse for prose such as `its module dependency` in comments
inside erased interfaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kdy1 added a commit to swc-project/swc-next that referenced this pull request Nov 3, 2026
## Motivation

Size-optimized builds keep the `#[inline]` wrappers in `swc_simd` as
function calls. Amaro builds `swc_next_type_strip` for
`wasm32-unknown-unknown` with `opt-level = "z"` and without `simd128`,
so the scalar backend runs. In a profile of that build, `simd_eq` and
`first_set` stay separate functions inside the block comment scanner,
and each call passes a 16-byte vector through memory. A 1 MB block
comment on one line takes 2.44 ms with `main`, compared with 1.72 ms on
Amaro `main`, which uses `swc_ts_fast_strip`.

## Changes

- Mark the vector and mask operations in `crates/swc_simd/src/lib.rs`
`#[inline(always)]`, like the backend functions that they call. Behavior
does not change.
- Amaro `transformSync`, wasm32 without `simd128`, this branch compared
with `main` (2fe537f). Medians of 6 rounds, measured locally:

  | Input | `main` | This PR | Speedup |
  | --- | ---: | ---: | ---: |
  | 1 MB block comment, one line | 2.44 ms | 2.19 ms | 1.11× |
  | 1 MB block comment, many lines | 2.00 ms | 1.91 ms | 1.05× |
  | 1 MB of JSDoc blocks | 2.14 ms | 2.04 ms | 1.05× |
  | 1 MB of plain block comments | 2.07 ms | 1.97 ms | 1.05× |
  | `typescript.js` (8 MB) | 56.61 ms | 54.88 ms | 1.03× |

The other measured inputs (jQuery, three.js, `checker.ts`, cal.com,
Hono, Effect, and 1 MB string, type, line comment, and whitespace
inputs) changed by less than 1.5%.
- The output matched the `main` build for 400 inputs: the Amaro
benchmark corpus and synthetic inputs, each also with comments, line
separators, and whitespace inserted.
- `cargo fmt --check`, `cargo clippy -- -D warnings` (host and
`wasm32-unknown-unknown`), and the tests of `swc_next_simd`,
`swc_next_ecma_parser`, `swc_next_type_strip`, and
`swc_next_ecma_codegen` pass. Native wall time was not measured.

## Related Links

- #1419
- nodejs/amaro#507

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@kdy1

kdy1 commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

@marco-ippolito Is removing mode: "transform" fine? I'm not sure if it's acceptable at this stage.

@kdy1

kdy1 commented Nov 3, 2026 •

Copy link
Copy Markdown
Member Author

Performance profiling result

  • idle linux machine
Input main PR + 0.5.0 PR + swc-next main vs main vs previous run
typescript.js (8 MB) 135.98 ms 55.94 ms 50.53 ms 2.69× 1.09×
checker-noenum.ts 44.61 ms 18.92 ms 17.80 ms 2.51× 1.09×
three-0.138.3.js 22.16 ms 10.05 ms 8.80 ms 2.52× 1.09×
cal.com-noenum.tsx 19.68 ms 7.92 ms 6.96 ms 2.83× 1.08×
jquery.mobile-1.4.2.js 7.95 ms 3.50 ms 3.23 ms 2.46× 1.07×
angular-1.2.5.js 6.82 ms 3.92 ms 3.07 ms 2.22× 1.15×
jquery-1.9.1.js 5.21 ms 2.32 ms 2.20 ms 2.37× 1.08×
mootools-1.4.5.js 4.02 ms 1.71 ms 1.59 ms 2.53× 1.07×
yui-3.12.0.js 3.97 ms 2.14 ms 1.72 ms 2.31× 1.14×
effect-schema-ast.ts 2.43 ms 1.13 ms 989.9 µs 2.46× 1.07×
err-large-enum.ts 1.70 ms 814.2 µs 651.7 µs 2.61× 1.64×
hono-types.ts 1.60 ms 671.6 µs 504.2 µs 3.17× 1.01×
backbone-1.1.0.js 907.1 µs 358.9 µs 337.8 µs 2.69× 1.14×
underscore-1.5.2.js 764.4 µs 278.9 µs 264.6 µs 2.89× 1.10×
astro-config.ts 388.5 µs 724.2 µs 239.0 µs 1.63× 1.53×
err-small.ts 7.6 µs 7.4 µs 7.4 µs 1.03× 1.00×
hello.ts 2.3 µs 1.6 µs 1.5 µs 1.55× 1.03×
hono-types.ts (check off) 1.61 ms 647.3 µs 481.2 µs 3.34× 1.02×
astro-config.ts (check off) 389.0 µs 676.4 µs 195.4 µs 1.99× 1.08×
checker-noenum.ts (check off) 45.24 ms 18.46 ms 17.37 ms 2.60× 1.01×
Synthetic: block-comments 1 MB 1.92 ms 3.60 ms 1.77 ms 1.09× 1.21×
Synthetic: plain-block-comments 1 MB 1.92 ms 3.39 ms 1.74 ms 1.10× 1.19×
Synthetic: line-comments 1 MB 2.05 ms 1.77 ms 1.77 ms 1.16× 1.11×
Synthetic: one-big-comment-lines 1 MB 1.71 ms 3.42 ms 1.68 ms 1.02× 1.19×
Synthetic: one-big-comment 1 MB 1.71 ms 6.76 ms 1.87 ms 0.91× 1.29×
Synthetic: strings 1 MB 7.27 ms 7.29 ms 3.73 ms 1.95× 1.07×
Synthetic: types-only 1 MB 26.60 ms 11.33 ms 9.33 ms 2.85× 0.99×
Synthetic: whitespace 1 MB 1.73 ms 1.96 ms 1.98 ms 0.87× 1.04×

@marco-ippolito

Copy link
Copy Markdown
Member

@marco-ippolito Is removing mode: "transform" fine? I'm not sure if it's acceptable at this stage.

I think its acceptable as semver major change

kdy1 added a commit to swc-project/swc-next that referenced this pull request Nov 3, 2026
…ckend (#1443)

## Motivation

#1422 made block comments slower in Amaro's `wasm32-unknown-unknown`
build (`opt-level = "z"`, no `simd128`), which uses the scalar backend.
In Amaro `transformSync`, a 1 MB block comment on one line went from
1.87 ms to 5.09 ms. In a profile of that build,
`Lexer::scan_block_comment` is no longer inlined into `next_token`.
Inside it, `<[u8; 16] as IndexMut<RangeTo<usize>>>::index_mut` stays a
separate call and takes 10% of the time. This call comes from
`lanes[..8].copy_from_slice(..)` in the scalar backend. `Option::expect`
in `Simd::from_slice` also stays a call. The 1 MB comment inputs do not
call `TokenReader::read`, so the slowdown comes from code generation,
not from extra work.

## Changes

- The scalar backend's `lanes()` builds the array by destructuring the
two words, and `Simd::from_slice` matches on `first_chunk` instead of
calling `expect`. Behavior does not change. Only the scalar backend uses
`lanes()`. The `from_slice` change applies to all backends.
- Amaro `transformSync`, wasm32 without `simd128`. I measured the same
diff locally on `69b31108`. Since then, `main` changed only
`crates/swc_type_strip` (#1442). Medians of 4 rounds:

| Input | Before #1422 (87adca5) | `69b31108` | `69b31108` + this PR |
  | --- | ---: | ---: | ---: |
  | 1 MB block comment, one line | 1.87 ms | 5.09 ms | 1.85 ms |
  | 1 MB block comment, many lines | 1.69 ms | 2.39 ms | 1.68 ms |
  | 1 MB of JSDoc blocks | 1.77 ms | 2.56 ms | 1.76 ms |
  | 1 MB of plain block comments | 1.73 ms | 2.42 ms | 1.71 ms |
  | Astro config types (125 KB) | 238.1 µs | 320.0 µs | 236.6 µs |
  | AngularJS 1.2.5 | 3.06 ms | 3.39 ms | 3.02 ms |
  | YUI 3.12.0 | 1.72 ms | 1.77 ms | 1.72 ms |
  | `typescript.js` (8 MB) | 49.95 ms | 51.03 ms | 48.89 ms |

- For 650 inputs, the output matched the `69b31108` build. The inputs
are the Amaro benchmark corpus and synthetic inputs, each also with
TypeScript `module` declarations and comments inserted at several
positions.
- The `swc_next_simd` tests and Amaro's 99 tests pass with this change.
`cargo fmt`, `cargo clippy`, and the tests of the other crates were not
run locally. Native wall time was not measured.

## Related Links

- #1422
- #1426
- nodejs/amaro#507

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants