ffi: add shared-buffer fast path for numeric and pointer signatures by bengl · Pull Request #62918 · nodejs/node · GitHub
Skip to content

ffi: add shared-buffer fast path for numeric and pointer signatures#62918

Draft
bengl wants to merge 2 commits intonodejs:mainfrom
bengl:bengl/shared-buffer-ffi
Draft

ffi: add shared-buffer fast path for numeric and pointer signatures#62918
bengl wants to merge 2 commits intonodejs:mainfrom
bengl:bengl/shared-buffer-ffi

Conversation

@bengl
Copy link
Copy Markdown
Member

@bengl bengl commented Apr 23, 2026

Adds an ArrayBuffer-based invocation path for FFI functions whose
signatures are composed entirely of numeric types (i8..i64, u8..u64,
f32, f64, bool, char) and/or pointer types. The JS wrapper packs
arguments directly into a per-function AB via primordial DataView
setters and the C++ invoker (InvokeFunctionSB) reads them without
going through V8's FunctionCallbackInfo. Results are returned the same
way.

Pointer arguments use runtime dispatch: BigInt, null, and undefined take
the fast path, while Buffer, ArrayBuffer, ArrayBufferView, and String
fall back transparently to the classic InvokeFunction path via a
stashed _invokeSlow function. Signatures containing
non-numeric/non-pointer types also bypass the fast path.

The fast path is disabled on big-endian platforms.

Callers do not opt in, and the fast path is transparent in every way
users should rely on. One observable change: function wrappers returned
by library.getFunction, library.getFunctions, and library.functions
now have .length equal to the declared parameter count rather than
0. Code that relied on the previous value will need to be updated.


This is largely based on #46905, which is largely based on https://github.com/bengl/sbffi.


Benchmark output:

                                       confidence improvement accuracy (*)   (**)  (***)
ffi/add-f64.js n=10000000                     ***     28.58 %       ±0.83% ±1.12% ±1.47%
ffi/add-i32.js n=10000000                     ***     24.01 %       ±0.77% ±1.02% ±1.33%
ffi/getpid.js n=10000000                               0.17 %       ±1.26% ±1.69% ±2.24%
ffi/many-args.js n=10000000                   ***    -11.76 %       ±0.61% ±0.81% ±1.06%
ffi/pointer-bigint.js n=10000000              ***     53.44 %       ±1.27% ±1.69% ±2.20%
ffi/sum-buffer.js n=1000000 size=1024         ***     16.97 %       ±0.78% ±1.04% ±1.35%
ffi/sum-buffer.js n=1000000 size=16384        ***      2.02 %       ±0.70% ±0.94% ±1.23%
ffi/sum-buffer.js n=1000000 size=64           ***     26.10 %       ±0.63% ±0.83% ±1.09%

TODO:

  • rebase

bengl added 2 commits April 23, 2026 09:59
Adds microbenchmarks covering the common FFI call shapes so future
changes to the invoker can be evaluated:

- add-i32.js: 2-arg integer
- add-f64.js: 2-arg float
- many-args.js: 6-arg integer
- pointer-bigint.js: 1-arg pointer (BigInt)
- sum-buffer.js: pointer + length (Buffer)

A `common.js` helper resolves the fixture-library path from
`test/ffi/fixture_library` without pulling in the test harness, and
throws a clear message if the fixture hasn't been built yet.

Also adds `sum_6_i32` to the fixture library for the many-args case.
Adds an ArrayBuffer-based invocation path for FFI functions whose
signatures are composed entirely of numeric types (i8..i64, u8..u64,
f32, f64, bool, char) and/or pointer types. The JS wrapper packs
arguments directly into a per-function AB via primordial DataView
setters and the C++ invoker (`InvokeFunctionSB`) reads them without
going through V8's `FunctionCallbackInfo`. Results are returned the same
way.

Pointer arguments use runtime dispatch: BigInt, null, and undefined take
the fast path, while Buffer, ArrayBuffer, ArrayBufferView, and String
fall back transparently to the classic `InvokeFunction` path via a
stashed `_invokeSlow` function. Signatures containing
non-numeric/non-pointer types also bypass the fast path.

The fast path is disabled on big-endian platforms.

Callers do not opt in, and the fast path is transparent in every way
users should rely on. One observable change: function wrappers returned
by `library.getFunction`, `library.getFunctions`, and `library.functions`
now have `.length` equal to the declared parameter count rather than
`0`. Code that relied on the previous value will need to be updated.
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants