Skip to content

buffer: support aligned allocations - #65003

Open
ronag wants to merge 1 commit into
nodejs:mainfrom
ronag:buffer-aligned-alloc
Open

buffer: support aligned allocations#65003
ronag wants to merge 1 commit into
nodejs:mainfrom
ronag:buffer-aligned-alloc

Conversation

@ronag

@ronag ronag commented Aug 4, 2026

Copy link
Copy Markdown
Member

Adds an optional alignment argument to Buffer.allocUnsafe() and
Buffer.allocUnsafeSlow(), guaranteeing that the memory backing the returned
buffer starts at an address that is a multiple of alignment.

// Block-aligned buffer, suitable for a read on an O_DIRECT descriptor.
const buf = Buffer.allocUnsafeSlow(4096, 4096);

Why

Some operating system interfaces refuse to work with unaligned memory. The
motivating case is unbuffered ("direct") file I/O: a read or write on a
descriptor opened with O_DIRECT fails with EINVAL unless the buffer address
is a multiple of the logical block size of the underlying device. There is
currently no way to obtain such a buffer from JS, since the address of a backing
store can neither be observed nor chosen.

Alignment is also worth having purely for performance, for instance to keep a hot
buffer from straddling one more cache line than its size requires.

How

V8 does not allow picking the address of a backing store, so alignment is
achieved by over-allocating alignment - 1 bytes and positioning the buffer at
the first suitably aligned byte within them. A new arrayBufferAlignedOffset()
binding computes that offset.

It returns an offset rather than a finished Buffer for two reasons: external
backing stores are rejected outright when the V8 sandbox is enabled, and
Buffer::New() is not usable during buffer.js evaluation — the buffer
prototype is only wired up after require('buffer') returns, which the pool
creation runs before.

Consequently buf.byteOffset is usually non-zero and buf.buffer is larger than
size, as is already the case for pooled buffers. This is documented.

The Buffer.allocUnsafe() pool is now cache-line aligned itself, so pooled
allocations can satisfy any alignment up to 64 bytes by padding their offset into
the pool rather than allocating separately. poolOffset is therefore tracked
relative to a new poolBase.

alignment must be a power of two no larger than 2 ** 30, and
size + alignment - 1 must fit within buffer.constants.MAX_LENGTH.

Notes for reviewers

  • The alignment of the resulting address is not observable from JS, so the
    binding CHECKs it; the added test sweeps sizes × alignments over both APIs to
    exercise those assertions.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/userland-migrations

@ronag
ronag requested a review from anonrig August 4, 2026 08:58
@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 Aug 4, 2026
@ronag
ronag requested a review from mcollina August 4, 2026 08:59
Add an optional `alignment` argument to `Buffer.allocUnsafe()` and
`Buffer.allocUnsafeSlow()`, which guarantees that the memory backing the
returned buffer starts at an address that is a multiple of `alignment`.

Some operating system interfaces refuse to work with unaligned memory.
The motivating case is unbuffered ("direct") file I/O: a read or write
on a descriptor opened with `O_DIRECT` fails with `EINVAL` unless the
buffer address is a multiple of the logical block size of the underlying
device. Until now there was no way to obtain such a buffer from JS,
since the address of a backing store can neither be observed nor chosen.
Alignment is also worth having purely for performance, for instance to
keep a hot buffer from straddling one more cache line than its size
requires.

V8 does not allow picking the address of a backing store, so alignment
is instead achieved by over-allocating `alignment - 1` bytes and
positioning the buffer at the first suitably aligned byte within them.
The new `arrayBufferAlignedOffset()` binding computes that offset.

The `Buffer.allocUnsafe()` pool is now aligned to a cache line itself,
which lets pooled allocations satisfy any alignment up to 64 bytes by
padding their offset into the pool rather than allocating separately.

Assisted-by: Claude/Opus 5
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ronag
ronag force-pushed the buffer-aligned-alloc branch from 1e1a826 to ef04e22 Compare August 4, 2026 08:59
@ronag
ronag requested review from jasnell and joyeecheung and a lite review from Copilot August 4, 2026 09:27

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.

Pull request overview

This PR adds optional alignment support to Buffer.allocUnsafe() and Buffer.allocUnsafeSlow() so callers can request that the returned Buffer’s backing memory starts at an address aligned to a power-of-two boundary, enabling use cases like O_DIRECT I/O and some alignment-based performance optimizations.

Changes:

  • Add a new internal binding (arrayBufferAlignedOffset) and supporting C++ allocation helper to compute aligned view offsets into over-allocated ArrayBuffers.
  • Implement aligned allocation plumbing in lib/buffer.js (including cache-line-aligned pool base and aligned pooled slicing up to 64 bytes).
  • Add documentation and tests covering aligned allocation behavior and argument validation.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/parallel/test-buffer-alloc-alignment.js Adds test coverage for aligned allocations and validation behavior.
src/node_buffer.cc Introduces AllocateUnsafeArrayBuffer() and the arrayBufferAlignedOffset() binding.
lib/internal/buffer.js Adds createUnsafeAlignedBuffer() used by Buffer APIs and pool creation.
lib/buffer.js Adds alignment parameter handling, alignment validation, and aligns the pool base.
doc/api/worker_threads.md Updates Buffer.allocUnsafe() documentation link anchor.
doc/api/deprecations.md Updates Buffer.allocUnsafe/allocUnsafeSlow documentation link anchors.
doc/api/buffer.md Documents the new alignment argument and adds an “Aligned allocations” section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +21
const buf = alloc(size, alignment);
assert.strictEqual(buf.length, size);
assert.strictEqual(buf.byteOffset % alignment, 0);
// The view must fit inside the (over-allocated) ArrayBuffer.
Comment thread lib/buffer.js
Comment on lines 488 to +492
* @param {number} size
* @param {number} [alignment] A power of two, at most 2 ** 30
* @returns {FastBuffer|undefined}
*/
Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) {
Buffer.allocUnsafeSlow = function allocUnsafeSlow(size, alignment) {
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.20000% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.27%. Comparing base (f00fb75) to head (ef04e22).
⚠️ Report is 13 commits behind head on main.

Files with missing lines Patch % Lines
src/node_buffer.cc 65.62% 2 Missing and 9 partials ⚠️
lib/internal/buffer.js 82.35% 3 Missing ⚠️
lib/buffer.js 97.36% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #65003    +/-   ##
========================================
  Coverage   90.27%   90.27%            
========================================
  Files         762      762            
  Lines      247534   247634   +100     
  Branches    46694    46715    +21     
========================================
+ Hits       223457   223553    +96     
+ Misses      15529    15498    -31     
- Partials     8548     8583    +35     
Files with missing lines Coverage Δ
lib/buffer.js 99.02% <97.36%> (-0.10%) ⬇️
lib/internal/buffer.js 98.50% <82.35%> (-0.25%) ⬇️
src/node_buffer.cc 69.27% <65.62%> (-0.13%) ⬇️

... and 37 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.

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.

3 participants