Skip to content

Comments

perf: improve compiler perf#177

Merged
aquapi merged 4 commits intoh3js:mainfrom
aquapi:main
Feb 21, 2026
Merged

perf: improve compiler perf#177
aquapi merged 4 commits intoh3js:mainfrom
aquapi:main

Conversation

@aquapi
Copy link
Contributor

@aquapi aquapi commented Feb 20, 2026

  • check params.length > 0 instead of using a Set to deduplicate static nodes.
  • use else if to prevent unnecessary checks.
  • prevent deopts from accessing s out of bound for some cases in compiler output.
  • use .concat instead of spread syntax for faster params array cloning and disable the lint rule that prefer spread.

resolves #176

Summary by CodeRabbit

  • Chores

    • Disabled a linting rule in the project ESLint configuration.
  • Refactor

    • Reworked route compilation to unify and simplify matching logic, improving control flow and parameter handling.
  • Tests

    • Updated compiled snapshots to reflect the revised routing output and new wildcard/length-based routing behavior.

- check `params.length > 0` instead of using a `Set` to deduplicate static nodes
- prevent deopts from accessing `s` out of bound for some cases in compiler output
- use `.concat` instead of spread syntax for faster `params` array cloning and disable the lint rule that prefer spread
@aquapi aquapi requested a review from pi0 as a code owner February 20, 2026 18:22
@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

The compiler's route-matching emission is refactored to use on-the-fly conditional branching (currentIdx/hasIf) instead of static node collections, adjusts length semantics from s.length - 1 to s.length, updates parameter/wildcard handling, updates test snapshots accordingly, and adds an ESLint rule override.

Changes

Cohort / File(s) Summary
Compiler core
src/compiler.ts
Refactors route codegen: removes staticNodes, introduces currentIdx/hasIf, changes bound-check semantics, uses params.concat(...) for param propagation, consolidates static/dynamic/wildcard emission paths, and adjusts end-parameter constraints.
Compiled snapshots
test/.snapshot/compiled-all.mjs, test/.snapshot/compiled-aot.mjs, test/.snapshot/compiled-jit.mjs
Updates generated routing output: converts separate if blocks into else if chains, switches length computation to s.length, adds explicit length guards, reorganizes nested route branches, and introduces/adjusts wildcard handling and parameter extraction.
Lint config
eslint.config.mjs
Disables unicorn/prefer-spread by adding the rule entry unicorn/prefer-spread: 0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble code where branches bloom and split,
I stitch the indexes, tidy every bit,
Else-if ribbons curl where once they lay,
Parameters hop along the brighter way,
Hooray — compiled paths leap light and fit!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'perf: improve compiler perf' is vague and uses generic phrasing that doesn't clearly convey the specific improvements being made. Consider a more descriptive title that highlights the main change, such as 'perf: refactor compiler to reduce deoptimizations' or 'perf: optimize route compiler with conditional logic and bounds checking'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully addresses all coding objectives from issue #176: prevents out-of-bounds access with length checks, eliminates unnecessary static path checks via else-if chains, and implements micro-optimizations including Set removal and .concat usage.
Out of Scope Changes check ✅ Passed All changes are within scope: compiler logic refactoring, eslint rule disabling for .concat usage, and snapshot updates reflect expected output changes from the optimization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/.snapshot/compiled-jit.mjs (1)

24-26: Minor: s[3] accessed when l === 3 for the optional * param.

When l === 3, s[3] is undefined (OOB). JS handles this gracefully (returns undefined), and the route correctly matches with the param absent. However, this OOB access pattern can still cause V8 to deoptimize the function by switching to a slower element access mode.

If the PR's goal of preventing deopt from OOB access extends to this case, the compiler could emit a conditional:

_0: l > 3 ? s[3] : undefined

