Skip to content

buffer: avoid fast API calls when computing UTF-8 byte length - #67134

Closed
ronag wants to merge 1 commit into
nodejs:mainfrom
ronag:codex/buffer-bytelength-gc-20261029
Closed

ronag wants to merge 1 commit into
nodejs:mainfrom
ronag:codex/buffer-bytelength-gc-20261029

Conversation

@ronag

@ronag ronag commented Oct 30, 2026

Copy link
Copy Markdown
Member

Buffer.byteLength() can abort when flattening a string triggers a GC that
lazily deoptimizes its optimized caller. V8 does not support that
deoptimization after a value-returning fast API call, including when the
call takes its slow fallback.

Register byteLengthUtf8 as a regular callback and remove its fast callback.
The existing SIMD path for large two-byte strings remains. This removes
the fast API optimization for this binding and avoids shared result state
or startup changes.

Add a GC regression and coverage for flat, Latin-1, cons, sliced, two-byte,
lone-surrogate and external strings before and after optimization.

Validation on Linux x64, release build of main:

  • Base 502c117: the new GC regression aborts in 3/3 runs.
  • Candidate 7f67aa5: the same regression passes in 10/10 runs.
  • All 502 buffer, HTTP and string-decoder tests pass.
  • ESLint, cpplint, import, C++ formatting and whitespace checks pass.

Fixes: #66870

AI assistance: Codex helped prepare the change and regression tests.

String flattening can allocate and invalidate the optimized caller during
a byteLengthUtf8 call. V8 cannot lazily deoptimize a value-returning fast
API call, including its slow fallback.

Register byteLengthUtf8 as a regular callback so both flat and non-flat
strings can be measured without the unsupported deoptimization. Keep the
existing SIMD path for large two-byte strings.

Add a GC regression and coverage for optimized calls with different string
representations.

Fixes: nodejs#66870
Assisted-by: Codex
Signed-off-by: Robert Nagy <ronagy@icloud.com>
@ronag
ronag requested a balanced review from Copilot October 30, 2026 07:22
@nodejs-github-bot nodejs-github-bot added buffer Issues and PRs related to the buffer subsystem. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Oct 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The regression depends on V8 optimization and GC-triggered lazy deoptimization, requiring human runtime verification of pre-fix failure, post-fix success, and stability across supported CI builds.

No open issues 🎉

What changed in this PR

This PR addresses the Buffer.byteLength() process abort reported in #66870 by replacing its UTF-8 fast API binding with a regular callback, allowing garbage-collection-triggered deoptimization.

Changes:

  • Removes the fast callback and its registration while retaining the existing SIMD path for large two-byte strings.
  • Adds a regression test for garbage collection during string flattening.
  • Adds string-representation coverage before and after optimization.
File Description
test/​parallel/​test-buffer-bytelength-strings.js Checks UTF-8 byte lengths across string representations before and after optimization.
test/​parallel/​test-buffer-bytelength-gc.js Exercises the GC-triggered deoptimization that caused the abort.
src/​node_buffer.cc Registers a regular UTF-8 byte-length callback and removes the fast API implementation.

Review effort: Balanced

@ronag
ronag marked this pull request as ready for review October 30, 2026 07:29
@ronag
ronag requested review from anonrig and mcollina October 30, 2026 07:29
@ronag

ronag commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

Investigating how to possibly re-enable the optimization. But this is pretty bad and should be fixed ASAP.

@codecov

codecov Bot commented Oct 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.68%. Comparing base (502c117) to head (7f67aa5).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #67134      +/-   ##
==========================================
+ Coverage   90.66%   90.68%   +0.02%     
==========================================
  Files         797      797              
  Lines      278791   278743      -48     
  Branches    53620    53602      -18     
==========================================
+ Hits       252768   252791      +23     
+ Misses      16376    16306      -70     
+ Partials     9647     9646       -1     
Files with missing lines Coverage Δ
src/node_buffer.cc 74.08% <100.00%> (+2.41%) ⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

nodejs-github-bot pushed a commit that referenced this pull request Nov 1, 2026
Replace the allocating UTF-8 fast call with a fast binding for
sequential one-byte strings. Its slow fallback only returns a Smi
sentinel. JavaScript sends rejected strings to a separate regular
binding that can flatten strings and handle lazy deoptimization.

Both native callbacks used by the fast API avoid JS-heap allocation.
Add GC regression and string-representation coverage, including
optimized fast acceptance and rejection checks.

Fixes: #66870
Refs: #67134
Assisted-by: Codex
Signed-off-by: Robert Nagy <ronagy@icloud.com>
PR-URL: #67136
Refs: #66870
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
aduh95 pushed a commit that referenced this pull request Nov 11, 2026
Replace the allocating UTF-8 fast call with a fast binding for
sequential one-byte strings. Its slow fallback only returns a Smi
sentinel. JavaScript sends rejected strings to a separate regular
binding that can flatten strings and handle lazy deoptimization.

Both native callbacks used by the fast API avoid JS-heap allocation.
Add GC regression and string-representation coverage, including
optimized fast acceptance and rejection checks.

Fixes: #66870
Refs: #67134
Assisted-by: Codex
Signed-off-by: Robert Nagy <ronagy@icloud.com>
PR-URL: #67136
Refs: #66870
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

buffer Issues and PRs related to the buffer subsystem. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Abort "Lazy deopt after a fast API call with return value is unsupported" in Buffer.byteLength called from maybePrepareFinalChunk / res.end(string)

3 participants