Otherwise, this is an acceptable trade-off for simplicity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/.snapshot/compiled-jit.mjs` around lines 24 - 26, The snippet emits an
out-of-bounds array access for the optional "*" param by using s[3] when l ===
3; change the emitter that generates the branch producing { data: $7, params: {
_0: s[3] } } to guard the access and emit a conditional expression (e.g., _0: l
> 3 ? s[3] : undefined) so the code reads _0 safely when l can be 3; update the
generation logic that composes this branch (look for the branch that checks l
=== 4 || l === 3 and returns params._0) to produce the guarded form.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/compiler.ts`:
- Around line 88-97: The bug is that hasIf is set unconditionally which causes
stray "else if" when a prior static node had no methods; change the logic in the
loop over ctx.router.static so hasIf is only set to true when you actually emit
code for that node (i.e., inside the if(node?.methods) block after appending the
compiled code from compileMethodMatch(ctx, node.methods, [], -1)); ensure nodes
without methods do not flip hasIf so subsequent emitted branches generate a
correct first "if" rather than "else if".

---

Nitpick comments:
In `@test/.snapshot/compiled-jit.mjs`:
- Around line 24-26: The snippet emits an out-of-bounds array access for the
optional "*" param by using s[3] when l === 3; change the emitter that generates
the branch producing { data: $7, params: { _0: s[3] } } to guard the access and
emit a conditional expression (e.g., _0: l > 3 ? s[3] : undefined) so the code
reads _0 safely when l can be 3; update the generation logic that composes this
branch (look for the branch that checks l === 4 || l === 3 and returns
params._0) to produce the guarded form.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/compiler.ts (1)

191-202: The params.length > 0 guard intentionally prevents double-emission of fully-static routes.

Static routes are handled in two places: first in the ctx.router.static lookup (lines 88–97), then skipped during tree traversal via this guard. This is not an implicit invariant that could be violated—it's the intended separation of concerns. Tree nodes with methods can be reached with params.length === 0 during tree traversal (for fully-static paths), and the guard ensures they're emitted only once via the static lookup.

A brief comment here would help future maintainers understand that the guard is a deliberate deduplication mechanism, not a safety check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/compiler.ts` around lines 191 - 202, Add a short clarifying comment above
the params.length > 0 guard to state that this check is intentional
deduplication: fully-static routes are emitted via the ctx.router.static lookup
and should not be re-emitted during tree traversal, so nodes with methods may
legitimately be reachable with params.length === 0 and must be skipped here;
reference the compileMethodMatch call and the ctx.router.static lookup to make
the separation of concerns explicit for future maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/compiler.ts`:
- Around line 88-97: The prior bug was that hasIf was being set unconditionally
causing incorrect else-if generation; ensure hasIf is assigned only when a node
has methods by keeping hasIf = true inside the if (node?.methods) block while
iterating ctx.router.static keys and using compileMethodMatch(ctx, node.methods,
[], -1) for the generated branch for each key (use key.replace(/\/$/, "") ||
"/") for p comparison); verify the code under the for loop updates hasIf only
when node?.methods is truthy.

---

Nitpick comments:
In `@src/compiler.ts`:
- Around line 191-202: Add a short clarifying comment above the params.length >
0 guard to state that this check is intentional deduplication: fully-static
routes are emitted via the ctx.router.static lookup and should not be re-emitted
during tree traversal, so nodes with methods may legitimately be reachable with
params.length === 0 and must be skipped here; reference the compileMethodMatch
call and the ctx.router.static lookup to make the separation of concerns
explicit for future maintainers.

@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Member

@pi0 pi0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@pi0 pi0 changed the title perf: compiler perf: improve compiler perf Feb 20, 2026
@aquapi aquapi merged commit 32412aa into h3js:main Feb 21, 2026
4 checks passed
@aquapi
Copy link
Contributor Author

aquapi commented Feb 21, 2026

there are two other cases that can cause deopts, I will try to fix later @pi0

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.

perf: compiler

2 participants